2 * Copyright (C) 2014-2016 Canonical, Ltd.
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.
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.
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/>.
18 import Unity.Application 0.1 // For Mir singleton
19 import Ubuntu.Components 1.3
21 import "../Components"
22 import "../Components/PanelState"
27 property Item target // appDelegate
28 property int stageWidth
29 property int stageHeight
30 property real buttonsWidth: 0
32 readonly property bool dragging: priv.dragging
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()
43 property QtObject priv: QtObject {
44 property real distanceX
45 property real distanceY
46 property bool dragging
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
57 property Timer mouseDownTimer: Timer {
59 onTriggered: Mir.cursorName = "grabbing"
62 function resetEdges() {
64 nearRightEdge = false;
66 nearTopLeftCorner = false;
67 nearTopRightCorner = false;
68 nearBottomLeftCorner = false;
69 nearBottomRightCorner = false;
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
84 property real progress: 0
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);
96 priv.distanceX = pos.x;
97 priv.distanceY = pos.y;
100 priv.dragging = true;
101 priv.mouseDownTimer.start();
103 priv.dragging = false;
104 priv.mouseDownTimer.stop();
109 function handlePositionChanged(mouse, sensingPoints) {
111 priv.mouseDownTimer.stop();
112 Mir.cursorName = "grabbing";
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) {
117 target.requestRestore();
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
124 target.windowedX = Math.round(pos.x - priv.distanceX);
125 target.windowedY = Math.round(Math.max(pos.y - priv.distanceY, PanelState.panelHeight));
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);
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);
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);
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);
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);
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);
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);
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) {
171 root.stopFakeAnimation();
177 function handleReleased(touchMode) {
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;