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 property alias sideStageVisible: spreadView.sideStageVisible
31 property alias sideStageWidth: spreadView.sideStageWidth
33 // Functions to be called from outside
34 function updateFocusedAppOrientation() {
35 var mainStageAppIndex = priv.indexOf(priv.mainStageAppId);
36 if (mainStageAppIndex >= 0 && mainStageAppIndex < spreadRepeater.count) {
37 spreadRepeater.itemAt(mainStageAppIndex).matchShellOrientation();
40 for (var i = 0; i < spreadRepeater.count; ++i) {
42 if (i === mainStageAppIndex) {
46 var spreadDelegate = spreadRepeater.itemAt(i);
48 var delta = spreadDelegate.appWindowOrientationAngle - root.shellOrientationAngle;
49 if (delta < 0) { delta += 360; }
52 var supportedOrientations = spreadDelegate.supportedOrientations;
53 if (supportedOrientations === Qt.PrimaryOrientation) {
54 supportedOrientations = spreadDelegate.orientations.primary;
57 if (delta === 180 && (supportedOrientations & spreadDelegate.shellOrientation)) {
58 spreadDelegate.matchShellOrientation();
62 function updateFocusedAppOrientationAnimated() {
63 var mainStageAppIndex = priv.indexOf(priv.mainStageAppId);
64 if (mainStageAppIndex >= 0 && mainStageAppIndex < spreadRepeater.count) {
65 spreadRepeater.itemAt(mainStageAppIndex).animateToShellOrientation();
68 if (priv.sideStageAppId) {
69 var sideStageAppIndex = priv.indexOf(priv.sideStageAppId);
70 if (sideStageAppIndex >= 0 && sideStageAppIndex < spreadRepeater.count) {
71 spreadRepeater.itemAt(sideStageAppIndex).matchShellOrientation();
76 function pushRightEdge(amount) {
77 if (spreadView.contentX == -spreadView.shift) {
78 edgeBarrier.push(amount);
82 mainAppWindow: priv.focusedAppDelegate ? priv.focusedAppDelegate.appWindow : null
84 orientationChangesEnabled: priv.mainAppOrientationChangesEnabled
86 supportedOrientations: {
88 var orientations = mainApp.supportedOrientations;
89 orientations |= Qt.LandscapeOrientation | Qt.InvertedLandscapeOrientation;
90 if (priv.sideStageAppId && !spreadView.surfaceDragging) {
91 // If we have a sidestage app, support Portrait orientation
92 // so that it will switch the sidestage app to mainstage on rotate
93 orientations |= Qt.PortraitOrientation|Qt.InvertedPortraitOrientation;
98 return Qt.PortraitOrientation |
99 Qt.LandscapeOrientation |
100 Qt.InvertedPortraitOrientation |
101 Qt.InvertedLandscapeOrientation;
105 // How far left the stage has been dragged, used externally by tutorial code
106 dragProgress: spreadRepeater.count > 0 ? spreadRepeater.itemAt(0).animatedProgress : 0
109 spreadView.selectedIndex = -1;
110 spreadView.phase = 0;
111 spreadView.contentX = -spreadView.shift;
114 onInverseProgressChanged: {
115 // This can't be a simple binding because that would be triggered after this handler
116 // while we need it active before doing the anition left/right
117 spreadView.animateX = (inverseProgress == 0)
118 if (inverseProgress == 0 && priv.oldInverseProgress > 0) {
119 // left edge drag released. Minimum distance is given by design.
120 if (priv.oldInverseProgress > units.gu(22)) {
121 ApplicationManager.requestFocusApplication("unity8-dash");
124 priv.oldInverseProgress = inverseProgress;
127 onAltTabPressedChanged: {
128 if (!spreadEnabled) {
132 priv.highlightIndex = Math.min(spreadRepeater.count - 1, 1);
133 spreadView.snapToSpread();
135 for (var i = 0; i < spreadRepeater.count; i++) {
136 if (spreadRepeater.itemAt(i).zIndex === priv.highlightIndex) {
137 spreadView.snapTo(i);
145 focus: root.altTabPressed
150 priv.highlightIndex = (priv.highlightIndex + 1) % spreadRepeater.count
153 priv.highlightIndex = (priv.highlightIndex + spreadRepeater.count - 1) % spreadRepeater.count
161 objectName: "stagesPriv"
163 property string focusedAppId: ApplicationManager.focusedApplicationId
164 readonly property var focusedAppDelegate: {
165 var index = indexOf(focusedAppId);
166 return index >= 0 && index < spreadRepeater.count ? spreadRepeater.itemAt(index) : null
169 property string oldFocusedAppId: ""
170 property bool mainAppOrientationChangesEnabled: false
172 property real landscapeHeight: root.orientations.native_ == Qt.LandscapeOrientation ?
173 root.nativeHeight : root.nativeWidth
175 property bool shellIsLandscape: root.shellOrientation === Qt.LandscapeOrientation
176 || root.shellOrientation === Qt.InvertedLandscapeOrientation
178 property string mainStageAppId
179 property string sideStageAppId
181 // For convenience, keep properties of the first two apps in the model
182 property string appId0
183 property string appId1
185 property int oldInverseProgress: 0
187 property int highlightIndex: 0
189 onFocusedAppIdChanged: updateStageApps()
191 onFocusedAppDelegateChanged: {
192 if (focusedAppDelegate) {
193 focusedAppDelegate.focus = true;
197 property bool focusedAppDelegateIsDislocated: focusedAppDelegate &&
198 (focusedAppDelegate.dragOffset !== 0 || focusedAppDelegate.xTranslateAnimating)
199 function indexOf(appId) {
200 for (var i = 0; i < ApplicationManager.count; i++) {
201 if (ApplicationManager.get(i).appId == appId) {
208 function evaluateOneWayFlick(gesturePoints) {
209 // Need to have at least 3 points to recognize it as a flick
210 if (gesturePoints.length < 3) {
213 // Need to have a movement of at least 2 grid units to recognize it as a flick
214 if (Math.abs(gesturePoints[gesturePoints.length - 1] - gesturePoints[0]) < units.gu(2)) {
218 var oneWayFlick = true;
219 var smallestX = gesturePoints[0];
220 var leftWards = gesturePoints[1] < gesturePoints[0];
221 for (var i = 1; i < gesturePoints.length; i++) {
222 if ((leftWards && gesturePoints[i] >= smallestX)
223 || (!leftWards && gesturePoints[i] <= smallestX)) {
227 smallestX = gesturePoints[i];
232 onHighlightIndexChanged: {
233 spreadView.contentX = highlightIndex * spreadView.contentWidth / (spreadRepeater.count + 2)
236 function getTopApp(stage) {
237 for (var i = 0; i < ApplicationManager.count; i++) {
238 var app = ApplicationManager.get(i)
239 if (app.stage === stage) {
246 function setAppStage(appId, stage, save) {
247 var app = ApplicationManager.findApplication(appId);
251 WindowStateStorage.saveStage(appId, stage);
256 function updateStageApps() {
257 var app = priv.getTopApp(ApplicationInfoInterface.MainStage);
258 priv.mainStageAppId = app ? app.appId : ""
261 if (sideStage.shown) {
262 app = priv.getTopApp(ApplicationInfoInterface.SideStage);
263 priv.sideStageAppId = app ? app.appId : ""
265 priv.sideStageAppId = "";
268 appId0 = ApplicationManager.count >= 1 ? ApplicationManager.get(0).appId : "";
269 appId1 = ApplicationManager.count > 1 ? ApplicationManager.get(1).appId : "";
272 readonly property bool sideStageEnabled: root.shellOrientation == Qt.LandscapeOrientation ||
273 root.shellOrientation == Qt.InvertedLandscapeOrientation
274 Component.onCompleted: updateStageApps();
278 target: ApplicationManager
280 if (spreadView.interactive) {
281 spreadView.snapTo(priv.indexOf(appId));
283 ApplicationManager.focusApplication(appId);
287 onApplicationAdded: {
288 if (spreadView.phase == 2) {
289 spreadView.snapTo(ApplicationManager.count - 1);
291 spreadView.phase = 0;
292 spreadView.contentX = -spreadView.shift;
293 ApplicationManager.focusApplication(appId);
297 onApplicationRemoved: {
298 if (priv.mainStageAppId == appId) {
299 ApplicationManager.focusApplication("unity8-dash")
301 if (priv.sideStageAppId == appId) {
302 var app = priv.getTopApp(ApplicationInfoInterface.SideStage);
303 priv.sideStageAppId = app === null ? "" : app.appId;
306 if (ApplicationManager.count == 0) {
307 spreadView.phase = 0;
308 spreadView.contentX = -spreadView.shift;
309 } else if (spreadView.closingIndex == -1) {
310 // Unless we're closing the app ourselves in the spread,
311 // lets make sure the spread doesn't mess up by the changing app list.
312 spreadView.phase = 0;
313 spreadView.contentX = -spreadView.shift;
315 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
322 objectName: "spreadView"
324 interactive: (spreadDragArea.dragging || phase > 1) && draggedDelegateCount === 0
325 contentWidth: spreadRow.width - shift
328 property int tileDistance: units.gu(20)
330 // This indicates when the spreadView is active. That means, all the animations
331 // are activated and tiles need to line up for the spread.
332 readonly property bool active: shiftedContentX > 0 || spreadDragArea.dragging
334 // The flickable needs to fill the screen in order to get touch events all over.
335 // However, we don't want to the user to be able to scroll back all the way. For
336 // that, the beginning of the gesture starts with a negative value for contentX
337 // so the flickable wants to pull it into the view already. "shift" tunes the
338 // distance where to "lock" the content.
339 readonly property real shift: width / 2
340 readonly property real shiftedContentX: contentX + shift
342 // Phase of the animation:
343 // 0: Starting from right edge, a new app (index 1) comes in from the right
344 // 1: The app has reached the first snap position.
345 // 2: The list is dragged further and snaps into the spread view when entering phase 2
348 readonly property int phase0Width: sideStageWidth
349 readonly property int phase1Width: sideStageWidth
351 // Those markers mark the various positions in the spread (ratio to screen width from right to left):
352 // 0 - 1: following finger, snap back to the beginning on release
353 readonly property real positionMarker1: 0.2
354 // 1 - 2: curved snapping movement, snap to nextInStack on release
355 readonly property real positionMarker2: sideStageWidth / spreadView.width
356 // 2 - 3: movement follows finger, snaps to phase 2 (full spread) on release
357 readonly property real positionMarker3: 0.6
358 // passing 3, we detach movement from the finger and snap to phase 2 (full spread)
359 readonly property real positionMarker4: 0.8
361 readonly property int startSnapPosition: phase0Width * 0.5
362 readonly property int endSnapPosition: phase0Width * 0.75
363 readonly property real snapPosition: 0.75
365 property int selectedIndex: -1
366 property int draggedDelegateCount: 0
367 property int closingIndex: -1
368 property var selectedApplication: selectedIndex !== -1 ? ApplicationManager.get(selectedIndex) : null
370 // FIXME: Workaround Flickable's not keepping its contentX still when resized
371 onContentXChanged: { forceItToRemainStillIfBeingResized(); }
372 onShiftChanged: { forceItToRemainStillIfBeingResized(); }
373 function forceItToRemainStillIfBeingResized() {
374 if (root.beingResized && contentX != -shift) {
379 property bool animateX: true
380 property bool beingResized: root.beingResized
381 onBeingResizedChanged: {
383 // Brace yourselves for impact!
390 property real sideStageDragProgress: sideStage.progress
391 property bool sideStageVisible: priv.sideStageAppId
392 property real sideStageWidth: units.gu(40)
394 property bool surfaceDragging: triGestureArea.recognisedDrag
396 // In case the ApplicationManager already holds an app when starting up we're missing animations
397 // Make sure we end up in the same state
398 Component.onCompleted: {
399 spreadView.contentX = -spreadView.shift
402 property int nextInStack: {
405 if (ApplicationManager.count > 1) {
409 case "mainAndOverlay":
410 if (ApplicationManager.count <= 2) {
413 if (priv.appId0 == priv.mainStageAppId || priv.appId0 == priv.sideStageAppId) {
414 if (priv.appId1 == priv.mainStageAppId || priv.appId1 == priv.sideStageAppId) {
425 property int nextZInStack: indexToZIndex(nextInStack)
434 State { // Side Stage only in overlay mode
437 State { // Main Stage and Side Stage in overlay mode
438 name: "mainAndOverlay"
440 State { // Main Stage and Side Stage in split mode
445 if ((priv.mainStageAppId && !priv.sideStageAppId) || !priv.sideStageEnabled) {
448 if (!priv.mainStageAppId && priv.sideStageAppId) {
451 if (priv.mainStageAppId && priv.sideStageAppId) {
452 return "mainAndOverlay";
457 onShiftedContentXChanged: {
458 if (root.beingResized) {
459 // Flickabe.contentX wiggles during resizes. Don't react to it.
465 // the "spreadEnabled" part is because when code does "phase = 0; contentX = -shift" to
466 // dismiss the spread because spreadEnabled went to false, for some reason, during tests,
467 // Flickable might jump in and change contentX value back, causing the code below to do
468 // "phase = 1" which will make the spread stay.
469 // It sucks that we have no control whatsoever over whether or when Flickable animates its
471 if (root.spreadEnabled && shiftedContentX > width * positionMarker2) {
476 if (shiftedContentX < width * positionMarker2) {
478 } else if (shiftedContentX >= width * positionMarker4 && !spreadDragArea.dragging) {
486 if (shiftedContentX < phase0Width) {
487 snapAnimation.targetContentX = -shift;
488 snapAnimation.start();
489 } else if (shiftedContentX < phase1Width) {
496 function snapToSpread() {
497 // Add 1 pixel to make sure we definitely hit positionMarker4 even with rounding errors of the animation.
498 snapAnimation.targetContentX = (spreadView.width * spreadView.positionMarker4) + 1 - shift;
499 snapAnimation.start();
502 function snapTo(index) {
503 snapAnimation.stop();
504 spreadView.selectedIndex = index;
505 snapAnimation.targetContentX = -shift;
506 snapAnimation.start();
509 // We need to shuffle z ordering a bit in order to keep side stage apps above main stage apps.
510 // We don't want to really reorder them in the model because that allows us to keep track
511 // of the last focused order.
512 function indexToZIndex(index) {
513 // only shuffle when we've got a main and overlay
514 if (state !== "mainAndOverlay") return index;
516 var app = ApplicationManager.get(index);
521 // don't shuffle indexes greater than "actives or next"
522 if (index > 2) return index;
524 if (app.appId === priv.mainStageAppId) {
525 // Active main stage always at 0
529 if (spreadView.nextInStack > 0) {
530 var nextAppInStack = ApplicationManager.get(spreadView.nextInStack);
532 if (index === spreadView.nextInStack) {
533 // this is the next app in stack.
535 if (app.stage === ApplicationInfoInterface.SideStage) {
536 // if the next app in stack is a sidestage app, it must order on top of other side stage app
537 return Math.min(2, ApplicationManager.count-1);
541 if (nextAppInStack.stage === ApplicationInfoInterface.SideStage) {
542 // if the next app in stack is a sidestage app, it must order on top of other side stage app
545 return Math.min(2, ApplicationManager.count-1);
547 return Math.min(index+1, ApplicationManager.count-1);
550 SequentialAnimation {
552 property int targetContentX: -spreadView.shift
554 UbuntuNumberAnimation {
557 to: snapAnimation.targetContentX
558 duration: UbuntuAnimation.FastDuration
563 if (spreadView.selectedIndex >= 0) {
564 var newIndex = spreadView.selectedIndex;
565 var application = ApplicationManager.get(newIndex);
566 if (application.stage === ApplicationInfoInterface.SideStage) {
569 spreadView.selectedIndex = -1;
570 ApplicationManager.focusApplication(application.appId);
571 spreadView.phase = 0;
572 spreadView.contentX = -spreadView.shift;
578 Behavior on contentX {
579 enabled: root.altTabPressed
580 UbuntuNumberAnimation {}
585 x: spreadView.contentX
586 width: spreadView.width + Math.max(spreadView.width, ApplicationManager.count * spreadView.tileDistance)
590 spreadView.snapTo(0);
594 objectName: "MainStageDropArea"
598 bottom: parent.bottom
600 width: spreadView.width - sideStage.width
601 enabled: priv.sideStageEnabled
604 priv.setAppStage(drag.source.appId, ApplicationInfoInterface.MainStage, true);
605 ApplicationManager.focusApplication(drag.source.appId);
612 objectName: "sideStage"
613 height: priv.landscapeHeight
614 x: spreadView.width - width
616 if (!priv.mainStageAppId) return 0;
618 if (priv.sideStageAppId && spreadView.nextInStack > 0) {
619 var nextAppInStack = ApplicationManager.get(spreadView.nextInStack);
621 if (nextAppInStack.stage === ApplicationInfoInterface.MainStage) {
622 // if the next app in stack is a main stage app, put the sidestage on top of it.
630 visible: progress != 0
631 enabled: priv.sideStageEnabled && sideStageDropArea.dropAllowed
632 opacity: priv.sideStageEnabled && !spreadView.active ? 1 : 0
633 Behavior on opacity { UbuntuNumberAnimation {} }
636 if (!shown && ApplicationManager.focusedApplicationId == priv.sideStageAppId) {
637 ApplicationManager.requestFocusApplication(priv.mainStageAppId);
639 priv.updateStageApps();
640 if (shown && priv.sideStageAppId) {
641 ApplicationManager.requestFocusApplication(priv.sideStageAppId);
646 id: sideStageDropArea
647 objectName: "SideStageDropArea"
650 property bool dropAllowed: true
653 dropAllowed = drag.keys != "Disabled";
659 if (drop.keys == "MainStage") {
660 priv.setAppStage(drop.source.appId, ApplicationInfoInterface.SideStage, true);
661 ApplicationManager.requestFocusApplication(drop.source.appId);
666 if (!sideStageDropArea.drag.source) {
676 objectName: "spreadRepeater"
677 model: ApplicationManager
679 delegate: TransformedTabletSpreadDelegate {
681 objectName: model.appId ? "tabletSpreadDelegate_" + model.appId
682 : "tabletSpreadDelegate_null";
683 width: spreadView.width
684 height: spreadView.height
685 active: appId == priv.mainStageAppId || appId == priv.sideStageAppId
686 zIndex: selected && stage == ApplicationInfoInterface.MainStage ? 0 : spreadView.indexToZIndex(index)
687 selected: spreadView.selectedIndex == index
688 otherSelected: spreadView.selectedIndex >= 0 && !selected
689 isInSideStage: priv.sideStageAppId === appId
690 interactive: !spreadView.interactive && spreadView.phase === 0 && root.interactive
691 swipeToCloseEnabled: spreadView.interactive && !snapAnimation.running
692 maximizedAppTopMargin: root.maximizedAppTopMargin
693 dragOffset: !isDash && appId == priv.mainStageAppId && root.inverseProgress > 0 && spreadView.phase === 0 ? root.inverseProgress : 0
694 application: ApplicationManager.get(index)
696 highlightShown: root.altTabPressed && priv.highlightIndex == zIndex
698 readonly property bool wantsMainStage: model.stage == ApplicationInfoInterface.MainStage
700 readonly property string appId: model.appId
701 readonly property bool isDash: model.appId == "unity8-dash"
705 if (mainApp && stage === ApplicationInfoInterface.SideStage) {
706 return mainApp.fullscreen;
708 return application ? application.fullscreen : false;
711 supportedOrientations: {
713 var orientations = application.supportedOrientations;
714 if (stage == ApplicationInfoInterface.MainStage) {
715 // When an app is in the mainstage, it always supports Landscape|InvertedLandscape
716 // so that we can drag it from the main stage to the side stage
717 orientations |= Qt.LandscapeOrientation | Qt.InvertedLandscapeOrientation;
721 // we just don't care
722 return Qt.PortraitOrientation |
723 Qt.LandscapeOrientation |
724 Qt.InvertedPortraitOrientation |
725 Qt.InvertedLandscapeOrientation;
731 target: spreadTile.application
732 property: "exemptFromLifecycle"
733 value: !model.isTouchApp || isExemptFromLifecycle(model.appId)
737 target: spreadTile.application
738 property: "requestedState"
739 value: (isDash && root.keepDashRunning)
740 || (!root.suspended && (model.appId == priv.mainStageAppId
741 || model.appId == priv.sideStageAppId))
742 ? ApplicationInfoInterface.RequestedRunning
743 : ApplicationInfoInterface.RequestedSuspended
746 // FIXME: A regular binding doesn't update any more after closing an app.
747 // Using a Binding for now.
751 value: (!spreadView.active && isDash && !active) ? -1 : spreadTile.zIndex
755 property real behavioredZIndex: zIndex
756 Behavior on behavioredZIndex {
757 enabled: spreadView.closingIndex >= 0
758 UbuntuNumberAnimation {}
762 onSideStageEnabledChanged: refreshStage()
765 Component.onCompleted: {
767 stageChanged.connect(priv.updateStageApps);
770 function refreshStage() {
771 var stage = ApplicationInfoInterface.MainStage;
772 if (priv.sideStageEnabled) {
773 if (application && application.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) {
774 stage = WindowStateStorage.getStage(appId);
778 if (model.stage !== stage) {
779 priv.setAppStage(appId, stage, false);
783 // This is required because none of the bindings are triggered in some cases:
784 // When an app is closed, it might happen that ApplicationManager.get(nextInStack)
785 // returns a different app even though the nextInStackIndex and all the related
786 // bindings (index, mainStageApp, sideStageApp, etc) don't change. Let's force a
787 // binding update in that case.
789 target: ApplicationManager
790 onApplicationRemoved: spreadTile.z = Qt.binding(function() {
791 return spreadView.indexToZIndex(index);
796 var tileProgress = (spreadView.shiftedContentX - behavioredZIndex * spreadView.tileDistance) / spreadView.width;
797 // Some tiles (nextInStack, active) need to move directly from the beginning, normalize progress to immediately start at 0
798 if ((index == spreadView.nextInStack && spreadView.phase < 2) || (active && spreadView.phase < 1)) {
799 tileProgress += behavioredZIndex * spreadView.tileDistance / spreadView.width;
804 // TODO: Hiding tile when progress is such that it will be off screen.
805 property bool occluded: {
806 if (spreadView.active && !offScreen) return false;
807 else if (spreadTile.active) return false;
808 else if (xTranslateAnimating) return false;
809 else if (z <= 1 && priv.focusedAppDelegateIsDislocated) return false;
813 visible: Powerd.status == Powerd.On &&
814 !greeter.fullyShown &&
818 if (spreadView.phase == 0 && (spreadTile.active || spreadView.nextInStack == index)) {
819 if (progress < spreadView.positionMarker1) {
821 } else if (progress < spreadView.positionMarker1 + snappingCurve.period) {
822 return spreadView.positionMarker1 + snappingCurve.value * 3;
824 return spreadView.positionMarker2;
830 shellOrientationAngle: root.shellOrientationAngle
831 shellOrientation: root.shellOrientation
832 orientations: root.orientations
837 when: spreadTile.stage == ApplicationInfoInterface.MainStage
841 when: spreadTile.stage == ApplicationInfoInterface.SideStage
845 width: spreadView.sideStageWidth
846 height: priv.landscapeHeight
848 supportedOrientations: Qt.PortraitOrientation
849 shellOrientationAngle: 0
850 shellOrientation: Qt.PortraitOrientation
851 orientations: sideStageOrientations
857 id: sideStageOrientations
858 primary: Qt.PortraitOrientation
859 native_: Qt.PortraitOrientation
860 portrait: root.orientations.portrait
861 invertedPortrait: root.orientations.invertedPortrait
862 landscape: root.orientations.landscape
863 invertedLandscape: root.orientations.invertedLandscape
869 SequentialAnimation {
872 properties: "width,height,supportedOrientations,shellOrientationAngle,shellOrientation,orientations"
876 // rotate immediately.
877 spreadTile.matchShellOrientation();
878 if (ApplicationManager.focusedApplicationId === spreadTile.appId &&
879 priv.sideStageEnabled && !sideStage.shown) {
880 // Sidestage was focused, so show the side stage.
882 // if we've switched to a main app which doesnt support portrait, hide the side stage.
883 } else if (mainApp && (mainApp.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) == 0) {
892 SequentialAnimation {
895 if (priv.sideStageAppId === spreadTile.appId &&
896 mainApp && (mainApp.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) == 0) {
897 // The mainstage app did not natively support portrait orientation, so focus the sidestage.
898 ApplicationManager.requestFocusApplication(spreadTile.appId);
904 properties: "width,height,supportedOrientations,shellOrientationAngle,shellOrientation,orientations"
906 ScriptAction { script: { spreadTile.matchShellOrientation(); } }
912 if (spreadView.phase == 2) {
913 spreadView.snapTo(index);
919 spreadView.draggedDelegateCount++;
921 spreadView.draggedDelegateCount--;
926 spreadView.closingIndex = index;
927 ApplicationManager.stopApplication(ApplicationManager.get(index).appId);
931 if (focus && ApplicationManager.focusedApplicationId !== appId) {
932 ApplicationManager.focusApplication(appId);
935 if (focus && priv.sideStageEnabled && stage === ApplicationInfoInterface.SideStage) {
942 when: model.appId == priv.mainStageAppId
943 property: "mainAppWindowOrientationAngle"
944 value: appWindowOrientationAngle
948 when: model.appId == priv.mainStageAppId
949 property: "mainAppOrientationChangesEnabled"
950 value: orientationChangesEnabled
955 type: EasingCurve.Linear
956 period: (spreadView.positionMarker2 - spreadView.positionMarker1) / 3
957 progress: spreadTile.progress - spreadView.positionMarker1
960 StagedFullscreenPolicy {
962 application: spreadTile.application
966 onStageAboutToBeUnloaded: fullscreenPolicy.active = false
973 TabletSideStageTouchGesture {
976 enabled: priv.sideStageEnabled && !spreadView.active
977 property var dragObject: null
978 property string appId: ""
979 dragComponent: dragComponent
980 dragComponentProperties: { "appId": appId }
983 function matchDelegate(obj) { return String(obj.objectName).indexOf("tabletSpreadDelegate") >= 0; }
985 var delegateAtCenter = Functions.itemAt(spreadRow, x, y, matchDelegate);
986 if (!delegateAtCenter) return;
988 appId = delegateAtCenter.appId;
992 if (sideStage.shown) {
1000 // If we're dragging to the sidestage.
1001 if (!sideStage.shown) {
1009 property string appId: ""
1010 property var application: ApplicationManager.findApplication(appId)
1012 session: application ? application.session : null
1014 resizeSurface: false
1018 height: units.gu(40)
1020 Drag.hotSpot.x: width/2
1021 Drag.hotSpot.y: height/2
1022 // only accept opposite stage.
1024 if (!application) return "Disabled";
1026 if (application.stage === ApplicationInfo.MainStage) {
1027 if (application.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) {
1038 //eat touch events during the right edge gesture
1040 anchors.fill: parent
1041 enabled: spreadDragArea.dragging
1044 DirectionalDragArea {
1046 objectName: "spreadDragArea"
1047 x: parent.width - root.dragAreaWidth
1048 anchors { top: parent.top; bottom: parent.bottom }
1049 width: root.dragAreaWidth
1050 direction: Direction.Leftwards
1051 enabled: (spreadView.phase != 2 && root.spreadEnabled) || dragging
1053 property var gesturePoints: new Array()
1057 spreadView.phase = 0;
1058 spreadView.contentX = -spreadView.shift;
1062 var dragX = -touchX + spreadDragArea.width - spreadView.shift;
1063 var maxDrag = spreadView.width * spreadView.positionMarker4 - spreadView.shift;
1064 spreadView.contentX = Math.min(dragX, maxDrag);
1066 gesturePoints.push(touchX);
1069 onDraggingChanged: {
1071 // Gesture recognized. Start recording this gesture
1074 // Ok. The user released. Find out if it was a one-way movement.
1075 var oneWayFlick = priv.evaluateOneWayFlick(gesturePoints);
1078 if (oneWayFlick && spreadView.shiftedContentX < spreadView.positionMarker1 * spreadView.width) {
1079 // If it was a short one-way movement, do the Alt+Tab switch
1080 // no matter if we didn't cross positionMarker1 yet.
1081 spreadView.snapTo(spreadView.nextInStack);
1083 if (spreadView.shiftedContentX < spreadView.width * spreadView.positionMarker1) {
1085 } else if (spreadView.shiftedContentX < spreadView.width * spreadView.positionMarker2) {
1086 spreadView.snapTo(spreadView.nextInStack);
1088 // otherwise snap to the closest snap position we can find
1089 // (might be back to start, to app 1 or to spread)
1100 // NB: it does its own positioning according to the specified edge
1104 spreadView.snapToSpread();
1106 material: Component {
1109 width: parent.height
1110 height: parent.width
1112 anchors.centerIn: parent
1113 gradient: Gradient {
1114 GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.7)}
1115 GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}