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 0.1
26
property bool selected: false
27
property bool otherSelected: false
29
// The progress animates the tiles. A value > 0 makes it appear from the right edge. At 1 it reaches the end position.
30
property real progress: 0
31
// This is required to snap tile 1 during phase 1 and 2.
32
property real animatedProgress: 0
34
property real startAngle: 0
35
property real endAngle: 0
37
property real startScale: 1
38
property real endScale: 1
40
// Specific to just one tile
41
property real tile1StartScale: startScale + .4
42
property real tile0SnapAngle: 10
44
property real startDistance: units.gu(5)
45
property real endDistance: units.gu(.5)
51
priv.isSelected = selected;
54
onOtherSelectedChanged: {
58
priv.otherSelected = otherSelected;
64
if (spreadView.phase == 1) {
66
priv.phase2startTranslate = priv.easingAnimation(0, spreadView.positionMarker4, 0, -spreadView.width, spreadView.positionMarker4) + spreadView.width;
67
priv.phase2startAngle = priv.easingAnimation(0, spreadView.positionMarker4, root.startAngle, root.endAngle, spreadView.positionMarker4);
68
priv.phase2startScale = priv.easingAnimation(0, spreadView.positionMarker4, root.startScale, root.endScale, spreadView.positionMarker4);
69
priv.phase2startTopMarginProgress = priv.easingAnimation(0, 1, 0, 1, spreadView.positionMarker4);
70
} else if (index == 1) {
71
// find where the main easing for Tile 1 would be when reaching phase 2
72
var phase2Progress = spreadView.positionMarker4 - spreadView.tileDistance / spreadView.width;
73
priv.phase2startTranslate = priv.easingAnimation(0, phase2Progress, 0, -spreadView.width + root.endDistance, phase2Progress);
74
priv.phase2startAngle = priv.easingAnimation(0, phase2Progress, root.startAngle, root.endAngle, phase2Progress);
75
priv.phase2startScale = priv.easingAnimation(0, phase2Progress, root.startScale, root.endScale, phase2Progress);
76
priv.phase2startTopMarginProgress = priv.easingAnimation(0, 1, 0, spreadView.positionMarker4, phase2Progress);
84
property bool isSelected: false
85
property bool otherSelected: false
86
property real selectedProgress
87
property real selectedXTranslate
88
property real selectedAngle
89
property real selectedScale
90
property real selectedOpacity
91
property real selectedTopMarginProgress
93
// Those values are needed as target values for the end of phase 1.
94
// As they are static values, lets calculate them once when entering phase 1 instead of calculating them in each animation pass.
95
property real phase2startTranslate
96
property real phase2startAngle
97
property real phase2startScale
98
property real phase2startTopMarginProgress
100
function snapshot() {
101
selectedProgress = root.progress;
102
selectedXTranslate = xTranslate;
103
selectedAngle = angle;
104
selectedScale = scale;
105
selectedOpacity = opacity;
106
selectedTopMarginProgress = topMarginProgress;
109
// This calculates how much negative progress there can be if unwinding the spread completely
110
// the progress for each tile starts at 0 when it crosses the right edge, so the later a tile comes in,
111
// the bigger its negativeProgress can be.
112
property real negativeProgress: {
113
if (index == 1 && spreadView.phase < 2) {
116
return -index * spreadView.tileDistance / spreadView.width;
119
function linearAnimation(startProgress, endProgress, startValue, endValue, progress) {
120
// progress : progressDiff = value : valueDiff => value = progress * valueDiff / progressDiff
121
return (progress - startProgress) * (endValue - startValue) / (endProgress - startProgress) + startValue;
124
function easingAnimation(startProgress, endProgress, startValue, endValue, progress) {
125
helperEasingCurve.progress = progress - startProgress;
126
helperEasingCurve.period = endProgress - startProgress;
127
return helperEasingCurve.value * (endValue - startValue) + startValue;
130
property real animatedEndDistance: linearAnimation(0, 2, root.endDistance, 0, root.progress)
132
// The following blocks handle the animation of the tile in the spread.
133
// At the beginning, each tile is attached at the right edge, outside the screen.
134
// The progress for each tile starts at 0 and it reaches its end position at a progress of 1.
135
// The first phases are handled special for the first 2 tiles. as we do the alt-tab and snapping in there
136
// Once we reached phase 3, the animation is the same for all tiles.
137
// When a tile is selected, the animation state is snapshotted, and the spreadView is unwound (progress animates
138
// back to negativeProgress). All tiles are kept in place and faded out to 0 opacity except
139
// the selected tile, which is animated from the snapshotted position to be fullscreen.
141
property real xTranslate: {
143
if (spreadView.phase < 2 && index == 0) {
144
return linearAnimation(selectedProgress, negativeProgress,
145
selectedXTranslate, selectedXTranslate - spreadView.tileDistance, root.progress);
148
return selectedXTranslate;
153
if (spreadView.phase == 0) {
154
return Math.min(0, linearAnimation(0, spreadView.positionMarker2,
155
0, -spreadView.width * .25, root.animatedProgress));
156
} else if (spreadView.phase == 1){
157
return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
158
-spreadView.width * .25, priv.phase2startTranslate, root.progress);
159
} else if (!priv.isSelected){ // phase 2
160
// Apply the same animation as with the rest but add spreadView.width to align it with the others.
161
return -easingCurve.value * spreadView.width + spreadView.width;
162
} else if (priv.isSelected) {
163
return linearAnimation(selectedProgress, negativeProgress, selectedXTranslate, 0, root.progress);
167
if (spreadView.phase == 0 && !priv.isSelected) {
168
return linearAnimation(0, spreadView.positionMarker2,
169
0, -spreadView.width * spreadView.snapPosition, root.animatedProgress);
170
} else if (spreadView.phase == 1 && !priv.isSelected) {
171
return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
172
-spreadView.width * spreadView.snapPosition, priv.phase2startTranslate,
177
if (priv.isSelected) {
178
// Distance to left edge
179
var targetTranslate = -spreadView.width - ((index - 1) * root.startDistance);
180
return linearAnimation(selectedProgress, negativeProgress,
181
selectedXTranslate, targetTranslate, root.progress);
184
// Fix it at the right edge...
185
var rightEdgeOffset = -((index - 1) * root.startDistance);
186
// ...and use our easing to move them to the left. Stop a bit earlier for each tile
187
return -easingCurve.value * spreadView.width + (index * animatedEndDistance) + rightEdgeOffset;
191
property real angle: {
192
if (priv.otherSelected) {
193
return priv.selectedAngle;
195
if (priv.isSelected) {
196
return linearAnimation(selectedProgress, negativeProgress, selectedAngle, 0, root.progress);
200
if (spreadView.phase == 0) {
201
return Math.max(0, linearAnimation(0, spreadView.positionMarker2,
202
0, root.tile0SnapAngle, root.animatedProgress));
203
} else if (spreadView.phase == 1) {
204
return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
205
root.tile0SnapAngle, phase2startAngle, root.progress);
208
if (spreadView.phase == 0) {
209
return linearAnimation(0, spreadView.positionMarker2, root.startAngle,
210
root.startAngle * (1-spreadView.snapPosition), root.animatedProgress);
211
} else if (spreadView.phase == 1) {
212
return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
213
root.startAngle * (1-spreadView.snapPosition), priv.phase2startAngle,
217
return root.startAngle - easingCurve.value * (root.startAngle - root.endAngle);
220
property real scale: {
221
if (priv.otherSelected) {
222
return priv.selectedScale;
224
if (priv.isSelected) {
225
return linearAnimation(selectedProgress, negativeProgress, selectedScale, 1, root.progress);
230
if (spreadView.phase == 0) {
232
} else if (spreadView.phase == 1) {
233
return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
234
1, phase2startScale, root.progress);
237
if (spreadView.phase == 0) {
238
var targetScale = tile1StartScale - ((tile1StartScale - 1) * spreadView.snapPosition);
239
return linearAnimation(0, spreadView.positionMarker2,
240
root.tile1StartScale, targetScale, root.animatedProgress);
241
} else if (spreadView.phase == 1) {
242
var startScale = tile1StartScale - ((tile1StartScale - 1) * spreadView.snapPosition);
243
return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
244
startScale, priv.phase2startScale, root.progress);
247
return root.startScale - easingCurve.value * (root.startScale - root.endScale);
250
property real opacity: {
251
if (priv.otherSelected) {
252
return linearAnimation (selectedProgress, Math.max(0, selectedProgress - .5),
253
selectedOpacity, 0, root.progress);
256
switch (spreadView.phase) {
258
return linearAnimation(0, spreadView.positionMarker2, 1, .3, root.animatedProgress);
260
return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
261
.3, 1, root.animatedProgress);
268
property real topMarginProgress: {
269
if (priv.isSelected) {
270
return linearAnimation(selectedProgress, negativeProgress, selectedTopMarginProgress, 0, root.progress);
275
if (spreadView.phase == 0) {
277
} else if (spreadView.phase == 1) {
278
return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
279
0, priv.phase2startTopMarginProgress, root.progress);
283
if (spreadView.phase == 0) {
285
} else if (spreadView.phase == 1) {
286
return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
287
0, priv.phase2startTopMarginProgress, root.progress);
291
return easingCurve.value;
297
origin { x: 0; y: spreadView.height / 2 }
298
axis { x: 0; y: 1; z: 0 }
302
origin { x: 0; y: spreadView.height / 2 }
310
opacity: priv.opacity
311
topMarginProgress: priv.topMarginProgress
315
type: EasingCurve.OutSine
316
period: 1 - spreadView.positionMarker2
317
progress: root.animatedProgress
320
// This is used as a calculation helper to figure values for progress other than the current one
321
// Do not bind anything to this...
323
id: helperEasingCurve
324
type: easingCurve.type
325
period: easingCurve.period