Unity 8
 All Classes Functions Properties
TransformedSpreadDelegate.qml
1 /*
2  * Copyright 2014 Canonical Ltd.
3  *
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.
7  *
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.
12  *
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/>.
15  *
16  * Authors: Michael Zanetti <michael.zanetti@canonical.com>
17 */
18 
19 import QtQuick 2.0
20 import Utils 0.1
21 import Ubuntu.Components 0.1
22 
23 SpreadDelegate {
24  id: root
25 
26  property bool selected: false
27  property bool otherSelected: false
28 
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
33 
34  property real startAngle: 0
35  property real endAngle: 0
36 
37  property real startScale: 1
38  property real endScale: 1
39 
40  // Specific to just one tile
41  property real tile1StartScale: startScale + .4
42  property real tile0SnapAngle: 10
43 
44  property real startDistance: units.gu(5)
45  property real endDistance: units.gu(.5)
46 
47  onSelectedChanged: {
48  if (selected) {
49  priv.snapshot();
50  }
51  priv.isSelected = selected;
52  }
53 
54  onOtherSelectedChanged: {
55  if (otherSelected) {
56  priv.snapshot();
57  }
58  priv.otherSelected = otherSelected;
59  }
60 
61  Connections {
62  target: spreadView
63  onPhaseChanged: {
64  if (spreadView.phase == 1) {
65  if (index == 0) {
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);
77  }
78  }
79  }
80  }
81 
82  QtObject {
83  id: priv
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
92 
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
99 
100  function snapshot() {
101  selectedProgress = root.progress;
102  selectedXTranslate = xTranslate;
103  selectedAngle = angle;
104  selectedScale = scale;
105  selectedOpacity = opacity;
106  selectedTopMarginProgress = topMarginProgress;
107  }
108 
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) {
114  return 0;
115  }
116  return -index * spreadView.tileDistance / spreadView.width;
117  }
118 
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;
122  }
123 
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;
128  }
129 
130  property real animatedEndDistance: linearAnimation(0, 2, root.endDistance, 0, root.progress)
131 
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.
140 
141  property real xTranslate: {
142  if (otherSelected) {
143  if (spreadView.phase < 2 && index == 0) {
144  return linearAnimation(selectedProgress, negativeProgress,
145  selectedXTranslate, selectedXTranslate - spreadView.tileDistance, root.progress);
146  }
147 
148  return selectedXTranslate;
149  }
150 
151  switch (index) {
152  case 0:
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);
164  }
165 
166  case 1:
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,
173  root.progress);
174  }
175  }
176 
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);
182  }
183 
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;
188 
189  }
190 
191  property real angle: {
192  if (priv.otherSelected) {
193  return priv.selectedAngle;
194  }
195  if (priv.isSelected) {
196  return linearAnimation(selectedProgress, negativeProgress, selectedAngle, 0, root.progress);
197  }
198  switch (index) {
199  case 0:
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);
206  }
207  case 1:
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,
214  root.progress);
215  }
216  }
217  return root.startAngle - easingCurve.value * (root.startAngle - root.endAngle);
218  }
219 
220  property real scale: {
221  if (priv.otherSelected) {
222  return priv.selectedScale;
223  }
224  if (priv.isSelected) {
225  return linearAnimation(selectedProgress, negativeProgress, selectedScale, 1, root.progress);
226  }
227 
228  switch (index) {
229  case 0:
230  if (spreadView.phase == 0) {
231  return 1;
232  } else if (spreadView.phase == 1) {
233  return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
234  1, phase2startScale, root.progress);
235  }
236  case 1:
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);
245  }
246  }
247  return root.startScale - easingCurve.value * (root.startScale - root.endScale);
248  }
249 
250  property real opacity: {
251  if (priv.otherSelected) {
252  return linearAnimation (selectedProgress, Math.max(0, selectedProgress - .5),
253  selectedOpacity, 0, root.progress);
254  }
255  if (index == 0) {
256  switch (spreadView.phase) {
257  case 0:
258  return linearAnimation(0, spreadView.positionMarker2, 1, .3, root.animatedProgress);
259  case 1:
260  return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
261  .3, 1, root.animatedProgress);
262  }
263  }
264 
265  return 1;
266  }
267 
268  property real topMarginProgress: {
269  if (priv.isSelected) {
270  return linearAnimation(selectedProgress, negativeProgress, selectedTopMarginProgress, 0, root.progress);
271  }
272 
273  switch (index) {
274  case 0:
275  if (spreadView.phase == 0) {
276  return 0;
277  } else if (spreadView.phase == 1) {
278  return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
279  0, priv.phase2startTopMarginProgress, root.progress);
280  }
281  break;
282  case 1:
283  if (spreadView.phase == 0) {
284  return 0;
285  } else if (spreadView.phase == 1) {
286  return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
287  0, priv.phase2startTopMarginProgress, root.progress);
288  }
289  }
290 
291  return easingCurve.value;
292  }
293  }
294 
295  transform: [
296  Rotation {
297  origin { x: 0; y: spreadView.height / 2 }
298  axis { x: 0; y: 1; z: 0 }
299  angle: priv.angle
300  },
301  Scale {
302  origin { x: 0; y: spreadView.height / 2 }
303  xScale: priv.scale
304  yScale: xScale
305  },
306  Translate {
307  x: priv.xTranslate
308  }
309  ]
310  opacity: priv.opacity
311  topMarginProgress: priv.topMarginProgress
312 
313  EasingCurve {
314  id: easingCurve
315  type: EasingCurve.OutSine
316  period: 1 - spreadView.positionMarker2
317  progress: root.animatedProgress
318  }
319 
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...
322  EasingCurve {
323  id: helperEasingCurve
324  type: easingCurve.type
325  period: easingCurve.period
326  }
327 }
The EasingCurve class.
Definition: easingcurve.h:36