Unity 8
 All Classes Functions
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  // 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
30 
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
35 
36  property real startAngle: 0
37  property real endAngle: 0
38 
39  property real startScale: 1
40  property real endScale: 1
41 
42  // Specific to just one tile
43  property real tile1StartScale: startScale + .4
44  property real tile0SnapAngle: 10
45 
46  property real startDistance: units.gu(5)
47  property real endDistance: units.gu(.5)
48 
49  onSelectedChanged: {
50  if (selected) {
51  priv.snapshot();
52  }
53  priv.isSelected = selected;
54  }
55 
56  onOtherSelectedChanged: {
57  if (otherSelected) {
58  priv.snapshot();
59  }
60  priv.otherSelected = otherSelected;
61  }
62 
63  Connections {
64  target: spreadView
65  onPhaseChanged: {
66  if (spreadView.phase == 1) {
67  if (index == 0) {
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);
79  }
80  }
81  }
82  }
83 
84  QtObject {
85  id: priv
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
94 
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
101 
102  function snapshot() {
103  selectedProgress = root.progress;
104  selectedXTranslate = xTranslate;
105  selectedAngle = angle;
106  selectedScale = scale;
107  selectedOpacity = opacity;
108  selectedTopMarginProgress = topMarginProgress;
109  }
110 
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;
114  }
115 
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;
120  }
121 
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.
130 
131  readonly property real xTranslate: {
132  if (!spreadView.active) {
133  return 0;
134  }
135 
136  if (otherSelected) {
137  if (spreadView.phase < 2 && index == 0) {
138  return linearAnimation(selectedProgress, 0, selectedXTranslate,
139  selectedXTranslate - spreadView.tileDistance, root.progress);
140  }
141 
142  return selectedXTranslate;
143  }
144 
145  switch (index) {
146  case 0:
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);
158  }
159 
160  case 1:
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,
167  root.progress);
168  }
169  }
170 
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);
176  }
177 
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;
183 
184  }
185 
186  readonly property real angle: {
187  if (!spreadView.active) {
188  return 0;
189  }
190 
191  if (priv.otherSelected) {
192  return priv.selectedAngle;
193  }
194  if (priv.isSelected) {
195  return linearAnimation(selectedProgress, 0, selectedAngle, 0, root.progress);
196  }
197  switch (index) {
198  case 0:
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);
205  }
206  case 1:
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,
213  root.progress);
214  }
215  }
216  return root.startAngle - easingCurve.value * (root.startAngle - root.endAngle);
217  }
218 
219  readonly property real scale: {
220  if (!spreadView.active) {
221  return 1;
222  }
223  if (priv.otherSelected) {
224  return priv.selectedScale;
225  }
226  if (priv.isSelected) {
227  return linearAnimation(selectedProgress, 0, selectedScale, 1, root.progress);
228  }
229 
230  switch (index) {
231  case 0:
232  if (spreadView.phase == 0) {
233  return 1;
234  } else if (spreadView.phase == 1) {
235  return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
236  1, phase2startScale, root.progress);
237  }
238  case 1:
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);
247  }
248  }
249  return root.startScale - easingCurve.value * (root.startScale - root.endScale);
250  }
251 
252  readonly property real opacity: {
253  if (priv.otherSelected) {
254  return linearAnimation (selectedProgress, Math.max(0, selectedProgress - .5),
255  selectedOpacity, 0, root.progress);
256  }
257  if (index == 0) {
258  switch (spreadView.phase) {
259  case 0:
260  return linearAnimation(0, spreadView.positionMarker2, 1, .3, root.animatedProgress);
261  case 1:
262  return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
263  .3, 1, root.animatedProgress);
264  }
265  }
266 
267  return 1;
268  }
269 
270  readonly property real topMarginProgress: {
271  if (priv.isSelected) {
272  return linearAnimation(selectedProgress, 0, selectedTopMarginProgress, 0, root.progress);
273  }
274 
275  switch (index) {
276  case 0:
277  if (spreadView.phase == 0) {
278  return 0;
279  } else if (spreadView.phase == 1) {
280  return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
281  0, priv.phase2startTopMarginProgress, root.progress);
282  }
283  break;
284  case 1:
285  if (spreadView.phase == 0) {
286  return 0;
287  } else if (spreadView.phase == 1) {
288  return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
289  0, priv.phase2startTopMarginProgress, root.progress);
290  }
291  }
292 
293  return easingCurve.value;
294  }
295 
296  }
297 
298  transform: [
299  Rotation {
300  origin { x: 0; y: spreadView.height / 2 }
301  axis { x: 0; y: 1; z: 0 }
302  angle: priv.angle
303  },
304  Scale {
305  origin { x: 0; y: spreadView.height / 2 }
306  xScale: priv.scale
307  yScale: xScale
308  },
309  Scale {
310  origin { x: 0; y: (spreadView.height * priv.scale) + maximizedAppTopMargin * 3 }
311  xScale: 1
312  yScale: fullscreen ? 1 - priv.topMarginProgress * maximizedAppTopMargin / spreadView.height : 1
313  },
314  Translate {
315  x: priv.xTranslate
316  }
317  ]
318  opacity: priv.opacity
319 
320  EasingCurve {
321  id: easingCurve
322  type: EasingCurve.OutSine
323  period: 1 - spreadView.positionMarker2
324  progress: root.progress
325  }
326 
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...
329  EasingCurve {
330  id: helperEasingCurve
331  type: easingCurve.type
332  period: easingCurve.period
333  }
334 }