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/>.
18import QtMir.Application 0.1 // For Mir singleton
19import Lomiri.Components 1.3
26 property Item target // appDelegate
27 property real buttonsWidth: 0
28 property Item boundsItem
29 property real boundsTopMargin: 0
31 readonly property bool dragging: priv.dragging
32 readonly property bool moving: priv.moving
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
49 readonly property int triggerArea: units.gu(8)
50 property bool nearLeftEdge: target && target.maximizedLeft
51 property bool nearTopEdge: target && target.maximized
52 property bool nearRightEdge: target && target.maximizedRight
53 property bool nearTopLeftCorner: target && target.maximizedTopLeft
54 property bool nearTopRightCorner: target && target.maximizedTopRight
55 property bool nearBottomLeftCorner: target && target.maximizedBottomLeft
56 property bool nearBottomRightCorner: target && target.maximizedBottomRight
58 property Timer mouseDownTimer: Timer {
60 onTriggered: Mir.cursorName = "grabbing"
63 function resetEdges() {
65 nearRightEdge = false;
67 nearTopLeftCorner = false;
68 nearTopRightCorner = false;
69 nearBottomLeftCorner = false;
70 nearBottomRightCorner = false;
73 // return the progress of mouse pointer movement from 0 to 1 within a corner square of the screen
74 // 0 -> before the mouse enters the square
75 // 1 -> mouse is in the outer corner
76 // a is the corner, b is the mouse pos
77 function progressInCorner(ax, ay, bx, by) {
78 // distance of two points, a and b, in pixels
79 var distance = Math.sqrt(Math.pow(bx-ax, 2) + Math.pow(by-ay, 2));
80 // length of the triggerArea square diagonal
81 var diagLength = Math.sqrt(2 * priv.triggerArea * priv.triggerArea);
82 var ratio = 1 - (distance / diagLength);
84 // everything "outside" of our square from the center is 1
85 var mousePosBoundsCoords = target.mapToItem(root.boundsItem, bx, by);
86 return root.boundsItem.contains(mousePosBoundsCoords) ? ratio : 1;
88 property real progress: 0
91 function handlePressedChanged(pressed, pressedButtons, mouseX, mouseY) {
92 if (pressed && pressedButtons === Qt.LeftButton) {
93 var pos = mapToItem(target, mouseX, mouseY);
94 if (target.anyMaximized) {
95 // keep distanceX relative to the normal window width minus the window control buttons (+spacing)
96 // so that dragging it back doesn't make the window jump around to weird positions, away from the mouse pointer
97 priv.distanceX = MathUtils.clampAndProject(pos.x, 0, target.width, buttonsWidth, target.normalWidth);
98 priv.distanceY = MathUtils.clampAndProject(pos.y, 0, target.height, 0, target.normalHeight);
100 priv.distanceX = pos.x;
101 priv.distanceY = pos.y;
104 priv.dragging = true;
105 priv.mouseDownTimer.start();
107 priv.dragging = false;
108 priv.mouseDownTimer.stop();
113 function handlePositionChanged(mouse, sensingPoints) {
116 priv.mouseDownTimer.stop();
117 Mir.cursorName = "grabbing";
119 // restore from maximized when dragging away from edges/corners; guard against inadvertent changes when going into maximized state
120 if (target.anyMaximized && !target.windowedTransitionRunning) {
122 target.requestRestore();
125 var pos = mapToItem(target.parent, mouse.x, mouse.y); // How can that work if we're just a QtObject (not an Item)?
126 var bounds = boundsItem.mapToItem(target.parent, 0, 0, boundsItem.width, boundsItem.height);
127 bounds.y += boundsTopMargin;
128 bounds.height -= boundsTopMargin;
129 // Use integer coordinate values to ensure that target is left in a pixel-aligned
130 // position. Mouse movement could have subpixel precision, yielding a fractional
132 target.windowedX = Math.round(pos.x - priv.distanceX);
133 target.windowedY = Math.round(Math.max(pos.y - priv.distanceY, bounds.top));
135 if (sensingPoints) { // edge/corner detection when dragging via the touch overlay
136 if (sensingPoints.topLeft.x < priv.triggerArea && sensingPoints.topLeft.y < bounds.top + priv.triggerArea
137 && target.canBeCornerMaximized) { // top left
138 priv.progress = priv.progressInCorner(bounds.left, bounds.top, sensingPoints.topLeft.x, sensingPoints.topLeft.y);
140 priv.nearTopLeftCorner = true;
141 root.fakeMaximizeTopLeftAnimationRequested(priv.progress);
142 } else if (sensingPoints.topRight.x > bounds.right - priv.triggerArea && sensingPoints.topRight.y < bounds.top + priv.triggerArea
143 && target.canBeCornerMaximized) { // top right
144 priv.progress = priv.progressInCorner(bounds.right, bounds.top, sensingPoints.topRight.x, sensingPoints.topRight.y);
146 priv.nearTopRightCorner = true;
147 root.fakeMaximizeTopRightAnimationRequested(priv.progress);
148 } else if (sensingPoints.bottomLeft.x < priv.triggerArea && sensingPoints.bottomLeft.y > bounds.bottom - priv.triggerArea
149 && target.canBeCornerMaximized) { // bottom left
150 priv.progress = priv.progressInCorner(bounds.left, bounds.bottom, sensingPoints.bottomLeft.x, sensingPoints.bottomLeft.y);
152 priv.nearBottomLeftCorner = true;
153 root.fakeMaximizeBottomLeftAnimationRequested(priv.progress);
154 } else if (sensingPoints.bottomRight.x > bounds.right - priv.triggerArea && sensingPoints.bottomRight.y > bounds.bottom - priv.triggerArea
155 && target.canBeCornerMaximized) { // bottom right
156 priv.progress = priv.progressInCorner(bounds.right, bounds.bottom, sensingPoints.bottomRight.x, sensingPoints.bottomRight.y);
158 priv.nearBottomRightCorner = true;
159 root.fakeMaximizeBottomRightAnimationRequested(priv.progress);
160 } else if (sensingPoints.left.x < priv.triggerArea && target.canBeMaximizedLeftRight) { // left
161 priv.progress = MathUtils.clampAndProject(sensingPoints.left.x, priv.triggerArea, 0, 0, 1);
163 priv.nearLeftEdge = true;
164 root.fakeMaximizeLeftAnimationRequested(priv.progress);
165 } else if (sensingPoints.right.x > bounds.right - priv.triggerArea && target.canBeMaximizedLeftRight) { // right
166 priv.progress = MathUtils.clampAndProject(sensingPoints.right.x, bounds.right - priv.triggerArea, bounds.right, 0, 1);
168 priv.nearRightEdge = true;
169 root.fakeMaximizeRightAnimationRequested(priv.progress);
170 } else if (sensingPoints.top.y < bounds.top + priv.triggerArea && target.canBeMaximized) { // top
171 priv.progress = MathUtils.clampAndProject(sensingPoints.top.y, bounds.top + priv.triggerArea, 0, 0, 1);
173 priv.nearTopEdge = true;
174 root.fakeMaximizeAnimationRequested(priv.progress);
175 } else if (priv.nearLeftEdge || priv.nearRightEdge || priv.nearTopEdge || priv.nearTopLeftCorner || priv.nearTopRightCorner ||
176 priv.nearBottomLeftCorner || priv.nearBottomRightCorner) {
179 root.stopFakeAnimation();
185 function handleReleased(touchMode) {
193 function cancelDrag() {
194 priv.dragging = false;
195 root.stopFakeAnimation();
196 priv.mouseDownTimer.stop();