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.4
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 real minimumY: -100000000 // By default, impose no limit
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 
156  function updateBorders() {
157  leftBorder = mouseX <= borderThickness;
158  rightBorder = mouseX >= width - borderThickness;
159  topBorder = mouseY <= borderThickness;
160  bottomBorder = mouseY >= height - borderThickness;
161  }
162  }
163 
164  Timer {
165  id: resetBordersToMoveTimer
166  interval: 2000
167  onTriggered: {
168  d.moveLeftBorder = false;
169  d.moveTopBorder = false;
170  }
171  }
172 
173  onPressedChanged: {
174  if (pressed) {
175  d.updateBorders();
176  resetBordersToMoveTimer.stop();
177  d.moveLeftBorder = d.leftBorder;
178  d.moveTopBorder = d.topBorder;
179 
180  var pos = mapToItem(root.target.parent, mouseX, mouseY);
181  d.startMousePosX = pos.x;
182  d.startMousePosY = pos.y;
183  d.startX = target.windowedX;
184  d.startY = target.windowedY;
185  d.startWidth = target.width;
186  d.startHeight = target.height;
187  d.currentWidth = target.width;
188  d.currentHeight = target.height;
189  d.dragging = true;
190  } else {
191  resetBordersToMoveTimer.start();
192  d.dragging = false;
193  if (containsMouse) {
194  d.updateBorders();
195  }
196  }
197  }
198 
199  onEntered: {
200  if (!pressed) {
201  d.updateBorders();
202  }
203  }
204 
205  onPositionChanged: {
206  if (!pressed) {
207  d.updateBorders();
208  }
209 
210  if (!d.dragging) {
211  return;
212  }
213 
214  var pos = mapToItem(target.parent, mouse.x, mouse.y);
215 
216  var deltaX = Math.floor((pos.x - d.startMousePosX) / d.widthIncrement) * d.widthIncrement;
217  var deltaY = Math.floor((pos.y - d.startMousePosY) / d.heightIncrement) * d.heightIncrement;
218 
219  if (d.leftBorder) {
220  var newTargetX = d.startX + deltaX;
221  var rightBorderX = target.windowedX + target.width;
222  if (rightBorderX > newTargetX + d.minimumWidth) {
223  if (rightBorderX < newTargetX + d.maximumWidth) {
224  target.windowedWidth = rightBorderX - newTargetX;
225  } else {
226  target.windowedWidth = d.maximumWidth;
227  }
228  } else {
229  target.windowedWidth = d.minimumWidth;
230  }
231 
232  } else if (d.rightBorder) {
233  var newWidth = d.startWidth + deltaX;
234  if (newWidth > d.minimumWidth) {
235  if (newWidth < d.maximumWidth) {
236  target.windowedWidth = newWidth;
237  } else {
238  target.windowedWidth = d.maximumWidth;
239  }
240  } else {
241  target.windowedWidth = d.minimumWidth;
242  }
243  }
244 
245  if (d.topBorder) {
246  var newTargetY = Math.max(d.startY + deltaY, root.minimumY);
247  var bottomBorderY = target.windowedY + target.height;
248  if (bottomBorderY > newTargetY + d.minimumHeight) {
249  if (bottomBorderY < newTargetY + d.maximumHeight) {
250  target.windowedHeight = bottomBorderY - newTargetY;
251  } else {
252  target.windowedHeight = d.maximumHeight;
253  }
254  } else {
255  target.windowedHeight = d.minimumHeight;
256  }
257 
258  } else if (d.bottomBorder) {
259  var newHeight = d.startHeight + deltaY;
260  if (newHeight > d.minimumHeight) {
261  if (newHeight < d.maximumHeight) {
262  target.windowedHeight = newHeight;
263  } else {
264  target.windowedHeight = d.maximumHeight;
265  }
266  } else {
267  target.windowedHeight = d.minimumHeight;
268  }
269  }
270  }
271 
272  Connections {
273  target: root.target
274  onWidthChanged: {
275  if (d.moveLeftBorder) {
276  target.windowedX += d.currentWidth - target.width;
277  }
278  d.currentWidth = target.width;
279  }
280  onHeightChanged: {
281  if (d.moveTopBorder) {
282  target.windowedY += d.currentHeight - target.height;
283  }
284  d.currentHeight = target.height;
285  }
286  }
287 }