@@ -188,25 +188,34 @@ Item {
188188 }
189189
190190 EdgeDragArea {
191− id: coverFlipDragArea
191+ id: spreadDragArea
192192 direction: Direction.Leftwards
193193
194194 //enabled: root.available
195195 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
196196 width: root.dragAreaWidth
197197
198+ property bool attachedToView: true
199+
198200 onTouchXChanged: {
199201 if (!dragging && !priv.waitingForScreenshot) {
200202 priv.requestNewScreenshot();
203+ spreadView.stage = 0;
204+ attachedToView = true;
205+ }
206+ if (dragging && !priv.waitingForScreenshot && attachedToView) {
207+ spreadView.contentX = -touchX
201208 }
202− if (dragging && !priv.waitingForScreenshot) {
203− coverFlickable.contentX = -touchX
209+ if (attachedToView && spreadView.contentX >= spreadView.width * spreadView.positionMarker3) {
210+ attachedToView = false;
211+ spreadView.snap();
204212 }
205213 }
206214
207215 onDraggingChanged: {
208− if (!dragging) {
209− coverFlip.snap();
216+ print("Dragging changed", dragging)
217+ if (!dragging && attachedToView) {
218+ spreadView.snap();
210219 }
211220 }
212221 }
@@ -215,372 +224,195 @@ Item {
215224 id: coverFlipBackground
216225 anchors.fill: parent
217226 color: "black"
218− visible: coverFlip.visible
227+ visible: spreadView.visible
219228 }
220229
221230 InputFilterArea {
222231 anchors.fill: root
223− blockInput: coverFlip.visible
232+ blockInput: spreadView.visible
224233 }
225234
226235 Flickable {
227− id: coverFlickable
228− anchors.fill: root
229− contentHeight: height
230− contentWidth: width * 2 + ((ApplicationManager.count - 2) * root.width * coverFlip.tileDistance * 1.5)
231− flickableDirection: Qt.Horizontal
232− enabled: coverFlip.visible
236+ id: spreadView
237+ anchors.fill: parent
238+ visible: contentX > 0
239+ contentWidth: spreadRow.width
233240
234− property bool passedFirstStage: false
241+ property int tileDistance: width / 4
242+
243+ property real positionMarker1: 0.25
244+ property real positionMarker2: 0.5
245+ property real positionMarker3: 0.7
246+ property real positionMarker4: 1.25
247+
248+ // Stage of the animation:
249+ // 0: Starting from right edge, a new app (index 1) comes in from the right
250+ // 1: The app has reached fullscreen. The list is reordered and shifted to the right in order to have the new app the leftmost one
251+ // 2: The list is dragged further and snaps into the spread view when entering stage 2
252+ property int stage: 0
235253
236254 onContentXChanged: {
237− if (coverFlickable.passedFirstStage && contentX < width * coverFlip.progressMarker2) {
238− contentX = width * coverFlip.progressMarker2;
239− return;
240− }
241− var progress = contentX / width
242− if (progress > coverFlip.progressMarker2) {
243− coverFlickable.passedFirstStage = true;
255+ switch (stage) {
256+ case 0:
257+ if (contentX > width * positionMarker2) {
258+ print("switching to app 1 .")
259+ ApplicationManager.focusApplication(ApplicationManager.get(1).appId)
260+ stage = 1;
261+ }
262+ break;
263+ case 1:
264+ if (contentX < width * positionMarker2) {
265+ print("switching to app 1 -")
266+ ApplicationManager.focusApplication(ApplicationManager.get(1).appId)
267+ stage = 0;
268+ } else if (contentX >= width * positionMarker4) {
269+ stage = 2;
270+ }
244271 }
245− coverFlip.progress = progress;
246272 }
247273
248− Row {
249− id: coverFlip
250− height: parent.height
251− // The MouseAreas on the AppImages need to be children of the flickable in order to make focus stealing
252− // for flicking vs. clicking work correctly.
253− // However, for the animation calculations to become easier we don't want the row to move so we don't
254− // always have to take contentX into account. So lets just compensate the flickable's movement here.
255− x: coverFlickable.contentX
256− visible: progress > 0
257−
258− property real progress: 0
259− property real startAngle: 45
260− property int endAngle: 10
261−
262− property real maxScale: 1.4
263− property real minScale: .6
264−
265− // Markers: relative screen position from right to left
266− // marks the line where first application is finished moving in from the right
267− property real progressMarker1: 0.15
268− property real progressMarker2: 0.5
269−
270− property real tileDistance: 0.1
271−
272− property bool animatingBack: false
273−
274− property real tileWidth: root.width
275−
276− property real oldProgress: 0
277− onProgressChanged: {
278− if (coverFlipDragArea.dragging) {
279− if (oldProgress < coverFlip.progressMarker2 && progress >= coverFlip.progressMarker2) {
280− ApplicationManager.move(0, 1)
281− } else if (oldProgress >= coverFlip.progressMarker2 && progress < coverFlip.progressMarker2) {
282− ApplicationManager.move(0, 1)
283− }
284− }
285− oldProgress = progress;
274+ function snap() {
275+ if (contentX < positionMarker1 * width) {
276+ snapAnimation.targetContentX = 0;
277+ snapAnimation.start();
278+ } else if (contentX < positionMarker2 * width) {
279+ snapTo(1)
280+ } else if (contentX < positionMarker3 * width) {
281+ snapAnimation.targetContentX = width * positionMarker2 + 1;
282+ snapAnimation.start();
283+ } else if (stage < 2){
284+ snapAnimation.targetContentX = width * positionMarker4;
285+ snapAnimation.start();
286286 }
287+ }
288+ function snapTo(index) {
289+ snapAnimation.targetContentX = width * positionMarker2;
290+ snapAnimation.toIndex = index;
291+ snapAnimation.start();
292+ }
287293
288− function snap() {
289− if (coverFlip.progress < coverFlip.progressMarker1) {
290− snapAnimation.targetContentX = 0
291− snapAnimation.targetAppId = ApplicationManager.get(0).appId;
292− } else if (coverFlip.progress < coverFlip.progressMarker2) {
293− snapAnimation.targetContentX = root.width * coverFlip.progressMarker2
294− snapAnimation.targetAppId = ApplicationManager.get(1).appId;
295− } else if (coverFlip.progress < 0.6) {
296− snapAnimation.targetContentX = root.width * coverFlip.progressMarker2
297− snapAnimation.targetAppId = ApplicationManager.get(0).appId;
298− } else {
299− if (ApplicationManager.count == 3) {
300− snapAnimation.targetContentX = root.width * 1.1;
301− } else {
302− snapAnimation.targetContentX = root.width * 1.25;
294+ SequentialAnimation {
295+ id: snapAnimation
296+ property int targetContentX: 0
297+ property int toIndex: -1
298+
299+ UbuntuNumberAnimation {
300+ target: spreadView
301+ property: "contentX"
302+ to: snapAnimation.targetContentX
303+ duration: UbuntuAnimation.SnapDuration
304+ }
305+ ScriptAction {
306+ script: {
307+ print("animation finished: stage", spreadView.stage, "contentX", spreadView.contentX, "focused app", ApplicationManager.get(0).appId, ApplicationManager.get(1).appId)
308+ if (snapAnimation.toIndex >= 0) {
309+ print("switching to app", snapAnimation.toIndex, ApplicationManager.get(snapAnimation.toIndex).name)
310+ ApplicationManager.focusApplication(ApplicationManager.get(snapAnimation.toIndex).appId);
311+ snapAnimation.toIndex = -1;
312+ spreadView.contentX = 0;
313+ }
314+ if (spreadView.stage == 1) {
315+ spreadView.stage = 0;
316+ spreadView.contentX = 0;
303317 }
304− snapAnimation.targetAppId = "";
305318 }
306− snapAnimation.start();
307319 }
320+ }
308321
309− function selectItem(index) {
310− tileRepeater.itemAt(index).select();
311− }
312322
313− SequentialAnimation {
314− id: snapAnimation
315− property int targetContentX: 0
316− property string targetAppId
323+ Row {
324+ id: spreadRow
325+ width: implicitWidth + (spreadView.width * spreadView.positionMarker2) + (spreadView.width - spreadView.tileDistance)
317326
318− UbuntuNumberAnimation {
319− target: coverFlickable
320− properties: "contentX"
321− to: snapAnimation.targetContentX
322− duration: UbuntuAnimation.SnapDuration
323− }
324− ScriptAction {
325− script: {
326− if (snapAnimation.targetAppId) {
327− coverFlickable.passedFirstStage = false;
328− ApplicationManager.focusApplication(snapAnimation.targetAppId);
329− }
330− if (snapAnimation.targetContentX == root.width * coverFlip.progressMarker2) {
331− coverFlickable.contentX = 0;
332− }
333− }
334− }
335− }
327+ x: spreadView.stage == 0 ? 0 : spreadView.width * spreadView.positionMarker2
336328
337329 Repeater {
338− id: tileRepeater
339330 model: ApplicationManager
340−
341− Item {
342− id: appItem
343− height: parent.height
344− width: coverFlip.tileWidth
345−
346− // This is the main progress, of the gesture, the same for every tile
347− property real progress: coverFlip.progress
348− // The progress, translated for the second stage of the animation, after the first app switch has happened
349− // Additionally it speeds it up a bit, depending on the distance of the tile
350− property real translatedProgress: appItem.progress - coverFlip.progressMarker2 - (coverFlip.tileDistance * (index-1))
351−
352− // Is this tile selected by a click?
353− property bool isSelected: false
354− // We need to remember some values when app is selected to be able to animate it to the foreground
355− property real selectedXTranslation: 0
356− property real selectedTranslatedProgress: 0
357− property real selectedProgress: 0
358− property real selectedAngle: 0
359− property real selectedXScale: 0
360−
361− function select() {
362− appItem.selectedXTranslation = appItem.xTranslation;
363− appItem.selectedAngle = appItem.angle;
364− appItem.selectedXScale = appItem.xScale;
365− appItem.selectedTranslatedProgress = appItem.translatedProgress - coverFlip.progressMarker2;
366− appItem.selectedProgress = appItem.progress - coverFlip.progressMarker2;
367− appItem.isSelected = true;
368− switchToAppAnimation.targetContentX = coverFlip.progressMarker2 * root.width
369− switchToAppAnimation.start();
370− }
371−
372− property int xTranslation: {
373− var xTranslate = 0;
374− var minXTranslate = -index * root.width + index * units.dp(3);
331+ delegate: TransformedSpreadDelegate {
332+ startAngle: 45
333+ endAngle: 10
334+ startScale: 1.4
335+ endScale: 0.8
336+ startDistance: spreadView.tileDistance
337+ endDistance: units.gu(.5)
338+
339+ z: index
340+ height: spreadView.height
341+ width: {
375342 switch (index) {
376343 case 0:
377− if (appItem.progress < coverFlip.progressMarker2) {
378− var progress = appItem.progress
379− var progressDiff = coverFlip.progressMarker2
380− var translateDiff = -root.width * 0.25
381− // progress : progressDiff = translate : translateDiff
382− xTranslate = progress * translateDiff / progressDiff
383− }
384− break;
344+ return spreadView.width;
385345 case 1:
386− if (appItem.progress < coverFlip.progressMarker2) {
387− var progress = appItem.progress;
388− var progressDiff = coverFlip.progressMarker2;
389− var translateDiff = -root.width;
390− // progress : progressDiff = translate : translateDiff
391− xTranslate = progress * translateDiff / progressDiff;
392− break;
393−// } else if (appItem.progress < coverFlip.progressMarker2) {
394−// xTranslate = -coverFlip.tileWidth
346+ if (spreadView.stage == 0) {
347+ return spreadView.width / 2;
395348 }
396−
397− // Intentionally no break here...
398349 default:
399− if (appItem.progress > coverFlip.progressMarker2) {
400− xTranslate = xTranslateEasing.value * xTranslateEasing.period;
401− if (appItem.isSelected) {
402− var translateDiff = root.width * index + appItem.selectedXTranslation
403− var progressDiff = appItem.selectedProgress
404− var progress = progressDiff - (appItem.progress - coverFlip.progressMarker2);
405− // progress : progressDiff = translate : translateDiff
406− var newTranslate = progress * translateDiff / progressDiff;
407−
408− xTranslate = appItem.selectedXTranslation - newTranslate;
409− }
410− break;
411− }
412− }
413− if (!appItem.isSelected) {
414− xTranslate += index * units.dp(4);
350+ return spreadView.tileDistance;
415351 }
416−
417− return xTranslate;
418352 }
419−// Behavior on xTranslation {
420−// id: animatedProgressBehavior
421−// enabled: appItem.progress >= coverFlip.progressMarker1 && appItem.progress <= coverFlip.progressMarker2
422−// SmoothedAnimation {
423−// velocity: 0.5;
424−// duration: UbuntuAnimation.BriskDuration;
425−// easing: UbuntuAnimation.StandardEasing
426−// }
427−// }
428−
429− property real angle: {
430− var newAngle = 0;
353+
354+ progress: {
431355 switch (index) {
432356 case 0:
433− if (appItem.progress < coverFlip.progressMarker2) {
434− var progress = appItem.progress;
435− var angleDiff = coverFlip.endAngle;
436− var progressDiff = coverFlip.progressMarker2;
437− // progress : progressDiff = angle : angleDiff
438− newAngle = progress * angleDiff / progressDiff;
439− } else {
440− var progress = appItem.progress - coverFlip.progressMarker2;
441− var angleDiff = coverFlip.endAngle;
442− var progressDiff = 1 - coverFlip.progressMarker2;
443− // progress : progressDiff = angle : angleDiff
444− newAngle = progress * angleDiff / progressDiff;
445− newAngle = Math.min(coverFlip.endAngle, newAngle);
357+ var progress = spreadView.contentX / spreadView.width;
358+ if (spreadView.stage == 2) {
359+ progress -= spreadView.positionMarker1;
446360 }
447− break;
361+
362+ return progress;
448363 case 1:
449− if (appItem.progress < coverFlip.progressMarker2) {
450− var progress = coverFlip.progress;
451− var angleDiff = coverFlip.startAngle;
452− var progressDiff = coverFlip.progressMarker2;
453− // progress : progressDiff = angle : angleDiff
454− var angle = progress * angleDiff / progressDiff;
455− newAngle = coverFlip.startAngle - angle;
456− break;
457− }
458− // Intentionally no break here...
459− default:
460− newAngle = coverFlip.startAngle - (angleEasing.value * angleEasing.period);
461− // make sure we stop at the left screen edge
462− newAngle = Math.max(newAngle, coverFlip.endAngle);
463−
464− if (appItem.isSelected) {
465−// var selectedAngleTranslate = selectedAngleEasing.value * selectedAngleEasing.period
466− var angleDiff = appItem.selectedAngle
467− var progressDiff = appItem.selectedProgress
468− var progress = progressDiff - (appItem.progress - coverFlip.progressMarker2);
469− // progress : progressDiff = angle : angleDiff
470− var selectedAngleTranslate = progress * angleDiff / progressDiff;
471−
472− newAngle = appItem.selectedAngle - selectedAngleTranslate;
364+ var progress = spreadView.contentX / spreadView.width;
365+ if (spreadView.stage != 0) {
366+ progress -= spreadView.positionMarker2;
473367 }
368+
369+ return Math.max(0, progress);
474370 }
475− return newAngle;
371+ return (spreadView.contentX - (spreadView.positionMarker2 * spreadView.width) - spreadView.tileDistance * (index - 1)) / spreadView.width;
476372 }
477373
478− property real xScale: {
479− var scale = 1;
480−
481− switch (index) {
482− case 0:
483− if (appItem.progress > coverFlip.progressMarker2) {
484− var scaleDiff = coverFlip.maxScale - 1;
485− var progressDiff = 1.5 - coverFlip.progressMarker2;
486− // progress : progressDiff = scale : scaleDiff
487− scale = 1 - (appItem.progress - coverFlip.progressMarker2) * scaleDiff / progressDiff;
488− }
489− break;
490− case 1:
491− if (appItem.progress < coverFlip.progressMarker2) {
492− var scaleDiff = coverFlip.maxScale - 1
493− var progressDiff = coverFlip.progressMarker2
494− // progress : progressDiff = scale : scaleDiff
495− scale = coverFlip.maxScale - (appItem.progress * scaleDiff / progressDiff);
496− break;
497− }
498− // Intentionally no break
499− default:
500− scale = coverFlip.maxScale - scaleEasing.value * scaleEasing.period;
501− if (appItem.isSelected) {
502− var scaleDiff = -(1 - appItem.selectedXScale)
503− var progressDiff = appItem.selectedProgress
504− var progress = progressDiff - (appItem.progress - coverFlip.progressMarker2);
505− // progress : progressDiff = angle : angleDiff
506− var selectedScaleTranslate = progress * scaleDiff / progressDiff;
507−
508− scale = appItem.selectedXScale - selectedScaleTranslate;
374+ animatedProgress: {
375+ if (spreadView.stage < 2 && index == 1) {
376+ if (spreadView.contentX < spreadView.width * spreadView.positionMarker1) {
377+ return spreadView.contentX / spreadView.width
378+ } else if (spreadView.stage == 0){
379+ return spreadView.positionMarker2
509380 }
510381 }
511− return Math.min(coverFlip.maxScale, Math.max(coverFlip.minScale, scale));
382+ return progress;
512383 }
513384
514− EasingCurve {
515− id: xTranslateEasing
516− type: EasingCurve.OutQuad
517− period: index * -width
518− progress: appItem.translatedProgress
519− }
520− EasingCurve {
521− id: angleEasing
522− type: EasingCurve.InQuad
523− period: coverFlip.startAngle - coverFlip.endAngle
524− progress: appItem.translatedProgress
525− }
526− EasingCurve {
527− id: scaleEasing
528− type: EasingCurve.Linear
529− period: coverFlip.maxScale - coverFlip.minScale
530− progress: appItem.translatedProgress
385+ onClicked: {
386+ spreadView.snapTo(index);
531387 }
532388
533− transform: [
534− Rotation {
535− origin { x: 0; y: coverFlip.height / 2 }
536− axis { x: 0; y: 1; z: 0 }
537− angle: appItem.angle
538− },
539− Translate {
540− x: appItem.xTranslation
541− },
542− Scale {
543− origin { x: appItem.xTranslation; y: coverFlip.height / 2 }
544− xScale: appItem.xScale
545− yScale: xScale
389+ // This timer is to decouple the application reordering with the enabling of the SmoothedAnimation
390+ // If both are done in same event loop pass, the reordering would animate.
391+ Connections {
392+ target: spreadView
393+ onStageChanged: {
394+ if (index == 1 && spreadView.stage == 0) {
395+ enableAnimationTimer.start();
396+ } else {
397+ stage1Animation.enabled = false;
398+ }
546399 }
547− ]
548−
549− Image {
550− id: dropShadow
551− anchors.fill: appImage
552− anchors.margins: -units.gu(2)
553− source: "graphics/dropshadow.png"
554− }
555− Image {
556− id: appImage
557− anchors { left: parent.left; bottom: parent.bottom }
558− width: root.width
559− source: ApplicationManager.get(index).screenshot
560− scale: 1
561400 }
562− MouseArea {
563− anchors.fill: parent
564− onClicked: {
565− appItem.select()
401+ Timer {
402+ id: enableAnimationTimer
403+ interval: 1
404+ onTriggered: {
405+ stage1Animation.enabled = true;
566406 }
567407 }
568408
569− SequentialAnimation {
570− id: switchToAppAnimation
571− property int targetContentX
572− UbuntuNumberAnimation {
573− target: coverFlickable;
574− property: "contentX";
575− to: switchToAppAnimation.targetContentX;
576− duration: UbuntuAnimation.SnapDuration
577− }
578− ScriptAction {
579− script: {
580− ApplicationManager.focusApplication(ApplicationManager.get(index).appId);
581− appItem.isSelected = false;
582− coverFlip.progress = 0;
583− coverFlickable.passedFirstStage = false;
409+ Behavior on animatedProgress {
410+ id: stage1Animation
411+ SequentialAnimation {
412+ SmoothedAnimation {
413+ velocity: 2;
414+ duration: UbuntuAnimation.BriskDuration;
415+ easing: UbuntuAnimation.StandardEasing
584416 }
585417 }
586418 }