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 function resetEdges() {
59 nearRightEdge = false;
61 nearTopLeftCorner = false;
62 nearTopRightCorner = false;
63 nearBottomLeftCorner = false;
64 nearBottomRightCorner = false;
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
79 property real progress: 0
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);
91 priv.distanceX = pos.x;
92 priv.distanceY = pos.y;
97 priv.dragging = false;
102 function handlePositionChanged(mouse, sensingPoints) {
104 Mir.cursorName = "grabbing";
106 if (target.anyMaximized) { // restore from maximized when dragging away from edges/corners
108 target.restore(false, WindowStateStorage.WindowStateNormal);
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
115 target.requestedX = Math.round(pos.x - priv.distanceX);
116 target.requestedY = Math.round(Math.max(pos.y - priv.distanceY, PanelState.panelHeight));
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);
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);
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);
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);
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);
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);
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);
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) {
162 root.stopFakeAnimation();
168 function handleReleased(touchMode) {
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;