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 // Set this to true when this tile is selected in the spread. The animation will change to bring the tile to front.
27 property bool selected: false
28 // Set this to true when another tile in the spread is selected. The animation will change to fade this tile out.
29 property bool otherSelected: false
31 // The progress animates the tiles. A value > 0 makes it appear from the right edge. At 1 it reaches the end position.
32 property real progress: 0
33 // This is required to snap tile 1 during phase 1 and 2.
34 property real animatedProgress: 0
36 property real startAngle: 0
37 property real endAngle: 0
39 property real startScale: 1
40 property real endScale: 1
42 // Specific to just one tile
43 property real tile1StartScale: startScale + .4
44 property real tile0SnapAngle: 10
46 property real startDistance: units.gu(5)
47 property real endDistance: units.gu(.5)
53 priv.isSelected = selected;
56 onOtherSelectedChanged: {
60 priv.otherSelected = otherSelected;
66 if (spreadView.phase == 1) {
68 priv.phase2startTranslate = priv.easingAnimation(0, spreadView.positionMarker4, 0, -spreadView.width, spreadView.positionMarker4) + spreadView.width;
69 priv.phase2startAngle = priv.easingAnimation(0, spreadView.positionMarker4, root.startAngle, root.endAngle, spreadView.positionMarker4);
70 priv.phase2startScale = priv.easingAnimation(0, spreadView.positionMarker4, root.startScale, root.endScale, spreadView.positionMarker4);
71 priv.phase2startTopMarginProgress = priv.easingAnimation(0, 1, 0, 1, spreadView.positionMarker4);
72 } else if (index == 1) {
73 // find where the main easing for Tile 1 would be when reaching phase 2
74 var phase2Progress = spreadView.positionMarker4 - spreadView.tileDistance / spreadView.width;
75 priv.phase2startTranslate = priv.easingAnimation(0, phase2Progress, 0, -spreadView.width + root.endDistance, phase2Progress);
76 priv.phase2startAngle = priv.easingAnimation(0, phase2Progress, root.startAngle, root.endAngle, phase2Progress);
77 priv.phase2startScale = priv.easingAnimation(0, phase2Progress, root.startScale, root.endScale, phase2Progress);
78 priv.phase2startTopMarginProgress = priv.easingAnimation(0, 1, 0, spreadView.positionMarker4, phase2Progress);
86 property bool isSelected: false
87 property bool otherSelected: false
88 property real selectedProgress
89 property real selectedXTranslate
90 property real selectedAngle
91 property real selectedScale
92 property real selectedOpacity
93 property real selectedTopMarginProgress
95 // Those values are needed as target values for the end of phase 1.
96 // As they are static values, lets calculate them once when entering phase 1 instead of calculating them in each animation pass.
97 property real phase2startTranslate
98 property real phase2startAngle
99 property real phase2startScale
100 property real phase2startTopMarginProgress
102 function snapshot() {
103 selectedProgress = root.progress;
104 selectedXTranslate = xTranslate;
105 selectedAngle = angle;
106 selectedScale = scale;
107 selectedOpacity = opacity;
108 selectedTopMarginProgress = topMarginProgress;
111 function linearAnimation(startProgress, endProgress, startValue, endValue, progress) {
112 // progress : progressDiff = value : valueDiff => value = progress * valueDiff / progressDiff
113 return (progress - startProgress) * (endValue - startValue) / (endProgress - startProgress) + startValue;
116 function easingAnimation(startProgress, endProgress, startValue, endValue, progress) {
117 helperEasingCurve.progress = progress - startProgress;
118 helperEasingCurve.period = endProgress - startProgress;
119 return helperEasingCurve.value * (endValue - startValue) + startValue;
122 // The following blocks handle the animation of the tile in the spread.
123 // At the beginning, each tile is attached at the right edge, outside the screen.
124 // The progress for each tile starts at 0 and it reaches its end position at a progress of 1.
125 // The first phases are handled special for the first 2 tiles. as we do the alt-tab and snapping
126 // in there. Once we reached phase 3, the animation is the same for all tiles.
127 // When a tile is selected, the animation state is snapshotted, and the spreadView is unwound.
128 // All tiles are kept in place and faded out to 0 opacity except
129 // the selected tile, which is animated from the snapshotted position to be fullscreen.
131 readonly property real xTranslate: {
132 if (!spreadView.active) {
137 if (spreadView.phase < 2 && index == 0) {
138 return linearAnimation(selectedProgress, 0, selectedXTranslate,
139 selectedXTranslate - spreadView.tileDistance, root.progress);
142 return selectedXTranslate;
147 if (spreadView.phase == 0) {
148 return Math.min(0, linearAnimation(0, spreadView.positionMarker2,
149 0, -spreadView.width * .25, root.animatedProgress));
150 } else if (spreadView.phase == 1){
151 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
152 -spreadView.width * .25, priv.phase2startTranslate, root.progress);
153 } else if (!priv.isSelected){ // phase 2
154 // Apply the same animation as with the rest but add spreadView.width to align it with the others.
155 return -easingCurve.value * spreadView.width + spreadView.width;
156 } else if (priv.isSelected) {
157 return linearAnimation(selectedProgress, 0, selectedXTranslate, 0, root.progress);
161 if (spreadView.phase == 0 && !priv.isSelected) {
162 return linearAnimation(0, spreadView.positionMarker2,
163 0, -spreadView.width * spreadView.snapPosition, root.animatedProgress);
164 } else if (spreadView.phase == 1 && !priv.isSelected) {
165 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
166 -spreadView.width * spreadView.snapPosition, priv.phase2startTranslate,
171 if (priv.isSelected) {
172 // Distance to left edge
173 var targetTranslate = -spreadView.width - ((index - 1) * root.startDistance);
174 return linearAnimation(selectedProgress, 0,
175 selectedXTranslate, targetTranslate, root.progress);
178 // Fix it at the right edge...
179 var rightEdgeOffset = -((index - 1) * root.startDistance);
180 // ...and use our easing to move them to the left. Stop a bit earlier for each tile
181 var animatedEndDistance = linearAnimation(0, 2, root.endDistance, 0, root.progress);
182 return -easingCurve.value * spreadView.width + (index * animatedEndDistance) + rightEdgeOffset;
186 readonly property real angle: {
187 if (!spreadView.active) {
191 if (priv.otherSelected) {
192 return priv.selectedAngle;
194 if (priv.isSelected) {
195 return linearAnimation(selectedProgress, 0, selectedAngle, 0, root.progress);
199 if (spreadView.phase == 0) {
200 return Math.max(0, linearAnimation(0, spreadView.positionMarker2,
201 0, root.tile0SnapAngle, root.animatedProgress));
202 } else if (spreadView.phase == 1) {
203 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
204 root.tile0SnapAngle, phase2startAngle, root.progress);
207 if (spreadView.phase == 0) {
208 return linearAnimation(0, spreadView.positionMarker2, root.startAngle,
209 root.startAngle * (1-spreadView.snapPosition), root.animatedProgress);
210 } else if (spreadView.phase == 1) {
211 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
212 root.startAngle * (1-spreadView.snapPosition), priv.phase2startAngle,
216 return root.startAngle - easingCurve.value * (root.startAngle - root.endAngle);
219 readonly property real scale: {
220 if (!spreadView.active) {
223 if (priv.otherSelected) {
224 return priv.selectedScale;
226 if (priv.isSelected) {
227 return linearAnimation(selectedProgress, 0, selectedScale, 1, root.progress);
232 if (spreadView.phase == 0) {
234 } else if (spreadView.phase == 1) {
235 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
236 1, phase2startScale, root.progress);
239 if (spreadView.phase == 0) {
240 var targetScale = tile1StartScale - ((tile1StartScale - 1) * spreadView.snapPosition);
241 return linearAnimation(0, spreadView.positionMarker2,
242 root.tile1StartScale, targetScale, root.animatedProgress);
243 } else if (spreadView.phase == 1) {
244 var startScale = tile1StartScale - ((tile1StartScale - 1) * spreadView.snapPosition);
245 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
246 startScale, priv.phase2startScale, root.progress);
249 return root.startScale - easingCurve.value * (root.startScale - root.endScale);
252 readonly property real opacity: {
253 if (priv.otherSelected) {
254 return linearAnimation (selectedProgress, Math.max(0, selectedProgress - .5),
255 selectedOpacity, 0, root.progress);
258 switch (spreadView.phase) {
260 return linearAnimation(0, spreadView.positionMarker2, 1, .3, root.animatedProgress);
262 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
263 .3, 1, root.animatedProgress);
270 readonly property real topMarginProgress: {
271 if (priv.isSelected) {
272 return linearAnimation(selectedProgress, 0, selectedTopMarginProgress, 0, root.progress);
277 if (spreadView.phase == 0) {
279 } else if (spreadView.phase == 1) {
280 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
281 0, priv.phase2startTopMarginProgress, root.progress);
285 if (spreadView.phase == 0) {
287 } else if (spreadView.phase == 1) {
288 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
289 0, priv.phase2startTopMarginProgress, root.progress);
293 return easingCurve.value;
300 origin { x: 0; y: spreadView.height / 2 }
301 axis { x: 0; y: 1; z: 0 }
305 origin { x: 0; y: spreadView.height / 2 }
310 origin { x: 0; y: (spreadView.height * priv.scale) + maximizedAppTopMargin * 3 }
312 yScale: fullscreen ? 1 - priv.topMarginProgress * maximizedAppTopMargin / spreadView.height : 1
318 opacity: priv.opacity
322 type: EasingCurve.OutSine
323 period: 1 - spreadView.positionMarker2
324 progress: root.progress
327 // This is used as a calculation helper to figure values for progress other than the current one
328 // Do not bind anything to this...
330 id: helperEasingCurve
331 type: easingCurve.type
332 period: easingCurve.period