Unity 8
WindowResizeArea.qml
1 /*
2  * Copyright (C) 2014-2015 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 import "../Components/PanelState"
22 
23 MouseArea {
24  id: root
25  anchors.fill: target
26  anchors.margins: -borderThickness
27 
28  hoverEnabled: target && !target.maximized // don't grab the resize under the panel
29 
30  property var windowStateStorage: WindowStateStorage
31 
32  // The target item managed by this. Must be a parent or a sibling
33  // The area will anchor to it and manage move and resize events
34  property Item target: null
35  property string windowId: ""
36  property int borderThickness: 0
37  property int minWidth: 0
38  property int minHeight: 0
39  property int defaultWidth: units.gu(60)
40  property int defaultHeight: units.gu(50)
41  property int screenWidth: 0
42  property int screenHeight: 0
43 
44  QtObject {
45  id: priv
46  objectName: "priv"
47 
48  property int normalX: 0
49  property int normalY: 0
50  property int normalWidth: 0
51  property int normalHeight: 0
52 
53  function updateNormalGeometry() {
54  if (root.target.state == "normal") {
55  normalX = root.target.x
56  normalY = root.target.y
57  normalWidth = root.target.width
58  normalHeight = root.target.height
59  }
60  }
61  }
62 
63  Connections {
64  target: root.target
65  onXChanged: priv.updateNormalGeometry();
66  onYChanged: priv.updateNormalGeometry();
67  onWidthChanged: priv.updateNormalGeometry();
68  onHeightChanged: priv.updateNormalGeometry();
69  }
70 
71  Component.onCompleted: {
72  var windowGeometry = windowStateStorage.getGeometry(root.windowId,
73  Qt.rect(target.x, target.y, defaultWidth, defaultHeight));
74 
75  target.requestedWidth = Math.min(Math.max(windowGeometry.width, minWidth), screenWidth);
76  target.requestedHeight = Math.min(Math.max(windowGeometry.height, minHeight), root.screenHeight - PanelState.panelHeight);
77  target.x = Math.max(Math.min(windowGeometry.x, root.screenWidth - target.requestedWidth), 0)
78  target.y = Math.max(Math.min(windowGeometry.y, root.screenHeight - target.requestedHeight), PanelState.panelHeight)
79 
80  var windowState = windowStateStorage.getState(root.windowId, WindowStateStorage.WindowStateNormal)
81  if (windowState === WindowStateStorage.WindowStateMaximized) {
82  target.maximize(false)
83  }
84  priv.updateNormalGeometry();
85  }
86 
87  Component.onDestruction: {
88  windowStateStorage.saveState(root.windowId, target.state == "maximized" ? WindowStateStorage.WindowStateMaximized : WindowStateStorage.WindowStateNormal)
89  windowStateStorage.saveGeometry(root.windowId, Qt.rect(priv.normalX, priv.normalY, priv.normalWidth, priv.normalHeight))
90  }
91 
92  QtObject {
93  id: d
94  property bool leftBorder: false
95  property bool rightBorder: false
96  property bool topBorder: false
97  property bool bottomBorder: false
98 
99  // true - A change in surface size will cause the left border of the window to move accordingly.
100  // The window's right border will stay in the same position.
101  // false - a change in surface size will cause the right border of the window to move accordingly.
102  // The window's left border will stay in the same position.
103  property bool moveLeftBorder: false
104 
105  // true - A change in surface size will cause the top border of the window to move accordingly.
106  // The window's bottom border will stay in the same position.
107  // false - a change in surface size will cause the bottom border of the window to move accordingly.
108  // The window's top border will stay in the same position.
109  property bool moveTopBorder: false
110 
111  property bool dragging: false
112  property real startMousePosX
113  property real startMousePosY
114  property real startX
115  property real startY
116  property real startWidth
117  property real startHeight
118  property real currentWidth
119  property real currentHeight
120 
121  property string cursorName: {
122  if (root.containsMouse || root.pressed) {
123  if (leftBorder && !topBorder && !bottomBorder) {
124  return "left_side";
125  } else if (rightBorder && !topBorder && !bottomBorder) {
126  return "right_side";
127  } else if (topBorder && !leftBorder && !rightBorder) {
128  return "top_side";
129  } else if (bottomBorder && !leftBorder && !rightBorder) {
130  return "bottom_side";
131  } else if (leftBorder && topBorder) {
132  return "top_left_corner";
133  } else if (leftBorder && bottomBorder) {
134  return "bottom_left_corner";
135  } else if (rightBorder && topBorder) {
136  return "top_right_corner";
137  } else if (rightBorder && bottomBorder) {
138  return "bottom_right_corner";
139  } else {
140  return "";
141  }
142  } else {
143  return "";
144  }
145  }
146  onCursorNameChanged: {
147  Mir.cursorName = cursorName;
148  }
149 
150  function updateBorders() {
151  leftBorder = mouseX <= borderThickness;
152  rightBorder = mouseX >= width - borderThickness;
153  topBorder = mouseY <= borderThickness;
154  bottomBorder = mouseY >= height - borderThickness;
155  }
156  }
157 
158  Timer {
159  id: resetBordersToMoveTimer
160  interval: 2000
161  onTriggered: {
162  d.moveLeftBorder = false;
163  d.moveTopBorder = false;
164  }
165  }
166 
167  onPressedChanged: {
168  var pos = mapToItem(target.parent, mouseX, mouseY);
169 
170  if (pressed) {
171  d.updateBorders();
172  resetBordersToMoveTimer.stop();
173  d.moveLeftBorder = d.leftBorder;
174  d.moveTopBorder = d.topBorder;
175 
176  var pos = mapToItem(root.target.parent, mouseX, mouseY);
177  d.startMousePosX = pos.x;
178  d.startMousePosY = pos.y;
179  d.startX = target.x;
180  d.startY = target.y;
181  d.startWidth = target.width;
182  d.startHeight = target.height;
183  d.currentWidth = target.width;
184  d.currentHeight = target.height;
185  d.dragging = true;
186  } else {
187  resetBordersToMoveTimer.start();
188  d.dragging = false;
189  if (containsMouse) {
190  d.updateBorders();
191  }
192  }
193  }
194 
195  onEntered: {
196  if (!pressed) {
197  d.updateBorders();
198  }
199  }
200 
201  onPositionChanged: {
202  if (!pressed) {
203  d.updateBorders();
204  }
205 
206  if (!d.dragging) {
207  return;
208  }
209 
210  var pos = mapToItem(target.parent, mouse.x, mouse.y);
211 
212  var deltaX = pos.x - d.startMousePosX;
213  var deltaY = pos.y - d.startMousePosY;
214 
215  if (d.leftBorder) {
216  var newTargetX = d.startX + deltaX;
217  if (target.x + target.width > newTargetX + minWidth) {
218  target.requestedWidth = target.x + target.width - newTargetX;
219  } else {
220  target.requestedWidth = minWidth;
221  }
222 
223  } else if (d.rightBorder) {
224  if (d.startWidth + deltaX >= minWidth) {
225  target.requestedWidth = d.startWidth + deltaX;
226  } else {
227  target.requestedWidth = minWidth;
228  }
229  }
230 
231  if (d.topBorder) {
232  var newTargetY = d.startY + deltaY;
233  if (target.y + target.height > newTargetY + minHeight) {
234  target.requestedHeight = target.y + target.height - newTargetY;
235  } else {
236  target.requestedHeight = minHeight;
237  }
238 
239  } else if (d.bottomBorder) {
240  if (d.startHeight + deltaY >= minHeight) {
241  target.requestedHeight = d.startHeight + deltaY;
242  } else {
243  target.requestedHeight = minHeight;
244  }
245  }
246  }
247 
248  Connections {
249  target: root.target
250  onWidthChanged: {
251  if (d.moveLeftBorder) {
252  target.x += d.currentWidth - target.width;
253  }
254  d.currentWidth = target.width;
255  }
256  onHeightChanged: {
257  if (d.moveTopBorder) {
258  target.y += d.currentHeight - target.height;
259  }
260  d.currentHeight = target.height;
261  }
262  }
263 }