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  property Timer mouseDownTimer: Timer {
58  interval: 175
59  onTriggered: Mir.cursorName = "grabbing"
60  }
61 
62  function resetEdges() {
63  nearLeftEdge = false;
64  nearRightEdge = false;
65  nearTopEdge = false;
66  nearTopLeftCorner = false;
67  nearTopRightCorner = false;
68  nearBottomLeftCorner = false;
69  nearBottomRightCorner = false;
70  }
71 
72  // return the progress of mouse pointer movement from 0 to 1 within a corner square of the screen
73  // 0 -> before the mouse enters the square
74  // 1 -> mouse is in the outer corner
75  // a is the corner, b is the mouse pos
76  function progressInCorner(ax, ay, bx, by) {
77  // distance of two points, a and b, in pixels
78  var distance = Math.sqrt(Math.pow(bx-ax, 2) + Math.pow(by-ay, 2));
79  // length of the triggerArea square diagonal
80  var diagLength = Math.sqrt(2 * priv.triggerArea * priv.triggerArea);
81  var ratio = 1 - (distance / diagLength);
82  return bx > 0 && bx <= stageWidth && by > 0 && by <= stageHeight ? ratio : 1; // everything "outside" of our square from the center is 1
83  }
84  property real progress: 0
85  }
86 
87  function handlePressedChanged(pressed, pressedButtons, mouseX, mouseY) {
88  if (pressed && pressedButtons === Qt.LeftButton) {
89  var pos = mapToItem(target, mouseX, mouseY);
90  if (target.anyMaximized) {
91  // keep distanceX relative to the normal window width minus the window control buttons (+spacing)
92  // so that dragging it back doesn't make the window jump around to weird positions, away from the mouse pointer
93  priv.distanceX = MathUtils.clampAndProject(pos.x, 0, target.width, buttonsWidth, target.normalWidth);
94  priv.distanceY = MathUtils.clampAndProject(pos.y, 0, target.height, 0, target.normalHeight);
95  } else {
96  priv.distanceX = pos.x;
97  priv.distanceY = pos.y;
98  }
99 
100  priv.dragging = true;
101  priv.mouseDownTimer.start();
102  } else {
103  priv.dragging = false;
104  priv.mouseDownTimer.stop();
105  Mir.cursorName = "";
106  }
107  }
108 
109  function handlePositionChanged(mouse, sensingPoints) {
110  if (priv.dragging) {
111  priv.mouseDownTimer.stop();
112  Mir.cursorName = "grabbing";
113 
114  // restore from maximized when dragging away from edges/corners; guard against inadvertent changes when going into maximized state
115  if (target.anyMaximized && !target.windowedTransitionRunning) {
116  priv.progress = 0;
117  target.requestRestore();
118  }
119 
120  var pos = mapToItem(target.parent, mouse.x, mouse.y);
121  // Use integer coordinate values to ensure that target is left in a pixel-aligned
122  // position. Mouse movement could have subpixel precision, yielding a fractional
123  // mouse position.
124  target.windowedX = Math.round(pos.x - priv.distanceX);
125  target.windowedY = Math.round(Math.max(pos.y - priv.distanceY, PanelState.panelHeight));
126 
127  if (sensingPoints) { // edge/corner detection when dragging via the touch overlay
128  if (sensingPoints.topLeft.x < priv.triggerArea && sensingPoints.topLeft.y < PanelState.panelHeight + priv.triggerArea
129  && target.canBeCornerMaximized) { // top left
130  priv.progress = priv.progressInCorner(0, PanelState.panelHeight, sensingPoints.topLeft.x, sensingPoints.topLeft.y);
131  priv.resetEdges();
132  priv.nearTopLeftCorner = true;
133  root.fakeMaximizeTopLeftAnimationRequested(priv.progress);
134  } else if (sensingPoints.topRight.x > stageWidth - priv.triggerArea && sensingPoints.topRight.y < PanelState.panelHeight + priv.triggerArea
135  && target.canBeCornerMaximized) { // top right
136  priv.progress = priv.progressInCorner(stageWidth, PanelState.panelHeight, sensingPoints.topRight.x, sensingPoints.topRight.y);
137  priv.resetEdges();
138  priv.nearTopRightCorner = true;
139  root.fakeMaximizeTopRightAnimationRequested(priv.progress);
140  } else if (sensingPoints.bottomLeft.x < priv.triggerArea && sensingPoints.bottomLeft.y > stageHeight - priv.triggerArea
141  && target.canBeCornerMaximized) { // bottom left
142  priv.progress = priv.progressInCorner(0, stageHeight, sensingPoints.bottomLeft.x, sensingPoints.bottomLeft.y);
143  priv.resetEdges();
144  priv.nearBottomLeftCorner = true;
145  root.fakeMaximizeBottomLeftAnimationRequested(priv.progress);
146  } else if (sensingPoints.bottomRight.x > stageWidth - priv.triggerArea && sensingPoints.bottomRight.y > stageHeight - priv.triggerArea
147  && target.canBeCornerMaximized) { // bottom right
148  priv.progress = priv.progressInCorner(stageWidth, stageHeight, sensingPoints.bottomRight.x, sensingPoints.bottomRight.y);
149  priv.resetEdges();
150  priv.nearBottomRightCorner = true;
151  root.fakeMaximizeBottomRightAnimationRequested(priv.progress);
152  } else if (sensingPoints.left.x < priv.triggerArea && target.canBeMaximizedLeftRight) { // left
153  priv.progress = MathUtils.clampAndProject(sensingPoints.left.x, priv.triggerArea, 0, 0, 1);
154  priv.resetEdges();
155  priv.nearLeftEdge = true;
156  root.fakeMaximizeLeftAnimationRequested(priv.progress);
157  } else if (sensingPoints.right.x > stageWidth - priv.triggerArea && target.canBeMaximizedLeftRight) { // right
158  priv.progress = MathUtils.clampAndProject(sensingPoints.right.x, stageWidth - priv.triggerArea, stageWidth, 0, 1);
159  priv.resetEdges();
160  priv.nearRightEdge = true;
161  root.fakeMaximizeRightAnimationRequested(priv.progress);
162  } else if (sensingPoints.top.y < PanelState.panelHeight + priv.triggerArea && target.canBeMaximized) { // top
163  priv.progress = MathUtils.clampAndProject(sensingPoints.top.y, PanelState.panelHeight + priv.triggerArea, 0, 0, 1);
164  priv.resetEdges();
165  priv.nearTopEdge = true;
166  root.fakeMaximizeAnimationRequested(priv.progress);
167  } else if (priv.nearLeftEdge || priv.nearRightEdge || priv.nearTopEdge || priv.nearTopLeftCorner || priv.nearTopRightCorner ||
168  priv.nearBottomLeftCorner || priv.nearBottomRightCorner) {
169  priv.progress = 0;
170  priv.resetEdges();
171  root.stopFakeAnimation();
172  }
173  }
174  }
175  }
176 
177  function handleReleased(touchMode) {
178  if (touchMode) {
179  priv.progress = 0;
180  priv.resetEdges();
181  }
182  if ((target.state == "normal" || target.state == "restored") && priv.progress == 0) {
183  // save the x/y to restore to
184  target.restoredX = target.x;
185  target.restoredY = target.y;
186  }
187  }
188 }