Unity 8
MoveHandler.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 Unity.Application 0.1 // For Mir singleton
19 import Ubuntu.Components 1.3
20 import Utils 0.1
21 import "../Components"
22 import "../Components/PanelState"
23 
24 QtObject {
25  id: root
26 
27  property Item target // appDelegate
28  property int stageWidth
29  property int stageHeight
30  property real buttonsWidth: 0
31 
32  readonly property bool dragging: priv.dragging
33 
34  signal fakeMaximizeAnimationRequested(real amount)
35  signal fakeMaximizeLeftAnimationRequested(real amount)
36  signal fakeMaximizeRightAnimationRequested(real amount)
37  signal fakeMaximizeTopLeftAnimationRequested(real amount)
38  signal fakeMaximizeTopRightAnimationRequested(real amount)
39  signal fakeMaximizeBottomLeftAnimationRequested(real amount)
40  signal fakeMaximizeBottomRightAnimationRequested(real amount)
41  signal stopFakeAnimation()
42 
43  property QtObject priv: QtObject {
44  property real distanceX
45  property real distanceY
46  property bool dragging
47 
48  readonly property int triggerArea: units.gu(8)
49  property bool nearLeftEdge: target.maximizedLeft
50  property bool nearTopEdge: target.maximized
51  property bool nearRightEdge: target.maximizedRight
52  property bool nearTopLeftCorner: target.maximizedTopLeft
53  property bool nearTopRightCorner: target.maximizedTopRight
54  property bool nearBottomLeftCorner: target.maximizedBottomLeft
55  property bool nearBottomRightCorner: target.maximizedBottomRight
56 
57  function resetEdges() {
58  nearLeftEdge = false;
59  nearRightEdge = false;
60  nearTopEdge = false;
61  nearTopLeftCorner = false;
62  nearTopRightCorner = false;
63  nearBottomLeftCorner = false;
64  nearBottomRightCorner = false;
65  }
66 
67  // return the progress of mouse pointer movement from 0 to 1 within a corner square of the screen
68  // 0 -> before the mouse enters the square
69  // 1 -> mouse is in the outer corner
70  // a is the corner, b is the mouse pos
71  function progressInCorner(ax, ay, bx, by) {
72  // distance of two points, a and b, in pixels
73  var distance = Math.sqrt(Math.pow(bx-ax, 2) + Math.pow(by-ay, 2));
74  // length of the triggerArea square diagonal
75  var diagLength = Math.sqrt(2 * priv.triggerArea * priv.triggerArea);
76  var ratio = 1 - (distance / diagLength);
77  return bx > 0 && bx <= stageWidth && by > 0 && by <= stageHeight ? ratio : 1; // everything "outside" of our square from the center is 1
78  }
79  property real progress: 0
80  }
81 
82  function handlePressedChanged(pressed, pressedButtons, mouseX, mouseY) {
83  if (pressed && pressedButtons === Qt.LeftButton) {
84  var pos = mapToItem(target, mouseX, mouseY);
85  if (target.anyMaximized) {
86  // keep distanceX relative to the normal window width minus the window control buttons (+spacing)
87  // so that dragging it back doesn't make the window jump around to weird positions, away from the mouse pointer
88  priv.distanceX = MathUtils.clampAndProject(pos.x, 0, target.width, buttonsWidth, target.resizeArea.normalWidth);
89  priv.distanceY = MathUtils.clampAndProject(pos.y, 0, target.height, 0, target.resizeArea.normalHeight);
90  } else {
91  priv.distanceX = pos.x;
92  priv.distanceY = pos.y;
93  }
94 
95  priv.dragging = true;
96  } else {
97  priv.dragging = false;
98  Mir.cursorName = "";
99  }
100  }
101 
102  function handlePositionChanged(mouse, sensingPoints) {
103  if (priv.dragging) {
104  Mir.cursorName = "grabbing";
105 
106  if (target.anyMaximized) { // restore from maximized when dragging away from edges/corners
107  priv.progress = 0;
108  target.restore(false, WindowStateStorage.WindowStateNormal);
109  }
110 
111  var pos = mapToItem(target.parent, mouse.x, mouse.y);
112  // Use integer coordinate values to ensure that target is left in a pixel-aligned
113  // position. Mouse movement could have subpixel precision, yielding a fractional
114  // mouse position.
115  target.requestedX = Math.round(pos.x - priv.distanceX);
116  target.requestedY = Math.round(Math.max(pos.y - priv.distanceY, PanelState.panelHeight));
117 
118  if (sensingPoints) { // edge/corner detection when dragging via the touch overlay
119  if (sensingPoints.topLeft.x < priv.triggerArea && sensingPoints.topLeft.y < PanelState.panelHeight + priv.triggerArea
120  && target.canBeCornerMaximized) { // top left
121  priv.progress = priv.progressInCorner(0, PanelState.panelHeight, sensingPoints.topLeft.x, sensingPoints.topLeft.y);
122  priv.resetEdges();
123  priv.nearTopLeftCorner = true;
124  root.fakeMaximizeTopLeftAnimationRequested(priv.progress);
125  } else if (sensingPoints.topRight.x > stageWidth - priv.triggerArea && sensingPoints.topRight.y < PanelState.panelHeight + priv.triggerArea
126  && target.canBeCornerMaximized) { // top right
127  priv.progress = priv.progressInCorner(stageWidth, PanelState.panelHeight, sensingPoints.topRight.x, sensingPoints.topRight.y);
128  priv.resetEdges();
129  priv.nearTopRightCorner = true;
130  root.fakeMaximizeTopRightAnimationRequested(priv.progress);
131  } else if (sensingPoints.bottomLeft.x < priv.triggerArea && sensingPoints.bottomLeft.y > stageHeight - priv.triggerArea
132  && target.canBeCornerMaximized) { // bottom left
133  priv.progress = priv.progressInCorner(0, stageHeight, sensingPoints.bottomLeft.x, sensingPoints.bottomLeft.y);
134  priv.resetEdges();
135  priv.nearBottomLeftCorner = true;
136  root.fakeMaximizeBottomLeftAnimationRequested(priv.progress);
137  } else if (sensingPoints.bottomRight.x > stageWidth - priv.triggerArea && sensingPoints.bottomRight.y > stageHeight - priv.triggerArea
138  && target.canBeCornerMaximized) { // bottom right
139  priv.progress = priv.progressInCorner(stageWidth, stageHeight, sensingPoints.bottomRight.x, sensingPoints.bottomRight.y);
140  priv.resetEdges();
141  priv.nearBottomRightCorner = true;
142  root.fakeMaximizeBottomRightAnimationRequested(priv.progress);
143  } else if (sensingPoints.left.x < priv.triggerArea && target.canBeMaximizedLeftRight) { // left
144  priv.progress = MathUtils.clampAndProject(sensingPoints.left.x, priv.triggerArea, 0, 0, 1);
145  priv.resetEdges();
146  priv.nearLeftEdge = true;
147  root.fakeMaximizeLeftAnimationRequested(priv.progress);
148  } else if (sensingPoints.right.x > stageWidth - priv.triggerArea && target.canBeMaximizedLeftRight) { // right
149  priv.progress = MathUtils.clampAndProject(sensingPoints.right.x, stageWidth - priv.triggerArea, stageWidth, 0, 1);
150  priv.resetEdges();
151  priv.nearRightEdge = true;
152  root.fakeMaximizeRightAnimationRequested(priv.progress);
153  } else if (sensingPoints.top.y < PanelState.panelHeight + priv.triggerArea && target.canBeMaximized) { // top
154  priv.progress = MathUtils.clampAndProject(sensingPoints.top.y, PanelState.panelHeight + priv.triggerArea, 0, 0, 1);
155  priv.resetEdges();
156  priv.nearTopEdge = true;
157  root.fakeMaximizeAnimationRequested(priv.progress);
158  } else if (priv.nearLeftEdge || priv.nearRightEdge || priv.nearTopEdge || priv.nearTopLeftCorner || priv.nearTopRightCorner ||
159  priv.nearBottomLeftCorner || priv.nearBottomRightCorner) {
160  priv.progress = 0;
161  priv.resetEdges();
162  root.stopFakeAnimation();
163  }
164  }
165  }
166  }
167 
168  function handleReleased(touchMode) {
169  if (touchMode) {
170  priv.progress = 0;
171  priv.resetEdges();
172  }
173  if ((target.state == "normal" || target.state == "restored") && priv.progress == 0) {
174  // save the x/y to restore to
175  target.restoredX = target.x;
176  target.restoredY = target.y;
177  }
178  }
179 }