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