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
22 import Unity.Application 0.1
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
36 property real progress: 0
37 property real animatedProgress: 0
39 property real startDistance: units.gu(5)
40 property real endDistance: units.gu(.5)
42 property real startScale: 1.1
43 property real endScale: 0.7
44 property real dragStartScale: startScale + .2
46 property real startAngle: 15
47 property real endAngle: 5
49 property bool isInSideStage: false
51 property int dragOffset: 0
52 readonly property alias xTranslateAnimating: xTranslateAnimation.running
53 readonly property bool offScreen: priv.xTranslate >= 0
55 dropShadow: spreadView.active ||
57 && (stage == ApplicationInfoInterface.MainStage || !priv.shellIsLandscape)
58 && priv.xTranslate != 0)
64 priv.isSelected = selected;
67 onOtherSelectedChanged: {
71 priv.otherSelected = otherSelected;
78 if (spreadView.phase == 1) {
79 var phase2Progress = spreadView.positionMarker4 - (root.zIndex * spreadView.tileDistance / spreadView.width);
80 priv.phase2StartTranslate = priv.easingAnimation(0, 1, 0, -spreadView.width + (root.zIndex * root.endDistance), phase2Progress);
81 priv.phase2StartScale = priv.easingAnimation(0, 1, root.startScale, root.endScale, phase2Progress);
82 priv.phase2StartAngle = priv.easingAnimation(0, 1, root.startAngle, root.endAngle, phase2Progress);
83 priv.phase2StartTopMarginProgress = priv.easingAnimation(0, 1, 0, 1, phase2Progress);
88 // Required to be an item because we're using states on it.
92 // true if this is the next tile on the stack that comes in when dragging from the right
93 property bool nextInStack: spreadView.nextZInStack == zIndex
94 // true if the next tile in the stack is the nextInStack one. This one will be moved a bit to the left
95 property bool movedActive: spreadView.nextZInStack == zIndex + 1
96 property real animatedEndDistance: linearAnimation(0, 2, root.endDistance, 0, root.progress)
98 property real phase2StartTranslate
99 property real phase2StartScale
100 property real phase2StartAngle
101 property real phase2StartTopMarginProgress
103 property bool isSelected: false
104 property bool otherSelected: false
105 property real selectedProgress
106 property real selectedXTranslate
107 property real selectedAngle
108 property real selectedScale
109 property real selectedOpacity
110 property real selectedTopMarginProgress
112 function snapshot() {
113 selectedProgress = root.progress;
114 selectedXTranslate = xTranslate;
115 selectedAngle = angle;
116 selectedScale = priv.scale;
117 selectedOpacity = priv.opacityTransform;
118 selectedTopMarginProgress = topMarginProgress;
121 // This calculates how much negative progress there can be if unwinding the spread completely
122 // the progress for each tile starts at 0 when it crosses the right edge, so the later a tile comes in,
123 // the bigger its negativeProgress can be.
124 property real negativeProgress: {
125 if (nextInStack && spreadView.phase < 2) {
128 return -root.zIndex * spreadView.tileDistance / spreadView.width;
131 function linearAnimation(startProgress, endProgress, startValue, endValue, progress) {
132 // progress : progressDiff = value : valueDiff => value = progress * valueDiff / progressDiff
133 return (progress - startProgress) * (endValue - startValue) / (endProgress - startProgress) + startValue;
136 function easingAnimation(startProgress, endProgress, startValue, endValue, progress) {
137 helperEasingCurve.progress = progress - startProgress;
138 helperEasingCurve.period = endProgress - startProgress;
139 return helperEasingCurve.value * (endValue - startValue) + startValue;
142 Behavior on xTranslate {
143 enabled: !spreadView.active &&
144 !snapAnimation.running &&
145 model.appId !== "unity8-dash" &&
146 spreadView.animateX &&
147 !spreadView.beingResized &&
148 priv.state !== "sideStage"
149 UbuntuNumberAnimation {
150 id: xTranslateAnimation
151 duration: UbuntuAnimation.FastDuration
155 property real xTranslate: {
156 var newTranslate = 0;
158 // selected app or opposite stage active app.
159 if (isSelected || (otherSelected && root.active && spreadView.selectedApplication && spreadView.selectedApplication.stage !== stage)) {
160 if (stage == ApplicationInfoInterface.MainStage) {
161 return linearAnimation(selectedProgress, negativeProgress, selectedXTranslate, -spreadView.width, root.progress);
163 return linearAnimation(selectedProgress, negativeProgress, selectedXTranslate, -spreadView.sideStageWidth, root.progress);
165 } else if (otherSelected) {
166 return selectedXTranslate;
169 // 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
170 // when we're only dragging the side stage in on top of a main stage app
171 var shouldMoveAway = spreadView.nextInStack >= 0 && priv.movedActive && stage === ApplicationInfoInterface.MainStage &&
172 ApplicationManager.get(spreadView.nextInStack).stage === ApplicationInfoInterface.MainStage;
175 newTranslate -= root.width
176 // Only do the hide animation for active apps in the mainstage, and not if we only drag the ss in
177 if (spreadView.phase == 0 && shouldMoveAway) {
178 newTranslate += linearAnimation(0, spreadView.positionMarker2, 0, -units.gu(4), root.animatedProgress);
180 newTranslate += root.dragOffset;
181 } else if (!spreadView.active && model.appId == "unity8-dash") {
182 newTranslate -= root.width;
185 if (!spreadView.active) {
189 var shadowOffset = units.gu(2);
191 if (spreadView.phase == 0) {
193 if (stage == ApplicationInfoInterface.MainStage) {
194 if (spreadView.sideStageVisible && root.progress > 0) {
195 // Move it so it appears from behind the side stage immediately
196 newTranslate += -spreadView.sideStageWidth;
200 if (stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
201 // This is when we only drag the side stage in, without rotation or snapping
202 newTranslate = linearAnimation(0, spreadView.positionMarker2, 0, -spreadView.sideStageWidth, root.animatedProgress);
204 newTranslate += linearAnimation(0, spreadView.positionMarker2, 0, -spreadView.sideStageWidth * spreadView.snapPosition, root.animatedProgress);
206 } else if (!root.active) {
207 newTranslate += shadowOffset;
211 if (spreadView.phase == 1) {
213 if (stage == ApplicationInfoInterface.MainStage) {
214 var startValue = -spreadView.sideStageWidth * spreadView.snapPosition + (spreadView.sideStageVisible ? -spreadView.sideStageWidth : 0);
215 newTranslate += linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, startValue, priv.phase2StartTranslate, root.animatedProgress);
217 var endValue = -spreadView.width + spreadView.width * root.zIndex / 6;
218 if (!spreadView.sideStageVisible) {
219 newTranslate += linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, -spreadView.sideStageWidth, priv.phase2StartTranslate, root.animatedProgress);
221 newTranslate += linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, -spreadView.sideStageWidth * spreadView.snapPosition, priv.phase2StartTranslate, root.animatedProgress);
224 } else if (root.active) {
225 var startProgress = spreadView.positionMarker2 - (zIndex * spreadView.positionMarker2 / 2);
226 var endProgress = spreadView.positionMarker4 - (zIndex * spreadView.tileDistance / spreadView.width);
227 var startTranslate = -root.width + (shouldMoveAway ? -units.gu(4) : 0);
228 newTranslate = linearAnimation(startProgress, endProgress, startTranslate, priv.phase2StartTranslate, root.animatedProgress);
230 var startProgress = spreadView.positionMarker2 - (zIndex * spreadView.positionMarker2 / 2);
231 var endProgress = spreadView.positionMarker4 - (zIndex * spreadView.tileDistance / spreadView.width);
232 newTranslate = linearAnimation(startProgress, endProgress, shadowOffset * Math.max(0, zIndex-2), priv.phase2StartTranslate, root.animatedProgress);
236 if (spreadView.phase == 2) {
237 if (easingCurve.value == 0 && !nextInStack) {
238 newTranslate = shadowOffset;
240 newTranslate = -easingCurve.value * (spreadView.width - root.zIndex * animatedEndDistance);
247 property real scale: {
248 if (!spreadView.active) {
252 // selected app or opposite stage active app.
253 if (isSelected || (otherSelected && root.active && spreadView.selectedApplication && spreadView.selectedApplication.stage !== stage)) {
254 return linearAnimation(selectedProgress, negativeProgress, selectedScale, 1, root.progress);
255 } else if (otherSelected) {
256 return selectedScale;
259 if (spreadView.phase == 0) {
261 if (stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
264 var targetScale = root.dragStartScale - ((root.dragStartScale - 1) * spreadView.snapPosition);
265 return linearAnimation(0, spreadView.positionMarker2, root.dragStartScale, targetScale, root.animatedProgress);
272 if (spreadView.phase == 1) {
275 if (stage !== ApplicationInfoInterface.SideStage || spreadView.sideStageVisible) {
276 startScale = root.dragStartScale - ((root.dragStartScale - 1) * spreadView.snapPosition);
278 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, startScale, priv.phase2StartScale, root.animatedProgress);
280 var startProgress = spreadView.positionMarker2 - (zIndex * spreadView.positionMarker2 / 2);
281 var endProgress = spreadView.positionMarker4 - (zIndex * spreadView.tileDistance / spreadView.width);
282 return linearAnimation(startProgress, endProgress, 1, priv.phase2StartScale, root.animatedProgress);
285 if (spreadView.phase == 2) {
286 return root.startScale - easingCurve.value * (root.startScale - root.endScale);
292 property real angle: {
293 if (!spreadView.active) {
297 // selected app or opposite stage active app.
298 if (isSelected || (otherSelected && root.active && spreadView.selectedApplication && spreadView.selectedApplication.stage !== stage)) {
299 return linearAnimation(selectedProgress, negativeProgress, selectedAngle, 0, root.progress);
300 } else if (otherSelected) {
301 return selectedAngle;
304 // The tile should rotate a bit when another one comes on top, but not when only dragging the side stage in
305 var shouldMoveAway = spreadView.nextInStack == -1 ||
306 spreadView.nextInStack >= 0 && priv.movedActive &&
307 (ApplicationManager.get(spreadView.nextInStack).stage === ApplicationInfoInterface.MainStage ||
308 stage == ApplicationInfoInterface.SideStage);
310 if (spreadView.phase == 0) {
312 if (stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
315 return linearAnimation(0, spreadView.positionMarker2, root.startAngle, root.startAngle * (1-spreadView.snapPosition), root.animatedProgress);
318 if (shouldMoveAway) {
319 return linearAnimation(0, spreadView.positionMarker2, 0, root.startAngle * (1-spreadView.snapPosition), root.animatedProgress);
322 if (spreadView.phase == 1) {
324 if (stage == ApplicationInfoInterface.SideStage && !spreadView.sideStageVisible) {
325 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, 0, priv.phase2StartAngle, root.animatedProgress);
327 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4, root.startAngle * (1-spreadView.snapPosition), priv.phase2StartAngle, root.animatedProgress);
330 var startProgress = spreadView.positionMarker2 - (zIndex * spreadView.positionMarker2 / 2);
331 var endProgress = spreadView.positionMarker4 - (zIndex * spreadView.tileDistance / spreadView.width);
332 var startAngle = shouldMoveAway ? root.startAngle * (1-spreadView.snapPosition) : 0;
333 return linearAnimation(startProgress, endProgress, startAngle, priv.phase2StartAngle, root.progress);
335 if (spreadView.phase == 2) {
336 return root.startAngle - easingCurve.value * (root.startAngle - root.endAngle);
342 property real opacityTransform: {
343 // animate opacity for items not snapping into view.
344 if (spreadView.phase !== 2) return 1;
345 if (root.isSelected) return 1;
348 if (root.active && spreadView.selectedApplication && spreadView.selectedApplication.stage !== stage) {
351 return linearAnimation(selectedProgress, negativeProgress, selectedOpacity, 0, root.progress);
356 property real topMarginProgress: {
357 // selected app or opposite stage active app.
358 if (isSelected || (otherSelected && root.active && spreadView.selectedApplication && spreadView.selectedApplication.stage !== stage)) {
359 return linearAnimation(selectedProgress, negativeProgress, selectedTopMarginProgress, 0, root.progress);
362 if (spreadView.phase == 0) {
364 } else if (spreadView.phase == 1) {
366 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
367 0, 1, root.progress);
369 var startProgress = spreadView.positionMarker2 - (zIndex * spreadView.positionMarker2 / 2);
370 var endProgress = spreadView.positionMarker4 - (zIndex * spreadView.tileDistance / spreadView.width);
371 return linearAnimation(startProgress, endProgress, 0, 1, root.progress);
379 when: root.isInSideStage && spreadView.shiftedContentX == 0 && spreadView.phase == 0
382 xTranslate: -spreadView.sideStageWidth + spreadView.sideStageWidth * (1-spreadView.sideStageDragProgress)
390 origin { x: 0; y: spreadView.height / 2 }
391 axis { x: 0; y: 1; z: 0 }
395 origin { x: 0; y: spreadView.height / 2 }
400 origin { x: 0; y: (spreadView.height * priv.scale) + maximizedAppTopMargin * 3 }
402 yScale: fullscreen ? 1 - priv.topMarginProgress * maximizedAppTopMargin / spreadView.height : 1
408 opacity: priv.opacityTransform
410 UbuntuNumberAnimation {
411 id: fadeBackInAnimation
414 duration: UbuntuAnimation.SlowDuration
421 type: EasingCurve.OutSine
423 progress: root.progress
427 id: helperEasingCurve
428 type: easingCurve.type
429 period: easingCurve.period