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.12
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 QtObject {
43 id: d
44
45 readonly property int maxSafeInt: 2147483647
46 readonly property int maxSizeIncrement: units.gu(40)
47
48 function reassesBounds() {
49 if (!readyToAssesBounds) return;
50
51 if (target.windowedWidth < minimumWidth) {
52 target.windowedWidth = minimumWidth;
53 }
54 if (target.windowedHeight < minimumHeight) {
55 target.windowedHeight = minimumHeight;
56 }
57 if (target.windowedHeight < minimumHeight) {
58 target.windowedHeight = minimumHeight;
59 }
60 if (target.windowedWidth > maximumWidth) {
61 target.windowedWidth = maximumWidth;
62 }
63 if (target.windowedHeight > maximumHeight) {
64 target.windowedHeight = maximumHeight;
65 }
66 }
67
68 readonly property int minimumWidth: root.target ? Math.max(root.minWidth, root.target.minimumWidth) : root.minWidth
69 onMinimumWidthChanged: {
70 if (readyToAssesBounds && target.windowedWidth < minimumWidth) {
71 target.windowedWidth = minimumWidth;
72 }
73 }
74 readonly property int minimumHeight: root.target ? Math.max(root.minHeight, root.target.minimumHeight) : root.minHeight
75 onMinimumHeightChanged: {
76 if (readyToAssesBounds && target.windowedHeight < minimumHeight) {
77 target.windowedHeight = minimumHeight;
78 }
79 }
80 readonly property int maximumWidth: root.target && root.target.maximumWidth >= minimumWidth && root.target.maximumWidth > 0
81 ? root.target.maximumWidth : maxSafeInt
82 onMaximumWidthChanged: {
83 if (readyToAssesBounds && target.windowedWidth > maximumWidth) {
84 target.windowedWidth = maximumWidth;
85 }
86 }
87 readonly property int maximumHeight: root.target && root.target.maximumHeight >= minimumHeight && root.target.maximumHeight > 0
88 ? root.target.maximumHeight : maxSafeInt
89 onMaximumHeightChanged: {
90 if (readyToAssesBounds && target.windowedHeight > maximumHeight) {
91 target.windowedHeight = maximumHeight;
92 }
93 }
94 readonly property int widthIncrement: {
95 if (!root.target) {
96 return 1;
97 }
98 if (root.target.widthIncrement > 0) {
99 if (root.target.widthIncrement < maxSizeIncrement) {
100 return root.target.widthIncrement;
101 } else {
102 return maxSizeIncrement;
103 }
104 } else {
105 return 1;
106 }
107 }
108 readonly property int heightIncrement: {
109 if (!root.target) {
110 return 1;
111 }
112 if (root.target.heightIncrement > 0) {
113 if (root.target.heightIncrement < maxSizeIncrement) {
114 return root.target.heightIncrement;
115 } else {
116 return maxSizeIncrement;
117 }
118 } else {
119 return 1;
120 }
121 }
122
123 property bool leftBorder: false
124 property bool rightBorder: false
125 property bool topBorder: false
126 property bool bottomBorder: false
127
128 // true - A change in surface size will cause the left border of the window to move accordingly.
129 // The window's right border will stay in the same position.
130 // false - a change in surface size will cause the right border of the window to move accordingly.
131 // The window's left border will stay in the same position.
132 property bool moveLeftBorder: false
133
134 // true - A change in surface size will cause the top border of the window to move accordingly.
135 // The window's bottom border will stay in the same position.
136 // false - a change in surface size will cause the bottom border of the window to move accordingly.
137 // The window's top border will stay in the same position.
138 property bool moveTopBorder: false
139
140 property bool dragging: false
141 property real startMousePosX
142 property real startMousePosY
143 property real startX
144 property real startY
145 property real startWidth
146 property real startHeight
147 property real currentWidth
148 property real currentHeight
149
150 readonly property string cursorName: {
151 if (root.containsMouse || root.pressed) {
152 if (leftBorder && !topBorder && !bottomBorder) {
153 return "left_side";
154 } else if (rightBorder && !topBorder && !bottomBorder) {
155 return "right_side";
156 } else if (topBorder && !leftBorder && !rightBorder) {
157 return "top_side";
158 } else if (bottomBorder && !leftBorder && !rightBorder) {
159 return "bottom_side";
160 } else if (leftBorder && topBorder) {
161 return "top_left_corner";
162 } else if (leftBorder && bottomBorder) {
163 return "bottom_left_corner";
164 } else if (rightBorder && topBorder) {
165 return "top_right_corner";
166 } else if (rightBorder && bottomBorder) {
167 return "bottom_right_corner";
168 } else {
169 return "";
170 }
171 } else {
172 return "";
173 }
174 }
175 onCursorNameChanged: {
176 Mir.cursorName = cursorName;
177 }
178 Component.onDestruction: {
179 // TODO Qt 5.8 has fixed the problem with containsMouse
180 // not being updated when the MouseArea that had containsMouse
181 // is hidden/removed. When we start using Qt 5.8 we should
182 // try to fix this scenario
183 // two windows side by side
184 // cursor in the resize left area of the right one
185 // close window by Alt+F4
186 // cursor should change to resize right of the left one
187 // currently changes to ""
188 Mir.cursorName = "";
189 }
190
191 function updateBorders() {
192 leftBorder = mouseX <= borderThickness;
193 rightBorder = mouseX >= width - borderThickness;
194 topBorder = mouseY <= borderThickness;
195 bottomBorder = mouseY >= height - borderThickness;
196 }
197 }
198
199 Timer {
200 id: resetBordersToMoveTimer
201 interval: 2000
202 onTriggered: {
203 d.moveLeftBorder = false;
204 d.moveTopBorder = false;
205 }
206 }
207
208 onPressedChanged: {
209 if (pressed) {
210 d.updateBorders();
211 resetBordersToMoveTimer.stop();
212 d.moveLeftBorder = d.leftBorder;
213 d.moveTopBorder = d.topBorder;
214
215 var pos = mapToItem(root.target.parent, mouseX, mouseY);
216 d.startMousePosX = pos.x;
217 d.startMousePosY = pos.y;
218 d.startX = target.windowedX;
219 d.startY = target.windowedY;
220 d.startWidth = target.width;
221 d.startHeight = target.height;
222 d.currentWidth = target.width;
223 d.currentHeight = target.height;
224 d.dragging = true;
225 } else {
226 resetBordersToMoveTimer.start();
227 d.dragging = false;
228 if (containsMouse) {
229 d.updateBorders();
230 }
231 }
232 }
233
234 onEntered: {
235 if (!pressed) {
236 d.updateBorders();
237 }
238 }
239
240 onPositionChanged: {
241 if (!pressed) {
242 d.updateBorders();
243 }
244
245 if (!d.dragging) {
246 return;
247 }
248
249 var pos = mapToItem(target.parent, mouse.x, mouse.y);
250
251 var deltaX = Math.floor((pos.x - d.startMousePosX) / d.widthIncrement) * d.widthIncrement;
252 var deltaY = Math.floor((pos.y - d.startMousePosY) / d.heightIncrement) * d.heightIncrement;
253
254 if (d.leftBorder) {
255 var newTargetX = d.startX + deltaX;
256 var rightBorderX = target.windowedX + target.width;
257 if (rightBorderX > newTargetX + d.minimumWidth) {
258 if (rightBorderX < newTargetX + d.maximumWidth) {
259 target.windowedWidth = rightBorderX - newTargetX;
260 } else {
261 target.windowedWidth = d.maximumWidth;
262 }
263 } else {
264 target.windowedWidth = d.minimumWidth;
265 }
266
267 } else if (d.rightBorder) {
268 var newWidth = d.startWidth + deltaX;
269 if (newWidth > d.minimumWidth) {
270 if (newWidth < d.maximumWidth) {
271 target.windowedWidth = newWidth;
272 } else {
273 target.windowedWidth = d.maximumWidth;
274 }
275 } else {
276 target.windowedWidth = d.minimumWidth;
277 }
278 }
279
280 if (d.topBorder) {
281 var bounds = boundsItem.mapToItem(target.parent, 0, 0, boundsItem.width, boundsItem.height);
282 var newTargetY = Math.max(d.startY + deltaY, bounds.y);
283 var bottomBorderY = target.windowedY + target.height;
284 if (bottomBorderY > newTargetY + d.minimumHeight) {
285 if (bottomBorderY < newTargetY + d.maximumHeight) {
286 target.windowedHeight = bottomBorderY - newTargetY;
287 } else {
288 target.windowedHeight = d.maximumHeight;
289 }
290 } else {
291 target.windowedHeight = d.minimumHeight;
292 }
293
294 } else if (d.bottomBorder) {
295 var newHeight = d.startHeight + deltaY;
296 if (newHeight > d.minimumHeight) {
297 if (newHeight < d.maximumHeight) {
298 target.windowedHeight = newHeight;
299 } else {
300 target.windowedHeight = d.maximumHeight;
301 }
302 } else {
303 target.windowedHeight = d.minimumHeight;
304 }
305 }
306 }
307
308 Connections {
309 target: root.target
310 onWidthChanged: {
311 if (d.moveLeftBorder) {
312 target.windowedX += d.currentWidth - target.width;
313 }
314 d.currentWidth = target.width;
315 }
316 onHeightChanged: {
317 if (d.moveTopBorder) {
318 target.windowedY += d.currentHeight - target.height;
319 }
320 d.currentHeight = target.height;
321 }
322 }
323}