on ae49e0e help sign in
unity8/qml/Stage/WindowResizeArea.qml
355 lines 14 kB history raw
1/*
2 * Copyright (C) 2014-2016 Canonical, Ltd.
3 *
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.
7 *
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.
12 *
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/>.
15 */
17import QtQuick 2.4
18import Ubuntu.Components 1.3
19import Utils 0.1
20import Unity.Application 0.1 // for Mir.cursorName
21import "../Components/PanelState"
23MouseArea {
24 id: root
26 anchors.fill: target
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
49 QtObject {
50 id: priv
51 objectName: "priv"
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
64 }
65 }
66 }
68 Connections {
69 target: root.target
70 onXChanged: priv.updateNormalGeometry();
71 onYChanged: priv.updateNormalGeometry();
72 onWidthChanged: priv.updateNormalGeometry();
73 onHeightChanged: priv.updateNormalGeometry();
74 }
76 function loadWindowState() {
77 var windowGeometry = windowStateStorage.getGeometry(root.windowId,
78 Qt.rect(target.windowedX, target.windowedY, defaultWidth, defaultHeight));
81 target.windowedWidth = Qt.binding(function() { return Math.min(Math.max(windowGeometry.width, d.minimumWidth), screenWidth - root.leftMargin); });
82 target.windowedHeight = Qt.binding(function() { return Math.min(Math.max(windowGeometry.height, d.minimumHeight),
83 root.screenHeight - (target.fullscreen ? 0 : PanelState.panelHeight)); });
84 target.windowedX = Qt.binding(function() { return Math.max(Math.min(windowGeometry.x, root.screenWidth - root.leftMargin - target.windowedWidth),
85 (target.fullscreen ? 0 : root.leftMargin)); });
86 target.windowedY = Qt.binding(function() { return Math.max(Math.min(windowGeometry.y, root.screenHeight - target.windowedHeight), PanelState.panelHeight); });
88 var windowState = windowStateStorage.getState(root.windowId, WindowStateStorage.WindowStateNormal)
89 target.restore(false /* animated */, windowState);
91 priv.updateNormalGeometry();
93 // initialize the x/y to restore to
94 target.restoredX = priv.normalX;
95 target.restoredY = priv.normalY;
96 }
98 function saveWindowState() {
99 var state = target.windowState;
100 if (state === WindowStateStorage.WindowStateRestored) {
101 state = WindowStateStorage.WindowStateNormal;
102 }
103 windowStateStorage.saveState(root.windowId, state & ~WindowStateStorage.WindowStateMinimized); // clear the minimized bit when saving
104 windowStateStorage.saveGeometry(root.windowId, Qt.rect(priv.normalX, priv.normalY, priv.normalWidth, priv.normalHeight));
105 }
107 QtObject {
108 id: d
110 readonly property int maxSafeInt: 2147483647
111 readonly property int maxSizeIncrement: units.gu(40)
113 readonly property int minimumWidth: root.target ? Math.max(root.minWidth, root.target.minimumWidth) : root.minWidth
114 onMinimumWidthChanged: {
115 if (target.windowedWidth < minimumWidth) {
116 target.windowedWidth = minimumWidth;
117 }
118 }
119 readonly property int minimumHeight: root.target ? Math.max(root.minHeight, root.target.minimumHeight) : root.minHeight
120 onMinimumHeightChanged: {
121 if (target.windowedHeight < minimumHeight) {
122 target.windowedHeight = minimumHeight;
123 }
124 }
125 readonly property int maximumWidth: root.target && root.target.maximumWidth >= minimumWidth && root.target.maximumWidth > 0
126 ? root.target.maximumWidth : maxSafeInt
127 onMaximumWidthChanged: {
128 if (target.windowedWidth > maximumWidth) {
129 target.windowedWidth = maximumWidth;
130 }
131 }
132 readonly property int maximumHeight: root.target && root.target.maximumHeight >= minimumHeight && root.target.maximumHeight > 0
133 ? root.target.maximumHeight : maxSafeInt
134 onMaximumHeightChanged: {
135 if (target.windowedHeight > maximumHeight) {
136 target.windowedHeight = maximumHeight;
137 }
138 }
139 readonly property int widthIncrement: {
140 if (!root.target) {
141 return 1;
142 }
143 if (root.target.widthIncrement > 0) {
144 if (root.target.widthIncrement < maxSizeIncrement) {
145 return root.target.widthIncrement;
146 } else {
147 return maxSizeIncrement;
148 }
149 } else {
150 return 1;
151 }
152 }
153 readonly property int heightIncrement: {
154 if (!root.target) {
155 return 1;
156 }
157 if (root.target.heightIncrement > 0) {
158 if (root.target.heightIncrement < maxSizeIncrement) {
159 return root.target.heightIncrement;
160 } else {
161 return maxSizeIncrement;
162 }
163 } else {
164 return 1;
165 }
166 }
168 property bool leftBorder: false
169 property bool rightBorder: false
170 property bool topBorder: false
171 property bool bottomBorder: false
173 // true - A change in surface size will cause the left border of the window to move accordingly.
174 // The window's right border will stay in the same position.
175 // false - a change in surface size will cause the right border of the window to move accordingly.
176 // The window's left border will stay in the same position.
177 property bool moveLeftBorder: false
179 // true - A change in surface size will cause the top border of the window to move accordingly.
180 // The window's bottom border will stay in the same position.
181 // false - a change in surface size will cause the bottom border of the window to move accordingly.
182 // The window's top border will stay in the same position.
183 property bool moveTopBorder: false
185 property bool dragging: false
186 property real startMousePosX
187 property real startMousePosY
188 property real startX
189 property real startY
190 property real startWidth
191 property real startHeight
192 property real currentWidth
193 property real currentHeight
195 readonly property string cursorName: {
196 if (root.containsMouse || root.pressed) {
197 if (leftBorder && !topBorder && !bottomBorder) {
198 return "left_side";
199 } else if (rightBorder && !topBorder && !bottomBorder) {
200 return "right_side";
201 } else if (topBorder && !leftBorder && !rightBorder) {
202 return "top_side";
203 } else if (bottomBorder && !leftBorder && !rightBorder) {
204 return "bottom_side";
205 } else if (leftBorder && topBorder) {
206 return "top_left_corner";
207 } else if (leftBorder && bottomBorder) {
208 return "bottom_left_corner";
209 } else if (rightBorder && topBorder) {
210 return "top_right_corner";
211 } else if (rightBorder && bottomBorder) {
212 return "bottom_right_corner";
213 } else {
214 return "";
215 }
216 } else {
217 return "";
218 }
219 }
220 onCursorNameChanged: {
221 Mir.cursorName = cursorName;
222 }
224 function updateBorders() {
225 leftBorder = mouseX <= borderThickness;
226 rightBorder = mouseX >= width - borderThickness;
227 topBorder = mouseY <= borderThickness;
228 bottomBorder = mouseY >= height - borderThickness;
229 }
230 }
232 Timer {
233 id: resetBordersToMoveTimer
234 interval: 2000
235 onTriggered: {
236 d.moveLeftBorder = false;
237 d.moveTopBorder = false;
238 }
239 }
241 onPressedChanged: {
242 if (pressed) {
243 d.updateBorders();
244 resetBordersToMoveTimer.stop();
245 d.moveLeftBorder = d.leftBorder;
246 d.moveTopBorder = d.topBorder;
248 var pos = mapToItem(root.target.parent, mouseX, mouseY);
249 d.startMousePosX = pos.x;
250 d.startMousePosY = pos.y;
251 d.startX = target.windowedX;
252 d.startY = target.windowedY;
253 d.startWidth = target.width;
254 d.startHeight = target.height;
255 d.currentWidth = target.width;
256 d.currentHeight = target.height;
257 d.dragging = true;
258 } else {
259 resetBordersToMoveTimer.start();
260 d.dragging = false;
261 if (containsMouse) {
262 d.updateBorders();
263 }
264 }
265 }
267 onEntered: {
268 if (!pressed) {
269 d.updateBorders();
270 }
271 }
273 onPositionChanged: {
274 if (!pressed) {
275 d.updateBorders();
276 }
278 if (!d.dragging) {
279 return;
280 }
282 var pos = mapToItem(target.parent, mouse.x, mouse.y);
284 var deltaX = Math.floor((pos.x - d.startMousePosX) / d.widthIncrement) * d.widthIncrement;
285 var deltaY = Math.floor((pos.y - d.startMousePosY) / d.heightIncrement) * d.heightIncrement;
287 if (d.leftBorder) {
288 var newTargetX = d.startX + deltaX;
289 var rightBorderX = target.windowedX + target.width;
290 if (rightBorderX > newTargetX + d.minimumWidth) {
291 if (rightBorderX < newTargetX + d.maximumWidth) {
292 target.windowedWidth = rightBorderX - newTargetX;
293 } else {
294 target.windowedWidth = d.maximumWidth;
295 }
296 } else {
297 target.windowedWidth = d.minimumWidth;
298 }
300 } else if (d.rightBorder) {
301 var newWidth = d.startWidth + deltaX;
302 if (newWidth > d.minimumWidth) {
303 if (newWidth < d.maximumWidth) {
304 target.windowedWidth = newWidth;
305 } else {
306 target.windowedWidth = d.maximumWidth;
307 }
308 } else {
309 target.windowedWidth = d.minimumWidth;
310 }
311 }
313 if (d.topBorder) {
314 var newTargetY = Math.max(d.startY + deltaY, PanelState.panelHeight); // disallow resizing up past Panel
315 var bottomBorderY = target.windowedY + target.height;
316 if (bottomBorderY > newTargetY + d.minimumHeight) {
317 if (bottomBorderY < newTargetY + d.maximumHeight) {
318 target.windowedHeight = bottomBorderY - newTargetY;
319 } else {
320 target.windowedHeight = d.maximumHeight;
321 }
322 } else {
323 target.windowedHeight = d.minimumHeight;
324 }
326 } else if (d.bottomBorder) {
327 var newHeight = d.startHeight + deltaY;
328 if (newHeight > d.minimumHeight) {
329 if (newHeight < d.maximumHeight) {
330 target.windowedHeight = newHeight;
331 } else {
332 target.windowedHeight = d.maximumHeight;
333 }
334 } else {
335 target.windowedHeight = d.minimumHeight;
336 }
337 }
338 }
340 Connections {
341 target: root.target
342 onWidthChanged: {
343 if (d.moveLeftBorder) {
344 target.windowedX += d.currentWidth - target.width;
345 }
346 d.currentWidth = target.width;
347 }
348 onHeightChanged: {
349 if (d.moveTopBorder) {
350 target.windowedY += d.currentHeight - target.height;
351 }
352 d.currentHeight = target.height;
353 }
354 }