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