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
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)
49 // Whether first app is displayed entirely vs just on the spread edge
50 property bool focusFirstApp: true
52 // Whether this delegate is displayed to the user outside of the spread
53 readonly property bool isFocused: focusFirstApp && index === 0
55 // Adjusted index for any spread animation calculations
56 readonly property int spreadIndex: index - (focusFirstApp ? 1 : 0)
58 // How far left this delegate has been translated
59 readonly property alias xTranslate: priv.xTranslate
65 priv.isSelected = selected;
68 onOtherSelectedChanged: {
72 priv.otherSelected = otherSelected;
78 if (root.focusFirstApp && spreadView.phase == 1) {
80 priv.phase2startTranslate = priv.easingAnimation(0, spreadView.positionMarker4, 0, -spreadView.width, spreadView.positionMarker4) + spreadView.width;
81 priv.phase2startAngle = priv.easingAnimation(0, spreadView.positionMarker4, root.startAngle, root.endAngle, spreadView.positionMarker4);
82 priv.phase2startScale = priv.easingAnimation(0, spreadView.positionMarker4, root.startScale, root.endScale, spreadView.positionMarker4);
83 priv.phase2startTopMarginProgress = priv.easingAnimation(0, 1, 0, 1, spreadView.positionMarker4);
84 } else if (index == 1) {
85 // find where the main easing for Tile 1 would be when reaching phase 2
86 var phase2Progress = spreadView.positionMarker4 - spreadView.tileDistance / spreadView.width;
87 priv.phase2startTranslate = priv.easingAnimation(0, phase2Progress, 0, -spreadView.width + root.endDistance, phase2Progress);
88 priv.phase2startAngle = priv.easingAnimation(0, phase2Progress, root.startAngle, root.endAngle, phase2Progress);
89 priv.phase2startScale = priv.easingAnimation(0, phase2Progress, root.startScale, root.endScale, phase2Progress);
90 priv.phase2startTopMarginProgress = priv.easingAnimation(0, 1, 0, spreadView.positionMarker4, phase2Progress);
98 property bool isSelected: false
99 property bool otherSelected: false
100 property real selectedProgress
101 property real selectedXTranslate
102 property real selectedAngle
103 property real selectedScale
104 property real selectedOpacity
105 property real selectedTopMarginProgress
107 // Those values are needed as target values for the end of phase 1.
108 // As they are static values, lets calculate them once when entering phase 1 instead of calculating them in each animation pass.
109 property real phase2startTranslate
110 property real phase2startAngle
111 property real phase2startScale
112 property real phase2startTopMarginProgress
114 function snapshot() {
115 selectedProgress = root.progress;
116 selectedXTranslate = xTranslate;
117 selectedAngle = angle;
118 selectedScale = scale;
119 selectedOpacity = opacity;
120 selectedTopMarginProgress = topMarginProgress;
123 function linearAnimation(startProgress, endProgress, startValue, endValue, progress) {
124 // progress : progressDiff = value : valueDiff => value = progress * valueDiff / progressDiff
125 return (progress - startProgress) * (endValue - startValue) / (endProgress - startProgress) + startValue;
128 function easingAnimation(startProgress, endProgress, startValue, endValue, progress) {
129 helperEasingCurve.progress = progress - startProgress;
130 helperEasingCurve.period = endProgress - startProgress;
131 return helperEasingCurve.value * (endValue - startValue) + startValue;
134 // The following blocks handle the animation of the tile in the spread.
135 // At the beginning, each tile is attached at the right edge, outside the screen.
136 // The progress for each tile starts at 0 and it reaches its end position at a progress of 1.
137 // The first phases are handled special for the first 2 tiles. as we do the alt-tab and snapping
138 // in there. Once we reached phase 3, the animation is the same for all tiles.
139 // When a tile is selected, the animation state is snapshotted, and the spreadView is unwound.
140 // All tiles are kept in place and faded out to 0 opacity except
141 // the selected tile, which is animated from the snapshotted position to be fullscreen.
143 readonly property real xTranslate: {
144 if (!spreadView.active) {
149 if (spreadView.phase < 2 && root.isFocused) {
150 return linearAnimation(selectedProgress, 0, selectedXTranslate,
151 selectedXTranslate - spreadView.tileDistance, root.progress);
154 return selectedXTranslate;
157 if (root.focusFirstApp && index === 0) {
158 if (spreadView.phase == 0) {
159 return Math.min(0, linearAnimation(0, spreadView.positionMarker2,
160 0, -spreadView.width * .25, root.animatedProgress));
161 } else if (spreadView.phase == 1){
162 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
163 -spreadView.width * .25, priv.phase2startTranslate, root.progress);
164 } else if (!priv.isSelected){ // phase 2
165 // Apply the same animation as with the rest but add spreadView.width to align it with the others.
166 return -easingCurve.value * spreadView.width + spreadView.width;
168 return linearAnimation(selectedProgress, 0, selectedXTranslate, 0, root.progress);
170 } else if (root.focusFirstApp && index === 1) {
171 if (spreadView.phase == 0 && !priv.isSelected) {
172 return linearAnimation(0, spreadView.positionMarker2,
173 0, -spreadView.width * spreadView.snapPosition, root.animatedProgress);
174 } else if (spreadView.phase == 1 && !priv.isSelected) {
175 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
176 -spreadView.width * spreadView.snapPosition, priv.phase2startTranslate,
181 if (priv.isSelected) {
182 // Distance to left edge
183 var targetTranslate = -spreadView.width - (root.spreadIndex * root.startDistance);
184 return linearAnimation(selectedProgress, 0,
185 selectedXTranslate, targetTranslate, root.progress);
188 // Fix it at the right edge...
189 var rightEdgeOffset = -(root.spreadIndex * root.startDistance);
190 // ...and use our easing to move them to the left. Stop a bit earlier for each tile
191 var animatedEndDistance = linearAnimation(0, 2, root.endDistance, 0, root.progress);
192 return -easingCurve.value * spreadView.width + (index * animatedEndDistance) + rightEdgeOffset;
196 readonly property real angle: {
197 if (!spreadView.active) {
201 if (priv.otherSelected) {
202 return priv.selectedAngle;
204 if (priv.isSelected) {
205 return linearAnimation(selectedProgress, 0, selectedAngle, 0, root.progress);
207 if (root.focusFirstApp && index === 0) {
208 if (spreadView.phase == 0) {
209 return Math.max(0, linearAnimation(0, spreadView.positionMarker2,
210 0, root.tile0SnapAngle, root.animatedProgress));
211 } else if (spreadView.phase == 1) {
212 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
213 root.tile0SnapAngle, phase2startAngle, root.progress);
215 } else if (root.focusFirstApp && index === 1) {
216 if (spreadView.phase == 0) {
217 return linearAnimation(0, spreadView.positionMarker2, root.startAngle,
218 root.startAngle * (1-spreadView.snapPosition), root.animatedProgress);
219 } else if (spreadView.phase == 1) {
220 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
221 root.startAngle * (1-spreadView.snapPosition), priv.phase2startAngle,
225 return root.startAngle - easingCurve.value * (root.startAngle - root.endAngle);
228 readonly property real scale: {
229 if (!spreadView.active) {
232 if (priv.otherSelected) {
233 return priv.selectedScale;
235 if (priv.isSelected) {
236 return linearAnimation(selectedProgress, 0, selectedScale, 1, root.progress);
239 if (root.focusFirstApp && index === 0) {
240 if (spreadView.phase == 0) {
242 } else if (spreadView.phase == 1) {
243 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
244 1, phase2startScale, root.progress);
246 } else if (root.focusFirstApp && index === 1) {
247 if (spreadView.phase == 0) {
248 var targetScale = tile1StartScale - ((tile1StartScale - 1) * spreadView.snapPosition);
249 return linearAnimation(0, spreadView.positionMarker2,
250 root.tile1StartScale, targetScale, root.animatedProgress);
251 } else if (spreadView.phase == 1) {
252 var startScale = tile1StartScale - ((tile1StartScale - 1) * spreadView.snapPosition);
253 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
254 startScale, priv.phase2startScale, root.progress);
257 return root.startScale - easingCurve.value * (root.startScale - root.endScale);
260 readonly property real opacity: {
261 if (priv.otherSelected) {
262 return linearAnimation (selectedProgress, Math.max(0, selectedProgress - .5),
263 selectedOpacity, 0, root.progress);
265 if (root.isFocused) {
266 switch (spreadView.phase) {
268 return linearAnimation(0, spreadView.positionMarker2, 1, .7, root.animatedProgress);
270 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
271 .7, 1, root.animatedProgress);
278 readonly property real topMarginProgress: {
279 if (priv.isSelected) {
280 return linearAnimation(selectedProgress, 0, selectedTopMarginProgress, 0, root.progress);
283 if (root.focusFirstApp && index === 0) {
284 if (spreadView.phase == 0) {
286 } else if (spreadView.phase == 1) {
287 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
288 0, priv.phase2startTopMarginProgress, root.progress);
290 } else if (root.focusFirstApp && index === 1) {
291 if (spreadView.phase == 0) {
293 } else if (spreadView.phase == 1) {
294 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
295 0, priv.phase2startTopMarginProgress, root.progress);
299 return easingCurve.value;
305 origin { x: 0; y: spreadView.height / 2 }
306 axis { x: 0; y: 1; z: 0 }
310 // The next two transformations are to ensure that fullscreen and rotated
311 // windows all have the same size and position as an unrotated (0 degrees)
312 // non-fullscreen window when they're stacked on top of each other on the
313 // far left of the spread.
315 y: !fullscreen && appWindowRotation === 180
316 ? priv.topMarginProgress * maximizedAppTopMargin
321 x: appWindowRotation === 270 ? spreadView.width : 0
326 switch (appWindowRotation) {
332 return 1 + priv.topMarginProgress * maximizedAppTopMargin / spreadView.width;
341 switch (appWindowRotation) {
344 return 1 - priv.topMarginProgress * maximizedAppTopMargin / spreadView.height;
351 return 1 - priv.topMarginProgress * maximizedAppTopMargin / spreadView.height;
360 origin { x: 0; y: spreadView.height / 2 }
369 opacity: spreadView.phase == 2 ? priv.opacity : 1
374 opacity: spreadView.phase < 2 ? 1 - priv.opacity : 0
379 type: EasingCurve.OutSine
380 period: 1 - spreadView.positionMarker2
381 progress: root.progress
384 // This is used as a calculation helper to figure values for progress other than the current one
385 // Do not bind anything to this...
387 id: helperEasingCurve
388 type: easingCurve.type
389 period: easingCurve.period