2
* Copyright 2014 Canonical Ltd.
4
* This program is free software; you can redistribute it and/or modify
5
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
13
* You should have received a copy of the GNU Lesser General Public License
14
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16
* Authors: Michael Zanetti <michael.zanetti@canonical.com>
21
import Ubuntu.Components 1.3
22
import Unity.Application 0.1
27
// Set this to true when this tile is selected in the spread. The animation will change to bring the tile to front.
28
property bool selected: false
29
// Set this to true when another tile in the spread is selected. The animation will change to fade this tile out.
30
property bool otherSelected: false
31
// Set this to true when this tile a currently active on either the MS or the SS.
32
property bool active: false
35
property real progress: 0
36
property real animatedProgress: 0
38
property real startDistance: units.gu(5)
39
property real endDistance: units.gu(.5)
41
property real startScale: 1.1
42
property real endScale: 0.7
43
property real dragStartScale: startScale + .2
45
property real startAngle: 15
46
property real endAngle: 5
48
property bool isInSideStage: false
50
property int stage: ApplicationInfoInterface.MainStage
52
property int dragOffset: 0
53
readonly property alias xTranslateAnimating: xTranslateAnimation.running
54
readonly property bool offScreen: priv.xTranslate >= 0
56
dropShadow: spreadView.active ||
58
&& (root.stage == ApplicationInfoInterface.MainStage || !priv.shellIsLandscape)
59
&& priv.xTranslate != 0)
65
priv.isSelected = selected;
68
onOtherSelectedChanged: {
72
priv.otherSelected = otherSelected;
79
if (spreadView.phase == 1) {
80
var phase2Progress = spreadView.positionMarker4 - (root.zIndex * spreadView.tileDistance / spreadView.width);
81
priv.phase2StartTranslate = priv.easingAnimation(0, 1, 0, -spreadView.width + (root.zIndex * root.endDistance), phase2Progress);
82
priv.phase2StartScale = priv.easingAnimation(0, 1, root.startScale, root.endScale, phase2Progress);
83
priv.phase2StartAngle = priv.easingAnimation(0, 1, root.startAngle, root.endAngle, phase2Progress);
84
priv.phase2StartTopMarginProgress = priv.easingAnimation(0, 1, 0, 1, phase2Progress);
89
// Required to be an item because we're using states on it.
93
// true if this is the next tile on the stack that comes in when dragging from the right
94
property bool nextInStack: spreadView.nextInStack == model.index
95
// true if the next tile in the stack is the nextInStack one. This one will be moved a bit to the left
96
property bool movedActive: spreadView.nextZInStack == zIndex + 1
97
property real animatedEndDistance: linearAnimation(0, 2, root.endDistance, 0, root.progress)
99
property real phase2StartTranslate
100
property real phase2StartScale
101
property real phase2StartAngle
102
property real phase2StartTopMarginProgress
104
property bool isSelected: false
105
property bool otherSelected: false
106
property real selectedProgress
107
property real selectedXTranslate
108
property real selectedAngle
109
property real selectedScale
110
property real selectedOpacity
111
property real selectedTopMarginProgress
113
function snapshot() {
114
selectedProgress = root.progress;
115
selectedXTranslate = xTranslate;
116
selectedAngle = angle;
117
selectedScale = priv.scale;
118
selectedOpacity = priv.opacityTransform;
119
selectedTopMarginProgress = topMarginProgress;
122
// This calculates how much negative progress there can be if unwinding the spread completely
123
// the progress for each tile starts at 0 when it crosses the right edge, so the later a tile comes in,
124
// the bigger its negativeProgress can be.
125
property real negativeProgress: {
126
if (nextInStack && spreadView.phase < 2) {
129
return -root.zIndex * spreadView.tileDistance / spreadView.width;
132
function linearAnimation(startProgress, endProgress, startValue, endValue, progress) {
133
// progress : progressDiff = value : valueDiff => value = progress * valueDiff / progressDiff
134
return (progress - startProgress) * (endValue - startValue) / (endProgress - startProgress) + startValue;
137
function easingAnimation(startProgress, endProgress, startValue, endValue, progress) {
138
helperEasingCurve.progress = progress - startProgress;
139
helperEasingCurve.period = endProgress - startProgress;
140
return helperEasingCurve.value * (endValue - startValue) + startValue;
143
Behavior on xTranslate {
144
enabled: !spreadView.active &&
145
!snapAnimation.running &&
146
root.application.appId !== "unity8-dash" &&
147
spreadView.animateX &&
148
!spreadView.beingResized &&
149
priv.state !== "sideStage"
150
UbuntuNumberAnimation {
151
id: xTranslateAnimation
152
duration: UbuntuAnimation.FastDuration
156
property real xTranslate: {
157
var newTranslate = 0;
159
// selected app or opposite stage active app.
160
if (isSelected || (otherSelected && root.active && spreadView.selectedDelegate
161
&& spreadView.selectedDelegate.stage !== root.stage)) {
162
if (root.stage == ApplicationInfoInterface.MainStage) {
163
return linearAnimation(selectedProgress, negativeProgress, selectedXTranslate, -spreadView.width, root.progress);
165
return linearAnimation(selectedProgress, negativeProgress, selectedXTranslate, -spreadView.sideStageWidth, root.progress);
167
} else if (otherSelected) {
168
return selectedXTranslate;
171
// The tile should move a bit to the left if a new one comes on top of it, but not for the Side Stage and not
172
// when we're only dragging the side stage in on top of a main stage app
173
var shouldMoveAway = spreadView.nextInStack >= 0 && priv.movedActive && root.stage === ApplicationInfoInterface.MainStage &&
174
topLevelSurfaceList.applicationAt(spreadView.nextInStack).stage === ApplicationInfoInterface.MainStage;
177
newTranslate -= root.width
178
// Only do the hide animation for active apps in the mainstage, and not if we only drag the ss in
179
if (spreadView.phase == 0 && shouldMoveAway) {
180
newTranslate += linearAnimation(0, spreadView.positionMarker2, 0, -units.gu(4), root.animatedProgress);
182
newTranslate += root.dragOffset;
183
} else if (!spreadView.active && root.application.appId == "unity8-dash") {
184
newTranslate -= root.width;
187
if (!spreadView.active) {
191
var shadowOffset = units.gu(2);
193
if (spreadView.phase == 0) {
195
if (root.stage == ApplicationInfoInterface.MainStage) {
196
if (spreadView.sideStageVisible && root.progress > 0) {
197
// Move it so it appears from behind the side stage immediately
198
newTranslate += -spreadView.sideStageWidth;
202
if (root.stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
203
// This is when we only drag the side stage in, without rotation or snapping
204
newTranslate = linearAnimation(0, spreadView.positionMarker2, 0, -spreadView.sideStageWidth, root.animatedProgress);
206
newTranslate += linearAnimation(0, spreadView.positionMarker2, 0, -spreadView.sideStageWidth * spreadView.snapPosition, root.animatedProgress);
208
} else if (!root.active) {
209
newTranslate += shadowOffset;
213
if (spreadView.phase == 1) {
215
if (root.stage == ApplicationInfoInterface.MainStage) {
216
var startValue = -spreadView.sideStageWidth * spreadView.snapPosition + (spreadView.sideStageVisible ? -spreadView.sideStageWidth : 0);
217
newTranslate += linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, startValue, priv.phase2StartTranslate, root.animatedProgress);
219
var endValue = -spreadView.width + spreadView.width * root.zIndex / 6;
220
if (!spreadView.sideStageVisible) {
221
newTranslate += linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, -spreadView.sideStageWidth, priv.phase2StartTranslate, root.animatedProgress);
223
newTranslate += linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, -spreadView.sideStageWidth * spreadView.snapPosition, priv.phase2StartTranslate, root.animatedProgress);
226
} else if (root.active) {
227
var startProgress = spreadView.positionMarker2 - (zIndex * spreadView.positionMarker2 / 2);
228
var endProgress = spreadView.positionMarker4 - (zIndex * spreadView.tileDistance / spreadView.width);
229
var startTranslate = -root.width + (shouldMoveAway ? -units.gu(4) : 0);
230
newTranslate = linearAnimation(startProgress, endProgress, startTranslate, priv.phase2StartTranslate, root.animatedProgress);
232
var startProgress = spreadView.positionMarker2 - (zIndex * spreadView.positionMarker2 / 2);
233
var endProgress = spreadView.positionMarker4 - (zIndex * spreadView.tileDistance / spreadView.width);
234
newTranslate = linearAnimation(startProgress, endProgress, shadowOffset * Math.max(0, zIndex-2), priv.phase2StartTranslate, root.animatedProgress);
238
if (spreadView.phase == 2) {
239
if (easingCurve.value == 0 && !nextInStack) {
240
newTranslate = shadowOffset;
242
newTranslate = -easingCurve.value * (spreadView.width - root.zIndex * animatedEndDistance);
249
property real scale: {
250
if (!spreadView.active) {
254
// selected app or opposite stage active app.
255
if (isSelected || (otherSelected && root.active && spreadView.selectedDelegate
256
&& spreadView.selectedDelegate.stage !== root.stage)) {
257
return linearAnimation(selectedProgress, negativeProgress, selectedScale, 1, root.progress);
258
} else if (otherSelected) {
259
return selectedScale;
262
if (spreadView.phase == 0) {
264
if (root.stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
267
var targetScale = root.dragStartScale - ((root.dragStartScale - 1) * spreadView.snapPosition);
268
return linearAnimation(0, spreadView.positionMarker2, root.dragStartScale, targetScale, root.animatedProgress);
275
if (spreadView.phase == 1) {
278
if (root.stage !== ApplicationInfoInterface.SideStage || spreadView.sideStageVisible) {
279
startScale = root.dragStartScale - ((root.dragStartScale - 1) * spreadView.snapPosition);
281
return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, startScale, priv.phase2StartScale, root.animatedProgress);
283
var startProgress = spreadView.positionMarker2 - (zIndex * spreadView.positionMarker2 / 2);
284
var endProgress = spreadView.positionMarker4 - (zIndex * spreadView.tileDistance / spreadView.width);
285
return linearAnimation(startProgress, endProgress, 1, priv.phase2StartScale, root.animatedProgress);
288
if (spreadView.phase == 2) {
289
return root.startScale - easingCurve.value * (root.startScale - root.endScale);
295
property real angle: {
296
if (!spreadView.active) {
300
// selected app or opposite stage active app.
301
if (isSelected || (otherSelected && root.active && spreadView.selectedDelegate
302
&& spreadView.selectedDelegate.stage !== root.stage)) {
303
return linearAnimation(selectedProgress, negativeProgress, selectedAngle, 0, root.progress);
304
} else if (otherSelected) {
305
return selectedAngle;
308
// The tile should rotate a bit when another one comes on top, but not when only dragging the side stage in
309
var shouldMoveAway = spreadView.nextInStack == -1 ||
310
spreadView.nextInStack >= 0 && priv.movedActive &&
311
(topLevelSurfaceList.applicationAt(spreadView.nextInStack).stage === ApplicationInfoInterface.MainStage ||
312
root.stage == ApplicationInfoInterface.SideStage);
314
if (spreadView.phase == 0) {
316
if (root.stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
319
return linearAnimation(0, spreadView.positionMarker2, root.startAngle, root.startAngle * (1-spreadView.snapPosition), root.animatedProgress);
322
if (shouldMoveAway) {
323
return linearAnimation(0, spreadView.positionMarker2, 0, root.startAngle * (1-spreadView.snapPosition), root.animatedProgress);
326
if (spreadView.phase == 1) {
328
if (root.stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
329
return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, 0, priv.phase2StartAngle, root.animatedProgress);
331
return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, root.startAngle * (1-spreadView.snapPosition), priv.phase2StartAngle, root.animatedProgress);
334
var startProgress = spreadView.positionMarker2 - (zIndex * spreadView.positionMarker2 / 2);
335
var endProgress = spreadView.positionMarker4 - (zIndex * spreadView.tileDistance / spreadView.width);
336
var startAngle = shouldMoveAway ? root.startAngle * (1-spreadView.snapPosition) : 0;
337
return linearAnimation(startProgress, endProgress, startAngle, priv.phase2StartAngle, root.progress);
339
if (spreadView.phase == 2) {
340
return root.startAngle - easingCurve.value * (root.startAngle - root.endAngle);
346
property real opacityTransform: {
347
// animate opacity for items not snapping into view.
348
if (spreadView.phase !== 2) return 1;
349
if (root.isSelected) return 1;
352
if (root.active && spreadView.selectedDelegate
353
&& spreadView.selectedDelegate.stage !== root.stage) {
356
return linearAnimation(selectedProgress, negativeProgress, selectedOpacity, 0, root.progress);
361
property real topMarginProgress: {
362
// selected app or opposite stage active app.
363
if (isSelected || (otherSelected && root.active && spreadView.selectedDelegate
364
&& spreadView.selectedDelegate.stage !== root.stage)) {
365
return linearAnimation(selectedProgress, negativeProgress, selectedTopMarginProgress, 0, root.progress);
368
if (spreadView.phase == 0) {
370
} else if (spreadView.phase == 1) {
372
return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
373
0, 1, root.progress);
375
var startProgress = spreadView.positionMarker2 - (zIndex * spreadView.positionMarker2 / 2);
376
var endProgress = spreadView.positionMarker4 - (zIndex * spreadView.tileDistance / spreadView.width);
377
return linearAnimation(startProgress, endProgress, 0, 1, root.progress);
385
when: root.isInSideStage && spreadView.shiftedContentX == 0 && spreadView.phase == 0
388
xTranslate: -spreadView.sideStageWidth + spreadView.sideStageWidth * (1-sideStage.progress)
396
origin { x: 0; y: spreadView.height / 2 }
397
axis { x: 0; y: 1; z: 0 }
401
origin { x: 0; y: spreadView.height / 2 }
406
origin { x: 0; y: (spreadView.height * priv.scale) + maximizedAppTopMargin * 3 }
408
yScale: fullscreen ? 1 - priv.topMarginProgress * maximizedAppTopMargin / spreadView.height : 1
414
opacity: priv.opacityTransform
416
UbuntuNumberAnimation {
417
id: fadeBackInAnimation
420
duration: UbuntuAnimation.SlowDuration
427
type: EasingCurve.OutSine
429
progress: root.progress
433
id: helperEasingCurve
434
type: easingCurve.type
435
period: easingCurve.period