2 * Copyright (C) 2014-2016 Canonical, Ltd.
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU 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 General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 import Ubuntu.Components 1.3
19 import Ubuntu.Gestures 0.1
20 import Unity.Application 0.1
23 import "../Components"
30 // <tutorial-hacks> The Tutorial looks into our implementation details
31 property alias sideStageVisible: spreadView.sideStageVisible
32 property alias sideStageWidth: spreadView.sideStageWidth
33 // The stage the currently focused surface is in
34 property int stageFocusedSurface: priv.focusedAppDelegate ? priv.focusedAppDelegate.stage : ApplicationInfoInterface.MainStage
37 paintBackground: spreadView.shiftedContentX !== 0
39 // Functions to be called from outside
40 function updateFocusedAppOrientation() {
41 var mainStageIndex = root.topLevelSurfaceList.indexForId(priv.mainStageItemId);
43 if (priv.mainStageItemId && mainStageIndex >= 0 && mainStageIndex < spreadRepeater.count) {
44 spreadRepeater.itemAt(mainStageIndex).matchShellOrientation();
47 for (var i = 0; i < spreadRepeater.count; ++i) {
49 if (i === mainStageIndex) {
53 var spreadDelegate = spreadRepeater.itemAt(i);
55 var delta = spreadDelegate.appWindowOrientationAngle - root.shellOrientationAngle;
56 if (delta < 0) { delta += 360; }
59 var supportedOrientations = spreadDelegate.supportedOrientations;
60 if (supportedOrientations === Qt.PrimaryOrientation) {
61 supportedOrientations = spreadDelegate.orientations.primary;
64 if (delta === 180 && (supportedOrientations & spreadDelegate.shellOrientation)) {
65 spreadDelegate.matchShellOrientation();
69 function updateFocusedAppOrientationAnimated() {
70 var mainStageIndex = root.topLevelSurfaceList.indexForId(priv.mainStageItemId);
71 if (priv.mainStageItemId && mainStageIndex >= 0 && mainStageIndex < spreadRepeater.count) {
72 spreadRepeater.itemAt(mainStageIndex).animateToShellOrientation();
75 var sideStageIndex = root.topLevelSurfaceList.indexForId(priv.sideStageItemId);
76 if (sideStageIndex >= 0 && sideStageIndex < spreadRepeater.count) {
77 spreadRepeater.itemAt(sideStageIndex).matchShellOrientation();
81 function pushRightEdge(amount) {
82 if (spreadView.contentX == -spreadView.shift) {
83 edgeBarrier.push(amount);
87 function closeFocusedDelegate() {
88 if (priv.focusedAppDelegate && priv.focusedAppDelegate.closeable) {
89 priv.focusedAppDelegate.closed();
93 orientationChangesEnabled: priv.mainAppOrientationChangesEnabled
96 if (priv.mainStageItemId > 0) {
97 var index = root.topLevelSurfaceList.indexForId(priv.mainStageItemId);
98 return root.topLevelSurfaceList.applicationAt(index);
104 supportedOrientations: {
106 var orientations = mainApp.supportedOrientations;
107 orientations |= Qt.LandscapeOrientation | Qt.InvertedLandscapeOrientation;
108 if (priv.sideStageItemId && !spreadView.surfaceDragging) {
109 // If we have a sidestage app, support Portrait orientation
110 // so that it will switch the sidestage app to mainstage on rotate to portrait
111 orientations |= Qt.PortraitOrientation|Qt.InvertedPortraitOrientation;
115 // we just don't care
116 return Qt.PortraitOrientation |
117 Qt.LandscapeOrientation |
118 Qt.InvertedPortraitOrientation |
119 Qt.InvertedLandscapeOrientation;
123 // How far left the stage has been dragged, used externally by tutorial code
124 dragProgress: spreadRepeater.count > 0 ? spreadRepeater.itemAt(0).animatedProgress : 0
127 spreadView.selectedIndex = -1;
128 spreadView.phase = 0;
129 spreadView.contentX = -spreadView.shift;
132 onInverseProgressChanged: {
133 // This can't be a simple binding because that would be triggered after this handler
134 // while we need it active before doing the anition left/right
135 spreadView.animateX = (inverseProgress == 0)
136 if (inverseProgress == 0 && priv.oldInverseProgress > 0) {
137 // left edge drag released. Minimum distance is given by design.
138 if (priv.oldInverseProgress > units.gu(22)) {
139 root.applicationManager.requestFocusApplication("unity8-dash");
142 priv.oldInverseProgress = inverseProgress;
145 onAltTabPressedChanged: {
146 if (!spreadEnabled) {
150 priv.highlightIndex = Math.min(spreadRepeater.count - 1, 1);
151 spreadView.snapToSpread();
153 for (var i = 0; i < spreadRepeater.count; i++) {
154 if (spreadRepeater.itemAt(i).zIndex === priv.highlightIndex) {
155 spreadView.snapTo(i);
163 focus: root.altTabPressed
168 priv.highlightIndex = (priv.highlightIndex + 1) % spreadRepeater.count
171 priv.highlightIndex = (priv.highlightIndex + spreadRepeater.count - 1) % spreadRepeater.count
178 target: root.topLevelSurfaceList
179 onListChanged: priv.updateMainAndSideStageIndexes()
184 objectName: "stagesPriv"
186 function updateMainAndSideStageIndexes() {
187 var choseMainStage = false;
188 var choseSideStage = false;
190 if (!root.topLevelSurfaceList)
193 for (var i = 0; i < spreadRepeater.count && (!choseMainStage || !choseSideStage); ++i) {
194 var spreadDelegate = spreadRepeater.itemAt(i);
195 if (sideStage.shown && spreadDelegate.stage == ApplicationInfoInterface.SideStage
196 && !choseSideStage) {
197 priv.sideStageDelegate = spreadDelegate
198 priv.sideStageItemId = root.topLevelSurfaceList.idAt(i);
199 priv.sideStageAppId = root.topLevelSurfaceList.applicationAt(i).appId;
200 choseSideStage = true;
201 } else if (!choseMainStage && spreadDelegate.stage == ApplicationInfoInterface.MainStage) {
202 priv.mainStageDelegate = spreadDelegate;
203 priv.mainStageItemId = root.topLevelSurfaceList.idAt(i);
204 priv.mainStageAppId = root.topLevelSurfaceList.applicationAt(i).appId;
205 choseMainStage = true;
208 if (!choseMainStage) {
209 priv.mainStageDelegate = null;
210 priv.mainStageItemId = 0;
211 priv.mainStageAppId = "";
213 if (!choseSideStage) {
214 priv.sideStageDelegate = null;
215 priv.sideStageItemId = 0;
216 priv.sideStageAppId = "";
220 property var focusedAppDelegate: null
222 property bool mainAppOrientationChangesEnabled: false
224 property real landscapeHeight: root.orientations.native_ == Qt.LandscapeOrientation ?
225 root.nativeHeight : root.nativeWidth
227 property bool shellIsLandscape: root.shellOrientation === Qt.LandscapeOrientation
228 || root.shellOrientation === Qt.InvertedLandscapeOrientation
230 property var mainStageDelegate: null
231 property var sideStageDelegate: null
233 property int mainStageItemId: 0
234 property int sideStageItemId: 0
236 property string mainStageAppId: ""
237 property string sideStageAppId: ""
239 property int oldInverseProgress: 0
241 property int highlightIndex: 0
243 property bool focusedAppDelegateIsDislocated: focusedAppDelegate &&
244 (focusedAppDelegate.dragOffset !== 0 || focusedAppDelegate.xTranslateAnimating)
245 function evaluateOneWayFlick(gesturePoints) {
246 // Need to have at least 3 points to recognize it as a flick
247 if (gesturePoints.length < 3) {
250 // Need to have a movement of at least 2 grid units to recognize it as a flick
251 if (Math.abs(gesturePoints[gesturePoints.length - 1] - gesturePoints[0]) < units.gu(2)) {
255 var oneWayFlick = true;
256 var smallestX = gesturePoints[0];
257 var leftWards = gesturePoints[1] < gesturePoints[0];
258 for (var i = 1; i < gesturePoints.length; i++) {
259 if ((leftWards && gesturePoints[i] >= smallestX)
260 || (!leftWards && gesturePoints[i] <= smallestX)) {
264 smallestX = gesturePoints[i];
269 onHighlightIndexChanged: {
270 spreadView.contentX = highlightIndex * spreadView.contentWidth / (spreadRepeater.count + 2)
273 readonly property bool sideStageEnabled: root.shellOrientation == Qt.LandscapeOrientation ||
274 root.shellOrientation == Qt.InvertedLandscapeOrientation
278 model: root.applicationManager
280 property var stateBinding: Binding {
281 readonly property bool isDash: model.application ? model.application.appId == "unity8-dash" : false
282 target: model.application
283 property: "requestedState"
285 // NB: the first application clause is just to ensure we never get warnings for trying to access
286 // members of a null variable.
287 value: model.application &&
289 (isDash && root.keepDashRunning)
290 || (!root.suspended && (model.application.appId === priv.mainStageAppId
291 || model.application.appId === priv.sideStageAppId))
293 ? ApplicationInfoInterface.RequestedRunning
294 : ApplicationInfoInterface.RequestedSuspended
297 property var lifecycleBinding: Binding {
298 target: model.application
299 property: "exemptFromLifecycle"
300 value: model.application
301 ? (!model.application.isTouchApp || isExemptFromLifecycle(model.application.appId))
308 target: MirFocusController
309 property: "focusedSurface"
310 value: priv.focusedAppDelegate ? priv.focusedAppDelegate.focusedSurface : null
311 when: root.parent && !spreadRepeater.startingUp
316 objectName: "spreadView"
318 interactive: (spreadDragArea.dragging || phase > 1) && draggedDelegateCount === 0
319 contentWidth: spreadRow.width - shift
322 property int tileDistance: units.gu(20)
324 // This indicates when the spreadView is active. That means, all the animations
325 // are activated and tiles need to line up for the spread.
326 readonly property bool active: shiftedContentX > 0 || spreadDragArea.dragging
328 // The flickable needs to fill the screen in order to get touch events all over.
329 // However, we don't want to the user to be able to scroll back all the way. For
330 // that, the beginning of the gesture starts with a negative value for contentX
331 // so the flickable wants to pull it into the view already. "shift" tunes the
332 // distance where to "lock" the content.
333 readonly property real shift: width / 2
334 readonly property real shiftedContentX: contentX + shift
336 // Phase of the animation:
337 // 0: Starting from right edge, a new app (index 1) comes in from the right
338 // 1: The app has reached the first snap position.
339 // 2: The list is dragged further and snaps into the spread view when entering phase 2
342 readonly property int phase0Width: sideStageWidth
343 readonly property int phase1Width: sideStageWidth
345 // Those markers mark the various positions in the spread (ratio to screen width from right to left):
346 // 0 - 1: following finger, snap back to the beginning on release
347 readonly property real positionMarker1: 0.2
348 // 1 - 2: curved snapping movement, snap to nextInStack on release
349 readonly property real positionMarker2: sideStageWidth / spreadView.width
350 // 2 - 3: movement follows finger, snaps to phase 2 (full spread) on release
351 readonly property real positionMarker3: 0.6
352 // passing 3, we detach movement from the finger and snap to phase 2 (full spread)
353 readonly property real positionMarker4: 0.8
355 readonly property int startSnapPosition: phase0Width * 0.5
356 readonly property int endSnapPosition: phase0Width * 0.75
357 readonly property real snapPosition: 0.75
359 property int selectedIndex: -1
360 property int draggedDelegateCount: 0
361 property int closingIndex: -1
362 property var selectedDelegate: selectedIndex !== -1 ? spreadRepeater.itemAt(selectedIndex) : null
364 // <FIXME-contentX> Workaround Flickable's behavior of bringing contentX back between valid boundaries
365 // when resized. The proper way to fix this is refactoring PhoneStage so that it doesn't
366 // rely on having Flickable.contentX keeping an out-of-bounds value when it's set programatically
367 // (as opposed to having contentX reaching an out-of-bounds value through dragging, which will trigger
368 // the Flickable.boundsBehavior upon release).
370 if (!undoContentXReset()) {
371 forceItToRemainStillIfBeingResized();
374 onShiftChanged: { forceItToRemainStillIfBeingResized(); }
375 function forceItToRemainStillIfBeingResized() {
376 if (root.beingResized && contentX != -spreadView.shift) {
377 contentX = -spreadView.shift;
380 function undoContentXReset() {
381 if (contentWidth <= 0) {
382 contentWidthOnLastContentXChange = contentWidth;
383 lastContentX = contentX;
387 if (contentWidth != contentWidthOnLastContentXChange
388 && lastContentX == -shift && contentX == 0) {
389 // Flickable is resetting contentX because contentWidth has changed. Undo it.
394 contentWidthOnLastContentXChange = contentWidth;
395 lastContentX = contentX;
398 property real contentWidthOnLastContentXChange: -1
399 property real lastContentX: 0
402 property bool animateX: true
403 property bool beingResized: root.beingResized
404 onBeingResizedChanged: {
406 // Brace yourselves for impact!
413 property real sideStageWidth: units.gu(40)
415 property bool surfaceDragging: triGestureArea.recognisedDrag
417 readonly property bool sideStageVisible: priv.sideStageItemId != 0
419 // In case applicationManager already holds an app when starting up we're missing animations
420 // Make sure we end up in the same state
421 Component.onCompleted: {
422 spreadView.contentX = -spreadView.shift
425 property int nextInStack: {
426 var mainStageIndex = priv.mainStageDelegate ? priv.mainStageDelegate.index : -1;
427 var sideStageIndex = priv.sideStageDelegate ? priv.sideStageDelegate.index : -1;
430 if (root.topLevelSurfaceList.count > 1) {
434 case "mainAndOverlay":
435 if (root.topLevelSurfaceList.count <= 2) {
438 if (mainStageIndex == 0 || sideStageIndex == 0) {
439 if (mainStageIndex == 1 || sideStageIndex == 1) {
450 property int nextZInStack
459 State { // Side Stage only in overlay mode
462 State { // Main Stage and Side Stage in overlay mode
463 name: "mainAndOverlay"
465 State { // Main Stage and Side Stage in split mode
470 if ((priv.mainStageItemId && !priv.sideStageItemId) || !priv.sideStageEnabled) {
473 if (!priv.mainStageItemId && priv.sideStageItemId) {
476 if (priv.mainStageItemId && priv.sideStageItemId) {
477 return "mainAndOverlay";
482 onShiftedContentXChanged: {
483 if (root.beingResized) {
484 // Flickabe.contentX wiggles during resizes. Don't react to it.
489 // the "spreadEnabled" part is because when code does "phase = 0; contentX = -shift" to
490 // dismiss the spread because spreadEnabled went to false, for some reason, during tests,
491 // Flickable might jump in and change contentX value back, causing the code below to do
492 // "phase = 1" which will make the spread stay.
493 // It sucks that we have no control whatsoever over whether or when Flickable animates its
495 if (root.spreadEnabled && shiftedContentX > width * positionMarker2) {
500 // Do not turn to else if
501 // Sometimes the animation of shiftedContentX is very fast and we need to jump from phase 0 to 1 to 2
502 // in the same onShiftedContentXChanged
504 if (shiftedContentX < width * positionMarker2) {
506 } else if (shiftedContentX >= width * positionMarker4 && !spreadDragArea.dragging) {
513 if (shiftedContentX < phase0Width) {
514 snapAnimation.targetContentX = -shift;
515 snapAnimation.start();
516 } else if (shiftedContentX < phase1Width) {
523 function snapToSpread() {
524 // Add 1 pixel to make sure we definitely hit positionMarker4 even with rounding errors of the animation.
525 snapAnimation.targetContentX = (spreadView.width * spreadView.positionMarker4) + 1 - shift;
526 snapAnimation.start();
529 function snapTo(index) {
530 snapAnimation.stop();
531 spreadView.selectedIndex = index;
532 snapAnimation.targetContentX = -shift;
533 snapAnimation.start();
536 // We need to shuffle z ordering a bit in order to keep side stage apps above main stage apps.
537 // We don't want to really reorder them in the model because that allows us to keep track
538 // of the last focused order.
539 function indexToZIndex(index) {
540 // only shuffle when we've got a main and overlay
541 if (state !== "mainAndOverlay") return index;
543 var app = root.topLevelSurfaceList.applicationAt(index);
547 var stage = spreadRepeater.itemAt(index) ? spreadRepeater.itemAt(index).stage : ApplicationInfoInterface.MainStage;
549 // don't shuffle indexes greater than "actives or next"
550 if (index > 2) return index;
552 var mainStageIndex = root.topLevelSurfaceList.indexForId(priv.mainStageItemId);
554 if (index == mainStageIndex) {
555 // Active main stage always at 0
559 if (spreadView.nextInStack > 0) {
560 var stageOfNextInStack = spreadRepeater.itemAt(spreadView.nextInStack).stage;
562 if (index === spreadView.nextInStack) {
563 // this is the next app in stack.
565 if (stage === ApplicationInfoInterface.SideStage) {
566 // if the next app in stack is a sidestage app, it must order on top of other side stage app
567 return Math.min(2, root.topLevelSurfaceList.count-1);
571 if (stageOfNextInStack === ApplicationInfoInterface.SideStage) {
572 // if the next app in stack is a sidestage app, it must order on top of other side stage app
575 return Math.min(2, root.topLevelSurfaceList.count-1);
577 return Math.min(index+1, root.topLevelSurfaceList.count-1);
580 SequentialAnimation {
582 property int targetContentX: -spreadView.shift
584 UbuntuNumberAnimation {
587 to: snapAnimation.targetContentX
588 duration: UbuntuAnimation.FastDuration
593 if (spreadView.selectedIndex >= 0) {
594 var newIndex = spreadView.selectedIndex;
595 var application = root.topLevelSurfaceList.applicationAt(newIndex);
596 var spreadDelegate = spreadRepeater.itemAt(newIndex);
597 if (spreadDelegate.stage === ApplicationInfoInterface.SideStage) {
600 spreadView.selectedIndex = -1;
601 spreadDelegate.focus = true;
602 spreadView.phase = 0;
603 spreadView.contentX = -spreadView.shift;
609 Behavior on contentX {
610 enabled: root.altTabPressed
611 UbuntuNumberAnimation {}
616 x: spreadView.contentX
617 width: spreadView.width + Math.max(spreadView.width, root.topLevelSurfaceList.count * spreadView.tileDistance)
621 spreadView.snapTo(0);
625 objectName: "MainStageDropArea"
629 bottom: parent.bottom
631 width: spreadView.width - sideStage.width
632 enabled: priv.sideStageEnabled
635 drop.source.spreadDelegate.saveStage(ApplicationInfoInterface.MainStage);
636 drop.source.spreadDelegate.focus = true;
643 objectName: "sideStage"
644 height: priv.landscapeHeight
645 x: spreadView.width - width
647 if (!priv.mainStageItemId) return 0;
649 if (priv.sideStageItemId && spreadView.nextInStack > 0) {
650 var nextDelegateInStack = spreadRepeater.itemAt(spreadView.nextInStack);
652 if (nextDelegateInStack.stage === ApplicationInfoInterface.MainStage) {
653 // if the next app in stack is a main stage app, put the sidestage on top of it.
661 visible: progress != 0
662 enabled: priv.sideStageEnabled && sideStageDropArea.dropAllowed
663 opacity: priv.sideStageEnabled && !spreadView.active ? 1 : 0
664 Behavior on opacity { UbuntuNumberAnimation {} }
667 if (!shown && priv.sideStageDelegate && priv.focusedAppDelegate === priv.sideStageDelegate
668 && priv.mainStageDelegate) {
669 priv.mainStageDelegate.focus = true;
670 } else if (shown && priv.sideStageDelegate) {
671 priv.sideStageDelegate.focus = true;
676 id: sideStageDropArea
677 objectName: "SideStageDropArea"
680 property bool dropAllowed: true
683 dropAllowed = drag.keys != "Disabled";
689 if (drop.keys == "MainStage") {
690 drop.source.spreadDelegate.saveStage(ApplicationInfoInterface.SideStage);
691 drop.source.spreadDelegate.focus = true;
696 if (!sideStageDropArea.drag.source) {
704 TopLevelSurfaceRepeater {
706 objectName: "spreadRepeater"
707 model: root.topLevelSurfaceList
710 priv.updateMainAndSideStageIndexes();
711 if (spreadView.phase == 2) {
712 spreadView.snapTo(index);
717 priv.updateMainAndSideStageIndexes();
718 // Unless we're closing the app ourselves,
719 // lets make sure the spread doesn't mess up by the changing app list.
720 if (spreadView.closingIndex == -1) {
721 spreadView.phase = 0;
722 spreadView.contentX = -spreadView.shift;
726 function focusTopMostApp() {
727 if (spreadRepeater.count > 0) {
728 var topmostDelegate = spreadRepeater.itemAt(0);
729 topmostDelegate.focus = true;
733 delegate: TransformedTabletSpreadDelegate {
735 objectName: "spreadDelegate_" + model.id
737 readonly property int index: model.index
738 width: spreadView.width
739 height: spreadView.height
740 active: model.id == priv.mainStageItemId || model.id == priv.sideStageItemId
741 zIndex: selected && stage == ApplicationInfoInterface.MainStage ? 0 : spreadView.indexToZIndex(index)
743 if (spreadView.nextInStack == model.index) {
744 spreadView.nextZInStack = zIndex;
747 selected: spreadView.selectedIndex == index
748 otherSelected: spreadView.selectedIndex >= 0 && !selected
749 isInSideStage: priv.sideStageItemId == model.id
750 interactive: !spreadView.interactive && spreadView.phase === 0 && root.interactive
751 swipeToCloseEnabled: spreadView.interactive && !snapAnimation.running
752 maximizedAppTopMargin: root.maximizedAppTopMargin
753 dragOffset: !isDash && model.id == priv.mainStageItemId && root.inverseProgress > 0
754 && spreadView.phase === 0 ? root.inverseProgress : 0
755 application: model.application
756 surface: model.surface
758 highlightShown: root.altTabPressed && priv.highlightIndex == zIndex
759 dropShadow: spreadView.active || priv.focusedAppDelegateIsDislocated
761 readonly property bool wantsMainStage: stage == ApplicationInfoInterface.MainStage
763 readonly property bool isDash: application.appId == "unity8-dash"
766 if (focus && !spreadRepeater.startingUp) {
767 priv.focusedAppDelegate = spreadTile;
768 root.topLevelSurfaceList.raiseId(model.id);
770 if (focus && priv.sideStageEnabled && stage === ApplicationInfoInterface.SideStage) {
775 target: model.surface
776 onFocusRequested: spreadTile.focus = true;
779 target: spreadTile.application
781 if (!model.surface) {
782 // when an app has no surfaces, we assume there's only one entry representing it:
784 spreadTile.focus = true;
786 // if the application has surfaces, focus request should be at surface-level.
792 if (priv.mainStageDelegate && stage === ApplicationInfoInterface.SideStage) {
793 return priv.mainStageDelegate.fullscreen;
794 } else if (surface) {
795 return surface.state === Mir.FullscreenState;
796 } else if (application) {
797 return application.fullscreen;
803 supportedOrientations: {
805 var orientations = application.supportedOrientations;
806 if (stage == ApplicationInfoInterface.MainStage) {
807 // When an app is in the mainstage, it always supports Landscape|InvertedLandscape
808 // so that we can drag it from the main stage to the side stage
809 orientations |= Qt.LandscapeOrientation | Qt.InvertedLandscapeOrientation;
813 // we just don't care
814 return Qt.PortraitOrientation |
815 Qt.LandscapeOrientation |
816 Qt.InvertedPortraitOrientation |
817 Qt.InvertedLandscapeOrientation;
821 function saveStage(newStage) {
823 WindowStateStorage.saveStage(application.appId, newStage);
826 // FIXME: A regular binding doesn't update any more after closing an app.
827 // Using a Binding for now.
831 value: (!spreadView.active && isDash && !active) ? -1 : spreadTile.zIndex
835 property real behavioredZIndex: zIndex
836 Behavior on behavioredZIndex {
837 enabled: spreadView.closingIndex >= 0
838 UbuntuNumberAnimation {}
842 onSideStageEnabledChanged: refreshStage()
845 property bool _constructing: true;
847 if (!_constructing) {
848 priv.updateMainAndSideStageIndexes();
852 Component.onCompleted: {
853 // a top level window is always the focused one when it first appears, unfocusing
854 // any preexisting one
857 _constructing = false;
860 function refreshStage() {
861 var newStage = ApplicationInfoInterface.MainStage;
862 if (priv.sideStageEnabled) { // we're in lanscape rotation.
863 if (!isDash && application && application.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) {
864 var defaultStage = ApplicationInfoInterface.SideStage; // if application supports portrait, it defaults to sidestage.
865 if (application.supportedOrientations & (Qt.LandscapeOrientation|Qt.InvertedLandscapeOrientation)) {
866 // if it supports lanscape, it defaults to mainstage.
867 defaultStage = ApplicationInfoInterface.MainStage;
869 newStage = WindowStateStorage.getStage(application.appId, defaultStage);
877 var tileProgress = (spreadView.shiftedContentX - behavioredZIndex * spreadView.tileDistance) / spreadView.width;
878 // Some tiles (nextInStack, active) need to move directly from the beginning, normalize progress to immediately start at 0
879 if ((index == spreadView.nextInStack && spreadView.phase < 2) || (active && spreadView.phase < 1)) {
880 tileProgress += behavioredZIndex * spreadView.tileDistance / spreadView.width;
885 // TODO: Hiding tile when progress is such that it will be off screen.
886 property bool occluded: {
887 if (spreadView.active && !offScreen) return false;
888 else if (spreadTile.active) return false;
889 else if (xTranslateAnimating) return false;
890 else if (z <= 1 && priv.focusedAppDelegateIsDislocated) return false;
894 visible: Powerd.status == Powerd.On &&
895 !greeter.fullyShown &&
899 if (spreadView.phase == 0 && (spreadTile.active || spreadView.nextInStack == index)) {
900 if (progress < spreadView.positionMarker1) {
902 } else if (progress < spreadView.positionMarker1 + snappingCurve.period) {
903 return spreadView.positionMarker1 + snappingCurve.value * 3;
905 return spreadView.positionMarker2;
911 shellOrientationAngle: root.shellOrientationAngle
912 shellOrientation: root.shellOrientation
913 orientations: root.orientations
918 when: spreadTile.stage == ApplicationInfoInterface.MainStage
922 when: spreadTile.stage == ApplicationInfoInterface.SideStage
926 width: spreadView.sideStageWidth
927 height: priv.landscapeHeight
929 supportedOrientations: Qt.PortraitOrientation
930 shellOrientationAngle: 0
931 shellOrientation: Qt.PortraitOrientation
932 orientations: sideStageOrientations
938 id: sideStageOrientations
939 primary: Qt.PortraitOrientation
940 native_: Qt.PortraitOrientation
941 portrait: root.orientations.portrait
942 invertedPortrait: root.orientations.invertedPortrait
943 landscape: root.orientations.landscape
944 invertedLandscape: root.orientations.invertedLandscape
950 SequentialAnimation {
953 properties: "width,height,supportedOrientations,shellOrientationAngle,shellOrientation,orientations"
957 // rotate immediately.
958 spreadTile.matchShellOrientation();
959 if (priv.focusedAppDelegate === spreadTile &&
960 priv.sideStageEnabled && !sideStage.shown) {
961 // Sidestage was focused, so show the side stage.
970 SequentialAnimation {
973 if (priv.sideStageDelegate === spreadTile &&
974 mainApp && (mainApp.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) == 0) {
975 // The mainstage app did not natively support portrait orientation, so focus the sidestage.
976 spreadTile.focus = true;
982 properties: "width,height,supportedOrientations,shellOrientationAngle,shellOrientation,orientations"
984 ScriptAction { script: { spreadTile.matchShellOrientation(); } }
990 if (spreadView.phase == 2) {
991 spreadView.snapTo(index);
997 spreadView.draggedDelegateCount++;
999 spreadView.draggedDelegateCount--;
1004 spreadView.closingIndex = index;
1005 if (spreadTile.surface) {
1006 spreadTile.surface.close();
1007 } else if (spreadTile.application) {
1008 root.applicationManager.stopApplication(spreadTile.application.appId);
1010 // should never happen
1011 console.warn("Can't close topLevelSurfaceList entry as it has neither"
1012 + " a surface nor an application");
1018 when: model.id == priv.mainStageItemId
1019 property: "mainAppWindowOrientationAngle"
1020 value: appWindowOrientationAngle
1024 when: model.id == priv.mainStageItemId
1025 property: "mainAppOrientationChangesEnabled"
1026 value: orientationChangesEnabled
1031 type: EasingCurve.Linear
1032 period: (spreadView.positionMarker2 - spreadView.positionMarker1) / 3
1033 progress: spreadTile.progress - spreadView.positionMarker1
1036 StagedFullscreenPolicy {
1037 id: fullscreenPolicy
1038 surface: model.surface
1042 onStageAboutToBeUnloaded: fullscreenPolicy.active = false
1049 TabletSideStageTouchGesture {
1051 anchors.fill: parent
1052 enabled: priv.sideStageEnabled && !spreadView.active
1054 property Item spreadDelegate
1056 dragComponent: dragComponent
1057 dragComponentProperties: { "spreadDelegate": spreadDelegate }
1060 function matchDelegate(obj) { return String(obj.objectName).indexOf("spreadDelegate") >= 0; }
1062 var delegateAtCenter = Functions.itemAt(spreadRow, x, y, matchDelegate);
1063 if (!delegateAtCenter) return;
1065 spreadDelegate = delegateAtCenter;
1069 if (sideStage.shown) {
1077 // If we're dragging to the sidestage.
1078 if (!sideStage.shown) {
1086 property Item spreadDelegate
1088 surface: spreadDelegate ? spreadDelegate.surface : null
1090 consumesInput: false
1092 resizeSurface: false
1096 height: units.gu(40)
1098 Drag.hotSpot.x: width/2
1099 Drag.hotSpot.y: height/2
1100 // only accept opposite stage.
1102 if (!surface) return "Disabled";
1103 if (spreadDelegate.isDash) return "Disabled";
1105 if (spreadDelegate.stage === ApplicationInfo.MainStage) {
1106 if (spreadDelegate.application.supportedOrientations
1107 & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) {
1118 //eat touch events during the right edge gesture
1120 anchors.fill: parent
1121 enabled: spreadDragArea.dragging
1126 objectName: "spreadDragArea"
1127 x: parent.width - root.dragAreaWidth
1128 anchors { top: parent.top; bottom: parent.bottom }
1129 width: root.dragAreaWidth
1130 direction: Direction.Leftwards
1131 enabled: (spreadView.phase != 2 && root.spreadEnabled) || dragging
1133 property var gesturePoints: new Array()
1135 onTouchPositionChanged: {
1137 spreadView.phase = 0;
1138 spreadView.contentX = -spreadView.shift;
1142 var dragX = -touchPosition.x + spreadDragArea.width - spreadView.shift;
1143 var maxDrag = spreadView.width * spreadView.positionMarker4 - spreadView.shift;
1144 spreadView.contentX = Math.min(dragX, maxDrag);
1146 gesturePoints.push(touchPosition.x);
1149 onDraggingChanged: {
1151 // Gesture recognized. Start recording this gesture
1154 // Ok. The user released. Find out if it was a one-way movement.
1155 var oneWayFlick = priv.evaluateOneWayFlick(gesturePoints);
1158 if (oneWayFlick && spreadView.shiftedContentX < spreadView.positionMarker1 * spreadView.width) {
1159 // If it was a short one-way movement, do the Alt+Tab switch
1160 // no matter if we didn't cross positionMarker1 yet.
1161 spreadView.snapTo(spreadView.nextInStack);
1163 if (spreadView.shiftedContentX < spreadView.width * spreadView.positionMarker1) {
1165 } else if (spreadView.shiftedContentX < spreadView.width * spreadView.positionMarker2) {
1166 spreadView.snapTo(spreadView.nextInStack);
1168 // otherwise snap to the closest snap position we can find
1169 // (might be back to start, to app 1 or to spread)
1180 // NB: it does its own positioning according to the specified edge
1184 spreadView.snapToSpread();
1186 material: Component {
1189 width: parent.height
1190 height: parent.width
1192 anchors.centerIn: parent
1193 gradient: Gradient {
1194 GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.7)}
1195 GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}