on xenial help sign in
refactor window moving logic into a dedicated component - MoveHandler
commit24430f3c04a98665639a763877f11869c3ea2957 authorLukáš Tinkl <lukas.tinkl@canonical.com> dateTue 23 Aug 2016, 22:38 (10 years ago) parentf6ec03f
3 files changed+233−188
qml/Stages/DecoratedWindow.qml+17−2 qml/Stages/MoveHandler.qml+214 qml/Stages/WindowDecoration.qml+2−186
qml/Stages/DecoratedWindow.qml+17−2
@@ -52,8 +52,8 @@ FocusScope {
5252 readonly property int heightIncrement: !counterRotate ? applicationWindow.heightIncrement : applicationWindow.widthIncrement
5353
5454 property alias overlayShown: decoration.overlayShown
55− property alias stageWidth: decoration.stageWidth
56− property alias stageHeight: decoration.stageHeight
55+ property alias stageWidth: moveHandler.stageWidth
56+ property alias stageHeight: moveHandler.stageHeight
5757
5858 signal closeClicked()
5959 signal maximizeClicked()
@@ -113,6 +113,19 @@ FocusScope {
113113 onMinimizeClicked: root.minimizeClicked();
114114 onPressed: root.decorationPressed();
115115
116+ onDoubleClicked: moveHandler.handleDoubleClicked(mouse)
117+ onPressedChanged: moveHandler.handlePressedChanged(pressed, pressedButtons, mouseX, mouseY)
118+ onPositionChanged: moveHandler.handlePositionChanged(mouseX, mouseY)
119+ onReleased: moveHandler.handleReleased(mouse)
120+ }
121+
122+ MoveHandler {
123+ id: moveHandler
124+ anchors.fill: decoration
125+ objectName: "moveHandler"
126+ target: root.parent
127+ buttonsWidth: decoration.buttonsWidth
128+
116129 onFakeMaximizeAnimationRequested: root.fakeMaximizeAnimationRequested(progress)
117130 onFakeMaximizeLeftAnimationRequested: root.fakeMaximizeLeftAnimationRequested(progress)
118131 onFakeMaximizeRightAnimationRequested: root.fakeMaximizeRightAnimationRequested(progress)
@@ -121,6 +134,8 @@ FocusScope {
121134 onFakeMaximizeBottomLeftAnimationRequested: root.fakeMaximizeBottomLeftAnimationRequested(progress)
122135 onFakeMaximizeBottomRightAnimationRequested: root.fakeMaximizeBottomRightAnimationRequested(progress)
123136 onStopFakeAnimation: root.stopFakeAnimation()
137+
138+ onDoubleClicked: root.maximizeClicked()
124139 }
125140
126141 ApplicationWindow {
qml/Stages/MoveHandler.qmlnew file+214
@@ -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+}
qml/Stages/WindowDecoration.qml+2−186
@@ -15,11 +15,8 @@
1515 */
1616
1717import QtQuick 2.4
18−import Unity.Application 0.1 // For Mir singleton
1918import Ubuntu.Components 1.3
20−import Utils 0.1
2119import "../Components"
22−import "../Components/PanelState"
2320
2421MouseArea {
2522 id: root
@@ -30,8 +27,8 @@ MouseArea {
3027 property alias maximizeButtonShown: buttons.maximizeButtonShown
3128 property bool active: false
3229 property alias overlayShown: buttons.overlayShown
33− property int stageWidth
34− property int stageHeight
30+
31+ readonly property real buttonsWidth: buttons.width + row.spacing
3532
3633 acceptedButtons: Qt.AllButtons // prevent leaking unhandled mouse events
3734 hoverEnabled: true
@@ -42,187 +39,6 @@ MouseArea {
4239 signal maximizeHorizontallyClicked()
4340 signal maximizeVerticallyClicked()
4441
45− signal fakeMaximizeAnimationRequested(real progress)
46− signal fakeMaximizeLeftAnimationRequested(real progress)
47− signal fakeMaximizeRightAnimationRequested(real progress)
48− signal fakeMaximizeTopLeftAnimationRequested(real progress)
49− signal fakeMaximizeTopRightAnimationRequested(real progress)
50− signal fakeMaximizeBottomLeftAnimationRequested(real progress)
51− signal fakeMaximizeBottomRightAnimationRequested(real progress)
52− signal stopFakeAnimation()
53−
54− onDoubleClicked: {
55− priv.resetEdges();
56− if (target.canBeMaximized && mouse.button == Qt.LeftButton) {
57− priv.dragging = false; // do not interfere with a quick double click followed by a mouse move/drag
58− root.maximizeClicked();
59− }
60− }
61−
62− QtObject {
63− id: priv
64− property real distanceX
65− property real distanceY
66− property bool dragging
67−
68− readonly property int triggerArea: units.gu(3)
69− property bool nearLeftEdge: target.maximizedLeft
70− property bool nearTopEdge: target.maximized
71− property bool nearRightEdge: target.maximizedRight
72− property bool nearTopLeftCorner: target.maximizedTopLeft
73− property bool nearTopRightCorner: target.maximizedTopRight
74− property bool nearBottomLeftCorner: target.maximizedBottomLeft
75− property bool nearBottomRightCorner: target.maximizedBottomRight
76−
77− function resetEdges() {
78− nearLeftEdge = false;
79− nearRightEdge = false;
80− nearTopEdge = false;
81− nearTopLeftCorner = false;
82− nearTopRightCorner = false;
83− nearBottomLeftCorner = false;
84− nearBottomRightCorner = false;
85− }
86−
87− // return the progress of mouse pointer movement from 0 to 1 within a corner square of the screen
88− // 0 -> before the mouse enters the square
89− // 1 -> mouse is in the outer corner
90− // a is the corner, b is the mouse pos
91− function progressInCorner(ax, ay, bx, by) {
92− // distance of two points, a and b, in pixels
93− var distance = Math.sqrt(Math.pow(bx-ax, 2) + Math.pow(by-ay, 2));
94− // length of the triggerArea square diagonal
95− var diagLength = Math.sqrt(2 * priv.triggerArea * priv.triggerArea);
96− var ratio = 1 - (distance / diagLength);
97− return bx > 0 && bx <= stageWidth && by > 0 && by <= stageHeight ? ratio : 1; // everything "outside" of our square from the center is 1
98− }
99− property real progress: 0
100− }
101−
102− onPressedChanged: {
103− if (pressed && pressedButtons == Qt.LeftButton) {
104− var pos = mapToItem(root.target, mouseX, mouseY);
105− if (target.anyMaximized) {
106− // keep distanceX relative to the normal window width minus the window control buttons (+spacing)
107− // so that dragging it back doesn't make the window jump around to weird positions, away from the mouse pointer
108− priv.distanceX = MathUtils.clampAndProject(pos.x, 0, root.target.width, buttons.width + row.spacing, root.target.resizeArea.normalWidth);
109− } else {
110− priv.distanceX = pos.x;
111− }
112−
113− priv.distanceY = pos.y;
114− priv.dragging = true;
115− } else {
116− priv.dragging = false;
117− Mir.cursorName = "";
118− }
119− }
120−
121− onPositionChanged: {
122− if (priv.dragging) {
123− Mir.cursorName = "grabbing";
124−
125− if (target.anyMaximized) { // restore from maximized when dragging away from edges/corners
126− priv.progress = 0;
127− target.restore(false, WindowStateStorage.WindowStateNormal);
128− }
129−
130− var pos = mapToItem(root.target.parent, mouseX, mouseY);
131− // Use integer coordinate values to ensure that target is left in a pixel-aligned
132− // position. Mouse movement could have subpixel precision, yielding a fractional
133− // mouse position.
134− root.target.requestedX = Math.round(pos.x - priv.distanceX);
135− root.target.requestedY = Math.round(Math.max(pos.y - priv.distanceY, PanelState.panelHeight));
136−
137− var globalPos = mapToItem(null, mouseX, mouseY);
138− var globalX = globalPos.x;
139− var globalY = globalPos.y;
140− if (globalX < priv.triggerArea && globalY < PanelState.panelHeight) { // top left
141− if (target.canBeCornerMaximized) {
142− priv.progress = priv.progressInCorner(0, PanelState.panelHeight, globalX, globalY);
143− priv.resetEdges();
144− priv.nearTopLeftCorner = true;
145− root.fakeMaximizeTopLeftAnimationRequested(priv.progress);
146− }
147− } else if (globalX > stageWidth - priv.triggerArea && globalY < PanelState.panelHeight) { // top right
148− if (target.canBeCornerMaximized) {
149− priv.progress = priv.progressInCorner(stageWidth, PanelState.panelHeight, globalX, globalY);
150− priv.resetEdges();
151− priv.nearTopRightCorner = true;
152− root.fakeMaximizeTopRightAnimationRequested(priv.progress);
153− }
154− } else if (globalX < priv.triggerArea && globalY > stageHeight - priv.triggerArea) { // bottom left
155− if (target.canBeCornerMaximized) {
156− priv.progress = priv.progressInCorner(0, stageHeight, globalX, globalY);
157− priv.resetEdges();
158− priv.nearBottomLeftCorner = true;
159− root.fakeMaximizeBottomLeftAnimationRequested(priv.progress);
160− }
161− } else if (globalX > stageWidth - priv.triggerArea && globalY > stageHeight - priv.triggerArea) { // bottom right
162− if (target.canBeCornerMaximized) {
163− priv.progress = priv.progressInCorner(stageWidth, stageHeight, globalX, globalY);
164− priv.resetEdges();
165− priv.nearBottomRightCorner = true;
166− root.fakeMaximizeBottomRightAnimationRequested(priv.progress);
167− }
168− } else if (globalX < priv.triggerArea) { // left
169− if (target.canBeMaximizedLeftRight) {
170− priv.progress = MathUtils.clampAndProject(globalX, priv.triggerArea, 0, 0, 1);
171− priv.resetEdges();
172− priv.nearLeftEdge = true;
173− root.fakeMaximizeLeftAnimationRequested(priv.progress);
174− }
175− } else if (globalX > stageWidth - priv.triggerArea) { // right
176− if (target.canBeMaximizedLeftRight) {
177− priv.progress = MathUtils.clampAndProject(globalX, stageWidth - priv.triggerArea, stageWidth, 0, 1);
178− priv.resetEdges();
179− priv.nearRightEdge = true;
180− root.fakeMaximizeRightAnimationRequested(priv.progress);
181− }
182− } else if (globalY < PanelState.panelHeight) { // top
183− if (target.canBeMaximized) {
184− priv.progress = MathUtils.clampAndProject(globalY, Math.max(PanelState.panelHeight, priv.triggerArea), 0, 0, 1);
185− priv.resetEdges();
186− priv.nearTopEdge = true;
187− root.fakeMaximizeAnimationRequested(priv.progress);
188− }
189− } else if (priv.nearLeftEdge || priv.nearRightEdge || priv.nearTopEdge || priv.nearTopLeftCorner || priv.nearTopRightCorner ||
190− priv.nearBottomLeftCorner || priv.nearBottomRightCorner) {
191− print("!!! Exited")
192− priv.progress = 0;
193− priv.resetEdges();
194− root.stopFakeAnimation();
195− }
196− }
197− }
198−
199− onReleased: {
200− print("Mouse released (left/top/right)", priv.nearLeftEdge, priv.nearTopEdge, priv.nearRightEdge)
201− if (mouse.button == Qt.LeftButton && (target.state == "normal" || target.state == "restored") && priv.progress == 0) {
202− // save the x/y to restore to
203− target.restoredX = target.x;
204− target.restoredY = target.y;
205− } else if (priv.progress < 0.3) { // cancel the preview shape if under 30%
206− priv.progress = 0;
207− priv.resetEdges();
208− root.stopFakeAnimation();
209− } else if (priv.nearLeftEdge) {
210− target.maximizeLeft();
211− } else if (priv.nearTopEdge) {
212− target.maximize();
213− } else if (priv.nearRightEdge) {
214− target.maximizeRight();
215− } else if (priv.nearTopLeftCorner) {
216− target.maximizeTopLeft();
217− } else if (priv.nearTopRightCorner) {
218− target.maximizeTopRight();
219− } else if (priv.nearBottomLeftCorner) {
220− target.maximizeBottomLeft();
221− } else if (priv.nearBottomRightCorner) {
222− target.maximizeBottomRight();
223− }
224− }
225−
22642 // do not let unhandled wheel event pass thru the decoration
22743 onWheel: wheel.accepted = true;
22844