on xenial help sign in
implement swipe to close in spread
a5c39f1Michael Zanetti
qml/Stages/PhoneStage.qml+34−7
@@ -117,8 +117,7 @@ Item {
117 id: spreadView
117 id: spreadView
118 objectName: "spreadView"
118 objectName: "spreadView"
119 anchors.fill: parent
119 anchors.fill: parent
120−// visible: spreadDragArea.status == DirectionalDragArea.Recognized || phase > 1 || snapAnimation.running
121− interactive: spreadDragArea.status == DirectionalDragArea.Recognized || phase > 1 || snapAnimation.running
120+ interactive: (spreadDragArea.status == DirectionalDragArea.Recognized || phase > 1) && draggedIndex == -1
122 contentWidth: spreadRow.width - shift
121 contentWidth: spreadRow.width - shift
123 contentX: -shift
122 contentX: -shift
124
123
@@ -127,8 +126,8 @@ Item {
127 // that, the beginning of the gesture starts with a negative value for contentX
126 // that, the beginning of the gesture starts with a negative value for contentX
128 // so the flickable wants to pull it into the view already. "shift" tunes the
127 // so the flickable wants to pull it into the view already. "shift" tunes the
129 // distance where to "lock" the content.
128 // distance where to "lock" the content.
130− property real shift: width / 2
129+ readonly property real shift: width / 2
131− property real shiftedContentX: contentX + shift
130+ readonly property real shiftedContentX: contentX + shift
132
131
133 property int tileDistance: width / 4
132 property int tileDistance: width / 4
134
133
@@ -152,6 +151,8 @@ Item {
152 property int phase: 0
151 property int phase: 0
153
152
154 property int selectedIndex: -1
153 property int selectedIndex: -1
154+ property int draggedIndex: -1
155+ property int closingIndex: -1
155
156
156 onShiftedContentXChanged: {
157 onShiftedContentXChanged: {
157 switch (phase) {
158 switch (phase) {
@@ -221,6 +222,15 @@ Item {
221 // tileDistance * app count (with a minimum of 3 apps, in order to also allow moving 1 and 2 apps a bit)
222 // tileDistance * app count (with a minimum of 3 apps, in order to also allow moving 1 and 2 apps a bit)
222 // + some constant value (still scales with the screen width) which looks good and somewhat fills the screen
223 // + some constant value (still scales with the screen width) which looks good and somewhat fills the screen
223 width: Math.max(3, ApplicationManager.count) * spreadView.tileDistance + (spreadView.width - spreadView.tileDistance) * 1.5
224 width: Math.max(3, ApplicationManager.count) * spreadView.tileDistance + (spreadView.width - spreadView.tileDistance) * 1.5
225+ Behavior on width {
226+ enabled: spreadView.closingIndex >= 0
227+ UbuntuNumberAnimation {}
228+ }
229+ onWidthChanged: {
230+ if (spreadView.closingIndex >= 0) {
231+ spreadView.contentX = Math.min(spreadView.contentX, width - spreadView.width - spreadView.shift)
232+ }
233+ }
224
234
225 x: spreadView.contentX
235 x: spreadView.contentX
226
236
@@ -244,15 +254,26 @@ Item {
244 maximizedAppTopMargin: root.maximizedAppTopMargin
254 maximizedAppTopMargin: root.maximizedAppTopMargin
245 dropShadow: spreadView.shiftedContentX > 0 || spreadDragArea.status == DirectionalDragArea.Undecided
255 dropShadow: spreadView.shiftedContentX > 0 || spreadDragArea.status == DirectionalDragArea.Undecided
246
256
247− z: index
257+ z: behavioredIndex
248 x: index == 0 ? 0 : spreadView.width + (index - 1) * spreadView.tileDistance
258 x: index == 0 ? 0 : spreadView.width + (index - 1) * spreadView.tileDistance
259+ property real behavioredIndex: index
260+ Behavior on behavioredIndex {
261+ enabled: spreadView.closingIndex >= 0
262+ UbuntuNumberAnimation {
263+ onRunningChanged: {
264+ if (!running) {
265+ spreadView.closingIndex = -1
266+ }
267+ }
268+ }
269+ }
249
270
250 // Each tile has a different progress value running from 0 to 1.
271 // Each tile has a different progress value running from 0 to 1.
251 // A progress value of 0 means the tile is at the right edge. 1 means the tile has reched the left edge.
272 // A progress value of 0 means the tile is at the right edge. 1 means the tile has reched the left edge.
252 progress: {
273 progress: {
253− var tileProgress = (spreadView.shiftedContentX - index * spreadView.tileDistance) / spreadView.width;
274+ var tileProgress = (spreadView.shiftedContentX - behavioredIndex * spreadView.tileDistance) / spreadView.width;
254 // Tile 1 needs to move directly from the beginning...
275 // Tile 1 needs to move directly from the beginning...
255− if (index == 1 && spreadView.phase < 2) {
276+ if (behavioredIndex == 1 && spreadView.phase < 2) {
256 tileProgress += spreadView.tileDistance / spreadView.width;
277 tileProgress += spreadView.tileDistance / spreadView.width;
257 }
278 }
258 return tileProgress;
279 return tileProgress;
@@ -288,6 +309,12 @@ Item {
288 }
309 }
289 }
310 }
290 }
311 }
312+
313+ onClosed: {
314+ spreadView.draggedIndex = -1
315+ spreadView.closingIndex = index
316+ ApplicationManager.stopApplication(ApplicationManager.get(index).appId)
317+ }
291 }
318 }
292 }
319 }
293 }
320 }
qml/Stages/SpreadDelegate.qml+122−54
@@ -18,39 +18,41 @@
18
18
19import QtQuick 2.0
19import QtQuick 2.0
20import Mir.Application 0.1
20import Mir.Application 0.1
21−import Ubuntu.Components 1.0
21+import Ubuntu.Components 1.1
22import "../Components"
22import "../Components"
23
23
24Item {
24Item {
25 id: root
25 id: root
26
26
27− signal clicked()
28 property bool interactive: true
27 property bool interactive: true
29− property real maximizedAppTopMargin
30− property bool isFullscreen: surface !== null && surface.anchors.topMargin == 0
31 property bool dropShadow: true
28 property bool dropShadow: true
29+ property real maximizedAppTopMargin
32
30
33− // FIXME: This really should be invisible to QML code.
31+ readonly property bool isFullscreen: surface !== null && surfaceContainer.surface.anchors.topMargin == 0
34− // e.g. Create a SurfaceItem {} in C++ which we just use without any imperative hacks.
35− readonly property var surface: model.surface
36− onSurfaceChanged: { print("SURFACE!!", model.surface)
37− if (surface) {
38− if (!priv.appHasCreatedASurface) {
39− surface.visible = false; // hide until splash screen removed
40− priv.appHasCreatedASurface = true;
41− }
42
32
43− surface.parent = root;
33+ signal clicked()
44− surface.anchors.fill = root;
34+ signal closed()
45− priv.checkFullscreen(surface);
46− surface.z = 1;
47− }
48− }
49
35
50− QtObject {
36+ Item {
51− id: priv
37+ id: surfaceContainer
38+ anchors.fill: parent
39+ readonly property var surface: model.surface
52 property bool appHasCreatedASurface: false
40 property bool appHasCreatedASurface: false
53
41
42+ onSurfaceChanged: { print("SURFACE!!", model.surface)
43+ if (surface) {
44+ if (!appHasCreatedASurface) {
45+ surface.visible = false; // hide until splash screen removed
46+ appHasCreatedASurface = true;
47+ }
48+
49+ surface.parent = surfaceContainer;
50+ surface.anchors.fill = surfaceContainer;
51+ checkFullscreen(surface);
52+ surface.z = 1;
53+ }
54+ }
55+
54 function checkFullscreen(surface) {
56 function checkFullscreen(surface) {
55 if (surface.state === MirSurfaceItem.Fullscreen) {
57 if (surface.state === MirSurfaceItem.Fullscreen) {
56 surface.anchors.topMargin = 0;
58 surface.anchors.topMargin = 0;
@@ -63,43 +65,58 @@ Item {
63 surface.visible = true;
65 surface.visible = true;
64 splashLoader.source = "";
66 splashLoader.source = "";
65 }
67 }
66− }
67
68
68− Timer { //FIXME - need to delay removing splash screen to allow surface resize to complete
69+ Binding {
69− id: surfaceRevealDelay
70+ target: surface
70− interval: 100
71+ property: "enabled"
71− onTriggered: priv.revealSurface()
72+ value: root.interactive
72− }
73+ }
73
74
74− Binding {
75+ Timer { //FIXME - need to delay removing splash screen to allow surface resize to complete
75− target: surface
76+ id: surfaceRevealDelay
76− property: "enabled"
77+ interval: 100
77− value: root.interactive
78+ onTriggered: surfaceContainer.revealSurface()
78− }
79+ }
79
80
80− Connections {
81+ Connections {
81− target: surface
82+ target: surface
82− onStateChanged: priv.checkFullscreen(surface);
83+ onStateChanged: checkFullscreen(surface);
84+ }
85+
86+ BorderImage {
87+ id: dropShadowImage
88+ anchors.fill: parent
89+ anchors.margins: -units.gu(2)
90+ source: "graphics/dropshadow.png"
91+ border { left: 50; right: 50; top: 50; bottom: 50 }
92+ opacity: root.dropShadow ? .4 : 0
93+ Behavior on opacity { UbuntuNumberAnimation {} }
94+ }
95+
96+ transform: Translate {
97+ y: dragArea.distance
98+ }
83 }
99 }
84
100
101+
85 StateGroup {
102 StateGroup {
86 id: appSurfaceState
103 id: appSurfaceState
87 states: [
104 states: [
88 State {
105 State {
89 name: "noSurfaceYet"
106 name: "noSurfaceYet"
90− when: !priv.appHasCreatedASurface
107+ when: !surfaceContainer.appHasCreatedASurface
91 StateChangeScript {
108 StateChangeScript {
92− script: {splashLoader.setSource("Splash.qml", { "name": model.name, "image": model.icon }); }
109+ script: { splashLoader.setSource("Splash.qml", { "name": model.name, "image": model.icon }); }
93 }
110 }
94 },
111 },
95 State {
112 State {
96 name: "hasSurface"
113 name: "hasSurface"
97− when: priv.appHasCreatedASurface && (root.surface !== null)
114+ when: surfaceContainer.appHasCreatedASurface && (surfaceContainer.surface !== null)
98 StateChangeScript { script: { surfaceRevealDelay.start(); } }
115 StateChangeScript { script: { surfaceRevealDelay.start(); } }
99 },
116 },
100 State {
117 State {
101 name: "surfaceLostButAppStillAlive"
118 name: "surfaceLostButAppStillAlive"
102− when: priv.appHasCreatedASurface && (root.surface === null)
119+ when: surfaceContainer.appHasCreatedASurface && (surfaceContainer.surface === null)
103 // TODO - use app snapshot
120 // TODO - use app snapshot
104 }
121 }
105 ]
122 ]
@@ -108,25 +125,76 @@ Item {
108
125
109 Loader {
126 Loader {
110 id: splashLoader
127 id: splashLoader
111− anchors.fill: parent
128+ anchors.fill: surfaceContainer
112− }
113−
114− BorderImage {
115− id: dropShadowImage
116− anchors.fill: surface
117− anchors.margins: -units.gu(2)
118− source: "graphics/dropshadow.png"
119− border { left: 50; right: 50; top: 50; bottom: 50 }
120− opacity: root.dropShadow ? .4 : 0
121− Behavior on opacity { UbuntuNumberAnimation {} }
122 }
129 }
123
130
124− MouseArea {
131+ DraggingArea {
132+ id: dragArea
125 anchors.fill: parent
133 anchors.fill: parent
126− z: 2
127 enabled: !root.interactive
134 enabled: !root.interactive
128− onClicked: {
135+
136+ property bool moving: false
137+ property real distance: 0
138+
139+ onMovingChanged: {
140+ spreadView.draggedIndex = moving ? index : -1
141+ }
142+
143+ onDragValueChanged: {
144+ moving = moving || Math.abs(dragValue) > units.gu(1)
145+ if (moving && dragging) {
146+ distance = dragValue;
147+ }
148+ }
149+
150+ onDragEnd: {
151+ if (!moving) {
129− root.clicked()
152+ root.clicked();
153+ return;
154+ }
155+
156+ if ((dragVelocity < -600 && distance < -units.gu(4)) || distance < -root.height / 2) {
157+ animation.animate("up")
158+ } else if ((dragVelocity > 600 && distance > units.gu(4)) || distance > root.height / 2) {
159+ animation.animate("down")
160+ } else {
161+ animation.animate("center")
162+ }
163+ }
164+
165+ UbuntuNumberAnimation {
166+ id: animation
167+ target: dragArea
168+ property: "distance"
169+ property bool requestClose: false
170+
171+ function animate(direction) {
172+ animation.from = dragArea.distance
173+ switch (direction) {
174+ case "up":
175+ animation.to = -root.height
176+ requestClose = true;
177+ break;
178+ case "down":
179+ animation.to = root.height
180+ requestClose = true;
181+ break;
182+ default:
183+ animation.to = 0
184+ }
185+ animation.start();
186+ }
187+
188+ onRunningChanged: {
189+ if (!running) {
190+ dragArea.moving = false;
191+ dragArea.distance = 0;
192+ if (requestClose) {
193+ root.closed()
194+ }
195+ }
196+ }
130 }
197 }
131 }
198 }
199+
132}
200}
qml/Stages/TabletStage.qml+17−3
@@ -135,7 +135,7 @@ Item {
135 id: spreadView
135 id: spreadView
136 anchors.fill: parent
136 anchors.fill: parent
137 contentWidth: spreadRow.width
137 contentWidth: spreadRow.width
138− interactive: phase == 2
138+ interactive: (spreadDragArea.status == DirectionalDragArea.Recognized || phase > 1) && draggedIndex == -1
139
139
140 property int tileDistance: units.gu(20)
140 property int tileDistance: units.gu(20)
141 property int sideStageWidth: units.gu(40)
141 property int sideStageWidth: units.gu(40)
@@ -157,6 +157,8 @@ Item {
157
157
158 property int selectedIndex: -1
158 property int selectedIndex: -1
159 onSelectedIndexChanged: print("selected index changed", selectedIndex)
159 onSelectedIndexChanged: print("selected index changed", selectedIndex)
160+ property int draggedIndex: -1
161+ property int closingIndex: -1
160
162
161 property bool sideStageDragging: sideStageDragHandle.dragging
163 property bool sideStageDragging: sideStageDragHandle.dragging
162 property real sideStageDragProgress: sideStageDragHandle.progress
164 property real sideStageDragProgress: sideStageDragHandle.progress
@@ -430,11 +432,17 @@ Item {
430 maximizedAppTopMargin: root.maximizedAppTopMargin
432 maximizedAppTopMargin: root.maximizedAppTopMargin
431 dropShadow: spreadView.contentX > 0 || spreadDragArea.status == DirectionalDragArea.Undecided
433 dropShadow: spreadView.contentX > 0 || spreadDragArea.status == DirectionalDragArea.Undecided
432
434
435+ property real behavioredZIndex: zIndex
436+ Behavior on behavioredZIndex {
437+ enabled: spreadView.closingIndex >= 0
438+ UbuntuNumberAnimation {}
439+ }
440+
433 progress: {
441 progress: {
434− var tileProgress = (spreadView.contentX - zIndex * spreadView.tileDistance) / spreadView.width;
442+ var tileProgress = (spreadView.contentX - behavioredZIndex * spreadView.tileDistance) / spreadView.width;
435 // Some tiles (nextInStack, active) need to move directly from the beginning, normalize progress to immediately start at 0
443 // Some tiles (nextInStack, active) need to move directly from the beginning, normalize progress to immediately start at 0
436 if ((index == spreadView.nextInStack && spreadView.phase < 2) || (active && spreadView.phase < 1)) {
444 if ((index == spreadView.nextInStack && spreadView.phase < 2) || (active && spreadView.phase < 1)) {
437− tileProgress += zIndex * spreadView.tileDistance / spreadView.width;
445+ tileProgress += behavioredZIndex * spreadView.tileDistance / spreadView.width;
438 }
446 }
439 return tileProgress;
447 return tileProgress;
440 }
448 }
@@ -462,6 +470,12 @@ Item {
462 }
470 }
463 }
471 }
464
472
473+ onClosed: {
474+ spreadView.draggedIndex = -1
475+ spreadView.closingIndex = index
476+ ApplicationManager.stopApplication(ApplicationManager.get(index).appId)
477+ }
478+
465 EasingCurve {
479 EasingCurve {
466 id: snappingCurve
480 id: snappingCurve
467 type: EasingCurve.Linear
481 type: EasingCurve.Linear