Unity 8
TransformedTabletSpreadDelegate.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 import Unity.Application 0.1
23 
24 SpreadDelegate {
25  id: root
26 
27  // Set this to true when this tile is selected in the spread. The animation will change to bring the tile to front.
28  property bool selected: false
29  // Set this to true when another tile in the spread is selected. The animation will change to fade this tile out.
30  property bool otherSelected: false
31  // Set this to true when this tile a currently active on either the MS or the SS.
32  property bool active: false
33 
34  property int zIndex
35  property real progress: 0
36  property real animatedProgress: 0
37 
38  property real startDistance: units.gu(5)
39  property real endDistance: units.gu(.5)
40 
41  property real startScale: 1.1
42  property real endScale: 0.7
43  property real dragStartScale: startScale + .2
44 
45  property real startAngle: 15
46  property real endAngle: 5
47 
48  property bool isInSideStage: false
49 
50  property int dragOffset: 0
51 
52  dropShadow: spreadView.active ||
53  (active
54  && (model.stage == ApplicationInfoInterface.MainStage || !priv.shellIsLandscape)
55  && priv.xTranslate != 0)
56 
57  onSelectedChanged: {
58  if (selected) {
59  priv.snapshot();
60  }
61  priv.isSelected = selected;
62  }
63 
64  onOtherSelectedChanged: {
65  if (otherSelected) {
66  priv.snapshot();
67  }
68  priv.otherSelected = otherSelected;
69  }
70 
71  Connections {
72  target: spreadView
73 
74  onPhaseChanged: {
75  if (spreadView.phase == 1) {
76  var phase2Progress = spreadView.positionMarker4 - (root.zIndex * spreadView.tileDistance / spreadView.width);
77  priv.phase2StartTranslate = priv.easingAnimation(0, 1, 0, -spreadView.width + (root.zIndex * root.endDistance), phase2Progress);
78  priv.phase2StartScale = priv.easingAnimation(0, 1, root.startScale, root.endScale, phase2Progress);
79  priv.phase2StartAngle = priv.easingAnimation(0, 1, root.startAngle, root.endAngle, phase2Progress);
80  priv.phase2StartTopMarginProgress = priv.easingAnimation(0, 1, 0, 1, phase2Progress);
81  }
82  }
83  }
84 
85  // Required to be an item because we're using states on it.
86  Item {
87  id: priv
88 
89  // true if this is the next tile on the stack that comes in when dragging from the right
90  property bool nextInStack: spreadView.nextZInStack == zIndex
91  // true if the next tile in the stack is the nextInStack one. This one will be moved a bit to the left
92  property bool movedActive: spreadView.nextZInStack == zIndex + 1
93  property real animatedEndDistance: linearAnimation(0, 2, root.endDistance, 0, root.progress)
94 
95  property real phase2StartTranslate
96  property real phase2StartScale
97  property real phase2StartAngle
98  property real phase2StartTopMarginProgress
99 
100  property bool isSelected: false
101  property bool otherSelected: false
102  property real selectedProgress
103  property real selectedXTranslate
104  property real selectedAngle
105  property real selectedScale
106  property real selectedOpacity
107  property real selectedTopMarginProgress
108 
109  function snapshot() {
110  selectedProgress = root.progress;
111  selectedXTranslate = xTranslate;
112  selectedAngle = angle;
113  selectedScale = priv.scale;
114  selectedOpacity = priv.opacityTransform;
115  selectedTopMarginProgress = topMarginProgress;
116  }
117 
118  // This calculates how much negative progress there can be if unwinding the spread completely
119  // the progress for each tile starts at 0 when it crosses the right edge, so the later a tile comes in,
120  // the bigger its negativeProgress can be.
121  property real negativeProgress: {
122  if (nextInStack && spreadView.phase < 2) {
123  return 0;
124  }
125  return -root.zIndex * spreadView.tileDistance / spreadView.width;
126  }
127 
128  function linearAnimation(startProgress, endProgress, startValue, endValue, progress) {
129  // progress : progressDiff = value : valueDiff => value = progress * valueDiff / progressDiff
130  return (progress - startProgress) * (endValue - startValue) / (endProgress - startProgress) + startValue;
131  }
132 
133  function easingAnimation(startProgress, endProgress, startValue, endValue, progress) {
134  helperEasingCurve.progress = progress - startProgress;
135  helperEasingCurve.period = endProgress - startProgress;
136  return helperEasingCurve.value * (endValue - startValue) + startValue;
137  }
138 
139  Behavior on xTranslate {
140  enabled: !spreadView.active &&
141  !snapAnimation.running &&
142  model.appId !== "unity8-dash" &&
143  !spreadView.sideStageDragging &&
144  spreadView.animateX &&
145  !spreadView.beingResized
146  UbuntuNumberAnimation {
147  duration: UbuntuAnimation.FastDuration
148  }
149  }
150 
151  property real xTranslate: {
152  var newTranslate = 0;
153 
154  if (otherSelected) {
155  return priv.selectedXTranslate;
156  }
157 
158  if (isSelected) {
159  if (model.stage == ApplicationInfoInterface.MainStage) {
160  return linearAnimation(selectedProgress, negativeProgress, selectedXTranslate, -spreadView.width, root.progress);
161  } else {
162  return linearAnimation(selectedProgress, negativeProgress, selectedXTranslate, -spreadView.sideStageWidth, root.progress);
163  }
164  }
165 
166  // The tile should move a bit to the left if a new one comes on top of it, but not for the Side Stage and not
167  // when we're only dragging the side stage in on top of a main stage app
168  var shouldMoveAway = spreadView.nextInStack >= 0 && priv.movedActive && model.stage === ApplicationInfoInterface.MainStage &&
169  ApplicationManager.get(spreadView.nextInStack).stage === ApplicationInfoInterface.MainStage;
170 
171  if (active) {
172  newTranslate -= root.width
173  // Only do the hide animation for active apps in the mainstage, and not if we only drag the ss in
174  if (spreadView.phase == 0 && shouldMoveAway) {
175  newTranslate += linearAnimation(0, spreadView.positionMarker2, 0, -units.gu(4), root.animatedProgress);
176  }
177  newTranslate += root.dragOffset;
178  }
179  if (!spreadView.active && model.appId == "unity8-dash" && !root.active) {
180  newTranslate -= root.width;
181  }
182 
183  if (nextInStack && spreadView.phase == 0) {
184  if (model.stage == ApplicationInfoInterface.MainStage) {
185  if (spreadView.sideStageVisible && root.progress > 0) {
186  // Move it so it appears from behind the side stage immediately
187  newTranslate += -spreadView.sideStageWidth;
188  }
189  }
190 
191  if (model.stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
192  // This is when we only drag the side stage in, without rotation or snapping
193  newTranslate = linearAnimation(0, spreadView.positionMarker2, 0, -spreadView.sideStageWidth, root.progress);
194  } else {
195  newTranslate += linearAnimation(0, spreadView.positionMarker2, 0, -spreadView.sideStageWidth * spreadView.snapPosition, root.animatedProgress);
196  }
197  }
198 
199  if (spreadView.phase == 1) {
200  if (nextInStack) {
201  if (model.stage == ApplicationInfoInterface.MainStage) {
202  var startValue = -spreadView.sideStageWidth * spreadView.snapPosition + (spreadView.sideStageVisible ? -spreadView.sideStageWidth : 0);
203  newTranslate += linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, startValue, priv.phase2StartTranslate, root.animatedProgress);
204  } else {
205  var endValue = -spreadView.width + spreadView.width * root.zIndex / 6;
206  if (!spreadView.sideStageVisible) {
207  newTranslate += linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, -spreadView.sideStageWidth, priv.phase2StartTranslate, root.progress);
208  } else {
209  newTranslate += linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, -spreadView.sideStageWidth * spreadView.snapPosition, priv.phase2StartTranslate, root.animatedProgress);
210  }
211  }
212  } else if (root.active) {
213  var startProgress = spreadView.positionMarker2 - (zIndex * spreadView.positionMarker2 / 2);
214  var endProgress = spreadView.positionMarker4 - (zIndex * spreadView.tileDistance / spreadView.width);
215  var startTranslate = -root.width + (shouldMoveAway ? -units.gu(4) : 0);
216  newTranslate = linearAnimation(startProgress, endProgress, startTranslate, priv.phase2StartTranslate, root.progress);
217  } else {
218  var startProgress = spreadView.positionMarker2 - (zIndex * spreadView.positionMarker2 / 2);
219  var endProgress = spreadView.positionMarker4 - (zIndex * spreadView.tileDistance / spreadView.width);
220  newTranslate = linearAnimation(startProgress, endProgress, 0, priv.phase2StartTranslate, root.progress);
221  }
222  }
223 
224  if (spreadView.phase == 2) {
225  newTranslate = -easingCurve.value * (spreadView.width - root.zIndex * animatedEndDistance);
226  }
227 
228  return newTranslate;
229  }
230 
231  property real scale: {
232  if (!spreadView.active) {
233  return 1;
234  }
235 
236  if (otherSelected) {
237  return selectedScale;
238  }
239 
240  if (isSelected) {
241  return linearAnimation(selectedProgress, negativeProgress, selectedScale, 1, root.progress);
242  }
243 
244  if (spreadView.phase == 0) {
245  if (nextInStack) {
246  if (model.stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
247  return 1;
248  } else {
249  var targetScale = root.dragStartScale - ((root.dragStartScale - 1) * spreadView.snapPosition);
250  return linearAnimation(0, spreadView.positionMarker2, root.dragStartScale, targetScale, root.animatedProgress);
251  }
252  } else if (active) {
253  return 1;
254  } else {
255  return linearAnimation(0, spreadView.positionMarker2, root.startScale, root.endScale, root.progress);
256  }
257  }
258 
259  if (spreadView.phase == 1) {
260  if (nextInStack) {
261  var startScale = 1;
262  if (model.stage !== ApplicationInfoInterface.SideStage || spreadView.sideStageVisible) {
263  startScale = root.dragStartScale - ((root.dragStartScale - 1) * spreadView.snapPosition);
264  }
265  return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, startScale, priv.phase2StartScale, root.animatedProgress);
266  }
267  var startProgress = spreadView.positionMarker2 - (zIndex * spreadView.positionMarker2 / 2);
268  var endProgress = spreadView.positionMarker4 - (zIndex * spreadView.tileDistance / spreadView.width);
269  return linearAnimation(startProgress, endProgress, 1, priv.phase2StartScale, root.animatedProgress);
270  }
271 
272  if (spreadView.phase == 2) {
273  return root.startScale - easingCurve.value * (root.startScale - root.endScale);
274  }
275 
276  return 1;
277  }
278 
279  property real angle: {
280  if (!spreadView.active) {
281  return 0;
282  }
283 
284  if (otherSelected) {
285  return selectedAngle;
286  }
287  if (isSelected) {
288  return linearAnimation(selectedProgress, negativeProgress, selectedAngle, 0, root.progress);
289  }
290 
291  // The tile should rotate a bit when another one comes on top, but not when only dragging the side stage in
292  var shouldMoveAway = spreadView.nextInStack >= 0 && movedActive &&
293  (ApplicationManager.get(spreadView.nextInStack).stage === ApplicationInfoInterface.MainStage ||
294  model.stage == ApplicationInfoInterface.SideStage);
295 
296  if (spreadView.phase == 0) {
297  if (nextInStack) {
298  if (model.stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
299  return 0;
300  } else {
301  return linearAnimation(0, spreadView.positionMarker2, root.startAngle, root.startAngle * (1-spreadView.snapPosition), root.animatedProgress);
302  }
303  }
304  if (shouldMoveAway) {
305  return linearAnimation(0, spreadView.positionMarker2, 0, root.startAngle * (1-spreadView.snapPosition), root.animatedProgress);
306  }
307  }
308  if (spreadView.phase == 1) {
309  if (nextInStack) {
310  if (model.stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
311  return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, 0, priv.phase2StartAngle, root.animatedProgress);
312  } else {
313  return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, root.startAngle * (1-spreadView.snapPosition), priv.phase2StartAngle, root.animatedProgress);
314  }
315  }
316  var startProgress = spreadView.positionMarker2 - (zIndex * spreadView.positionMarker2 / 2);
317  var endProgress = spreadView.positionMarker4 - (zIndex * spreadView.tileDistance / spreadView.width);
318  var startAngle = shouldMoveAway ? root.startAngle * (1-spreadView.snapPosition) : 0;
319  return linearAnimation(startProgress, endProgress, startAngle, priv.phase2StartAngle, root.progress);
320  }
321  if (spreadView.phase == 2) {
322  return root.startAngle - easingCurve.value * (root.startAngle - root.endAngle);
323  }
324 
325  return 0;
326  }
327 
328  property real opacityTransform: {
329  if (otherSelected && spreadView.phase == 2) {
330  return linearAnimation(selectedProgress, negativeProgress, selectedOpacity, 0, root.progress);
331  }
332 
333  return 1;
334  }
335 
336  property real topMarginProgress: {
337  if (priv.isSelected) {
338  return linearAnimation(selectedProgress, negativeProgress, selectedTopMarginProgress, 0, root.progress);
339  }
340  switch (spreadView.phase) {
341  case 0:
342  return 0;
343  case 1:
344  return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
345  0, priv.phase2StartTopMarginProgress, root.progress);
346  }
347  return 1;
348  }
349 
350  states: [
351  State {
352  name: "sideStageDragging"; when: spreadView.sideStageDragging && root.isInSideStage
353  PropertyChanges { target: priv; xTranslate: -spreadView.sideStageWidth + spreadView.sideStageWidth * spreadView.sideStageDragProgress }
354  }
355  ]
356  }
357 
358  transform: [
359  Rotation {
360  origin { x: 0; y: spreadView.height / 2 }
361  axis { x: 0; y: 1; z: 0 }
362  angle: priv.angle
363  },
364  Scale {
365  origin { x: 0; y: spreadView.height / 2 }
366  xScale: priv.scale
367  yScale: xScale
368  },
369  Scale {
370  origin { x: 0; y: (spreadView.height * priv.scale) + maximizedAppTopMargin * 3 }
371  xScale: 1
372  yScale: fullscreen ? 1 - priv.topMarginProgress * maximizedAppTopMargin / spreadView.height : 1
373  },
374  Translate {
375  x: priv.xTranslate
376  }
377  ]
378  opacity: priv.opacityTransform
379 
380  UbuntuNumberAnimation {
381  id: fadeBackInAnimation
382  target: root
383  property: "opacity"
384  duration: UbuntuAnimation.SlowDuration
385  from: 0
386  to: 1
387  }
388 
389  EasingCurve {
390  id: easingCurve
391  type: EasingCurve.OutSine
392  period: 1
393  progress: root.progress
394  }
395 
396  EasingCurve {
397  id: helperEasingCurve
398  type: easingCurve.type
399  period: easingCurve.period
400  }
401 }