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