Lomiri
Loading...
Searching...
No Matches
WindowResizeArea.qml
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 */
16
17import QtQuick 2.15
18import Lomiri.Components 1.3
19import Utils 0.1
20import QtMir.Application 0.1 // for Mir.cursorName
21
22MouseArea {
23 id: root
24
25 anchors.margins: -borderThickness
26
27 hoverEnabled: target && !target.maximized // don't grab the resize under the panel
28
29 readonly property alias dragging: d.dragging
30
31 // The target item managed by this. Must be a parent or a sibling
32 // The area will anchor to it and manage resize events
33 property Item target: null
34 property int borderThickness: 0
35 property Item boundsItem
36 property int minWidth: 0
37 property int minHeight: 0
38
39 property bool readyToAssesBounds: false
40 onReadyToAssesBoundsChanged: d.reassesBounds()
41
42 signal pressedChangedEx(bool pressed, point mouse)
43 signal positionChangedEx(point mouse)
44
45 QtObject {
46 id: d
47
48 readonly property int maxSafeInt: 2147483647
49 readonly property int maxSizeIncrement: units.gu(40)
50
51 function reassesBounds() {
52 if (!readyToAssesBounds) return;
53
54 if (target.windowedWidth < minimumWidth) {
55 target.windowedWidth = minimumWidth;
56 }
57 if (target.windowedHeight < minimumHeight) {
58 target.windowedHeight = minimumHeight;
59 }
60 if (target.windowedHeight < minimumHeight) {
61 target.windowedHeight = minimumHeight;
62 }
63 if (target.windowedWidth > maximumWidth) {
64 target.windowedWidth = maximumWidth;
65 }
66 if (target.windowedHeight > maximumHeight) {
67 target.windowedHeight = maximumHeight;
68 }
69 }
70
71 readonly property int minimumWidth: root.target ? Math.max(root.minWidth, root.target.minimumWidth) : root.minWidth
72 onMinimumWidthChanged: {
73 if (readyToAssesBounds && target.windowedWidth < minimumWidth) {
74 target.windowedWidth = minimumWidth;
75 }
76 }
77 readonly property int minimumHeight: root.target ? Math.max(root.minHeight, root.target.minimumHeight) : root.minHeight
78 onMinimumHeightChanged: {
79 if (readyToAssesBounds && target.windowedHeight < minimumHeight) {
80 target.windowedHeight = minimumHeight;
81 }
82 }
83 readonly property int maximumWidth: root.target && root.target.maximumWidth >= minimumWidth && root.target.maximumWidth > 0
84 ? root.target.maximumWidth : maxSafeInt
85 onMaximumWidthChanged: {
86 if (readyToAssesBounds && target.windowedWidth > maximumWidth) {
87 target.windowedWidth = maximumWidth;
88 }
89 }
90 readonly property int maximumHeight: root.target && root.target.maximumHeight >= minimumHeight && root.target.maximumHeight > 0
91 ? root.target.maximumHeight : maxSafeInt
92 onMaximumHeightChanged: {
93 if (readyToAssesBounds && target.windowedHeight > maximumHeight) {
94 target.windowedHeight = maximumHeight;
95 }
96 }
97 readonly property int widthIncrement: {
98 if (!root.target) {
99 return 1;
100 }
101 if (root.target.widthIncrement > 0) {
102 if (root.target.widthIncrement < maxSizeIncrement) {
103 return root.target.widthIncrement;
104 } else {
105 return maxSizeIncrement;
106 }
107 } else {
108 return 1;
109 }
110 }
111 readonly property int heightIncrement: {
112 if (!root.target) {
113 return 1;
114 }
115 if (root.target.heightIncrement > 0) {
116 if (root.target.heightIncrement < maxSizeIncrement) {
117 return root.target.heightIncrement;
118 } else {
119 return maxSizeIncrement;
120 }
121 } else {
122 return 1;
123 }
124 }
125
126 property bool leftBorder: false
127 property bool rightBorder: false
128 property bool topBorder: false
129 property bool bottomBorder: false
130
131 // true - A change in surface size will cause the left border of the window to move accordingly.
132 // The window's right border will stay in the same position.
133 // false - a change in surface size will cause the right border of the window to move accordingly.
134 // The window's left border will stay in the same position.
135 property bool moveLeftBorder: false
136
137 // true - A change in surface size will cause the top border of the window to move accordingly.
138 // The window's bottom border will stay in the same position.
139 // false - a change in surface size will cause the bottom border of the window to move accordingly.
140 // The window's top border will stay in the same position.
141 property bool moveTopBorder: false
142
143 property bool dragging: false
144 property bool externalDragging: false
145 property real startMousePosX
146 property real startMousePosY
147 property real startX
148 property real startY
149 property real startWidth
150 property real startHeight
151 property real currentWidth
152 property real currentHeight
153
154 readonly property string cursorName: {
155 if (root.containsMouse || root.pressed || d.externalDragging) {
156 if (leftBorder && !topBorder && !bottomBorder) {
157 return "left_side";
158 } else if (rightBorder && !topBorder && !bottomBorder) {
159 return "right_side";
160 } else if (topBorder && !leftBorder && !rightBorder) {
161 return "top_side";
162 } else if (bottomBorder && !leftBorder && !rightBorder) {
163 return "bottom_side";
164 } else if (leftBorder && topBorder) {
165 return "top_left_corner";
166 } else if (leftBorder && bottomBorder) {
167 return "bottom_left_corner";
168 } else if (rightBorder && topBorder) {
169 return "top_right_corner";
170 } else if (rightBorder && bottomBorder) {
171 return "bottom_right_corner";
172 } else {
173 return "";
174 }
175 } else {
176 return "";
177 }
178 }
179 onCursorNameChanged: {
180 Mir.cursorName = cursorName;
181 }
182 Component.onDestruction: {
183 // TODO Qt 5.8 has fixed the problem with containsMouse
184 // not being updated when the MouseArea that had containsMouse
185 // is hidden/removed. When we start using Qt 5.8 we should
186 // try to fix this scenario
187 // two windows side by side
188 // cursor in the resize left area of the right one
189 // close window by Alt+F4
190 // cursor should change to resize right of the left one
191 // currently changes to ""
192 Mir.cursorName = "";
193 }
194
195 function updateBorders(externalMousePos) {
196 if (externalMousePos) {
197 // Zoning to determine where and how the window will be resized
198 const halfWidth = root.width / 2;
199 const halfHeight = root.height / 2;
200
201 leftBorder = externalMousePos.x <= halfWidth;
202 rightBorder = externalMousePos.x > halfWidth;
203 topBorder = externalMousePos.y <= halfHeight;
204 bottomBorder = externalMousePos.y > halfHeight;
205 } else {
206 leftBorder = mouseX <= borderThickness;
207 rightBorder = mouseX >= width - borderThickness;
208 topBorder = mouseY <= borderThickness;
209 bottomBorder = mouseY >= height - borderThickness;
210 }
211 }
212
213 function startResize(isExternal, mousePos) {
214 if (isExternal) {
215 d.externalDragging = true;
216 d.updateBorders(mousePos);
217 } else {
218 d.updateBorders();
219 }
220 resetBordersToMoveTimer.stop();
221 d.moveLeftBorder = d.leftBorder;
222 d.moveTopBorder = d.topBorder;
223
224 var pos = mapToItem(root.target.parent, mousePos.x, mousePos.y);
225 d.startMousePosX = pos.x;
226 d.startMousePosY = pos.y;
227 d.startX = target.windowedX;
228 d.startY = target.windowedY;
229 d.startWidth = target.width;
230 d.startHeight = target.height;
231 d.currentWidth = target.width;
232 d.currentHeight = target.height;
233 d.dragging = true;
234 }
235
236 function endResize(isExternal) {
237 resetBordersToMoveTimer.start();
238 d.dragging = false;
239
240 if (isExternal) {
241 d.externalDragging = false;
242 } else if (containsMouse) {
243 d.updateBorders();
244 }
245 }
246
247 function updateResize(mouse) {
248 if (!d.dragging) {
249 return;
250 }
251
252 var pos = mapToItem(target.parent, mouse.x, mouse.y);
253
254 var deltaX = Math.floor((pos.x - d.startMousePosX) / d.widthIncrement) * d.widthIncrement;
255 var deltaY = Math.floor((pos.y - d.startMousePosY) / d.heightIncrement) * d.heightIncrement;
256
257 if (d.leftBorder) {
258 var newTargetX = d.startX + deltaX;
259 var rightBorderX = target.windowedX + target.width;
260 if (rightBorderX > newTargetX + d.minimumWidth) {
261 if (rightBorderX < newTargetX + d.maximumWidth) {
262 target.windowedWidth = rightBorderX - newTargetX;
263 } else {
264 target.windowedWidth = d.maximumWidth;
265 }
266 } else {
267 target.windowedWidth = d.minimumWidth;
268 }
269
270 } else if (d.rightBorder) {
271 var newWidth = d.startWidth + deltaX;
272 if (newWidth > d.minimumWidth) {
273 if (newWidth < d.maximumWidth) {
274 target.windowedWidth = newWidth;
275 } else {
276 target.windowedWidth = d.maximumWidth;
277 }
278 } else {
279 target.windowedWidth = d.minimumWidth;
280 }
281 }
282
283 if (d.topBorder) {
284 var bounds = boundsItem.mapToItem(target.parent, 0, 0, boundsItem.width, boundsItem.height);
285 var newTargetY = Math.max(d.startY + deltaY, bounds.y);
286 var bottomBorderY = target.windowedY + target.height;
287 if (bottomBorderY > newTargetY + d.minimumHeight) {
288 if (bottomBorderY < newTargetY + d.maximumHeight) {
289 target.windowedHeight = bottomBorderY - newTargetY;
290 } else {
291 target.windowedHeight = d.maximumHeight;
292 }
293 } else {
294 target.windowedHeight = d.minimumHeight;
295 }
296
297 } else if (d.bottomBorder) {
298 var newHeight = d.startHeight + deltaY;
299 if (newHeight > d.minimumHeight) {
300 if (newHeight < d.maximumHeight) {
301 target.windowedHeight = newHeight;
302 } else {
303 target.windowedHeight = d.maximumHeight;
304 }
305 } else {
306 target.windowedHeight = d.minimumHeight;
307 }
308 }
309 }
310 }
311
312 Timer {
313 id: resetBordersToMoveTimer
314 interval: 2000
315 onTriggered: {
316 d.moveLeftBorder = false;
317 d.moveTopBorder = false;
318 }
319 }
320
321 onPressedChangedEx: {
322 if (pressed) {
323 d.startResize(true, mouse);
324 } else {
325 d.endResize(true, mouse);
326 }
327 }
328 onPressedChanged: {
329 const mousePos = Qt.point(mouseX, mouseY);
330 if (pressed) {
331 d.startResize(false, mousePos);
332 } else {
333 d.endResize(false, mousePos);
334 }
335 }
336
337 onEntered: {
338 if (!pressed && !d.externalDragging) {
339 d.updateBorders();
340 }
341 }
342
343 onPositionChangedEx: d.updateResize(mouse);
344 onPositionChanged: {
345 if (!pressed) {
346 d.updateBorders();
347 }
348
349 d.updateResize(mouse);
350 }
351
352 Connections {
353 target: root.target
354 function onWidthChanged() {
355 if (d.moveLeftBorder) {
356 target.windowedX += d.currentWidth - target.width;
357 }
358 d.currentWidth = target.width;
359 }
360 function onHeightChanged() {
361 if (d.moveTopBorder) {
362 target.windowedY += d.currentHeight - target.height;
363 }
364 d.currentHeight = target.height;
365 }
366 }
367}