Lomiri
Loading...
Searching...
No Matches
Stage.qml
1/*
2 * Copyright (C) 2014-2017 Canonical Ltd.
3 * Copyright (C) 2021 UBports Foundation
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; version 3.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18import QtQuick 2.15
19import QtQml 2.15
20import QtQuick.Window 2.2
21import Lomiri.Components 1.3
22import QtMir.Application 0.1
23import "../Components/PanelState"
24import "../Components"
25import Utils 0.1
26import Lomiri.Gestures 0.1
27import GlobalShortcut 1.0
28import GSettings 1.0
29import "Spread"
30import "Spread/MathUtils.js" as MathUtils
31import ProcessControl 0.1
32import WindowManager 1.0
33
34FocusScope {
35 id: root
36 anchors.fill: parent
37
38 property QtObject applicationManager
39 property QtObject topLevelSurfaceList
40 property bool altTabPressed
41 property url background
42 property alias backgroundSourceSize: wallpaper.sourceSize
43 property int dragAreaWidth
44 property real nativeHeight
45 property real nativeWidth
46 property QtObject orientations
47 property int shellOrientation
48 property int shellOrientationAngle
49 property bool spreadEnabled: true // If false, animations and right edge will be disabled
50 property bool suspended
51 property bool oskEnabled: false
52 property rect inputMethodRect
53 property real rightEdgePushProgress: 0
54 property Item availableDesktopArea
55 property PanelState panelState
56
57 // Whether outside forces say that the Stage may have focus
58 property bool allowInteractivity
59
60 readonly property bool interactive: (state === "staged" || state === "stagedWithSideStage" || state === "windowed") && allowInteractivity
61
62 // Configuration
63 property string mode: "staged"
64
65 readonly property var temporarySelectedWorkspace: state == "spread" ? screensAndWorkspaces.activeWorkspace : null
66 property bool workspaceEnabled: (mode == "windowed" && settings.enableWorkspace) || settings.forceEnableWorkspace
67
68 // Used by the tutorial code
69 readonly property real rightEdgeDragProgress: rightEdgeDragArea.dragging ? rightEdgeDragArea.progress : 0 // How far left the stage has been dragged
70
71 // used by the snap windows (edge maximize) feature
72 readonly property alias previewRectangle: fakeRectangle
73
74 readonly property bool spreadShown: state == "spread"
75 readonly property var mainApp: priv.focusedAppDelegate ? priv.focusedAppDelegate.application : null
76
77 // application windows never rotate independently
78 property int mainAppWindowOrientationAngle: shellOrientationAngle
79
80 property bool orientationChangesEnabled: !priv.focusedAppDelegate || priv.focusedAppDelegate.orientationChangesEnabled
81
82 property int supportedOrientations: {
83 if (mainApp) {
84 switch (mode) {
85 case "staged":
86 return mainApp.supportedOrientations;
87 case "stagedWithSideStage":
88 var orientations = mainApp.supportedOrientations;
89 orientations |= Qt.LandscapeOrientation | Qt.InvertedLandscapeOrientation;
90 if (priv.sideStageItemId) {
91 // If we have a sidestage app, support Portrait orientation
92 // so that it will switch the sidestage app to mainstage on rotate to portrait
93 orientations |= Qt.PortraitOrientation|Qt.InvertedPortraitOrientation;
94 }
95 return orientations;
96 }
97 }
98
99 return Qt.PortraitOrientation |
100 Qt.LandscapeOrientation |
101 Qt.InvertedPortraitOrientation |
102 Qt.InvertedLandscapeOrientation;
103 }
104
105 GSettings {
106 id: settings
107 schema.id: "com.lomiri.Shell"
108 }
109
110 property int launcherLeftMargin : 0
111
112 Binding {
113 target: topLevelSurfaceList
114 restoreMode: Binding.RestoreBinding
115 property: "rootFocus"
116 value: interactive
117 }
118
119 onInteractiveChanged: {
120 // Stage must have focus before activating windows, including null
121 if (interactive) {
122 focus = true;
123 }
124 }
125
126 onAltTabPressedChanged: {
127 root.focus = true;
128 if (altTabPressed) {
129 if (root.spreadEnabled) {
130 altTabDelayTimer.start();
131 }
132 } else {
133 // Alt Tab has been released, did we already go to spread?
134 if (priv.goneToSpread) {
135 priv.goneToSpread = false;
136 } else {
137 // No we didn't, do a quick alt-tab
138 if (appRepeater.count > 1) {
139 appRepeater.itemAt(1).activate();
140 } else if (appRepeater.count > 0) {
141 appRepeater.itemAt(0).activate(); // quick alt-tab to the only (minimized) window should still activate it
142 }
143 }
144 }
145 }
146
147 Timer {
148 id: altTabDelayTimer
149 interval: 140
150 repeat: false
151 onTriggered: {
152 if (root.altTabPressed) {
153 priv.goneToSpread = true;
154 }
155 }
156 }
157
158 // For MirAL window management
159 WindowMargins {
160 normal: Qt.rect(0, root.mode === "windowed" ? priv.windowDecorationHeight : 0, 0, 0)
161 dialog: normal
162 }
163
164 property Item itemConfiningMouseCursor: !spreadShown && priv.focusedAppDelegate && priv.focusedAppDelegate.window && priv.focusedAppDelegate.window.confinesMousePointer ?
165 priv.focusedAppDelegate.clientAreaItem : null;
166
167 signal itemSnapshotRequested(Item item)
168
169 // functions to be called from outside
170 function updateFocusedAppOrientation() { /* TODO */ }
171 function updateFocusedAppOrientationAnimated() { /* TODO */}
172
173 function closeSpread() {
174 spreadItem.highlightedIndex = -1;
175 priv.goneToSpread = false;
176 }
177
178 onSpreadEnabledChanged: {
179 if (!spreadEnabled && spreadShown) {
180 closeSpread();
181 }
182 }
183
184 onRightEdgePushProgressChanged: {
185 if (spreadEnabled && rightEdgePushProgress >= 1) {
186 priv.goneToSpread = true
187 }
188 }
189
190 GSettings {
191 id: lifecycleExceptions
192 schema.id: "com.canonical.qtmir"
193 }
194
195 function isExemptFromLifecycle(appId) {
196 var shortAppId = appId.split('_')[0];
197 for (var i = 0; i < lifecycleExceptions.lifecycleExemptAppids.length; i++) {
198 if (shortAppId === lifecycleExceptions.lifecycleExemptAppids[i]) {
199 return true;
200 }
201 }
202 return false;
203 }
204
205 GlobalShortcut {
206 id: closeFocusedShortcut
207 shortcut: Qt.AltModifier|Qt.Key_F4
208 onTriggered: {
209 if (priv.focusedAppDelegate) {
210 priv.focusedAppDelegate.close();
211 }
212 }
213 }
214
215 GlobalShortcut {
216 id: showSpreadShortcut
217 shortcut: Qt.MetaModifier|Qt.Key_W
218 active: root.spreadEnabled
219 onTriggered: priv.goneToSpread = true
220 }
221
222 GlobalShortcut {
223 id: toggleSideStageShortcut
224 shortcut: Qt.MetaModifier|Qt.Key_S
225 active: priv.sideStageEnabled
226 onTriggered: {
227 priv.toggleSideStage()
228 }
229 }
230
231 GlobalShortcut {
232 id: minimizeAllShortcut
233 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_D
234 onTriggered: priv.minimizeAllWindows()
235 active: root.state == "windowed"
236 }
237
238 GlobalShortcut {
239 id: maximizeWindowShortcut
240 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Up
241 onTriggered: priv.focusedAppDelegate.requestMaximize()
242 active: root.state == "windowed" && priv.focusedAppDelegate && priv.focusedAppDelegate.canBeMaximized
243 }
244
245 GlobalShortcut {
246 id: maximizeWindowLeftShortcut
247 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Left
248 onTriggered: {
249 switch (root.mode) {
250 case "stagedWithSideStage":
251 if ( priv.focusedAppDelegate
252 && priv.focusedAppDelegate.stage == ApplicationInfoInterface.SideStage
253 ) {
254 priv.focusedAppDelegate.saveStage(ApplicationInfoInterface.MainStage);
255 priv.focusedAppDelegate.focus = true;
256 }
257 break;
258 case "windowed":
259 if (priv.focusedAppDelegate) {
260 priv.focusedAppDelegate.requestMaximizeLeft();
261 }
262 break;
263 }
264 }
265 active: {
266 switch (root.state) {
267 case "stagedWithSideStage":
268 return priv.focusedAppDelegate
269 && priv.focusedAppDelegate.stage == ApplicationInfoInterface.SideStage
270 && priv.sideStageEnabled;
271 case "windowed":
272 return priv.focusedAppDelegate
273 && priv.focusedAppDelegate.canBeMaximizedLeftRight;
274 default:
275 return false;
276 }
277 }
278 }
279
280 GlobalShortcut {
281 id: maximizeWindowRightShortcut
282 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Right
283 onTriggered: {
284 switch (root.mode) {
285 case "stagedWithSideStage":
286 if ( priv.focusedAppDelegate
287 && priv.focusedAppDelegate.stage == ApplicationInfoInterface.MainStage
288 ) {
289 priv.focusedAppDelegate.saveStage(ApplicationInfoInterface.SideStage);
290 priv.focusedAppDelegate.focus = true;
291 sideStage.show();
292 priv.updateMainAndSideStageIndexes();
293 }
294 break;
295 case "windowed":
296 if (priv.focusedAppDelegate) {
297 priv.focusedAppDelegate.requestMaximizeRight();
298 }
299 break;
300 }
301 }
302 active: {
303 switch (root.state) {
304 case "stagedWithSideStage":
305 return priv.focusedAppDelegate
306 && priv.focusedAppDelegate.stage == ApplicationInfoInterface.MainStage
307 && priv.sideStageEnabled;
308 case "windowed":
309 return priv.focusedAppDelegate
310 && priv.focusedAppDelegate.canBeMaximizedLeftRight;
311 default:
312 return false;
313 }
314 }
315 }
316
317 GlobalShortcut {
318 id: minimizeRestoreShortcut
319 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Down
320 onTriggered: {
321 if (priv.focusedAppDelegate.anyMaximized) {
322 priv.focusedAppDelegate.requestRestore();
323 } else {
324 priv.focusedAppDelegate.requestMinimize();
325 }
326 }
327 active: root.state == "windowed" && priv.focusedAppDelegate
328 }
329
330 GlobalShortcut {
331 shortcut: Qt.AltModifier|Qt.Key_Print
332 onTriggered: root.itemSnapshotRequested(priv.focusedAppDelegate)
333 active: priv.focusedAppDelegate !== null
334 }
335
336 GlobalShortcut {
337 shortcut: Qt.ControlModifier|Qt.AltModifier|Qt.Key_T
338 onTriggered: {
339 // try in this order: snap pkg, new deb name, old deb name
340 var candidates = ["lomiri-terminal-app_lomiri-terminal-app", "lomiri-terminal-app", "com.lomiri.terminal_terminal"];
341 for (var i = 0; i < candidates.length; i++) {
342 if (priv.startApp(candidates[i]))
343 break;
344 }
345 }
346 }
347
348 GlobalShortcut {
349 id: showWorkspaceSwitcherShortcutLeft
350 shortcut: Qt.AltModifier|Qt.ControlModifier|Qt.Key_Left
351 active: !workspaceSwitcher.active && root.workspaceEnabled
352 onTriggered: {
353 root.focus = true;
354 workspaceSwitcher.showLeft()
355 }
356 }
357 GlobalShortcut {
358 id: showWorkspaceSwitcherShortcutRight
359 shortcut: Qt.AltModifier|Qt.ControlModifier|Qt.Key_Right
360 active: !workspaceSwitcher.active && root.workspaceEnabled
361 onTriggered: {
362 root.focus = true;
363 workspaceSwitcher.showRight()
364 }
365 }
366 GlobalShortcut {
367 id: showWorkspaceSwitcherShortcutUp
368 shortcut: Qt.AltModifier|Qt.ControlModifier|Qt.Key_Up
369 active: !workspaceSwitcher.active && root.workspaceEnabled
370 onTriggered: {
371 root.focus = true;
372 workspaceSwitcher.showUp()
373 }
374 }
375 GlobalShortcut {
376 id: showWorkspaceSwitcherShortcutDown
377 shortcut: Qt.AltModifier|Qt.ControlModifier|Qt.Key_Down
378 active: !workspaceSwitcher.active && root.workspaceEnabled
379 onTriggered: {
380 root.focus = true;
381 workspaceSwitcher.showDown()
382 }
383 }
384
385 GlobalShortcut {
386 id: moveAppShowWorkspaceSwitcherShortcutLeft
387 shortcut: Qt.AltModifier|Qt.ControlModifier|Qt.ShiftModifier|Qt.Key_Left
388 active: !workspaceSwitcher.active && root.workspaceEnabled && priv.focusedAppDelegate !== null
389 onTriggered: {
390 root.focus = true;
391 workspaceSwitcher.showLeftMoveApp(priv.focusedAppDelegate.surface)
392 }
393 }
394 GlobalShortcut {
395 id: moveAppShowWorkspaceSwitcherShortcutRight
396 shortcut: Qt.AltModifier|Qt.ControlModifier|Qt.ShiftModifier|Qt.Key_Right
397 active: !workspaceSwitcher.active && root.workspaceEnabled && priv.focusedAppDelegate !== null
398 onTriggered: {
399 root.focus = true;
400 workspaceSwitcher.showRightMoveApp(priv.focusedAppDelegate.surface)
401 }
402 }
403 GlobalShortcut {
404 id: moveAppShowWorkspaceSwitcherShortcutUp
405 shortcut: Qt.AltModifier|Qt.ControlModifier|Qt.ShiftModifier|Qt.Key_Up
406 active: !workspaceSwitcher.active && root.workspaceEnabled && priv.focusedAppDelegate !== null
407 onTriggered: {
408 root.focus = true;
409 workspaceSwitcher.showUpMoveApp(priv.focusedAppDelegate.surface)
410 }
411 }
412 GlobalShortcut {
413 id: moveAppShowWorkspaceSwitcherShortcutDown
414 shortcut: Qt.AltModifier|Qt.ControlModifier|Qt.ShiftModifier|Qt.Key_Down
415 active: !workspaceSwitcher.active && root.workspaceEnabled && priv.focusedAppDelegate !== null
416 onTriggered: {
417 root.focus = true;
418 workspaceSwitcher.showDownMoveApp(priv.focusedAppDelegate.surface)
419 }
420 }
421
422 QtObject {
423 id: priv
424 objectName: "DesktopStagePrivate"
425
426 function startApp(appId) {
427 if (root.applicationManager.findApplication(appId)) {
428 return root.applicationManager.requestFocusApplication(appId);
429 } else {
430 return root.applicationManager.startApplication(appId) !== null;
431 }
432 }
433
434 property var focusedAppDelegate: null
435 property var foregroundMaximizedAppDelegate: null // for stuff like drop shadow and focusing maximized app by clicking panel
436
437 property bool goneToSpread: false
438 property int closingIndex: -1
439 property int animationDuration: LomiriAnimation.FastDuration
440
441 function updateForegroundMaximizedApp() {
442 var found = false;
443 for (var i = 0; i < appRepeater.count && !found; i++) {
444 var item = appRepeater.itemAt(i);
445 if (item && item.visuallyMaximized) {
446 foregroundMaximizedAppDelegate = item;
447 found = true;
448 }
449 }
450 if (!found) {
451 foregroundMaximizedAppDelegate = null;
452 }
453 }
454
455 function minimizeAllWindows() {
456 for (var i = appRepeater.count - 1; i >= 0; i--) {
457 var appDelegate = appRepeater.itemAt(i);
458 if (appDelegate && !appDelegate.minimized) {
459 appDelegate.requestMinimize();
460 }
461 }
462 }
463
464 readonly property bool sideStageEnabled: root.mode === "stagedWithSideStage" &&
465 (root.shellOrientation == Qt.LandscapeOrientation ||
466 root.shellOrientation == Qt.InvertedLandscapeOrientation)
467 onSideStageEnabledChanged: {
468 for (var i = 0; i < appRepeater.count; i++) {
469 appRepeater.itemAt(i).refreshStage();
470 }
471 priv.updateMainAndSideStageIndexes();
472 }
473
474 property var mainStageDelegate: null
475 property var sideStageDelegate: null
476 property int mainStageItemId: 0
477 property int sideStageItemId: 0
478 property string mainStageAppId: ""
479 property string sideStageAppId: ""
480
481 onSideStageDelegateChanged: {
482 if (!sideStageDelegate) {
483 sideStage.hide();
484 }
485 }
486
487 function toggleSideStage() {
488 if (sideStage.shown) {
489 sideStage.hide();
490 } else {
491 sideStage.show();
492 updateMainAndSideStageIndexes()
493 }
494 }
495
496 function updateMainAndSideStageIndexes() {
497 if (root.mode != "stagedWithSideStage") {
498 priv.sideStageDelegate = null;
499 priv.sideStageItemId = 0;
500 priv.sideStageAppId = "";
501 priv.mainStageDelegate = appRepeater.itemAt(0);
502 priv.mainStageItemId = topLevelSurfaceList.idAt(0);
503 priv.mainStageAppId = topLevelSurfaceList.applicationAt(0) ? topLevelSurfaceList.applicationAt(0).appId : ""
504 return;
505 }
506
507 var choseMainStage = false;
508 var choseSideStage = false;
509
510 if (!root.topLevelSurfaceList)
511 return;
512
513 for (var i = 0; i < appRepeater.count && (!choseMainStage || !choseSideStage); ++i) {
514 var appDelegate = appRepeater.itemAt(i);
515 if (!appDelegate) {
516 // This might happen during startup phase... If the delegate appears and claims focus
517 // things are updated and appRepeater.itemAt(x) still returns null while appRepeater.count >= x
518 // Lets just skip it, on startup it will be generated at a later point too...
519 continue;
520 }
521 if (sideStage.shown && appDelegate.stage == ApplicationInfoInterface.SideStage
522 && !choseSideStage) {
523 priv.sideStageDelegate = appDelegate
524 priv.sideStageItemId = root.topLevelSurfaceList.idAt(i);
525 priv.sideStageAppId = root.topLevelSurfaceList.applicationAt(i).appId;
526 choseSideStage = true;
527 } else if (!choseMainStage && appDelegate.stage == ApplicationInfoInterface.MainStage) {
528 priv.mainStageDelegate = appDelegate;
529 priv.mainStageItemId = root.topLevelSurfaceList.idAt(i);
530 priv.mainStageAppId = root.topLevelSurfaceList.applicationAt(i).appId;
531 choseMainStage = true;
532 }
533 }
534 if (!choseMainStage && priv.mainStageDelegate) {
535 priv.mainStageDelegate = null;
536 priv.mainStageItemId = 0;
537 priv.mainStageAppId = "";
538 }
539 if (!choseSideStage && priv.sideStageDelegate) {
540 priv.sideStageDelegate = null;
541 priv.sideStageItemId = 0;
542 priv.sideStageAppId = "";
543 }
544 }
545
546 property int nextInStack: {
547 var mainStageIndex = priv.mainStageDelegate ? priv.mainStageDelegate.itemIndex : -1;
548 var sideStageIndex = priv.sideStageDelegate ? priv.sideStageDelegate.itemIndex : -1;
549 if (sideStageIndex == -1) {
550 return topLevelSurfaceList.count > 1 ? 1 : -1;
551 }
552 if (mainStageIndex == 0 || sideStageIndex == 0) {
553 if (mainStageIndex == 1 || sideStageIndex == 1) {
554 return topLevelSurfaceList.count > 2 ? 2 : -1;
555 }
556 return 1;
557 }
558 return -1;
559 }
560
561 readonly property real virtualKeyboardHeight: root.inputMethodRect.height
562
563 readonly property real windowDecorationHeight: units.gu(3)
564 }
565
566 Component.onCompleted: priv.updateMainAndSideStageIndexes()
567
568 Connections {
569 target: panelState
570 function onCloseClicked() { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.close(); } }
571 function onMinimizeClicked() { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.requestMinimize(); } }
572 function onRestoreClicked() { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.requestRestore(); } }
573 }
574
575 Binding {
576 target: panelState
577 restoreMode: Binding.RestoreBinding
578 property: "decorationsVisible"
579 value: mode == "windowed" && priv.focusedAppDelegate !== null && priv.focusedAppDelegate.maximized && !root.spreadShown
580 }
581
582 Binding {
583 target: panelState
584 restoreMode: Binding.RestoreBinding
585 property: "title"
586 value: {
587 if (priv.focusedAppDelegate !== null) {
588 if (priv.focusedAppDelegate.maximized)
589 return priv.focusedAppDelegate.title
590 else
591 return priv.focusedAppDelegate.appName
592 }
593 return ""
594 }
595 when: priv.focusedAppDelegate
596 }
597
598 Binding {
599 target: panelState
600 restoreMode: Binding.RestoreBinding
601 property: "focusedPersistentSurfaceId"
602 value: {
603 if (priv.focusedAppDelegate !== null) {
604 if (priv.focusedAppDelegate.surface) {
605 return priv.focusedAppDelegate.surface.persistentId;
606 }
607 }
608 return "";
609 }
610 when: priv.focusedAppDelegate
611 }
612
613 Binding {
614 target: panelState
615 restoreMode: Binding.RestoreBinding
616 property: "dropShadow"
617 value: priv.focusedAppDelegate && !priv.focusedAppDelegate.maximized && priv.foregroundMaximizedAppDelegate !== null && mode == "windowed"
618 }
619
620 Binding {
621 target: panelState
622 restoreMode: Binding.RestoreBinding
623 property: "closeButtonShown"
624 value: priv.focusedAppDelegate && priv.focusedAppDelegate.maximized
625 }
626
627 Component.onDestruction: {
628 panelState.title = "";
629 panelState.decorationsVisible = false;
630 panelState.dropShadow = false;
631 }
632
633 Instantiator {
634 model: root.applicationManager
635 delegate: QtObject {
636 id: applicationDelegate
637 // TODO: figure out some lifecycle policy, like suspending minimized apps
638 // or something if running windowed.
639 // TODO: If the device has a dozen suspended apps because it was running
640 // in staged mode, when it switches to Windowed mode it will suddenly
641 // resume all those apps at once. We might want to avoid that.
642 property var requestedState: ApplicationInfoInterface.RequestedRunning
643 property bool temporaryAwaken: ProcessControl.awakenProcesses.indexOf(model.application.appId) >= 0
644
645 property var stateBinding: Binding {
646 target: model.application
647 property: "requestedState"
648 value: applicationDelegate.requestedState
649 restoreMode: Binding.RestoreBinding
650 }
651
652 property var lifecycleBinding: Binding {
653 target: model.application
654 property: "exemptFromLifecycle"
655 restoreMode: Binding.RestoreBinding
656 value: model.application
657 ? (!model.application.isTouchApp ||
658 isExemptFromLifecycle(model.application.appId) ||
659 applicationDelegate.temporaryAwaken)
660 : false
661
662 }
663
664 property var focusRequestedConnection: Connections {
665 target: model.application
666
667 function onFocusRequested() {
668 // Application emits focusRequested when it has no surface (i.e. their processes died).
669 // Find the topmost window for this application and activate it, after which the app
670 // will be requested to be running.
671
672 for (var i = 0; i < appRepeater.count; i++) {
673 var appDelegate = appRepeater.itemAt(i);
674 if (appDelegate.application.appId === model.application.appId) {
675 appDelegate.activate();
676 return;
677 }
678 }
679
680 console.warn("Application requested te be focused but no window for it. What should we do?");
681 }
682 }
683 }
684 }
685
686 states: [
687 State {
688 name: "spread"; when: priv.goneToSpread
689 PropertyChanges { target: floatingFlickable; enabled: true }
690 PropertyChanges { target: root; focus: true }
691 PropertyChanges { target: spreadItem; focus: true }
692 PropertyChanges { target: hoverMouseArea; enabled: true }
693 PropertyChanges { target: rightEdgeDragArea; enabled: false }
694 PropertyChanges { target: cancelSpreadMouseArea; enabled: true }
695 PropertyChanges { target: noAppsRunningHint; visible: (root.topLevelSurfaceList.count < 1) }
696 PropertyChanges { target: blurLayer; visible: true; blurRadius: 32; brightness: .50; opacity: 1 }
697 PropertyChanges { target: wallpaper; visible: false }
698 PropertyChanges { target: screensAndWorkspaces.showTimer; running: true }
699 },
700 State {
701 name: "stagedRightEdge"; when: root.spreadEnabled && (rightEdgeDragArea.dragging || rightEdgePushProgress > 0) && root.mode == "staged"
702 PropertyChanges {
703 target: blurLayer;
704 visible: true;
705 blurRadius: 32
706 brightness: .65
707 opacity: 1
708 }
709 PropertyChanges { target: noAppsRunningHint; visible: (root.topLevelSurfaceList.count < 1) }
710 },
711 State {
712 name: "sideStagedRightEdge"; when: root.spreadEnabled && (rightEdgeDragArea.dragging || rightEdgePushProgress > 0) && root.mode == "stagedWithSideStage"
713 extend: "stagedRightEdge"
714 PropertyChanges {
715 target: sideStage
716 opacity: priv.sideStageDelegate && priv.sideStageDelegate.x === sideStage.x ? 1 : 0
717 visible: true
718 }
719 },
720 State {
721 name: "windowedRightEdge"; when: root.spreadEnabled && (rightEdgeDragArea.dragging || rightEdgePushProgress > 0) && root.mode == "windowed"
722 PropertyChanges {
723 target: blurLayer;
724 visible: true
725 blurRadius: 32
726 brightness: .65
727 opacity: MathUtils.linearAnimation(spreadItem.rightEdgeBreakPoint, 1, 0, 1, Math.max(rightEdgeDragArea.dragging ? rightEdgeDragArea.progress : 0, rightEdgePushProgress))
728 }
729 },
730 State {
731 name: "staged"; when: root.mode === "staged"
732 PropertyChanges { target: root; focus: true }
733 PropertyChanges { target: appContainer; focus: true }
734 },
735 State {
736 name: "stagedWithSideStage"; when: root.mode === "stagedWithSideStage"
737 PropertyChanges { target: triGestureArea; enabled: priv.sideStageEnabled }
738 PropertyChanges { target: sideStage; visible: true }
739 PropertyChanges { target: root; focus: true }
740 PropertyChanges { target: appContainer; focus: true }
741 },
742 State {
743 name: "windowed"; when: root.mode === "windowed"
744 PropertyChanges { target: root; focus: true }
745 PropertyChanges { target: appContainer; focus: true }
746 }
747 ]
748 transitions: [
749 Transition {
750 from: "stagedRightEdge,sideStagedRightEdge,windowedRightEdge"; to: "spread"
751 PropertyAction { target: spreadItem; property: "highlightedIndex"; value: -1 }
752 PropertyAction { target: screensAndWorkspaces; property: "activeWorkspace"; value: WMScreen.currentWorkspace }
753 PropertyAnimation { target: blurLayer; properties: "brightness,blurRadius"; duration: priv.animationDuration }
754 },
755 Transition {
756 to: "spread"
757 PropertyAction { target: screensAndWorkspaces; property: "activeWorkspace"; value: WMScreen.currentWorkspace }
758 PropertyAction { target: spreadItem; property: "highlightedIndex"; value: appRepeater.count > 1 ? 1 : 0 }
759 PropertyAction { target: floatingFlickable; property: "contentX"; value: 0 }
760 },
761 Transition {
762 from: "spread"
763 SequentialAnimation {
764 ScriptAction {
765 script: {
766 var item = appRepeater.itemAt(Math.max(0, spreadItem.highlightedIndex));
767 if (item) {
768 if (item.stage == ApplicationInfoInterface.SideStage && !sideStage.shown) {
769 sideStage.show();
770 }
771 item.playFocusAnimation();
772 }
773 }
774 }
775 PropertyAction { target: spreadItem; property: "highlightedIndex"; value: -1 }
776 PropertyAction { target: floatingFlickable; property: "contentX"; value: 0 }
777 }
778 },
779 Transition {
780 to: "stagedRightEdge,sideStagedRightEdge"
781 PropertyAction { target: floatingFlickable; property: "contentX"; value: 0 }
782 },
783 Transition {
784 to: "stagedWithSideStage"
785 ScriptAction { script: priv.updateMainAndSideStageIndexes(); }
786 }
787
788 ]
789
790 MouseArea {
791 id: cancelSpreadMouseArea
792 anchors.fill: parent
793 enabled: false
794 onClicked: priv.goneToSpread = false
795 }
796
797 FocusScope {
798 id: appContainer
799 objectName: "appContainer"
800 anchors.fill: parent
801 focus: true
802
803 Wallpaper {
804 id: wallpaper
805 objectName: "stageBackground"
806 anchors.fill: parent
807 source: root.background
808 // Make sure it's the lowest item. Due to the left edge drag we sometimes need
809 // to put the dash at -1 and we don't want it behind the Wallpaper
810 z: -2
811 }
812
813 BlurLayer {
814 id: blurLayer
815 anchors.fill: parent
816 source: wallpaper
817 visible: false
818 }
819
820 ScreensAndWorkspaces {
821 id: screensAndWorkspaces
822 anchors { left: parent.left; top: parent.top; right: parent.right; leftMargin: root.launcherLeftMargin }
823 height: Math.max(units.gu(30), parent.height * .3)
824 background: root.background
825 visible: showAllowed
826 enabled: workspaceEnabled
827 mode: root.mode
828 availableDesktopArea: root.availableDesktopArea
829 onCloseSpread: priv.goneToSpread = false;
830 // Clicking a workspace should put it front and center
831 onActiveWorkspaceChanged: activeWorkspace.activate()
832 opacity: visible ? 1.0 : 0.0
833 Behavior on opacity {
834 NumberAnimation { duration: priv.animationDuration }
835 }
836
837 property bool showAllowed : false
838 property var showTimer: Timer {
839 running: false
840 repeat: false
841 interval: priv.animationDuration
842 onTriggered: {
843 if (!priv.goneToSpread)
844 return;
845 screensAndWorkspaces.showAllowed = root.workspaceEnabled;
846 }
847 }
848 Connections {
849 target: priv
850 onGoneToSpreadChanged: if (!priv.goneToSpread) screensAndWorkspaces.showAllowed = false
851 }
852 }
853
854 Spread {
855 id: spreadItem
856 objectName: "spreadItem"
857 anchors {
858 left: parent.left;
859 bottom: parent.bottom;
860 right: parent.right;
861 top: workspaceEnabled ? screensAndWorkspaces.bottom : parent.top;
862 }
863 leftMargin: root.availableDesktopArea.x
864 model: root.topLevelSurfaceList
865 spreadFlickable: floatingFlickable
866 z: root.topLevelSurfaceList.count
867
868 onLeaveSpread: {
869 priv.goneToSpread = false;
870 }
871
872 onCloseCurrentApp: {
873 appRepeater.itemAt(highlightedIndex).close();
874 }
875
876 FloatingFlickable {
877 id: floatingFlickable
878 objectName: "spreadFlickable"
879 anchors.fill: parent
880 enabled: false
881 contentWidth: spreadItem.spreadTotalWidth
882
883 function snap(toIndex) {
884 var delegate = appRepeater.itemAt(toIndex)
885 var targetContentX = floatingFlickable.contentWidth / spreadItem.totalItemCount * toIndex;
886 if (targetContentX - floatingFlickable.contentX > spreadItem.rightStackXPos - (spreadItem.spreadItemWidth / 2)) {
887 var offset = (spreadItem.rightStackXPos - (spreadItem.spreadItemWidth / 2)) - (targetContentX - floatingFlickable.contentX)
888 snapAnimation.to = floatingFlickable.contentX - offset;
889 snapAnimation.start();
890 } else if (targetContentX - floatingFlickable.contentX < spreadItem.leftStackXPos + units.gu(1)) {
891 var offset = (spreadItem.leftStackXPos + units.gu(1)) - (targetContentX - floatingFlickable.contentX);
892 snapAnimation.to = floatingFlickable.contentX - offset;
893 snapAnimation.start();
894 }
895 }
896 LomiriNumberAnimation {id: snapAnimation; target: floatingFlickable; property: "contentX"}
897 }
898
899 MouseArea {
900 id: hoverMouseArea
901 objectName: "hoverMouseArea"
902 anchors.fill: parent
903 propagateComposedEvents: true
904 hoverEnabled: true
905 enabled: false
906 visible: enabled
907 property bool wasTouchPress: false
908
909 property int scrollAreaWidth: width / 3
910 property bool progressiveScrollingEnabled: false
911
912 onMouseXChanged: {
913 mouse.accepted = false
914
915 if (hoverMouseArea.pressed || wasTouchPress) {
916 return;
917 }
918
919 // Find the hovered item and mark it active
920 for (var i = appRepeater.count - 1; i >= 0; i--) {
921 var appDelegate = appRepeater.itemAt(i);
922 var mapped = mapToItem(appDelegate, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
923 var itemUnder = appDelegate.childAt(mapped.x, mapped.y);
924 if (itemUnder && (itemUnder.objectName === "dragArea" || itemUnder.objectName === "windowInfoItem" || itemUnder.objectName == "closeMouseArea")) {
925 spreadItem.highlightedIndex = i;
926 break;
927 }
928 }
929
930 if (floatingFlickable.contentWidth > floatingFlickable.width) {
931 var margins = floatingFlickable.width * 0.05;
932
933 if (!progressiveScrollingEnabled && mouseX < floatingFlickable.width - scrollAreaWidth) {
934 progressiveScrollingEnabled = true
935 }
936
937 // do we need to scroll?
938 if (mouseX < scrollAreaWidth + margins) {
939 var progress = Math.min(1, (scrollAreaWidth + margins - mouseX) / (scrollAreaWidth - margins));
940 var contentX = (1 - progress) * (floatingFlickable.contentWidth - floatingFlickable.width)
941 floatingFlickable.contentX = Math.max(0, Math.min(floatingFlickable.contentX, contentX))
942 }
943 if (mouseX > floatingFlickable.width - scrollAreaWidth && progressiveScrollingEnabled) {
944 var progress = Math.min(1, (mouseX - (floatingFlickable.width - scrollAreaWidth)) / (scrollAreaWidth - margins))
945 var contentX = progress * (floatingFlickable.contentWidth - floatingFlickable.width)
946 floatingFlickable.contentX = Math.min(floatingFlickable.contentWidth - floatingFlickable.width, Math.max(floatingFlickable.contentX, contentX))
947 }
948 }
949 }
950
951 onPressed: {
952 mouse.accepted = false;
953 wasTouchPress = mouse.source === Qt.MouseEventSynthesizedByQt;
954 }
955
956 onExited: wasTouchPress = false;
957 }
958 }
959
960 Label {
961 id: noAppsRunningHint
962 visible: false
963 anchors.horizontalCenter: parent.horizontalCenter
964 anchors.verticalCenter: parent.verticalCenter
965 anchors.fill: parent
966 horizontalAlignment: Qt.AlignHCenter
967 verticalAlignment: Qt.AlignVCenter
968 anchors.leftMargin: root.launcherLeftMargin
969 wrapMode: Label.WordWrap
970 fontSize: "large"
971 text: i18n.tr("No running apps")
972 color: "#FFFFFF"
973 }
974
975 Connections {
976 target: root.topLevelSurfaceList
977 function onListChanged() { priv.updateMainAndSideStageIndexes() }
978 }
979
980
981 DropArea {
982 objectName: "MainStageDropArea"
983 anchors {
984 left: parent.left
985 top: parent.top
986 bottom: parent.bottom
987 }
988 width: appContainer.width - sideStage.width
989 enabled: priv.sideStageEnabled
990
991 onDropped: {
992 drop.source.appDelegate.saveStage(ApplicationInfoInterface.MainStage);
993 drop.source.appDelegate.activate();
994 }
995 keys: "SideStage"
996 }
997
998 SideStage {
999 id: sideStage
1000 objectName: "sideStage"
1001 shown: false
1002 height: appContainer.height
1003 x: appContainer.width - width
1004 visible: false
1005 showHint: !priv.sideStageDelegate
1006 Behavior on opacity { LomiriNumberAnimation {} }
1007 z: {
1008 if (!priv.mainStageItemId) return 0;
1009
1010 if (priv.sideStageItemId && priv.nextInStack > 0) {
1011
1012 // Due the order in which bindings are evaluated, this might be triggered while shuffling
1013 // the list and index doesn't yet match with itemIndex (even though itemIndex: index)
1014 // Let's walk the list and compare itemIndex to make sure we have the correct one.
1015 var nextDelegateInStack = -1;
1016 for (var i = 0; i < appRepeater.count; i++) {
1017 if (appRepeater.itemAt(i).itemIndex == priv.nextInStack) {
1018 nextDelegateInStack = appRepeater.itemAt(i);
1019 break;
1020 }
1021 }
1022
1023 if (nextDelegateInStack.stage === ApplicationInfoInterface.MainStage) {
1024 // if the next app in stack is a main stage app, put the sidestage on top of it.
1025 return 2;
1026 }
1027 return 1;
1028 }
1029
1030 return 1;
1031 }
1032
1033 onShownChanged: {
1034 if (!shown && priv.mainStageDelegate && !root.spreadShown) {
1035 priv.mainStageDelegate.activate();
1036 }
1037 }
1038
1039 DropArea {
1040 id: sideStageDropArea
1041 objectName: "SideStageDropArea"
1042 anchors.fill: parent
1043
1044 property bool dropAllowed: true
1045
1046 onEntered: {
1047 dropAllowed = drag.keys != "Disabled";
1048 }
1049 onExited: {
1050 dropAllowed = true;
1051 }
1052 onDropped: {
1053 if (drop.keys == "MainStage") {
1054 drop.source.appDelegate.saveStage(ApplicationInfoInterface.SideStage);
1055 drop.source.appDelegate.activate();
1056 }
1057 }
1058 drag {
1059 onSourceChanged: {
1060 if (!sideStageDropArea.drag.source) {
1061 dropAllowed = true;
1062 }
1063 }
1064 }
1065 }
1066 }
1067
1068 MirSurfaceItem {
1069 id: fakeDragItem
1070 property real previewScale: .5
1071 height: (screensAndWorkspaces.height - units.gu(8)) / 2
1072 // w : h = iw : ih
1073 width: implicitWidth * height / implicitHeight
1074 surfaceWidth: -1
1075 surfaceHeight: -1
1076 opacity: surface != null ? 1 : 0
1077 Behavior on opacity { LomiriNumberAnimation {} }
1078 visible: opacity > 0
1079 enabled: workspaceSwitcher
1080
1081 Drag.active: surface != null
1082 Drag.keys: ["application"]
1083
1084 z: 1000
1085 }
1086
1087 Repeater {
1088 id: appRepeater
1089 model: topLevelSurfaceList
1090 objectName: "appRepeater"
1091
1092 function indexOf(delegateItem) {
1093 for (var i = 0; i < count; i++) {
1094 if (itemAt(i) === delegateItem) {
1095 return i;
1096 }
1097 }
1098 return -1;
1099 }
1100
1101 delegate: FocusScope {
1102 id: appDelegate
1103 objectName: "appDelegate_" + model.window.id
1104 property int itemIndex: index // We need this from outside the repeater
1105 // z might be overriden in some cases by effects, but we need z ordering
1106 // to calculate occlusion detection
1107 property int normalZ: topLevelSurfaceList.count - index
1108 onNormalZChanged: {
1109 if (visuallyMaximized) {
1110 priv.updateForegroundMaximizedApp();
1111 }
1112 }
1113 z: normalZ
1114
1115 opacity: fakeDragItem.surface == model.window.surface && fakeDragItem.Drag.active ? 0 : 1
1116 Behavior on opacity { LomiriNumberAnimation {} }
1117
1118 // Set these as propertyes as they wont update otherwise
1119 property real screenOffsetX: Screen.virtualX
1120 property real screenOffsetY: Screen.virtualY
1121
1122 // Normally we want x/y where the surface thinks it is. Width/height of our delegate will
1123 // match what the actual surface size is.
1124 // Don't write to those, they will be set by states
1125 // --
1126 // Here we will also need to remove the screen offset from miral's results
1127 // as lomiri x,y will be relative to the current screen only
1128 // FIXME: when proper multiscreen lands
1129 x: model.window.position.x - clientAreaItem.x - screenOffsetX
1130 y: model.window.position.y - clientAreaItem.y - screenOffsetY
1131 width: decoratedWindow.implicitWidth
1132 height: decoratedWindow.implicitHeight
1133
1134 // requestedX/Y/width/height is what we ask the actual surface to be.
1135 // Do not write to those, they will be set by states
1136 property real requestedX: windowedX
1137 property real requestedY: windowedY
1138 property real requestedWidth: windowedWidth
1139 property real requestedHeight: windowedHeight
1140
1141 // For both windowed and staged need to tell miral what screen we are on,
1142 // so we need to add the screen offset to the position we tell miral
1143 // FIXME: when proper multiscreen lands
1144 Binding {
1145 target: model.window; property: "requestedPosition"
1146 // miral doesn't know about our window decorations. So we have to deduct them
1147 value: Qt.point(appDelegate.requestedX + appDelegate.clientAreaItem.x + screenOffsetX,
1148 appDelegate.requestedY + appDelegate.clientAreaItem.y + screenOffsetY)
1149 when: root.mode == "windowed"
1150 restoreMode: Binding.RestoreBinding
1151 }
1152 Binding {
1153 target: model.window; property: "requestedPosition"
1154 value: Qt.point(screenOffsetX, screenOffsetY)
1155 when: root.mode != "windowed"
1156 restoreMode: Binding.RestoreBinding
1157 }
1158
1159 // In those are for windowed mode. Those values basically store the window's properties
1160 // when having a floating window. If you want to move/resize a window in normal mode, this is what you want to write to.
1161 property real windowedX
1162 property real windowedY
1163 property real windowedWidth
1164 property real windowedHeight
1165
1166 // unlike windowedX/Y, this is the last known grab position before being pushed against edges/corners
1167 // when restoring, the window should return to these, not to the place where it was dropped near the edge
1168 property real restoredX
1169 property real restoredY
1170
1171 // Keeps track of the window geometry while in normal or restored state
1172 // Useful when returning from some maxmized state or when saving the geometry while maximized
1173 // FIXME: find a better solution
1174 property real normalX: 0
1175 property real normalY: 0
1176 property real normalWidth: 0
1177 property real normalHeight: 0
1178 function updateNormalGeometry() {
1179 if (appDelegate.state == "normal" || appDelegate.state == "restored") {
1180 normalX = appDelegate.requestedX;
1181 normalY = appDelegate.requestedY;
1182 normalWidth = appDelegate.width;
1183 normalHeight = appDelegate.height;
1184 }
1185 }
1186 function updateRestoredGeometry() {
1187 if (appDelegate.state == "normal" || appDelegate.state == "restored") {
1188 // save the x/y to restore to
1189 restoredX = appDelegate.x;
1190 restoredY = appDelegate.y;
1191 }
1192 }
1193
1194 Connections {
1195 target: appDelegate
1196 function onXChanged() { appDelegate.updateNormalGeometry(); }
1197 function onYChanged() { appDelegate.updateNormalGeometry(); }
1198 function onWidthChanged() { appDelegate.updateNormalGeometry(); }
1199 function onHeightChanged() { appDelegate.updateNormalGeometry(); }
1200 }
1201
1202 // True when the Stage is focusing this app and playing its own animation.
1203 // Stays true until the app is unfocused.
1204 // If it is, we don't want to play the slide in/out transition from StageMaths.
1205 // Setting it imperatively is not great, but any declarative solution hits
1206 // race conditions, causing two animations to play for one focus event.
1207 property bool inhibitSlideAnimation: false
1208
1209 Binding {
1210 target: appDelegate
1211 property: "y"
1212 value: appDelegate.requestedY -
1213 Math.min(appDelegate.requestedY - root.availableDesktopArea.y,
1214 Math.max(0, priv.virtualKeyboardHeight - (appContainer.height - (appDelegate.requestedY + appDelegate.height))))
1215 when: root.oskEnabled && appDelegate.focus && (appDelegate.state == "normal" || appDelegate.state == "restored")
1216 && root.inputMethodRect.height > 0
1217 restoreMode: Binding.RestoreBinding
1218 }
1219
1220 Behavior on x { id: xBehavior; enabled: priv.closingIndex >= 0; LomiriNumberAnimation { onRunningChanged: if (!running) priv.closingIndex = -1} }
1221
1222 Connections {
1223 target: root
1224 function onShellOrientationAngleChanged() {
1225 // at this point decoratedWindow.surfaceOrientationAngle is the old shellOrientationAngle
1226 if (appDelegate.application && appDelegate.application.rotatesWindowContents) {
1227 if (root.state == "windowed") {
1228 var angleDiff = decoratedWindow.surfaceOrientationAngle - shellOrientationAngle;
1229 angleDiff = (360 + angleDiff) % 360;
1230 if (angleDiff === 90 || angleDiff === 270) {
1231 var aux = decoratedWindow.requestedHeight;
1232 decoratedWindow.requestedHeight = decoratedWindow.requestedWidth + decoratedWindow.actualDecorationHeight;
1233 decoratedWindow.requestedWidth = aux - decoratedWindow.actualDecorationHeight;
1234 }
1235 }
1236 decoratedWindow.surfaceOrientationAngle = shellOrientationAngle;
1237 } else {
1238 decoratedWindow.surfaceOrientationAngle = 0;
1239 }
1240 }
1241 }
1242
1243 readonly property alias application: decoratedWindow.application
1244 readonly property alias minimumWidth: decoratedWindow.minimumWidth
1245 readonly property alias minimumHeight: decoratedWindow.minimumHeight
1246 readonly property alias maximumWidth: decoratedWindow.maximumWidth
1247 readonly property alias maximumHeight: decoratedWindow.maximumHeight
1248 readonly property alias widthIncrement: decoratedWindow.widthIncrement
1249 readonly property alias heightIncrement: decoratedWindow.heightIncrement
1250
1251 readonly property bool maximized: windowState === WindowStateStorage.WindowStateMaximized
1252 readonly property bool maximizedLeft: windowState === WindowStateStorage.WindowStateMaximizedLeft
1253 readonly property bool maximizedRight: windowState === WindowStateStorage.WindowStateMaximizedRight
1254 readonly property bool maximizedHorizontally: windowState === WindowStateStorage.WindowStateMaximizedHorizontally
1255 readonly property bool maximizedVertically: windowState === WindowStateStorage.WindowStateMaximizedVertically
1256 readonly property bool maximizedTopLeft: windowState === WindowStateStorage.WindowStateMaximizedTopLeft
1257 readonly property bool maximizedTopRight: windowState === WindowStateStorage.WindowStateMaximizedTopRight
1258 readonly property bool maximizedBottomLeft: windowState === WindowStateStorage.WindowStateMaximizedBottomLeft
1259 readonly property bool maximizedBottomRight: windowState === WindowStateStorage.WindowStateMaximizedBottomRight
1260 readonly property bool anyMaximized: maximized || maximizedLeft || maximizedRight || maximizedHorizontally || maximizedVertically ||
1261 maximizedTopLeft || maximizedTopRight || maximizedBottomLeft || maximizedBottomRight
1262
1263 readonly property bool minimized: windowState & WindowStateStorage.WindowStateMinimized
1264 readonly property bool fullscreen: windowState === WindowStateStorage.WindowStateFullscreen
1265
1266 readonly property bool canBeMaximized: canBeMaximizedHorizontally && canBeMaximizedVertically
1267 readonly property bool canBeMaximizedLeftRight: (maximumWidth == 0 || maximumWidth >= appContainer.width/2) &&
1268 (maximumHeight == 0 || maximumHeight >= appContainer.height)
1269 readonly property bool canBeCornerMaximized: (maximumWidth == 0 || maximumWidth >= appContainer.width/2) &&
1270 (maximumHeight == 0 || maximumHeight >= appContainer.height/2)
1271 readonly property bool canBeMaximizedHorizontally: maximumWidth == 0 || maximumWidth >= appContainer.width
1272 readonly property bool canBeMaximizedVertically: maximumHeight == 0 || maximumHeight >= appContainer.height
1273 readonly property alias orientationChangesEnabled: decoratedWindow.orientationChangesEnabled
1274
1275 // TODO drop our own windowType once Mir/Miral/Qtmir gets in sync with ours
1276 property int windowState: WindowStateStorage.WindowStateNormal
1277 property int prevWindowState: WindowStateStorage.WindowStateRestored
1278
1279 property bool animationsEnabled: true
1280 property alias title: decoratedWindow.title
1281 readonly property string appName: model.application ? model.application.name : ""
1282 property bool visuallyMaximized: false
1283 property bool visuallyMinimized: false
1284 readonly property alias windowedTransitionRunning: windowedTransition.running
1285
1286 property int stage: ApplicationInfoInterface.MainStage
1287 function saveStage(newStage) {
1288 appDelegate.stage = newStage;
1289 WindowStateStorage.saveStage(appId, newStage);
1290 priv.updateMainAndSideStageIndexes()
1291 }
1292
1293 readonly property var surface: model.window.surface
1294 readonly property var window: model.window
1295
1296 readonly property alias focusedSurface: decoratedWindow.focusedSurface
1297 readonly property bool dragging: touchControls.overlayShown ? touchControls.dragging : decoratedWindow.dragging
1298
1299 readonly property string appId: model.application.appId
1300 readonly property alias clientAreaItem: decoratedWindow.clientAreaItem
1301
1302 // It is Lomiri policy to close any window but the last one during OOM teardown
1303/*
1304 Connections {
1305 target: model.window.surface
1306 onLiveChanged: {
1307 if ((!surface.live && application && application.surfaceCount > 1) || !application)
1308 topLevelSurfaceList.removeAt(appRepeater.indexOf(appDelegate));
1309 }
1310 }
1311*/
1312
1313
1314 function activate() {
1315 if (model.window.focused) {
1316 updateQmlFocusFromMirSurfaceFocus();
1317 } else {
1318 if (surface.live) {
1319 // Activate the window since it has a surface (with a running app) backing it
1320 model.window.activate();
1321 } else {
1322 // Otherwise, cause a respawn of the app, and trigger it's refocusing as the last window
1323 topLevelSurfaceList.raiseId(model.window.id);
1324 }
1325 }
1326 }
1327 function requestMaximize() { model.window.requestState(Mir.MaximizedState); }
1328 function requestMaximizeVertically() { model.window.requestState(Mir.VertMaximizedState); }
1329 function requestMaximizeHorizontally() { model.window.requestState(Mir.HorizMaximizedState); }
1330 function requestMaximizeLeft() { model.window.requestState(Mir.MaximizedLeftState); }
1331 function requestMaximizeRight() { model.window.requestState(Mir.MaximizedRightState); }
1332 function requestMaximizeTopLeft() { model.window.requestState(Mir.MaximizedTopLeftState); }
1333 function requestMaximizeTopRight() { model.window.requestState(Mir.MaximizedTopRightState); }
1334 function requestMaximizeBottomLeft() { model.window.requestState(Mir.MaximizedBottomLeftState); }
1335 function requestMaximizeBottomRight() { model.window.requestState(Mir.MaximizedBottomRightState); }
1336 function requestMinimize() { model.window.requestState(Mir.MinimizedState); }
1337 function requestRestore() { model.window.requestState(Mir.RestoredState); }
1338
1339 function claimFocus() {
1340 if (root.state == "spread") {
1341 spreadItem.highlightedIndex = index
1342 // force pendingActivation so that when switching to staged mode, topLevelSurfaceList focus won't got to previous app ( case when apps are launched from outside )
1343 topLevelSurfaceList.pendingActivation();
1344 priv.goneToSpread = false;
1345 }
1346 if (root.mode == "stagedWithSideStage") {
1347 if (appDelegate.stage == ApplicationInfoInterface.SideStage && !sideStage.shown) {
1348 sideStage.show();
1349 }
1350 priv.updateMainAndSideStageIndexes();
1351 }
1352 appDelegate.focus = true;
1353
1354 // Don't set focusedAppDelegate (and signal mainAppChanged) unnecessarily
1355 // which can happen after getting interactive again.
1356 if (priv.focusedAppDelegate !== appDelegate)
1357 priv.focusedAppDelegate = appDelegate;
1358 }
1359
1360 function updateQmlFocusFromMirSurfaceFocus() {
1361 if (model.window.focused) {
1362 claimFocus();
1363 decoratedWindow.focus = true;
1364 }
1365 }
1366
1367 WindowStateSaver {
1368 id: windowStateSaver
1369 target: appDelegate
1370 screenWidth: appContainer.width
1371 screenHeight: appContainer.height
1372 leftMargin: root.availableDesktopArea.x
1373 minimumY: root.availableDesktopArea.y
1374 }
1375
1376 Connections {
1377 target: model.window
1378 function onFocusedChanged() {
1379 updateQmlFocusFromMirSurfaceFocus();
1380 if (!model.window.focused) {
1381 inhibitSlideAnimation = false;
1382 }
1383 }
1384 function onFocusRequested() {
1385 appDelegate.activate();
1386 }
1387 function onStateChanged(value) {
1388 if (value == Mir.MinimizedState) {
1389 appDelegate.minimize();
1390 } else if (value == Mir.MaximizedState) {
1391 appDelegate.maximize();
1392 } else if (value == Mir.VertMaximizedState) {
1393 appDelegate.maximizeVertically();
1394 } else if (value == Mir.HorizMaximizedState) {
1395 appDelegate.maximizeHorizontally();
1396 } else if (value == Mir.MaximizedLeftState) {
1397 appDelegate.maximizeLeft();
1398 } else if (value == Mir.MaximizedRightState) {
1399 appDelegate.maximizeRight();
1400 } else if (value == Mir.MaximizedTopLeftState) {
1401 appDelegate.maximizeTopLeft();
1402 } else if (value == Mir.MaximizedTopRightState) {
1403 appDelegate.maximizeTopRight();
1404 } else if (value == Mir.MaximizedBottomLeftState) {
1405 appDelegate.maximizeBottomLeft();
1406 } else if (value == Mir.MaximizedBottomRightState) {
1407 appDelegate.maximizeBottomRight();
1408 } else if (value == Mir.RestoredState) {
1409 if (appDelegate.fullscreen && appDelegate.prevWindowState != WindowStateStorage.WindowStateRestored
1410 && appDelegate.prevWindowState != WindowStateStorage.WindowStateNormal) {
1411 model.window.requestState(WindowStateStorage.toMirState(appDelegate.prevWindowState));
1412 } else {
1413 appDelegate.restore();
1414 }
1415 } else if (value == Mir.FullscreenState) {
1416 appDelegate.prevWindowState = appDelegate.windowState;
1417 appDelegate.windowState = WindowStateStorage.WindowStateFullscreen;
1418 }
1419 }
1420 }
1421
1422 readonly property bool windowReady: clientAreaItem.surfaceInitialized
1423 onWindowReadyChanged: {
1424 if (windowReady) {
1425 var loadedMirState = WindowStateStorage.toMirState(windowStateSaver.loadedState);
1426 var state = loadedMirState;
1427
1428 if (window.state == Mir.FullscreenState) {
1429 // If the app is fullscreen at startup, we should not use saved state
1430 // Example of why: if you open game that only requests fullscreen at
1431 // Statup, this will automaticly be set to "restored state" since
1432 // thats the default value of stateStorage, this will result in the app
1433 // having the "restored state" as it will not make a fullscreen
1434 // call after the app has started.
1435 console.log("Initial window state is fullscreen, not using saved state.");
1436 state = window.state;
1437 } else if (loadedMirState == Mir.FullscreenState) {
1438 // If saved state is fullscreen, we should use app initial state
1439 // Example of why: if you open browser with youtube video at fullscreen
1440 // and close this app, it will be fullscreen next time you open the app.
1441 console.log("Saved window state is fullscreen, using initial window state");
1442 state = window.state;
1443 }
1444
1445 // need to apply the shell chrome policy on top the saved window state
1446 var policy;
1447 if (root.mode == "windowed") {
1448 policy = windowedFullscreenPolicy;
1449 } else {
1450 policy = stagedFullscreenPolicy
1451 }
1452 window.requestState(policy.applyPolicy(state, surface.shellChrome));
1453 }
1454 }
1455
1456 Component.onCompleted: {
1457 if (application && application.rotatesWindowContents) {
1458 decoratedWindow.surfaceOrientationAngle = shellOrientationAngle;
1459 } else {
1460 decoratedWindow.surfaceOrientationAngle = 0;
1461 }
1462
1463 // First, cascade the newly created window, relative to the currently/old focused window.
1464 windowedX = priv.focusedAppDelegate ? priv.focusedAppDelegate.windowedX + units.gu(3) : (normalZ - 1) * units.gu(3)
1465 windowedY = priv.focusedAppDelegate ? priv.focusedAppDelegate.windowedY + units.gu(3) : normalZ * units.gu(3)
1466 // Now load any saved state. This needs to happen *after* the cascading!
1467 windowStateSaver.load();
1468
1469 if (!root.spreadShown) {
1470 updateQmlFocusFromMirSurfaceFocus();
1471 }
1472
1473 // Make apps maximized on phones & tablets
1474 if (root.mode == "staged" || root.mode == "stagedWithSideStage")
1475 appDelegate.maximize()
1476
1477 refreshStage();
1478 _constructing = false;
1479 }
1480 Component.onDestruction: {
1481 windowStateSaver.save();
1482
1483 if (!root.parent) {
1484 // This stage is about to be destroyed. Don't mess up with the model at this point
1485 return;
1486 }
1487
1488 if (visuallyMaximized) {
1489 priv.updateForegroundMaximizedApp();
1490 }
1491 }
1492
1493 onVisuallyMaximizedChanged: priv.updateForegroundMaximizedApp()
1494
1495 property bool _constructing: true;
1496 onStageChanged: {
1497 if (!_constructing) {
1498 priv.updateMainAndSideStageIndexes();
1499 }
1500 }
1501
1502 visible: (
1503 !visuallyMinimized
1504 && !greeter.fullyShown
1505 && (priv.foregroundMaximizedAppDelegate === null || priv.foregroundMaximizedAppDelegate.normalZ <= z)
1506 )
1507 || appDelegate.fullscreen
1508 || focusAnimation.running || rightEdgeFocusAnimation.running || hidingAnimation.running
1509
1510 function close() {
1511 model.window.close();
1512 }
1513
1514 function maximize(animated) {
1515 animationsEnabled = (animated === undefined) || animated;
1516 windowState = WindowStateStorage.WindowStateMaximized;
1517 }
1518 function maximizeLeft(animated) {
1519 animationsEnabled = (animated === undefined) || animated;
1520 windowState = WindowStateStorage.WindowStateMaximizedLeft;
1521 }
1522 function maximizeRight(animated) {
1523 animationsEnabled = (animated === undefined) || animated;
1524 windowState = WindowStateStorage.WindowStateMaximizedRight;
1525 }
1526 function maximizeHorizontally(animated) {
1527 animationsEnabled = (animated === undefined) || animated;
1528 windowState = WindowStateStorage.WindowStateMaximizedHorizontally;
1529 }
1530 function maximizeVertically(animated) {
1531 animationsEnabled = (animated === undefined) || animated;
1532 windowState = WindowStateStorage.WindowStateMaximizedVertically;
1533 }
1534 function maximizeTopLeft(animated) {
1535 animationsEnabled = (animated === undefined) || animated;
1536 windowState = WindowStateStorage.WindowStateMaximizedTopLeft;
1537 }
1538 function maximizeTopRight(animated) {
1539 animationsEnabled = (animated === undefined) || animated;
1540 windowState = WindowStateStorage.WindowStateMaximizedTopRight;
1541 }
1542 function maximizeBottomLeft(animated) {
1543 animationsEnabled = (animated === undefined) || animated;
1544 windowState = WindowStateStorage.WindowStateMaximizedBottomLeft;
1545 }
1546 function maximizeBottomRight(animated) {
1547 animationsEnabled = (animated === undefined) || animated;
1548 windowState = WindowStateStorage.WindowStateMaximizedBottomRight;
1549 }
1550 function minimize(animated) {
1551 animationsEnabled = (animated === undefined) || animated;
1552 windowState |= WindowStateStorage.WindowStateMinimized; // add the minimized bit
1553 }
1554 function restore(animated,state) {
1555 animationsEnabled = (animated === undefined) || animated;
1556 windowState = state || WindowStateStorage.WindowStateRestored;
1557 windowState &= ~WindowStateStorage.WindowStateMinimized; // clear the minimized bit
1558 prevWindowState = windowState;
1559 }
1560
1561 function playFocusAnimation() {
1562 if (state == "stagedRightEdge") {
1563 // TODO: Can we drop this if and find something that always works?
1564 if (root.mode == "staged") {
1565 rightEdgeFocusAnimation.targetX = 0
1566 rightEdgeFocusAnimation.start()
1567 } else if (root.mode == "stagedWithSideStage") {
1568 rightEdgeFocusAnimation.targetX = appDelegate.stage == ApplicationInfoInterface.SideStage ? sideStage.x : 0
1569 rightEdgeFocusAnimation.start()
1570 }
1571 } else {
1572 focusAnimation.start()
1573 }
1574 }
1575 function playHidingAnimation() {
1576 if (state != "windowedRightEdge") {
1577 hidingAnimation.start()
1578 }
1579 }
1580
1581 function refreshStage() {
1582 var newStage = ApplicationInfoInterface.MainStage;
1583 if (priv.sideStageEnabled) { // we're in lanscape rotation.
1584 if (application && application.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) {
1585 var defaultStage = ApplicationInfoInterface.SideStage; // if application supports portrait, it defaults to sidestage.
1586 if (application.supportedOrientations & (Qt.LandscapeOrientation|Qt.InvertedLandscapeOrientation)) {
1587 // if it supports lanscape, it defaults to mainstage.
1588 defaultStage = ApplicationInfoInterface.MainStage;
1589 }
1590 newStage = WindowStateStorage.getStage(application.appId, defaultStage);
1591 }
1592 }
1593
1594 stage = newStage;
1595 if (focus && stage == ApplicationInfoInterface.SideStage && !sideStage.shown) {
1596 sideStage.show();
1597 }
1598 }
1599
1600 LomiriNumberAnimation {
1601 id: focusAnimation
1602 target: appDelegate
1603 property: "scale"
1604 from: 0.98
1605 to: 1
1606 duration: LomiriAnimation.SnapDuration
1607 onStarted: {
1608 topLevelSurfaceList.pendingActivation();
1609 topLevelSurfaceList.raiseId(model.window.id);
1610 }
1611 onStopped: {
1612 appDelegate.activate();
1613 }
1614 }
1615 ParallelAnimation {
1616 id: rightEdgeFocusAnimation
1617 property int targetX: 0
1618 LomiriNumberAnimation { target: appDelegate; properties: "x"; to: rightEdgeFocusAnimation.targetX; duration: priv.animationDuration }
1619 LomiriNumberAnimation { target: decoratedWindow; properties: "angle"; to: 0; duration: priv.animationDuration }
1620 LomiriNumberAnimation { target: decoratedWindow; properties: "itemScale"; to: 1; duration: priv.animationDuration }
1621 onStarted: {
1622 topLevelSurfaceList.pendingActivation();
1623 inhibitSlideAnimation = true;
1624 }
1625 onStopped: {
1626 appDelegate.activate();
1627 }
1628 }
1629 ParallelAnimation {
1630 id: hidingAnimation
1631 LomiriNumberAnimation { target: appDelegate; property: "opacity"; to: 0; duration: priv.animationDuration }
1632 onStopped: appDelegate.opacity = 1
1633 }
1634
1635 SpreadMaths {
1636 id: spreadMaths
1637 spread: spreadItem
1638 itemIndex: index
1639 flickable: floatingFlickable
1640 }
1641 StageMaths {
1642 id: stageMaths
1643 sceneWidth: root.width
1644 stage: appDelegate.stage
1645 thisDelegate: appDelegate
1646 mainStageDelegate: priv.mainStageDelegate
1647 sideStageDelegate: priv.sideStageDelegate
1648 sideStageWidth: sideStage.panelWidth
1649 sideStageHandleWidth: sideStage.handleWidth
1650 sideStageX: sideStage.x
1651 itemIndex: appDelegate.itemIndex
1652 nextInStack: priv.nextInStack
1653 animationDuration: priv.animationDuration
1654 }
1655
1656 StagedRightEdgeMaths {
1657 id: stagedRightEdgeMaths
1658 sceneWidth: root.availableDesktopArea.width
1659 sceneHeight: appContainer.height
1660 isMainStageApp: priv.mainStageDelegate == appDelegate
1661 isSideStageApp: priv.sideStageDelegate == appDelegate
1662 sideStageWidth: sideStage.width
1663 sideStageOpen: sideStage.shown
1664 itemIndex: index
1665 nextInStack: priv.nextInStack
1666 progress: 0
1667 targetHeight: spreadItem.stackHeight
1668 targetX: spreadMaths.targetX
1669 startY: appDelegate.fullscreen ? 0 : root.availableDesktopArea.y
1670 targetY: spreadMaths.targetY
1671 targetAngle: spreadMaths.targetAngle
1672 targetScale: spreadMaths.targetScale
1673 shuffledZ: stageMaths.itemZ
1674 breakPoint: spreadItem.rightEdgeBreakPoint
1675 }
1676
1677 WindowedRightEdgeMaths {
1678 id: windowedRightEdgeMaths
1679 itemIndex: index
1680 startWidth: appDelegate.requestedWidth
1681 startHeight: appDelegate.requestedHeight
1682 targetHeight: spreadItem.stackHeight
1683 targetX: spreadMaths.targetX
1684 targetY: spreadMaths.targetY
1685 normalZ: appDelegate.normalZ
1686 targetAngle: spreadMaths.targetAngle
1687 targetScale: spreadMaths.targetScale
1688 breakPoint: spreadItem.rightEdgeBreakPoint
1689 }
1690
1691 states: [
1692 State {
1693 name: "spread"; when: root.state == "spread"
1694 StateChangeScript { script: { decoratedWindow.cancelDrag(); } }
1695 PropertyChanges {
1696 target: decoratedWindow;
1697 showDecoration: false;
1698 angle: spreadMaths.targetAngle
1699 itemScale: spreadMaths.targetScale
1700 scaleToPreviewSize: spreadItem.stackHeight
1701 scaleToPreviewProgress: 1
1702 hasDecoration: root.mode === "windowed"
1703 shadowOpacity: spreadMaths.shadowOpacity
1704 showHighlight: spreadItem.highlightedIndex === index
1705 darkening: spreadItem.highlightedIndex >= 0
1706 anchors.topMargin: dragArea.distance
1707 }
1708 PropertyChanges {
1709 target: appDelegate
1710 x: spreadMaths.targetX
1711 y: spreadMaths.targetY
1712 z: index
1713 height: spreadItem.spreadItemHeight
1714 visible: spreadMaths.itemVisible
1715 }
1716 PropertyChanges { target: dragArea; enabled: true }
1717 PropertyChanges { target: windowInfoItem; opacity: spreadMaths.tileInfoOpacity; visible: spreadMaths.itemVisible }
1718 PropertyChanges { target: touchControls; enabled: false }
1719 },
1720 State {
1721 name: "stagedRightEdge"
1722 when: (root.mode == "staged" || root.mode == "stagedWithSideStage") && (root.state == "sideStagedRightEdge" || root.state == "stagedRightEdge" || rightEdgeFocusAnimation.running || hidingAnimation.running)
1723 PropertyChanges {
1724 target: stagedRightEdgeMaths
1725 progress: Math.max(rightEdgePushProgress, rightEdgeDragArea.draggedProgress)
1726 }
1727 PropertyChanges {
1728 target: appDelegate
1729 x: stagedRightEdgeMaths.animatedX
1730 y: stagedRightEdgeMaths.animatedY
1731 z: stagedRightEdgeMaths.animatedZ
1732 height: stagedRightEdgeMaths.animatedHeight
1733 visible: appDelegate.x < root.width
1734 }
1735 PropertyChanges {
1736 target: decoratedWindow
1737 hasDecoration: false
1738 angle: stagedRightEdgeMaths.animatedAngle
1739 itemScale: stagedRightEdgeMaths.animatedScale
1740 scaleToPreviewSize: spreadItem.stackHeight
1741 scaleToPreviewProgress: stagedRightEdgeMaths.scaleToPreviewProgress
1742 shadowOpacity: .3
1743 }
1744 // make sure it's visible but transparent so it fades in when we transition to spread
1745 PropertyChanges { target: windowInfoItem; opacity: 0; visible: true }
1746 },
1747 State {
1748 name: "windowedRightEdge"
1749 when: root.mode == "windowed" && (root.state == "windowedRightEdge" || rightEdgeFocusAnimation.running || hidingAnimation.running || rightEdgePushProgress > 0)
1750 PropertyChanges {
1751 target: windowedRightEdgeMaths
1752 swipeProgress: rightEdgeDragArea.dragging ? rightEdgeDragArea.progress : 0
1753 pushProgress: rightEdgePushProgress
1754 }
1755 PropertyChanges {
1756 target: appDelegate
1757 x: windowedRightEdgeMaths.animatedX
1758 y: windowedRightEdgeMaths.animatedY
1759 z: windowedRightEdgeMaths.animatedZ
1760 height: stagedRightEdgeMaths.animatedHeight
1761 }
1762 PropertyChanges {
1763 target: decoratedWindow
1764 showDecoration: windowedRightEdgeMaths.decorationHeight
1765 angle: windowedRightEdgeMaths.animatedAngle
1766 itemScale: windowedRightEdgeMaths.animatedScale
1767 scaleToPreviewSize: spreadItem.stackHeight
1768 scaleToPreviewProgress: windowedRightEdgeMaths.scaleToPreviewProgress
1769 shadowOpacity: .3
1770 }
1771 PropertyChanges {
1772 target: opacityEffect;
1773 opacityValue: windowedRightEdgeMaths.opacityMask
1774 sourceItem: windowedRightEdgeMaths.opacityMask < 1 ? decoratedWindow : null
1775 }
1776 },
1777 State {
1778 name: "staged"; when: root.state == "staged"
1779 PropertyChanges {
1780 target: appDelegate
1781 x: stageMaths.itemX
1782 y: root.availableDesktopArea.y
1783 visuallyMaximized: true
1784 visible: appDelegate.x < root.width
1785 }
1786 PropertyChanges {
1787 target: appDelegate
1788 requestedWidth: appContainer.width
1789 requestedHeight: root.availableDesktopArea.height
1790 restoreEntryValues: false
1791 }
1792 PropertyChanges {
1793 target: decoratedWindow
1794 hasDecoration: false
1795 }
1796 PropertyChanges {
1797 target: resizeArea
1798 enabled: false
1799 }
1800 PropertyChanges {
1801 target: stageMaths
1802 animateX: !focusAnimation.running && !rightEdgeFocusAnimation.running && itemIndex !== spreadItem.highlightedIndex && !inhibitSlideAnimation
1803 }
1804 PropertyChanges {
1805 target: appDelegate.window
1806 allowClientResize: false
1807 }
1808 },
1809 State {
1810 name: "stagedWithSideStage"; when: root.state == "stagedWithSideStage"
1811 PropertyChanges {
1812 target: stageMaths
1813 itemIndex: index
1814 }
1815 PropertyChanges {
1816 target: appDelegate
1817 x: stageMaths.itemX
1818 y: root.availableDesktopArea.y
1819 z: stageMaths.itemZ
1820 visuallyMaximized: true
1821 visible: appDelegate.x < root.width
1822 }
1823 PropertyChanges {
1824 target: appDelegate
1825 requestedWidth: stageMaths.itemWidth
1826 requestedHeight: root.availableDesktopArea.height
1827 restoreEntryValues: false
1828 }
1829 PropertyChanges {
1830 target: decoratedWindow
1831 hasDecoration: false
1832 }
1833 PropertyChanges {
1834 target: resizeArea
1835 enabled: false
1836 }
1837 PropertyChanges {
1838 target: appDelegate.window
1839 allowClientResize: false
1840 }
1841 },
1842 State {
1843 name: "maximized"; when: appDelegate.maximized && !appDelegate.minimized
1844 PropertyChanges {
1845 target: appDelegate;
1846 requestedX: root.availableDesktopArea.x;
1847 requestedY: 0;
1848 visuallyMinimized: false;
1849 visuallyMaximized: true
1850 }
1851 PropertyChanges {
1852 target: appDelegate
1853 requestedWidth: root.availableDesktopArea.width;
1854 requestedHeight: appContainer.height;
1855 restoreEntryValues: false
1856 }
1857 PropertyChanges { target: touchControls; enabled: true }
1858 PropertyChanges { target: decoratedWindow; windowControlButtonsVisible: false }
1859 },
1860 State {
1861 name: "fullscreen"; when: appDelegate.fullscreen && !appDelegate.minimized
1862 PropertyChanges {
1863 target: appDelegate;
1864 requestedX: 0
1865 requestedY: 0
1866 }
1867 PropertyChanges {
1868 target: appDelegate
1869 requestedWidth: appContainer.width
1870 requestedHeight: appContainer.height
1871 restoreEntryValues: false
1872 }
1873 PropertyChanges { target: decoratedWindow; hasDecoration: false }
1874 },
1875 State {
1876 name: "normal";
1877 when: appDelegate.windowState == WindowStateStorage.WindowStateNormal
1878 PropertyChanges {
1879 target: appDelegate
1880 visuallyMinimized: false
1881 }
1882 PropertyChanges { target: touchControls; enabled: true }
1883 PropertyChanges { target: resizeArea; enabled: true }
1884 PropertyChanges { target: decoratedWindow; shadowOpacity: .3; windowControlButtonsVisible: true}
1885 PropertyChanges {
1886 target: appDelegate
1887 requestedWidth: windowedWidth
1888 requestedHeight: windowedHeight
1889 restoreEntryValues: false
1890 }
1891 },
1892 State {
1893 name: "restored";
1894 when: appDelegate.windowState == WindowStateStorage.WindowStateRestored
1895 extend: "normal"
1896 PropertyChanges {
1897 restoreEntryValues: false
1898 target: appDelegate;
1899 windowedX: restoredX;
1900 windowedY: restoredY;
1901 }
1902 },
1903 State {
1904 name: "maximizedLeft"; when: appDelegate.maximizedLeft && !appDelegate.minimized
1905 extend: "normal"
1906 PropertyChanges {
1907 target: appDelegate
1908 windowedX: root.availableDesktopArea.x
1909 windowedY: root.availableDesktopArea.y
1910 windowedWidth: root.availableDesktopArea.width / 2
1911 windowedHeight: root.availableDesktopArea.height
1912 }
1913 },
1914 State {
1915 name: "maximizedRight"; when: appDelegate.maximizedRight && !appDelegate.minimized
1916 extend: "maximizedLeft"
1917 PropertyChanges {
1918 target: appDelegate;
1919 windowedX: root.availableDesktopArea.x + (root.availableDesktopArea.width / 2)
1920 }
1921 },
1922 State {
1923 name: "maximizedTopLeft"; when: appDelegate.maximizedTopLeft && !appDelegate.minimized
1924 extend: "normal"
1925 PropertyChanges {
1926 target: appDelegate
1927 windowedX: root.availableDesktopArea.x
1928 windowedY: root.availableDesktopArea.y
1929 windowedWidth: root.availableDesktopArea.width / 2
1930 windowedHeight: root.availableDesktopArea.height / 2
1931 }
1932 },
1933 State {
1934 name: "maximizedTopRight"; when: appDelegate.maximizedTopRight && !appDelegate.minimized
1935 extend: "maximizedTopLeft"
1936 PropertyChanges {
1937 target: appDelegate
1938 windowedX: root.availableDesktopArea.x + (root.availableDesktopArea.width / 2)
1939 }
1940 },
1941 State {
1942 name: "maximizedBottomLeft"; when: appDelegate.maximizedBottomLeft && !appDelegate.minimized
1943 extend: "normal"
1944 PropertyChanges {
1945 target: appDelegate
1946 windowedX: root.availableDesktopArea.x
1947 windowedY: root.availableDesktopArea.y + (root.availableDesktopArea.height / 2)
1948 windowedWidth: root.availableDesktopArea.width / 2
1949 windowedHeight: root.availableDesktopArea.height / 2
1950 }
1951 },
1952 State {
1953 name: "maximizedBottomRight"; when: appDelegate.maximizedBottomRight && !appDelegate.minimized
1954 extend: "maximizedBottomLeft"
1955 PropertyChanges {
1956 target: appDelegate
1957 windowedX: root.availableDesktopArea.x + (root.availableDesktopArea.width / 2)
1958 }
1959 },
1960 State {
1961 name: "maximizedHorizontally"; when: appDelegate.maximizedHorizontally && !appDelegate.minimized
1962 extend: "normal"
1963 PropertyChanges {
1964 target: appDelegate
1965 windowedX: root.availableDesktopArea.x; windowedY: windowedY
1966 windowedWidth: root.availableDesktopArea.width; windowedHeight: windowedHeight
1967 }
1968 },
1969 State {
1970 name: "maximizedVertically"; when: appDelegate.maximizedVertically && !appDelegate.minimized
1971 extend: "normal"
1972 PropertyChanges {
1973 target: appDelegate
1974 windowedX: windowedX; windowedY: root.availableDesktopArea.y
1975 windowedWidth: windowedWidth; windowedHeight: root.availableDesktopArea.height
1976 }
1977 },
1978 State {
1979 name: "minimized"; when: appDelegate.minimized
1980 PropertyChanges {
1981 target: appDelegate
1982 scale: units.gu(5) / appDelegate.width
1983 opacity: 0;
1984 visuallyMinimized: true
1985 visuallyMaximized: false
1986 x: -appDelegate.width / 2
1987 y: root.height / 2
1988 }
1989 }
1990 ]
1991
1992 transitions: [
1993
1994 // These two animate applications into position from Staged to Desktop and back
1995 Transition {
1996 from: "staged,stagedWithSideStage"
1997 to: "normal,restored,maximized,maximizedHorizontally,maximizedVertically,maximizedLeft,maximizedRight,maximizedTopLeft,maximizedBottomLeft,maximizedTopRight,maximizedBottomRight"
1998 enabled: appDelegate.animationsEnabled
1999 PropertyAction { target: appDelegate; properties: "visuallyMinimized,visuallyMaximized" }
2000 LomiriNumberAnimation { target: appDelegate; properties: "x,y,requestedX,requestedY,opacity,requestedWidth,requestedHeight,scale"; duration: priv.animationDuration }
2001 },
2002 Transition {
2003 from: "normal,restored,maximized,maximizedHorizontally,maximizedVertically,maximizedLeft,maximizedRight,maximizedTopLeft,maximizedBottomLeft,maximizedTopRight,maximizedBottomRight"
2004 to: "staged,stagedWithSideStage"
2005 LomiriNumberAnimation { target: appDelegate; properties: "x,y,requestedX,requestedY,requestedWidth,requestedHeight"; duration: priv.animationDuration}
2006 },
2007
2008 Transition {
2009 from: "normal,restored,maximized,maximizedHorizontally,maximizedVertically,maximizedLeft,maximizedRight,maximizedTopLeft,maximizedBottomLeft,maximizedTopRight,maximizedBottomRight,staged,stagedWithSideStage,windowedRightEdge,stagedRightEdge";
2010 to: "spread"
2011 // DecoratedWindow wants the scaleToPreviewSize set before enabling scaleToPreview
2012 PropertyAction { target: appDelegate; properties: "z,visible" }
2013 PropertyAction { target: decoratedWindow; property: "scaleToPreviewSize" }
2014 LomiriNumberAnimation { target: appDelegate; properties: "x,y,height"; duration: priv.animationDuration }
2015 LomiriNumberAnimation { target: decoratedWindow; properties: "width,height,itemScale,angle,scaleToPreviewProgress"; duration: priv.animationDuration }
2016 LomiriNumberAnimation { target: windowInfoItem; properties: "opacity"; duration: priv.animationDuration }
2017 },
2018 Transition {
2019 from: "normal,staged"; to: "stagedWithSideStage"
2020 LomiriNumberAnimation { target: appDelegate; properties: "x,y,requestedWidth,requestedHeight"; duration: priv.animationDuration }
2021 },
2022 Transition {
2023 to: "windowedRightEdge"
2024 ScriptAction {
2025 script: {
2026 windowedRightEdgeMaths.startX = appDelegate.requestedX
2027 windowedRightEdgeMaths.startY = appDelegate.requestedY
2028
2029 if (index == 1) {
2030 var thisRect = { x: appDelegate.windowedX, y: appDelegate.windowedY, width: appDelegate.requestedWidth, height: appDelegate.requestedHeight }
2031 var otherDelegate = appRepeater.itemAt(0);
2032 var otherRect = { x: otherDelegate.windowedX, y: otherDelegate.windowedY, width: otherDelegate.requestedWidth, height: otherDelegate.requestedHeight }
2033 var intersectionRect = MathUtils.intersectionRect(thisRect, otherRect)
2034 var mappedInterSectionRect = appDelegate.mapFromItem(root, intersectionRect.x, intersectionRect.y)
2035 opacityEffect.maskX = mappedInterSectionRect.x
2036 opacityEffect.maskY = mappedInterSectionRect.y
2037 opacityEffect.maskWidth = intersectionRect.width
2038 opacityEffect.maskHeight = intersectionRect.height
2039 }
2040 }
2041 }
2042 },
2043 Transition {
2044 from: "stagedRightEdge"; to: "staged"
2045 enabled: rightEdgeDragArea.cancelled // only transition back to state if the gesture was cancelled, in the other cases we play the focusAnimations.
2046 SequentialAnimation {
2047 ParallelAnimation {
2048 LomiriNumberAnimation { target: appDelegate; properties: "x,y,height,width,scale"; duration: priv.animationDuration }
2049 LomiriNumberAnimation { target: decoratedWindow; properties: "width,height,itemScale,angle,scaleToPreviewProgress"; duration: priv.animationDuration }
2050 }
2051 // We need to release scaleToPreviewSize at last
2052 PropertyAction { target: decoratedWindow; property: "scaleToPreviewSize" }
2053 PropertyAction { target: appDelegate; property: "visible" }
2054 }
2055 },
2056 Transition {
2057 from: ",normal,restored,maximized,maximizedLeft,maximizedRight,maximizedTopLeft,maximizedTopRight,maximizedBottomLeft,maximizedBottomRight,maximizedHorizontally,maximizedVertically,fullscreen"
2058 to: "minimized"
2059 SequentialAnimation {
2060 ScriptAction { script: { fakeRectangle.stop(); } }
2061 PropertyAction { target: appDelegate; property: "visuallyMaximized" }
2062 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
2063 LomiriNumberAnimation { target: appDelegate; properties: "x,y,scale,opacity"; duration: priv.animationDuration }
2064 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
2065 }
2066 },
2067 Transition {
2068 from: "minimized"
2069 to: ",normal,restored,maximized,maximizedLeft,maximizedRight,maximizedTopLeft,maximizedTopRight,maximizedBottomLeft,maximizedBottomRight,maximizedHorizontally,maximizedVertically,fullscreen"
2070 SequentialAnimation {
2071 PropertyAction { target: appDelegate; property: "visuallyMinimized,z" }
2072 ParallelAnimation {
2073 LomiriNumberAnimation { target: appDelegate; properties: "x"; from: -appDelegate.width / 2; duration: priv.animationDuration }
2074 LomiriNumberAnimation { target: appDelegate; properties: "y,opacity"; duration: priv.animationDuration }
2075 LomiriNumberAnimation { target: appDelegate; properties: "scale"; from: 0; duration: priv.animationDuration }
2076 }
2077 PropertyAction { target: appDelegate; property: "visuallyMaximized" }
2078 }
2079 },
2080 Transition {
2081 id: windowedTransition
2082 from: ",normal,restored,maximized,maximizedLeft,maximizedRight,maximizedTopLeft,maximizedTopRight,maximizedBottomLeft,maximizedBottomRight,maximizedHorizontally,maximizedVertically,fullscreen,minimized"
2083 to: ",normal,restored,maximized,maximizedLeft,maximizedRight,maximizedTopLeft,maximizedTopRight,maximizedBottomLeft,maximizedBottomRight,maximizedHorizontally,maximizedVertically,fullscreen"
2084 enabled: appDelegate.animationsEnabled
2085 SequentialAnimation {
2086 ScriptAction { script: {
2087 if (appDelegate.visuallyMaximized) visuallyMaximized = false; // maximized before -> going to restored
2088 }
2089 }
2090 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
2091 LomiriNumberAnimation { target: appDelegate; properties: "requestedX,requestedY,windowedX,windowedY,opacity,scale,requestedWidth,requestedHeight,windowedWidth,windowedHeight";
2092 duration: priv.animationDuration }
2093 ScriptAction { script: {
2094 fakeRectangle.stop();
2095 appDelegate.visuallyMaximized = appDelegate.maximized; // reflect the target state
2096 }
2097 }
2098 }
2099 }
2100 ]
2101
2102 Binding {
2103 target: panelState
2104 property: "decorationsAlwaysVisible"
2105 value: appDelegate && appDelegate.maximized && touchControls.overlayShown
2106 restoreMode: Binding.RestoreBinding
2107 }
2108
2109 WindowResizeArea {
2110 id: resizeArea
2111 objectName: "windowResizeArea"
2112
2113 anchors.fill: appDelegate
2114
2115 // workaround so that it chooses the correct resize borders when you drag from a corner ResizeGrip
2116 anchors.margins: touchControls.overlayShown ? borderThickness/2 : -borderThickness
2117
2118 target: appDelegate
2119 boundsItem: root.availableDesktopArea
2120 minWidth: units.gu(10)
2121 minHeight: units.gu(10)
2122 borderThickness: units.gu(2)
2123 enabled: false
2124 visible: enabled
2125 readyToAssesBounds: !appDelegate._constructing
2126
2127 onPressed: {
2128 appDelegate.activate();
2129 }
2130 }
2131
2132 DecoratedWindow {
2133 id: decoratedWindow
2134 objectName: "decoratedWindow"
2135 anchors.left: appDelegate.left
2136 anchors.top: appDelegate.top
2137 application: model.application
2138 surface: model.window.surface
2139 active: model.window.focused
2140 focus: true
2141 interactive: root.interactive
2142 showDecoration: 1
2143 decorationHeight: priv.windowDecorationHeight
2144 maximizeButtonShown: appDelegate.canBeMaximized
2145 overlayShown: touchControls.overlayShown
2146 width: implicitWidth
2147 height: implicitHeight
2148 highlightSize: windowInfoItem.iconMargin
2149 boundsItem: root.availableDesktopArea
2150 panelState: root.panelState
2151 altDragEnabled: root.mode == "windowed"
2152
2153 requestedWidth: appDelegate.requestedWidth
2154 requestedHeight: appDelegate.requestedHeight
2155
2156 onCloseClicked: { appDelegate.close(); }
2157 onMaximizeClicked: {
2158 if (appDelegate.canBeMaximized) {
2159 appDelegate.anyMaximized ? appDelegate.requestRestore() : appDelegate.requestMaximize();
2160 }
2161 }
2162 onMaximizeHorizontallyClicked: {
2163 if (appDelegate.canBeMaximizedHorizontally) {
2164 appDelegate.maximizedHorizontally ? appDelegate.requestRestore() : appDelegate.requestMaximizeHorizontally()
2165 }
2166 }
2167 onMaximizeVerticallyClicked: {
2168 if (appDelegate.canBeMaximizedVertically) {
2169 appDelegate.maximizedVertically ? appDelegate.requestRestore() : appDelegate.requestMaximizeVertically()
2170 }
2171 }
2172 onMinimizeClicked: { appDelegate.requestMinimize(); }
2173 onDecorationPressed: { appDelegate.activate(); }
2174 onDecorationReleased: fakeRectangle.visible ? fakeRectangle.commit() : appDelegate.updateRestoredGeometry()
2175
2176 onDragResizePressed: {
2177 appDelegate.activate();
2178 resizeArea.pressedChangedEx(true, mouse);
2179 }
2180 onDragResizeReleased: resizeArea.pressedChangedEx(false, mouse);
2181 onDragResizePositionChanged: resizeArea.positionChangedEx(mouse);
2182
2183 property real angle: 0
2184 Behavior on angle { enabled: priv.closingIndex >= 0; LomiriNumberAnimation {} }
2185 property real itemScale: 1
2186 Behavior on itemScale { enabled: priv.closingIndex >= 0; LomiriNumberAnimation {} }
2187
2188 transform: [
2189 Scale {
2190 origin.x: 0
2191 origin.y: decoratedWindow.implicitHeight / 2
2192 xScale: decoratedWindow.itemScale
2193 yScale: decoratedWindow.itemScale
2194 },
2195 Rotation {
2196 origin { x: 0; y: (decoratedWindow.height / 2) }
2197 axis { x: 0; y: 1; z: 0 }
2198 angle: decoratedWindow.angle
2199 }
2200 ]
2201 }
2202
2203 OpacityMask {
2204 id: opacityEffect
2205 anchors.fill: decoratedWindow
2206 }
2207
2208 WindowControlsOverlay {
2209 id: touchControls
2210 anchors.fill: appDelegate
2211 target: appDelegate
2212 resizeArea: resizeArea
2213 enabled: false
2214 visible: enabled
2215 boundsItem: root.availableDesktopArea
2216
2217 onFakeMaximizeAnimationRequested: if (!appDelegate.maximized) fakeRectangle.maximize(amount, true)
2218 onFakeMaximizeLeftAnimationRequested: if (!appDelegate.maximizedLeft) fakeRectangle.maximizeLeft(amount, true)
2219 onFakeMaximizeRightAnimationRequested: if (!appDelegate.maximizedRight) fakeRectangle.maximizeRight(amount, true)
2220 onFakeMaximizeTopLeftAnimationRequested: if (!appDelegate.maximizedTopLeft) fakeRectangle.maximizeTopLeft(amount, true);
2221 onFakeMaximizeTopRightAnimationRequested: if (!appDelegate.maximizedTopRight) fakeRectangle.maximizeTopRight(amount, true);
2222 onFakeMaximizeBottomLeftAnimationRequested: if (!appDelegate.maximizedBottomLeft) fakeRectangle.maximizeBottomLeft(amount, true);
2223 onFakeMaximizeBottomRightAnimationRequested: if (!appDelegate.maximizedBottomRight) fakeRectangle.maximizeBottomRight(amount, true);
2224 onStopFakeAnimation: fakeRectangle.stop();
2225 onDragReleased: fakeRectangle.visible ? fakeRectangle.commit() : appDelegate.updateRestoredGeometry()
2226 }
2227
2228 WindowedFullscreenPolicy {
2229 id: windowedFullscreenPolicy
2230 }
2231 StagedFullscreenPolicy {
2232 id: stagedFullscreenPolicy
2233 active: root.mode == "staged" || root.mode == "stagedWithSideStage"
2234 surface: model.window.surface
2235 }
2236
2237 SpreadDelegateInputArea {
2238 id: dragArea
2239 objectName: "dragArea"
2240 anchors.fill: decoratedWindow
2241 enabled: false
2242 closeable: true
2243 stage: root
2244 dragDelegate: fakeDragItem
2245
2246 onClicked: {
2247 spreadItem.highlightedIndex = index;
2248 if (distance == 0) {
2249 priv.goneToSpread = false;
2250 }
2251 }
2252 onClose: {
2253 priv.closingIndex = index
2254 appDelegate.close();
2255 }
2256 }
2257
2258 WindowInfoItem {
2259 id: windowInfoItem
2260 objectName: "windowInfoItem"
2261 anchors { left: parent.left; top: decoratedWindow.bottom; topMargin: units.gu(1) }
2262 title: model.application.name
2263 iconSource: model.application.icon
2264 height: spreadItem.appInfoHeight
2265 opacity: 0
2266 z: 1
2267 visible: opacity > 0
2268 maxWidth: {
2269 var nextApp = appRepeater.itemAt(index + 1);
2270 if (nextApp) {
2271 return Math.max(iconHeight, nextApp.x - appDelegate.x - units.gu(1))
2272 }
2273 return appDelegate.width;
2274 }
2275
2276 onClicked: {
2277 spreadItem.highlightedIndex = index;
2278 priv.goneToSpread = false;
2279 }
2280 }
2281
2282 MouseArea {
2283 id: closeMouseArea
2284 objectName: "closeMouseArea"
2285 anchors { left: parent.left; top: parent.top; leftMargin: -height / 2; topMargin: -height / 2 + spreadMaths.closeIconOffset }
2286 readonly property var mousePos: hoverMouseArea.mapToItem(appDelegate, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
2287 readonly property bool shown: dragArea.distance == 0
2288 && index == spreadItem.highlightedIndex
2289 && mousePos.y < (decoratedWindow.height / 3)
2290 && mousePos.y > -units.gu(4)
2291 && mousePos.x > -units.gu(4)
2292 && mousePos.x < (decoratedWindow.width * 2 / 3)
2293 opacity: shown ? 1 : 0
2294 visible: opacity > 0
2295 Behavior on opacity { LomiriNumberAnimation { duration: LomiriAnimation.SnapDuration } }
2296 height: units.gu(6)
2297 width: height
2298
2299 onClicked: {
2300 priv.closingIndex = index;
2301 appDelegate.close();
2302 }
2303 Image {
2304 id: closeImage
2305 source: "graphics/window-close.svg"
2306 anchors.fill: closeMouseArea
2307 anchors.margins: units.gu(2)
2308 sourceSize.width: width
2309 sourceSize.height: height
2310 }
2311 }
2312
2313 Item {
2314 // Group all child windows in this item so that we can fade them out together when going to the spread
2315 // (and fade them in back again when returning from it)
2316 readonly property bool stageOnProperState: root.state === "windowed"
2317 || root.state === "staged"
2318 || root.state === "stagedWithSideStage"
2319
2320 // TODO: Is it worth the extra cost of layering to avoid the opacity artifacts of intersecting children?
2321 // Btw, will involve more than uncommenting the line below as children won't necessarily fit this item's
2322 // geometry. This is just a reference.
2323 //layer.enabled: opacity !== 0.0 && opacity !== 1.0
2324
2325 opacity: stageOnProperState ? 1.0 : 0.0
2326 visible: opacity !== 0.0 // make it transparent to input as well
2327 Behavior on opacity { LomiriNumberAnimation {} }
2328
2329 Repeater {
2330 id: childWindowRepeater
2331 model: appDelegate.surface ? appDelegate.surface.childSurfaceList : null
2332
2333 delegate: ChildWindowTree {
2334 surface: model.surface
2335
2336 // Account for the displacement caused by window decoration in the top-level surface
2337 // Ie, the top-level surface is not positioned at (0,0) of this ChildWindow's parent (appDelegate)
2338 displacementX: appDelegate.clientAreaItem.x
2339 displacementY: appDelegate.clientAreaItem.y
2340
2341 boundsItem: root.availableDesktopArea
2342 decorationHeight: priv.windowDecorationHeight
2343
2344 z: childWindowRepeater.count - model.index
2345
2346 onFocusChanged: {
2347 if (focus) {
2348 // some child surface in this tree got focus.
2349 // Ensure we also have it at the top-level hierarchy
2350 appDelegate.claimFocus();
2351 }
2352 }
2353 }
2354 }
2355 }
2356 }
2357 }
2358 }
2359
2360 FakeMaximizeDelegate {
2361 id: fakeRectangle
2362 target: priv.focusedAppDelegate
2363 leftMargin: root.availableDesktopArea.x
2364 appContainerWidth: appContainer.width
2365 appContainerHeight: appContainer.height
2366 panelState: root.panelState
2367 }
2368
2369 WorkspaceSwitcher {
2370 id: workspaceSwitcher
2371 enabled: workspaceEnabled
2372 anchors.centerIn: parent
2373 height: units.gu(20)
2374 width: root.width - units.gu(8)
2375 background: root.background
2376 availableDesktopArea: root.availableDesktopArea
2377 onWorkspaceSelected: screensAndWorkspaces.activeWorkspace = selectedWorkspace;
2378 onActiveChanged: {
2379 if (!active) {
2380 appContainer.focus = true;
2381 }
2382 }
2383 }
2384
2385 PropertyAnimation {
2386 id: shortRightEdgeSwipeAnimation
2387 property: "x"
2388 to: 0
2389 duration: priv.animationDuration
2390 }
2391
2392 SwipeArea {
2393 id: rightEdgeDragArea
2394 objectName: "rightEdgeDragArea"
2395 direction: Direction.Leftwards
2396 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
2397 width: root.dragAreaWidth
2398 enabled: root.spreadEnabled
2399
2400 property var gesturePoints: []
2401 property bool cancelled: false
2402
2403 property real progress: -touchPosition.x / root.width
2404 onProgressChanged: {
2405 if (dragging) {
2406 draggedProgress = progress;
2407 }
2408 }
2409
2410 property real draggedProgress: 0
2411
2412 onTouchPositionChanged: {
2413 gesturePoints.push(touchPosition.x);
2414 if (gesturePoints.length > 10) {
2415 gesturePoints.splice(0, gesturePoints.length - 10)
2416 }
2417 }
2418
2419 onDraggingChanged: {
2420 if (dragging) {
2421 // A potential edge-drag gesture has started. Start recording it
2422 gesturePoints = [];
2423 cancelled = false;
2424 draggedProgress = 0;
2425 } else {
2426 // Ok. The user released. Did he drag far enough to go to full spread?
2427 if (gesturePoints[gesturePoints.length - 1] < -spreadItem.rightEdgeBreakPoint * spreadItem.width ) {
2428
2429 // He dragged far enough, but if the last movement was a flick to the right again, he wants to cancel the spread again.
2430 var oneWayFlickToRight = true;
2431 var smallestX = gesturePoints[0]-1;
2432 for (var i = 0; i < gesturePoints.length; i++) {
2433 if (gesturePoints[i] <= smallestX) {
2434 oneWayFlickToRight = false;
2435 break;
2436 }
2437 smallestX = gesturePoints[i];
2438 }
2439
2440 if (!oneWayFlickToRight) {
2441 // Ok, the user made it, let's go to spread!
2442 priv.goneToSpread = true;
2443 } else {
2444 cancelled = true;
2445 }
2446 } else {
2447 // Ok, the user didn't drag far enough to cross the breakPoint
2448 // Find out if it was a one-way movement to the left, in which case we just switch directly to next app.
2449 var oneWayFlick = true;
2450 var smallestX = rightEdgeDragArea.width;
2451 for (var i = 0; i < gesturePoints.length; i++) {
2452 if (gesturePoints[i] >= smallestX) {
2453 oneWayFlick = false;
2454 break;
2455 }
2456 smallestX = gesturePoints[i];
2457 }
2458
2459 if (appRepeater.count > 1 &&
2460 (oneWayFlick && rightEdgeDragArea.distance > units.gu(2) || rightEdgeDragArea.distance > spreadItem.rightEdgeBreakPoint * spreadItem.width)) {
2461 var nextStage = appRepeater.itemAt(priv.nextInStack).stage
2462 for (var i = 0; i < appRepeater.count; i++) {
2463 if (i != priv.nextInStack && appRepeater.itemAt(i).stage == nextStage) {
2464 appRepeater.itemAt(i).playHidingAnimation()
2465 break;
2466 }
2467 }
2468 appRepeater.itemAt(priv.nextInStack).playFocusAnimation()
2469 if (appRepeater.itemAt(priv.nextInStack).stage == ApplicationInfoInterface.SideStage && !sideStage.shown) {
2470 sideStage.show();
2471 }
2472
2473 } else {
2474 cancelled = true;
2475 }
2476
2477 gesturePoints = [];
2478 }
2479 }
2480 }
2481
2482 GestureAreaSizeHint {
2483 anchors.fill: parent
2484 }
2485 }
2486
2487 TabletSideStageTouchGesture {
2488 id: triGestureArea
2489 objectName: "triGestureArea"
2490 anchors.fill: parent
2491 enabled: false
2492 property Item appDelegate
2493
2494 dragComponent: dragComponent
2495 dragComponentProperties: { "appDelegate": appDelegate }
2496
2497 onPressed: {
2498 function matchDelegate(obj) { return String(obj.objectName).indexOf("appDelegate") >= 0; }
2499
2500 var delegateAtCenter = Functions.itemAt(appContainer, x, y, matchDelegate);
2501 if (!delegateAtCenter) return;
2502
2503 appDelegate = delegateAtCenter;
2504 }
2505
2506 onClicked: {
2507 priv.toggleSideStage()
2508 }
2509
2510 onDragStarted: {
2511 // If we're dragging to the sidestage.
2512 if (!sideStage.shown) {
2513 sideStage.show();
2514 }
2515 }
2516
2517 onDropped: {
2518 // Hide side stage if the app drag was cancelled
2519 if (!priv.sideStageDelegate) {
2520 sideStage.hide();
2521 }
2522 }
2523
2524 Component {
2525 id: dragComponent
2526 SurfaceContainer {
2527 property Item appDelegate
2528
2529 surface: appDelegate ? appDelegate.surface : null
2530
2531 consumesInput: false
2532 interactive: false
2533 focus: false
2534 requestedWidth: appDelegate ? appDelegate.requestedWidth : 0
2535 requestedHeight: appDelegate ? appDelegate.requestedHeight : 0
2536
2537 width: units.gu(40)
2538 height: units.gu(40)
2539
2540 Drag.hotSpot.x: width/2
2541 Drag.hotSpot.y: height/2
2542 // only accept opposite stage.
2543 Drag.keys: {
2544 if (!surface) return "Disabled";
2545
2546 if (appDelegate.stage === ApplicationInfo.MainStage) {
2547 if (appDelegate.application.supportedOrientations
2548 & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) {
2549 return "MainStage";
2550 }
2551 return "Disabled";
2552 }
2553 return "SideStage";
2554 }
2555 }
2556 }
2557 }
2558}