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/>.
18import Lomiri.Components 1.3
20import QtMir.Application 0.1 // for Mir.cursorName
25 anchors.margins: -borderThickness
27 hoverEnabled: target && !target.maximized // don't grab the resize under the panel
29 readonly property alias dragging: d.dragging
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
39 property bool readyToAssesBounds: false
40 onReadyToAssesBoundsChanged: d.reassesBounds()
42 signal pressedChangedEx(bool pressed, point mouse)
43 signal positionChangedEx(point mouse)
48 readonly property int maxSafeInt: 2147483647
49 readonly property int maxSizeIncrement: units.gu(40)
51 function reassesBounds() {
52 if (!readyToAssesBounds) return;
54 if (target.windowedWidth < minimumWidth) {
55 target.windowedWidth = minimumWidth;
57 if (target.windowedHeight < minimumHeight) {
58 target.windowedHeight = minimumHeight;
60 if (target.windowedHeight < minimumHeight) {
61 target.windowedHeight = minimumHeight;
63 if (target.windowedWidth > maximumWidth) {
64 target.windowedWidth = maximumWidth;
66 if (target.windowedHeight > maximumHeight) {
67 target.windowedHeight = maximumHeight;
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;
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;
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;
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;
97 readonly property int widthIncrement: {
101 if (root.target.widthIncrement > 0) {
102 if (root.target.widthIncrement < maxSizeIncrement) {
103 return root.target.widthIncrement;
105 return maxSizeIncrement;
111 readonly property int heightIncrement: {
115 if (root.target.heightIncrement > 0) {
116 if (root.target.heightIncrement < maxSizeIncrement) {
117 return root.target.heightIncrement;
119 return maxSizeIncrement;
126 property bool leftBorder: false
127 property bool rightBorder: false
128 property bool topBorder: false
129 property bool bottomBorder: false
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
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
143 property bool dragging: false
144 property bool externalDragging: false
145 property real startMousePosX
146 property real startMousePosY
149 property real startWidth
150 property real startHeight
151 property real currentWidth
152 property real currentHeight
154 readonly property string cursorName: {
155 if (root.containsMouse || root.pressed || d.externalDragging) {
156 if (leftBorder && !topBorder && !bottomBorder) {
158 } else if (rightBorder && !topBorder && !bottomBorder) {
160 } else if (topBorder && !leftBorder && !rightBorder) {
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";
179 onCursorNameChanged: {
180 Mir.cursorName = cursorName;
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 ""
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;
201 leftBorder = externalMousePos.x <= halfWidth;
202 rightBorder = externalMousePos.x > halfWidth;
203 topBorder = externalMousePos.y <= halfHeight;
204 bottomBorder = externalMousePos.y > halfHeight;
206 leftBorder = mouseX <= borderThickness;
207 rightBorder = mouseX >= width - borderThickness;
208 topBorder = mouseY <= borderThickness;
209 bottomBorder = mouseY >= height - borderThickness;
213 function startResize(isExternal, mousePos) {
215 d.externalDragging = true;
216 d.updateBorders(mousePos);
220 resetBordersToMoveTimer.stop();
221 d.moveLeftBorder = d.leftBorder;
222 d.moveTopBorder = d.topBorder;
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;
236 function endResize(isExternal) {
237 resetBordersToMoveTimer.start();
241 d.externalDragging = false;
242 } else if (containsMouse) {
247 function updateResize(mouse) {
252 var pos = mapToItem(target.parent, mouse.x, mouse.y);
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;
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;
264 target.windowedWidth = d.maximumWidth;
267 target.windowedWidth = d.minimumWidth;
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;
276 target.windowedWidth = d.maximumWidth;
279 target.windowedWidth = d.minimumWidth;
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;
291 target.windowedHeight = d.maximumHeight;
294 target.windowedHeight = d.minimumHeight;
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;
303 target.windowedHeight = d.maximumHeight;
306 target.windowedHeight = d.minimumHeight;
313 id: resetBordersToMoveTimer
316 d.moveLeftBorder = false;
317 d.moveTopBorder = false;
321 onPressedChangedEx: {
323 d.startResize(true, mouse);
325 d.endResize(true, mouse);
329 const mousePos = Qt.point(mouseX, mouseY);
331 d.startResize(false, mousePos);
333 d.endResize(false, mousePos);
338 if (!pressed && !d.externalDragging) {
343 onPositionChangedEx: d.updateResize(mouse);
349 d.updateResize(mouse);
354 function onWidthChanged() {
355 if (d.moveLeftBorder) {
356 target.windowedX += d.currentWidth - target.width;
358 d.currentWidth = target.width;
360 function onHeightChanged() {
361 if (d.moveTopBorder) {
362 target.windowedY += d.currentHeight - target.height;
364 d.currentHeight = target.height;