@@ -0,0 +1,214 @@
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+Item {
25+ id: root
26+
27+ property Item target // appDelegate
28+ property int stageWidth
29+ property int stageHeight
30+ property real buttonsWidth: 0
31+
32+ signal fakeMaximizeAnimationRequested(real progress)
33+ signal fakeMaximizeLeftAnimationRequested(real progress)
34+ signal fakeMaximizeRightAnimationRequested(real progress)
35+ signal fakeMaximizeTopLeftAnimationRequested(real progress)
36+ signal fakeMaximizeTopRightAnimationRequested(real progress)
37+ signal fakeMaximizeBottomLeftAnimationRequested(real progress)
38+ signal fakeMaximizeBottomRightAnimationRequested(real progress)
39+ signal stopFakeAnimation()
40+
41+ signal doubleClicked()
42+
43+ QtObject {
44+ id: priv
45+ property real distanceX
46+ property real distanceY
47+ property bool dragging
48+
49+ readonly property int triggerArea: units.gu(3)
50+ property bool nearLeftEdge: target.maximizedLeft
51+ property bool nearTopEdge: target.maximized
52+ property bool nearRightEdge: target.maximizedRight
53+ property bool nearTopLeftCorner: target.maximizedTopLeft
54+ property bool nearTopRightCorner: target.maximizedTopRight
55+ property bool nearBottomLeftCorner: target.maximizedBottomLeft
56+ property bool nearBottomRightCorner: target.maximizedBottomRight
57+
58+ function resetEdges() {
59+ nearLeftEdge = false;
60+ nearRightEdge = false;
61+ nearTopEdge = false;
62+ nearTopLeftCorner = false;
63+ nearTopRightCorner = false;
64+ nearBottomLeftCorner = false;
65+ nearBottomRightCorner = false;
66+ }
67+
68+ // return the progress of mouse pointer movement from 0 to 1 within a corner square of the screen
69+ // 0 -> before the mouse enters the square
70+ // 1 -> mouse is in the outer corner
71+ // a is the corner, b is the mouse pos
72+ function progressInCorner(ax, ay, bx, by) {
73+ // distance of two points, a and b, in pixels
74+ var distance = Math.sqrt(Math.pow(bx-ax, 2) + Math.pow(by-ay, 2));
75+ // length of the triggerArea square diagonal
76+ var diagLength = Math.sqrt(2 * priv.triggerArea * priv.triggerArea);
77+ var ratio = 1 - (distance / diagLength);
78+ return bx > 0 && bx <= stageWidth && by > 0 && by <= stageHeight ? ratio : 1; // everything "outside" of our square from the center is 1
79+ }
80+ property real progress: 0
81+ }
82+
83+ function handleDoubleClicked(mouse) {
84+ priv.resetEdges();
85+ if (target.canBeMaximized && mouse.button == Qt.LeftButton) {
86+ priv.dragging = false; // do not interfere with a quick double click followed by a mouse move/drag
87+ root.doubleClicked();
88+ }
89+ }
90+
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.resizeArea.normalWidth);
98+ } else {
99+ priv.distanceX = pos.x;
100+ }
101+
102+ priv.distanceY = pos.y;
103+ priv.dragging = true;
104+ } else {
105+ priv.dragging = false;
106+ Mir.cursorName = "";
107+ }
108+ }
109+
110+ function handlePositionChanged(mouseX, mouseY) {
111+ if (priv.dragging) {
112+ Mir.cursorName = "grabbing";
113+
114+ if (target.anyMaximized) { // restore from maximized when dragging away from edges/corners
115+ priv.progress = 0;
116+ target.restore(false, WindowStateStorage.WindowStateNormal);
117+ }
118+
119+ var pos = mapToItem(target.parent, mouseX, mouseY);
120+ // Use integer coordinate values to ensure that target is left in a pixel-aligned
121+ // position. Mouse movement could have subpixel precision, yielding a fractional
122+ // mouse position.
123+ target.requestedX = Math.round(pos.x - priv.distanceX);
124+ target.requestedY = Math.round(Math.max(pos.y - priv.distanceY, PanelState.panelHeight));
125+
126+ var globalPos = mapToItem(null, mouseX, mouseY);
127+ var globalX = globalPos.x;
128+ var globalY = globalPos.y;
129+ if (globalX < priv.triggerArea && globalY < PanelState.panelHeight) { // top left
130+ if (target.canBeCornerMaximized) {
131+ priv.progress = priv.progressInCorner(0, PanelState.panelHeight, globalX, globalY);
132+ priv.resetEdges();
133+ priv.nearTopLeftCorner = true;
134+ root.fakeMaximizeTopLeftAnimationRequested(priv.progress);
135+ }
136+ } else if (globalX > stageWidth - priv.triggerArea && globalY < PanelState.panelHeight) { // top right
137+ if (target.canBeCornerMaximized) {
138+ priv.progress = priv.progressInCorner(stageWidth, PanelState.panelHeight, globalX, globalY);
139+ priv.resetEdges();
140+ priv.nearTopRightCorner = true;
141+ root.fakeMaximizeTopRightAnimationRequested(priv.progress);
142+ }
143+ } else if (globalX < priv.triggerArea && globalY > stageHeight - priv.triggerArea) { // bottom left
144+ if (target.canBeCornerMaximized) {
145+ priv.progress = priv.progressInCorner(0, stageHeight, globalX, globalY);
146+ priv.resetEdges();
147+ priv.nearBottomLeftCorner = true;
148+ root.fakeMaximizeBottomLeftAnimationRequested(priv.progress);
149+ }
150+ } else if (globalX > stageWidth - priv.triggerArea && globalY > stageHeight - priv.triggerArea) { // bottom right
151+ if (target.canBeCornerMaximized) {
152+ priv.progress = priv.progressInCorner(stageWidth, stageHeight, globalX, globalY);
153+ priv.resetEdges();
154+ priv.nearBottomRightCorner = true;
155+ root.fakeMaximizeBottomRightAnimationRequested(priv.progress);
156+ }
157+ } else if (globalX < priv.triggerArea) { // left
158+ if (target.canBeMaximizedLeftRight) {
159+ priv.progress = MathUtils.clampAndProject(globalX, priv.triggerArea, 0, 0, 1);
160+ priv.resetEdges();
161+ priv.nearLeftEdge = true;
162+ root.fakeMaximizeLeftAnimationRequested(priv.progress);
163+ }
164+ } else if (globalX > stageWidth - priv.triggerArea) { // right
165+ if (target.canBeMaximizedLeftRight) {
166+ priv.progress = MathUtils.clampAndProject(globalX, stageWidth - priv.triggerArea, stageWidth, 0, 1);
167+ priv.resetEdges();
168+ priv.nearRightEdge = true;
169+ root.fakeMaximizeRightAnimationRequested(priv.progress);
170+ }
171+ } else if (globalY < PanelState.panelHeight) { // top
172+ if (target.canBeMaximized) {
173+ priv.progress = MathUtils.clampAndProject(globalY, Math.max(PanelState.panelHeight, priv.triggerArea), 0, 0, 1);
174+ priv.resetEdges();
175+ priv.nearTopEdge = true;
176+ root.fakeMaximizeAnimationRequested(priv.progress);
177+ }
178+ } else if (priv.nearLeftEdge || priv.nearRightEdge || priv.nearTopEdge || priv.nearTopLeftCorner || priv.nearTopRightCorner ||
179+ priv.nearBottomLeftCorner || priv.nearBottomRightCorner) {
180+ print("!!! Exited")
181+ priv.progress = 0;
182+ priv.resetEdges();
183+ root.stopFakeAnimation();
184+ }
185+ }
186+ }
187+
188+ function handleReleased(mouse) {
189+ print("Mouse released (left/top/right)", priv.nearLeftEdge, priv.nearTopEdge, priv.nearRightEdge)
190+ if (mouse.button == Qt.LeftButton && (target.state == "normal" || target.state == "restored") && priv.progress == 0) {
191+ // save the x/y to restore to
192+ target.restoredX = target.x;
193+ target.restoredY = target.y;
194+ } else if (priv.progress < 0.3) { // cancel the preview shape if under 30%
195+ priv.progress = 0;
196+ priv.resetEdges();
197+ root.stopFakeAnimation();
198+ } else if (priv.nearLeftEdge) {
199+ target.maximizeLeft();
200+ } else if (priv.nearTopEdge) {
201+ target.maximize();
202+ } else if (priv.nearRightEdge) {
203+ target.maximizeRight();
204+ } else if (priv.nearTopLeftCorner) {
205+ target.maximizeTopLeft();
206+ } else if (priv.nearTopRightCorner) {
207+ target.maximizeTopRight();
208+ } else if (priv.nearBottomLeftCorner) {
209+ target.maximizeBottomLeft();
210+ } else if (priv.nearBottomRightCorner) {
211+ target.maximizeBottomRight();
212+ }
213+ }
214+}