2 * Copyright (C) 2014-2016 Canonical, Ltd.
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 import Ubuntu.Components 1.3
20 import Unity.Application 0.1 // for Mir.cursorName
21 import "../Components/PanelState"
27 anchors.margins: -borderThickness
29 hoverEnabled: target && !target.maximized // don't grab the resize under the panel
31 property var windowStateStorage: WindowStateStorage
32 readonly property alias dragging: d.dragging
33 readonly property alias normalWidth: priv.normalWidth
34 readonly property alias normalHeight: priv.normalHeight
36 // The target item managed by this. Must be a parent or a sibling
37 // The area will anchor to it and manage resize events
38 property Item target: null
39 property string windowId: ""
40 property int borderThickness: 0
41 property int minWidth: 0
42 property int minHeight: 0
43 property int defaultWidth: units.gu(60)
44 property int defaultHeight: units.gu(50)
45 property int screenWidth: 0
46 property int screenHeight: 0
47 property int leftMargin: 0
53 property int normalX: 0
54 property int normalY: 0
55 property int normalWidth: 0
56 property int normalHeight: 0
58 function updateNormalGeometry() {
59 if (root.target.state == "normal" || root.target.state == "restored") {
60 normalX = root.target.requestedX
61 normalY = root.target.requestedY
62 normalWidth = root.target.width
63 normalHeight = root.target.height
70 onXChanged: priv.updateNormalGeometry();
71 onYChanged: priv.updateNormalGeometry();
72 onWidthChanged: priv.updateNormalGeometry();
73 onHeightChanged: priv.updateNormalGeometry();
76 function loadWindowState() {
77 var windowGeometry = windowStateStorage.getGeometry(root.windowId,
78 Qt.rect(target.requestedX, target.requestedY, defaultWidth, defaultHeight));
80 target.requestedWidth = Qt.binding(function() { return Math.min(Math.max(windowGeometry.width, d.minimumWidth), screenWidth - root.leftMargin); });
81 target.requestedHeight = Qt.binding(function() { return Math.min(Math.max(windowGeometry.height, d.minimumHeight),
82 root.screenHeight - (target.fullscreen ? 0 : PanelState.panelHeight)); });
83 target.requestedX = Qt.binding(function() { return Math.max(Math.min(windowGeometry.x, root.screenWidth - root.leftMargin - target.requestedWidth),
84 (target.fullscreen ? 0 : root.leftMargin)); });
85 target.requestedY = Qt.binding(function() { return Math.max(Math.min(windowGeometry.y, root.screenHeight - target.requestedHeight), PanelState.panelHeight); });
87 var windowState = windowStateStorage.getState(root.windowId, WindowStateStorage.WindowStateNormal)
88 target.restore(false /* animated */, windowState);
90 priv.updateNormalGeometry();
92 // initialize the x/y to restore to
93 target.restoredX = priv.normalX;
94 target.restoredY = priv.normalY;
97 function saveWindowState() {
98 var state = target.windowState;
99 if (state === WindowStateStorage.WindowStateRestored) {
100 state = WindowStateStorage.WindowStateNormal;
102 windowStateStorage.saveState(root.windowId, state & ~WindowStateStorage.WindowStateMinimized); // clear the minimized bit when saving
103 windowStateStorage.saveGeometry(root.windowId, Qt.rect(priv.normalX, priv.normalY, priv.normalWidth, priv.normalHeight));
109 readonly property int maxSafeInt: 2147483647
110 readonly property int maxSizeIncrement: units.gu(40)
112 readonly property int minimumWidth: root.target ? Math.max(root.minWidth, root.target.minimumWidth) : root.minWidth
113 onMinimumWidthChanged: {
114 if (target.requestedWidth < minimumWidth) {
115 target.requestedWidth = minimumWidth;
118 readonly property int minimumHeight: root.target ? Math.max(root.minHeight, root.target.minimumHeight) : root.minHeight
119 onMinimumHeightChanged: {
120 if (target.requestedHeight < minimumHeight) {
121 target.requestedHeight = minimumHeight;
124 readonly property int maximumWidth: root.target && root.target.maximumWidth >= minimumWidth && root.target.maximumWidth > 0
125 ? root.target.maximumWidth : maxSafeInt
126 onMaximumWidthChanged: {
127 if (target.requestedWidth > maximumWidth) {
128 target.requestedWidth = maximumWidth;
131 readonly property int maximumHeight: root.target && root.target.maximumHeight >= minimumHeight && root.target.maximumHeight > 0
132 ? root.target.maximumHeight : maxSafeInt
133 onMaximumHeightChanged: {
134 if (target.requestedHeight > maximumHeight) {
135 target.requestedHeight = maximumHeight;
138 readonly property int widthIncrement: {
142 if (root.target.widthIncrement > 0) {
143 if (root.target.widthIncrement < maxSizeIncrement) {
144 return root.target.widthIncrement;
146 return maxSizeIncrement;
152 readonly property int heightIncrement: {
156 if (root.target.heightIncrement > 0) {
157 if (root.target.heightIncrement < maxSizeIncrement) {
158 return root.target.heightIncrement;
160 return maxSizeIncrement;
167 property bool leftBorder: false
168 property bool rightBorder: false
169 property bool topBorder: false
170 property bool bottomBorder: false
172 // true - A change in surface size will cause the left border of the window to move accordingly.
173 // The window's right border will stay in the same position.
174 // false - a change in surface size will cause the right border of the window to move accordingly.
175 // The window's left border will stay in the same position.
176 property bool moveLeftBorder: false
178 // true - A change in surface size will cause the top border of the window to move accordingly.
179 // The window's bottom border will stay in the same position.
180 // false - a change in surface size will cause the bottom border of the window to move accordingly.
181 // The window's top border will stay in the same position.
182 property bool moveTopBorder: false
184 property bool dragging: false
185 property real startMousePosX
186 property real startMousePosY
189 property real startWidth
190 property real startHeight
191 property real currentWidth
192 property real currentHeight
194 readonly property string cursorName: {
195 if (root.containsMouse || root.pressed) {
196 if (leftBorder && !topBorder && !bottomBorder) {
198 } else if (rightBorder && !topBorder && !bottomBorder) {
200 } else if (topBorder && !leftBorder && !rightBorder) {
202 } else if (bottomBorder && !leftBorder && !rightBorder) {
203 return "bottom_side";
204 } else if (leftBorder && topBorder) {
205 return "top_left_corner";
206 } else if (leftBorder && bottomBorder) {
207 return "bottom_left_corner";
208 } else if (rightBorder && topBorder) {
209 return "top_right_corner";
210 } else if (rightBorder && bottomBorder) {
211 return "bottom_right_corner";
219 onCursorNameChanged: {
220 Mir.cursorName = cursorName;
223 function updateBorders() {
224 leftBorder = mouseX <= borderThickness;
225 rightBorder = mouseX >= width - borderThickness;
226 topBorder = mouseY <= borderThickness;
227 bottomBorder = mouseY >= height - borderThickness;
232 id: resetBordersToMoveTimer
235 d.moveLeftBorder = false;
236 d.moveTopBorder = false;
243 resetBordersToMoveTimer.stop();
244 d.moveLeftBorder = d.leftBorder;
245 d.moveTopBorder = d.topBorder;
247 var pos = mapToItem(root.target.parent, mouseX, mouseY);
248 d.startMousePosX = pos.x;
249 d.startMousePosY = pos.y;
250 d.startX = target.requestedX;
251 d.startY = target.requestedY;
252 d.startWidth = target.width;
253 d.startHeight = target.height;
254 d.currentWidth = target.width;
255 d.currentHeight = target.height;
258 resetBordersToMoveTimer.start();
281 var pos = mapToItem(target.parent, mouse.x, mouse.y);
283 var deltaX = Math.floor((pos.x - d.startMousePosX) / d.widthIncrement) * d.widthIncrement;
284 var deltaY = Math.floor((pos.y - d.startMousePosY) / d.heightIncrement) * d.heightIncrement;
287 var newTargetX = d.startX + deltaX;
288 var rightBorderX = target.requestedX + target.width;
289 if (rightBorderX > newTargetX + d.minimumWidth) {
290 if (rightBorderX < newTargetX + d.maximumWidth) {
291 target.requestedWidth = rightBorderX - newTargetX;
293 target.requestedWidth = d.maximumWidth;
296 target.requestedWidth = d.minimumWidth;
299 } else if (d.rightBorder) {
300 var newWidth = d.startWidth + deltaX;
301 if (newWidth > d.minimumWidth) {
302 if (newWidth < d.maximumWidth) {
303 target.requestedWidth = newWidth;
305 target.requestedWidth = d.maximumWidth;
308 target.requestedWidth = d.minimumWidth;
313 var newTargetY = Math.max(d.startY + deltaY, PanelState.panelHeight); // disallow resizing up past Panel
314 var bottomBorderY = target.requestedY + target.height;
315 if (bottomBorderY > newTargetY + d.minimumHeight) {
316 if (bottomBorderY < newTargetY + d.maximumHeight) {
317 target.requestedHeight = bottomBorderY - newTargetY;
319 target.requestedHeight = d.maximumHeight;
322 target.requestedHeight = d.minimumHeight;
325 } else if (d.bottomBorder) {
326 var newHeight = d.startHeight + deltaY;
327 if (newHeight > d.minimumHeight) {
328 if (newHeight < d.maximumHeight) {
329 target.requestedHeight = newHeight;
331 target.requestedHeight = d.maximumHeight;
334 target.requestedHeight = d.minimumHeight;
342 if (d.moveLeftBorder) {
343 target.requestedX += d.currentWidth - target.width;
345 d.currentWidth = target.width;
348 if (d.moveTopBorder) {
349 target.requestedY += d.currentHeight - target.height;
351 d.currentHeight = target.height;