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