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.4
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.forceEnableWorkspace) && settings.enableWorkspace
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.leftMargin }
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 onCloseSpread: priv.goneToSpread = false;
707 }
708
709 Spread {
710 id: spreadItem
711 objectName: "spreadItem"
712 anchors {
713 left: parent.left;
714 bottom: parent.bottom;
715 right: parent.right;
716 top: workspaceEnabled ? screensAndWorkspaces.bottom : parent.top;
717 }
718 leftMargin: root.availableDesktopArea.x
719 model: root.topLevelSurfaceList
720 spreadFlickable: floatingFlickable
721 z: 10
722
723 onLeaveSpread: {
724 priv.goneToSpread = false;
725 }
726
727 onCloseCurrentApp: {
728 appRepeater.itemAt(highlightedIndex).close();
729 }
730
731 FloatingFlickable {
732 id: floatingFlickable
733 objectName: "spreadFlickable"
734 anchors.fill: parent
735 enabled: false
736 contentWidth: spreadItem.spreadTotalWidth
737
738 function snap(toIndex) {
739 var delegate = appRepeater.itemAt(toIndex)
740 var targetContentX = floatingFlickable.contentWidth / spreadItem.totalItemCount * toIndex;
741 if (targetContentX - floatingFlickable.contentX > spreadItem.rightStackXPos - (spreadItem.spreadItemWidth / 2)) {
742 var offset = (spreadItem.rightStackXPos - (spreadItem.spreadItemWidth / 2)) - (targetContentX - floatingFlickable.contentX)
743 snapAnimation.to = floatingFlickable.contentX - offset;
744 snapAnimation.start();
745 } else if (targetContentX - floatingFlickable.contentX < spreadItem.leftStackXPos + units.gu(1)) {
746 var offset = (spreadItem.leftStackXPos + units.gu(1)) - (targetContentX - floatingFlickable.contentX);
747 snapAnimation.to = floatingFlickable.contentX - offset;
748 snapAnimation.start();
749 }
750 }
751 LomiriNumberAnimation {id: snapAnimation; target: floatingFlickable; property: "contentX"}
752 }
753
754 MouseArea {
755 id: hoverMouseArea
756 objectName: "hoverMouseArea"
757 anchors.fill: parent
758 propagateComposedEvents: true
759 hoverEnabled: true
760 enabled: false
761 visible: enabled
762 property bool wasTouchPress: false
763
764 property int scrollAreaWidth: width / 3
765 property bool progressiveScrollingEnabled: false
766
767 onMouseXChanged: {
768 mouse.accepted = false
769
770 if (hoverMouseArea.pressed || wasTouchPress) {
771 return;
772 }
773
774 // Find the hovered item and mark it active
775 for (var i = appRepeater.count - 1; i >= 0; i--) {
776 var appDelegate = appRepeater.itemAt(i);
777 var mapped = mapToItem(appDelegate, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
778 var itemUnder = appDelegate.childAt(mapped.x, mapped.y);
779 if (itemUnder && (itemUnder.objectName === "dragArea" || itemUnder.objectName === "windowInfoItem" || itemUnder.objectName == "closeMouseArea")) {
780 spreadItem.highlightedIndex = i;
781 break;
782 }
783 }
784
785 if (floatingFlickable.contentWidth > floatingFlickable.width) {
786 var margins = floatingFlickable.width * 0.05;
787
788 if (!progressiveScrollingEnabled && mouseX < floatingFlickable.width - scrollAreaWidth) {
789 progressiveScrollingEnabled = true
790 }
791
792 // do we need to scroll?
793 if (mouseX < scrollAreaWidth + margins) {
794 var progress = Math.min(1, (scrollAreaWidth + margins - mouseX) / (scrollAreaWidth - margins));
795 var contentX = (1 - progress) * (floatingFlickable.contentWidth - floatingFlickable.width)
796 floatingFlickable.contentX = Math.max(0, Math.min(floatingFlickable.contentX, contentX))
797 }
798 if (mouseX > floatingFlickable.width - scrollAreaWidth && progressiveScrollingEnabled) {
799 var progress = Math.min(1, (mouseX - (floatingFlickable.width - scrollAreaWidth)) / (scrollAreaWidth - margins))
800 var contentX = progress * (floatingFlickable.contentWidth - floatingFlickable.width)
801 floatingFlickable.contentX = Math.min(floatingFlickable.contentWidth - floatingFlickable.width, Math.max(floatingFlickable.contentX, contentX))
802 }
803 }
804 }
805
806 onPressed: {
807 mouse.accepted = false;
808 wasTouchPress = mouse.source === Qt.MouseEventSynthesizedByQt;
809 }
810
811 onExited: wasTouchPress = false;
812 }
813 }
814
815 Label {
816 id: noAppsRunningHint
817 visible: false
818 anchors.horizontalCenter: parent.horizontalCenter
819 anchors.verticalCenter: parent.verticalCenter
820 anchors.fill: parent
821 horizontalAlignment: Qt.AlignHCenter
822 verticalAlignment: Qt.AlignVCenter
823 anchors.leftMargin: root.launcherLeftMargin
824 wrapMode: Label.WordWrap
825 fontSize: "large"
826 text: i18n.tr("No running apps")
827 }
828
829 Connections {
830 target: root.topLevelSurfaceList
831 onListChanged: priv.updateMainAndSideStageIndexes()
832 }
833
834
835 DropArea {
836 objectName: "MainStageDropArea"
837 anchors {
838 left: parent.left
839 top: parent.top
840 bottom: parent.bottom
841 }
842 width: appContainer.width - sideStage.width
843 enabled: priv.sideStageEnabled
844
845 onDropped: {
846 drop.source.appDelegate.saveStage(ApplicationInfoInterface.MainStage);
847 drop.source.appDelegate.focus = true;
848 }
849 keys: "SideStage"
850 }
851
852 SideStage {
853 id: sideStage
854 objectName: "sideStage"
855 shown: false
856 height: appContainer.height
857 x: appContainer.width - width
858 visible: false
859 Behavior on opacity { LomiriNumberAnimation {} }
860 z: {
861 if (!priv.mainStageItemId) return 0;
862
863 if (priv.sideStageItemId && priv.nextInStack > 0) {
864
865 // Due the order in which bindings are evaluated, this might be triggered while shuffling
866 // the list and index doesn't yet match with itemIndex (even though itemIndex: index)
867 // Let's walk the list and compare itemIndex to make sure we have the correct one.
868 var nextDelegateInStack = -1;
869 for (var i = 0; i < appRepeater.count; i++) {
870 if (appRepeater.itemAt(i).itemIndex == priv.nextInStack) {
871 nextDelegateInStack = appRepeater.itemAt(i);
872 break;
873 }
874 }
875
876 if (nextDelegateInStack.stage === ApplicationInfoInterface.MainStage) {
877 // if the next app in stack is a main stage app, put the sidestage on top of it.
878 return 2;
879 }
880 return 1;
881 }
882
883 return 1;
884 }
885
886 onShownChanged: {
887 if (!shown && priv.mainStageDelegate && !root.spreadShown) {
888 priv.mainStageDelegate.activate();
889 }
890 }
891
892 DropArea {
893 id: sideStageDropArea
894 objectName: "SideStageDropArea"
895 anchors.fill: parent
896
897 property bool dropAllowed: true
898
899 onEntered: {
900 dropAllowed = drag.keys != "Disabled";
901 }
902 onExited: {
903 dropAllowed = true;
904 }
905 onDropped: {
906 if (drop.keys == "MainStage") {
907 drop.source.appDelegate.saveStage(ApplicationInfoInterface.SideStage);
908 drop.source.appDelegate.focus = true;
909 }
910 }
911 drag {
912 onSourceChanged: {
913 if (!sideStageDropArea.drag.source) {
914 dropAllowed = true;
915 }
916 }
917 }
918 }
919 }
920
921 MirSurfaceItem {
922 id: fakeDragItem
923 property real previewScale: .5
924 height: (screensAndWorkspaces.height - units.gu(8)) / 2
925 // w : h = iw : ih
926 width: implicitWidth * height / implicitHeight
927 surfaceWidth: -1
928 surfaceHeight: -1
929 opacity: surface != null ? 1 : 0
930 Behavior on opacity { LomiriNumberAnimation {} }
931 visible: opacity > 0
932 enabled: workspaceSwitcher
933
934 Drag.active: surface != null
935 Drag.keys: ["application"]
936
937 z: 1000
938 }
939
940 Repeater {
941 id: appRepeater
942 model: topLevelSurfaceList
943 objectName: "appRepeater"
944
945 function indexOf(delegateItem) {
946 for (var i = 0; i < count; i++) {
947 if (itemAt(i) === delegateItem) {
948 return i;
949 }
950 }
951 return -1;
952 }
953
954 delegate: FocusScope {
955 id: appDelegate
956 objectName: "appDelegate_" + model.window.id
957 property int itemIndex: index // We need this from outside the repeater
958 // z might be overriden in some cases by effects, but we need z ordering
959 // to calculate occlusion detection
960 property int normalZ: topLevelSurfaceList.count - index
961 onNormalZChanged: {
962 if (visuallyMaximized) {
963 priv.updateForegroundMaximizedApp();
964 }
965 }
966 z: normalZ
967
968 opacity: fakeDragItem.surface == model.window.surface && fakeDragItem.Drag.active ? 0 : 1
969 Behavior on opacity { LomiriNumberAnimation {} }
970
971 // Set these as propertyes as they wont update otherwise
972 property real screenOffsetX: Screen.virtualX
973 property real screenOffsetY: Screen.virtualY
974
975 // Normally we want x/y where the surface thinks it is. Width/height of our delegate will
976 // match what the actual surface size is.
977 // Don't write to those, they will be set by states
978 // --
979 // Here we will also need to remove the screen offset from miral's results
980 // as lomiri x,y will be relative to the current screen only
981 // FIXME: when proper multiscreen lands
982 x: model.window.position.x - clientAreaItem.x - screenOffsetX
983 y: model.window.position.y - clientAreaItem.y - screenOffsetY
984 width: decoratedWindow.implicitWidth
985 height: decoratedWindow.implicitHeight
986
987 // requestedX/Y/width/height is what we ask the actual surface to be.
988 // Do not write to those, they will be set by states
989 property real requestedX: windowedX
990 property real requestedY: windowedY
991 property real requestedWidth: windowedWidth
992 property real requestedHeight: windowedHeight
993
994 // For both windowed and staged need to tell miral what screen we are on,
995 // so we need to add the screen offset to the position we tell miral
996 // FIXME: when proper multiscreen lands
997 Binding {
998 target: model.window; property: "requestedPosition"
999 // miral doesn't know about our window decorations. So we have to deduct them
1000 value: Qt.point(appDelegate.requestedX + appDelegate.clientAreaItem.x + screenOffsetX,
1001 appDelegate.requestedY + appDelegate.clientAreaItem.y + screenOffsetY)
1002 when: root.mode == "windowed"
1003 }
1004 Binding {
1005 target: model.window; property: "requestedPosition"
1006 value: Qt.point(screenOffsetX, screenOffsetY)
1007 when: root.mode != "windowed"
1008 }
1009
1010 // In those are for windowed mode. Those values basically store the window's properties
1011 // when having a floating window. If you want to move/resize a window in normal mode, this is what you want to write to.
1012 property real windowedX
1013 property real windowedY
1014 property real windowedWidth
1015 property real windowedHeight
1016
1017 // unlike windowedX/Y, this is the last known grab position before being pushed against edges/corners
1018 // when restoring, the window should return to these, not to the place where it was dropped near the edge
1019 property real restoredX
1020 property real restoredY
1021
1022 // Keeps track of the window geometry while in normal or restored state
1023 // Useful when returning from some maxmized state or when saving the geometry while maximized
1024 // FIXME: find a better solution
1025 property real normalX: 0
1026 property real normalY: 0
1027 property real normalWidth: 0
1028 property real normalHeight: 0
1029 function updateNormalGeometry() {
1030 if (appDelegate.state == "normal" || appDelegate.state == "restored") {
1031 normalX = appDelegate.requestedX;
1032 normalY = appDelegate.requestedY;
1033 normalWidth = appDelegate.width;
1034 normalHeight = appDelegate.height;
1035 }
1036 }
1037 function updateRestoredGeometry() {
1038 if (appDelegate.state == "normal" || appDelegate.state == "restored") {
1039 // save the x/y to restore to
1040 restoredX = appDelegate.x;
1041 restoredY = appDelegate.y;
1042 }
1043 }
1044
1045 Connections {
1046 target: appDelegate
1047 onXChanged: appDelegate.updateNormalGeometry();
1048 onYChanged: appDelegate.updateNormalGeometry();
1049 onWidthChanged: appDelegate.updateNormalGeometry();
1050 onHeightChanged: appDelegate.updateNormalGeometry();
1051 }
1052
1053 // True when the Stage is focusing this app and playing its own animation.
1054 // Stays true until the app is unfocused.
1055 // If it is, we don't want to play the slide in/out transition from StageMaths.
1056 // Setting it imperatively is not great, but any declarative solution hits
1057 // race conditions, causing two animations to play for one focus event.
1058 property bool inhibitSlideAnimation: false
1059
1060 Binding {
1061 target: appDelegate
1062 property: "y"
1063 value: appDelegate.requestedY -
1064 Math.min(appDelegate.requestedY - root.availableDesktopArea.y,
1065 Math.max(0, priv.virtualKeyboardHeight - (appContainer.height - (appDelegate.requestedY + appDelegate.height))))
1066 when: root.oskEnabled && appDelegate.focus && (appDelegate.state == "normal" || appDelegate.state == "restored")
1067 && root.inputMethodRect.height > 0
1068 }
1069
1070 Behavior on x { id: xBehavior; enabled: priv.closingIndex >= 0; LomiriNumberAnimation { onRunningChanged: if (!running) priv.closingIndex = -1} }
1071
1072 Connections {
1073 target: root
1074 onShellOrientationAngleChanged: {
1075 // at this point decoratedWindow.surfaceOrientationAngle is the old shellOrientationAngle
1076 if (application && application.rotatesWindowContents) {
1077 if (root.state == "windowed") {
1078 var angleDiff = decoratedWindow.surfaceOrientationAngle - shellOrientationAngle;
1079 angleDiff = (360 + angleDiff) % 360;
1080 if (angleDiff === 90 || angleDiff === 270) {
1081 var aux = decoratedWindow.requestedHeight;
1082 decoratedWindow.requestedHeight = decoratedWindow.requestedWidth + decoratedWindow.actualDecorationHeight;
1083 decoratedWindow.requestedWidth = aux - decoratedWindow.actualDecorationHeight;
1084 }
1085 }
1086 decoratedWindow.surfaceOrientationAngle = shellOrientationAngle;
1087 } else {
1088 decoratedWindow.surfaceOrientationAngle = 0;
1089 }
1090 }
1091 }
1092
1093 readonly property alias application: decoratedWindow.application
1094 readonly property alias minimumWidth: decoratedWindow.minimumWidth
1095 readonly property alias minimumHeight: decoratedWindow.minimumHeight
1096 readonly property alias maximumWidth: decoratedWindow.maximumWidth
1097 readonly property alias maximumHeight: decoratedWindow.maximumHeight
1098 readonly property alias widthIncrement: decoratedWindow.widthIncrement
1099 readonly property alias heightIncrement: decoratedWindow.heightIncrement
1100
1101 readonly property bool maximized: windowState === WindowStateStorage.WindowStateMaximized
1102 readonly property bool maximizedLeft: windowState === WindowStateStorage.WindowStateMaximizedLeft
1103 readonly property bool maximizedRight: windowState === WindowStateStorage.WindowStateMaximizedRight
1104 readonly property bool maximizedHorizontally: windowState === WindowStateStorage.WindowStateMaximizedHorizontally
1105 readonly property bool maximizedVertically: windowState === WindowStateStorage.WindowStateMaximizedVertically
1106 readonly property bool maximizedTopLeft: windowState === WindowStateStorage.WindowStateMaximizedTopLeft
1107 readonly property bool maximizedTopRight: windowState === WindowStateStorage.WindowStateMaximizedTopRight
1108 readonly property bool maximizedBottomLeft: windowState === WindowStateStorage.WindowStateMaximizedBottomLeft
1109 readonly property bool maximizedBottomRight: windowState === WindowStateStorage.WindowStateMaximizedBottomRight
1110 readonly property bool anyMaximized: maximized || maximizedLeft || maximizedRight || maximizedHorizontally || maximizedVertically ||
1111 maximizedTopLeft || maximizedTopRight || maximizedBottomLeft || maximizedBottomRight
1112
1113 readonly property bool minimized: windowState & WindowStateStorage.WindowStateMinimized
1114 readonly property bool fullscreen: windowState === WindowStateStorage.WindowStateFullscreen
1115
1116 readonly property bool canBeMaximized: canBeMaximizedHorizontally && canBeMaximizedVertically
1117 readonly property bool canBeMaximizedLeftRight: (maximumWidth == 0 || maximumWidth >= appContainer.width/2) &&
1118 (maximumHeight == 0 || maximumHeight >= appContainer.height)
1119 readonly property bool canBeCornerMaximized: (maximumWidth == 0 || maximumWidth >= appContainer.width/2) &&
1120 (maximumHeight == 0 || maximumHeight >= appContainer.height/2)
1121 readonly property bool canBeMaximizedHorizontally: maximumWidth == 0 || maximumWidth >= appContainer.width
1122 readonly property bool canBeMaximizedVertically: maximumHeight == 0 || maximumHeight >= appContainer.height
1123 readonly property alias orientationChangesEnabled: decoratedWindow.orientationChangesEnabled
1124
1125 // TODO drop our own windowType once Mir/Miral/Qtmir gets in sync with ours
1126 property int windowState: WindowStateStorage.WindowStateNormal
1127 property int prevWindowState: WindowStateStorage.WindowStateRestored
1128
1129 property bool animationsEnabled: true
1130 property alias title: decoratedWindow.title
1131 readonly property string appName: model.application ? model.application.name : ""
1132 property bool visuallyMaximized: false
1133 property bool visuallyMinimized: false
1134 readonly property alias windowedTransitionRunning: windowedTransition.running
1135
1136 property int stage: ApplicationInfoInterface.MainStage
1137 function saveStage(newStage) {
1138 appDelegate.stage = newStage;
1139 WindowStateStorage.saveStage(appId, newStage);
1140 priv.updateMainAndSideStageIndexes()
1141 }
1142
1143 readonly property var surface: model.window.surface
1144 readonly property var window: model.window
1145
1146 readonly property alias focusedSurface: decoratedWindow.focusedSurface
1147 readonly property bool dragging: touchControls.overlayShown ? touchControls.dragging : decoratedWindow.dragging
1148
1149 readonly property string appId: model.application.appId
1150 readonly property alias clientAreaItem: decoratedWindow.clientAreaItem
1151
1152 function activate() {
1153 if (model.window.focused) {
1154 updateQmlFocusFromMirSurfaceFocus();
1155 } else {
1156 model.window.activate();
1157 }
1158 }
1159 function requestMaximize() { model.window.requestState(Mir.MaximizedState); }
1160 function requestMaximizeVertically() { model.window.requestState(Mir.VertMaximizedState); }
1161 function requestMaximizeHorizontally() { model.window.requestState(Mir.HorizMaximizedState); }
1162 function requestMaximizeLeft() { model.window.requestState(Mir.MaximizedLeftState); }
1163 function requestMaximizeRight() { model.window.requestState(Mir.MaximizedRightState); }
1164 function requestMaximizeTopLeft() { model.window.requestState(Mir.MaximizedTopLeftState); }
1165 function requestMaximizeTopRight() { model.window.requestState(Mir.MaximizedTopRightState); }
1166 function requestMaximizeBottomLeft() { model.window.requestState(Mir.MaximizedBottomLeftState); }
1167 function requestMaximizeBottomRight() { model.window.requestState(Mir.MaximizedBottomRightState); }
1168 function requestMinimize() { model.window.requestState(Mir.MinimizedState); }
1169 function requestRestore() { model.window.requestState(Mir.RestoredState); }
1170
1171 function claimFocus() {
1172 if (root.state == "spread") {
1173 spreadItem.highlightedIndex = index
1174 }
1175 if (root.mode == "stagedWithSideStage") {
1176 if (appDelegate.stage == ApplicationInfoInterface.SideStage && !sideStage.shown) {
1177 sideStage.show();
1178 }
1179 priv.updateMainAndSideStageIndexes();
1180 }
1181 appDelegate.focus = true;
1182
1183 // Don't set focusedAppDelegate (and signal mainAppChanged) unnecessarily
1184 // which can happen after getting interactive again.
1185 if (priv.focusedAppDelegate !== appDelegate)
1186 priv.focusedAppDelegate = appDelegate;
1187 }
1188
1189 function updateQmlFocusFromMirSurfaceFocus() {
1190 if (model.window.focused) {
1191 claimFocus();
1192 decoratedWindow.focus = true;
1193 }
1194 }
1195
1196 WindowStateSaver {
1197 id: windowStateSaver
1198 target: appDelegate
1199 screenWidth: appContainer.width
1200 screenHeight: appContainer.height
1201 leftMargin: root.availableDesktopArea.x
1202 minimumY: root.availableDesktopArea.y
1203 }
1204
1205 Connections {
1206 target: model.window
1207 onFocusedChanged: {
1208 updateQmlFocusFromMirSurfaceFocus();
1209 if (!model.window.focused) {
1210 inhibitSlideAnimation = false;
1211 }
1212 }
1213 onFocusRequested: {
1214 appDelegate.activate();
1215 }
1216 onStateChanged: {
1217 if (value == Mir.MinimizedState) {
1218 appDelegate.minimize();
1219 } else if (value == Mir.MaximizedState) {
1220 appDelegate.maximize();
1221 } else if (value == Mir.VertMaximizedState) {
1222 appDelegate.maximizeVertically();
1223 } else if (value == Mir.HorizMaximizedState) {
1224 appDelegate.maximizeHorizontally();
1225 } else if (value == Mir.MaximizedLeftState) {
1226 appDelegate.maximizeLeft();
1227 } else if (value == Mir.MaximizedRightState) {
1228 appDelegate.maximizeRight();
1229 } else if (value == Mir.MaximizedTopLeftState) {
1230 appDelegate.maximizeTopLeft();
1231 } else if (value == Mir.MaximizedTopRightState) {
1232 appDelegate.maximizeTopRight();
1233 } else if (value == Mir.MaximizedBottomLeftState) {
1234 appDelegate.maximizeBottomLeft();
1235 } else if (value == Mir.MaximizedBottomRightState) {
1236 appDelegate.maximizeBottomRight();
1237 } else if (value == Mir.RestoredState) {
1238 if (appDelegate.fullscreen && appDelegate.prevWindowState != WindowStateStorage.WindowStateRestored
1239 && appDelegate.prevWindowState != WindowStateStorage.WindowStateNormal) {
1240 model.window.requestState(WindowStateStorage.toMirState(appDelegate.prevWindowState));
1241 } else {
1242 appDelegate.restore();
1243 }
1244 } else if (value == Mir.FullscreenState) {
1245 appDelegate.prevWindowState = appDelegate.windowState;
1246 appDelegate.windowState = WindowStateStorage.WindowStateFullscreen;
1247 }
1248 }
1249 }
1250
1251 readonly property bool windowReady: clientAreaItem.surfaceInitialized
1252 onWindowReadyChanged: {
1253 if (windowReady) {
1254 var loadedMirState = WindowStateStorage.toMirState(windowStateSaver.loadedState);
1255 var state = loadedMirState;
1256
1257 if (window.state == Mir.FullscreenState) {
1258 // If the app is fullscreen at startup, we should not use saved state
1259 // Example of why: if you open game that only requests fullscreen at
1260 // Statup, this will automaticly be set to "restored state" since
1261 // thats the default value of stateStorage, this will result in the app
1262 // having the "restored state" as it will not make a fullscreen
1263 // call after the app has started.
1264 console.log("Inital window state is fullscreen, not using saved state.");
1265 state = window.state;
1266 } else if (loadedMirState == Mir.FullscreenState) {
1267 // If saved state is fullscreen, we should use app inital state
1268 // Example of why: if you open browser with youtube video at fullscreen
1269 // and close this app, it will be fullscreen next time you open the app.
1270 console.log("Saved window state is fullscreen, using inital window state");
1271 state = window.state;
1272 }
1273
1274 // need to apply the shell chrome policy on top the saved window state
1275 var policy;
1276 if (root.mode == "windowed") {
1277 policy = windowedFullscreenPolicy;
1278 } else {
1279 policy = stagedFullscreenPolicy
1280 }
1281 window.requestState(policy.applyPolicy(state, surface.shellChrome));
1282 }
1283 }
1284
1285 Component.onCompleted: {
1286 if (application && application.rotatesWindowContents) {
1287 decoratedWindow.surfaceOrientationAngle = shellOrientationAngle;
1288 } else {
1289 decoratedWindow.surfaceOrientationAngle = 0;
1290 }
1291
1292 // First, cascade the newly created window, relative to the currently/old focused window.
1293 windowedX = priv.focusedAppDelegate ? priv.focusedAppDelegate.windowedX + units.gu(3) : (normalZ - 1) * units.gu(3)
1294 windowedY = priv.focusedAppDelegate ? priv.focusedAppDelegate.windowedY + units.gu(3) : normalZ * units.gu(3)
1295 // Now load any saved state. This needs to happen *after* the cascading!
1296 windowStateSaver.load();
1297
1298 updateQmlFocusFromMirSurfaceFocus();
1299
1300 refreshStage();
1301 _constructing = false;
1302 }
1303 Component.onDestruction: {
1304 windowStateSaver.save();
1305
1306 if (!root.parent) {
1307 // This stage is about to be destroyed. Don't mess up with the model at this point
1308 return;
1309 }
1310
1311 if (visuallyMaximized) {
1312 priv.updateForegroundMaximizedApp();
1313 }
1314 }
1315
1316 onVisuallyMaximizedChanged: priv.updateForegroundMaximizedApp()
1317
1318 property bool _constructing: true;
1319 onStageChanged: {
1320 if (!_constructing) {
1321 priv.updateMainAndSideStageIndexes();
1322 }
1323 }
1324
1325 visible: (
1326 !visuallyMinimized
1327 && !greeter.fullyShown
1328 && (priv.foregroundMaximizedAppDelegate === null || priv.foregroundMaximizedAppDelegate.normalZ <= z)
1329 )
1330 || appDelegate.fullscreen
1331 || focusAnimation.running || rightEdgeFocusAnimation.running || hidingAnimation.running
1332
1333 function close() {
1334 model.window.close();
1335 }
1336
1337 function maximize(animated) {
1338 animationsEnabled = (animated === undefined) || animated;
1339 windowState = WindowStateStorage.WindowStateMaximized;
1340 }
1341 function maximizeLeft(animated) {
1342 animationsEnabled = (animated === undefined) || animated;
1343 windowState = WindowStateStorage.WindowStateMaximizedLeft;
1344 }
1345 function maximizeRight(animated) {
1346 animationsEnabled = (animated === undefined) || animated;
1347 windowState = WindowStateStorage.WindowStateMaximizedRight;
1348 }
1349 function maximizeHorizontally(animated) {
1350 animationsEnabled = (animated === undefined) || animated;
1351 windowState = WindowStateStorage.WindowStateMaximizedHorizontally;
1352 }
1353 function maximizeVertically(animated) {
1354 animationsEnabled = (animated === undefined) || animated;
1355 windowState = WindowStateStorage.WindowStateMaximizedVertically;
1356 }
1357 function maximizeTopLeft(animated) {
1358 animationsEnabled = (animated === undefined) || animated;
1359 windowState = WindowStateStorage.WindowStateMaximizedTopLeft;
1360 }
1361 function maximizeTopRight(animated) {
1362 animationsEnabled = (animated === undefined) || animated;
1363 windowState = WindowStateStorage.WindowStateMaximizedTopRight;
1364 }
1365 function maximizeBottomLeft(animated) {
1366 animationsEnabled = (animated === undefined) || animated;
1367 windowState = WindowStateStorage.WindowStateMaximizedBottomLeft;
1368 }
1369 function maximizeBottomRight(animated) {
1370 animationsEnabled = (animated === undefined) || animated;
1371 windowState = WindowStateStorage.WindowStateMaximizedBottomRight;
1372 }
1373 function minimize(animated) {
1374 animationsEnabled = (animated === undefined) || animated;
1375 windowState |= WindowStateStorage.WindowStateMinimized; // add the minimized bit
1376 }
1377 function restore(animated,state) {
1378 animationsEnabled = (animated === undefined) || animated;
1379 windowState = state || WindowStateStorage.WindowStateRestored;
1380 windowState &= ~WindowStateStorage.WindowStateMinimized; // clear the minimized bit
1381 prevWindowState = windowState;
1382 }
1383
1384 function playFocusAnimation() {
1385 if (state == "stagedRightEdge") {
1386 // TODO: Can we drop this if and find something that always works?
1387 if (root.mode == "staged") {
1388 rightEdgeFocusAnimation.targetX = 0
1389 rightEdgeFocusAnimation.start()
1390 } else if (root.mode == "stagedWithSideStage") {
1391 rightEdgeFocusAnimation.targetX = appDelegate.stage == ApplicationInfoInterface.SideStage ? sideStage.x : 0
1392 rightEdgeFocusAnimation.start()
1393 }
1394 } else if (state == "windowedRightEdge" || state == "windowed") {
1395 activate();
1396 } else {
1397 focusAnimation.start()
1398 }
1399 }
1400 function playHidingAnimation() {
1401 if (state != "windowedRightEdge") {
1402 hidingAnimation.start()
1403 }
1404 }
1405
1406 function refreshStage() {
1407 var newStage = ApplicationInfoInterface.MainStage;
1408 if (priv.sideStageEnabled) { // we're in lanscape rotation.
1409 if (application && application.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) {
1410 var defaultStage = ApplicationInfoInterface.SideStage; // if application supports portrait, it defaults to sidestage.
1411 if (application.supportedOrientations & (Qt.LandscapeOrientation|Qt.InvertedLandscapeOrientation)) {
1412 // if it supports lanscape, it defaults to mainstage.
1413 defaultStage = ApplicationInfoInterface.MainStage;
1414 }
1415 newStage = WindowStateStorage.getStage(application.appId, defaultStage);
1416 }
1417 }
1418
1419 stage = newStage;
1420 if (focus && stage == ApplicationInfoInterface.SideStage && !sideStage.shown) {
1421 sideStage.show();
1422 }
1423 }
1424
1425 LomiriNumberAnimation {
1426 id: focusAnimation
1427 target: appDelegate
1428 property: "scale"
1429 from: 0.98
1430 to: 1
1431 duration: LomiriAnimation.SnapDuration
1432 onStarted: {
1433 topLevelSurfaceList.pendingActivation();
1434 topLevelSurfaceList.raiseId(model.window.id);
1435 }
1436 onStopped: {
1437 appDelegate.activate();
1438 }
1439 }
1440 ParallelAnimation {
1441 id: rightEdgeFocusAnimation
1442 property int targetX: 0
1443 LomiriNumberAnimation { target: appDelegate; properties: "x"; to: rightEdgeFocusAnimation.targetX; duration: priv.animationDuration }
1444 LomiriNumberAnimation { target: decoratedWindow; properties: "angle"; to: 0; duration: priv.animationDuration }
1445 LomiriNumberAnimation { target: decoratedWindow; properties: "itemScale"; to: 1; duration: priv.animationDuration }
1446 onStarted: {
1447 topLevelSurfaceList.pendingActivation();
1448 inhibitSlideAnimation = true;
1449 }
1450 onStopped: {
1451 appDelegate.activate();
1452 }
1453 }
1454 ParallelAnimation {
1455 id: hidingAnimation
1456 LomiriNumberAnimation { target: appDelegate; property: "opacity"; to: 0; duration: priv.animationDuration }
1457 onStopped: appDelegate.opacity = 1
1458 }
1459
1460 SpreadMaths {
1461 id: spreadMaths
1462 spread: spreadItem
1463 itemIndex: index
1464 flickable: floatingFlickable
1465 }
1466 StageMaths {
1467 id: stageMaths
1468 sceneWidth: root.width
1469 stage: appDelegate.stage
1470 thisDelegate: appDelegate
1471 mainStageDelegate: priv.mainStageDelegate
1472 sideStageDelegate: priv.sideStageDelegate
1473 sideStageWidth: sideStage.panelWidth
1474 sideStageHandleWidth: sideStage.handleWidth
1475 sideStageX: sideStage.x
1476 itemIndex: appDelegate.itemIndex
1477 nextInStack: priv.nextInStack
1478 animationDuration: priv.animationDuration
1479 }
1480
1481 StagedRightEdgeMaths {
1482 id: stagedRightEdgeMaths
1483 sceneWidth: root.availableDesktopArea.width
1484 sceneHeight: appContainer.height
1485 isMainStageApp: priv.mainStageDelegate == appDelegate
1486 isSideStageApp: priv.sideStageDelegate == appDelegate
1487 sideStageWidth: sideStage.width
1488 sideStageOpen: sideStage.shown
1489 itemIndex: index
1490 nextInStack: priv.nextInStack
1491 progress: 0
1492 targetHeight: spreadItem.stackHeight
1493 targetX: spreadMaths.targetX
1494 startY: appDelegate.fullscreen ? 0 : root.availableDesktopArea.y
1495 targetY: spreadMaths.targetY
1496 targetAngle: spreadMaths.targetAngle
1497 targetScale: spreadMaths.targetScale
1498 shuffledZ: stageMaths.itemZ
1499 breakPoint: spreadItem.rightEdgeBreakPoint
1500 }
1501
1502 WindowedRightEdgeMaths {
1503 id: windowedRightEdgeMaths
1504 itemIndex: index
1505 startWidth: appDelegate.requestedWidth
1506 startHeight: appDelegate.requestedHeight
1507 targetHeight: spreadItem.stackHeight
1508 targetX: spreadMaths.targetX
1509 targetY: spreadMaths.targetY
1510 normalZ: appDelegate.normalZ
1511 targetAngle: spreadMaths.targetAngle
1512 targetScale: spreadMaths.targetScale
1513 breakPoint: spreadItem.rightEdgeBreakPoint
1514 }
1515
1516 states: [
1517 State {
1518 name: "spread"; when: root.state == "spread"
1519 StateChangeScript { script: { decoratedWindow.cancelDrag(); } }
1520 PropertyChanges {
1521 target: decoratedWindow;
1522 showDecoration: false;
1523 angle: spreadMaths.targetAngle
1524 itemScale: spreadMaths.targetScale
1525 scaleToPreviewSize: spreadItem.stackHeight
1526 scaleToPreviewProgress: 1
1527 hasDecoration: root.mode === "windowed"
1528 shadowOpacity: spreadMaths.shadowOpacity
1529 showHighlight: spreadItem.highlightedIndex === index
1530 darkening: spreadItem.highlightedIndex >= 0
1531 anchors.topMargin: dragArea.distance
1532 }
1533 PropertyChanges {
1534 target: appDelegate
1535 x: spreadMaths.targetX
1536 y: spreadMaths.targetY
1537 z: index
1538 height: spreadItem.spreadItemHeight
1539 visible: spreadMaths.itemVisible
1540 }
1541 PropertyChanges { target: dragArea; enabled: true }
1542 PropertyChanges { target: windowInfoItem; opacity: spreadMaths.tileInfoOpacity; visible: spreadMaths.itemVisible }
1543 PropertyChanges { target: touchControls; enabled: false }
1544 },
1545 State {
1546 name: "stagedRightEdge"
1547 when: (root.mode == "staged" || root.mode == "stagedWithSideStage") && (root.state == "sideStagedRightEdge" || root.state == "stagedRightEdge" || rightEdgeFocusAnimation.running || hidingAnimation.running)
1548 PropertyChanges {
1549 target: stagedRightEdgeMaths
1550 progress: Math.max(rightEdgePushProgress, rightEdgeDragArea.draggedProgress)
1551 }
1552 PropertyChanges {
1553 target: appDelegate
1554 x: stagedRightEdgeMaths.animatedX
1555 y: stagedRightEdgeMaths.animatedY
1556 z: stagedRightEdgeMaths.animatedZ
1557 height: stagedRightEdgeMaths.animatedHeight
1558 visible: appDelegate.x < root.width
1559 }
1560 PropertyChanges {
1561 target: decoratedWindow
1562 hasDecoration: false
1563 angle: stagedRightEdgeMaths.animatedAngle
1564 itemScale: stagedRightEdgeMaths.animatedScale
1565 scaleToPreviewSize: spreadItem.stackHeight
1566 scaleToPreviewProgress: stagedRightEdgeMaths.scaleToPreviewProgress
1567 shadowOpacity: .3
1568 }
1569 // make sure it's visible but transparent so it fades in when we transition to spread
1570 PropertyChanges { target: windowInfoItem; opacity: 0; visible: true }
1571 },
1572 State {
1573 name: "windowedRightEdge"
1574 when: root.mode == "windowed" && (root.state == "windowedRightEdge" || rightEdgeFocusAnimation.running || hidingAnimation.running || rightEdgePushProgress > 0)
1575 PropertyChanges {
1576 target: windowedRightEdgeMaths
1577 swipeProgress: rightEdgeDragArea.dragging ? rightEdgeDragArea.progress : 0
1578 pushProgress: rightEdgePushProgress
1579 }
1580 PropertyChanges {
1581 target: appDelegate
1582 x: windowedRightEdgeMaths.animatedX
1583 y: windowedRightEdgeMaths.animatedY
1584 z: windowedRightEdgeMaths.animatedZ
1585 height: stagedRightEdgeMaths.animatedHeight
1586 }
1587 PropertyChanges {
1588 target: decoratedWindow
1589 showDecoration: windowedRightEdgeMaths.decorationHeight
1590 angle: windowedRightEdgeMaths.animatedAngle
1591 itemScale: windowedRightEdgeMaths.animatedScale
1592 scaleToPreviewSize: spreadItem.stackHeight
1593 scaleToPreviewProgress: windowedRightEdgeMaths.scaleToPreviewProgress
1594 shadowOpacity: .3
1595 }
1596 PropertyChanges {
1597 target: opacityEffect;
1598 opacityValue: windowedRightEdgeMaths.opacityMask
1599 sourceItem: windowedRightEdgeMaths.opacityMask < 1 ? decoratedWindow : null
1600 }
1601 },
1602 State {
1603 name: "staged"; when: root.state == "staged"
1604 PropertyChanges {
1605 target: appDelegate
1606 x: stageMaths.itemX
1607 y: root.availableDesktopArea.y
1608 visuallyMaximized: true
1609 visible: appDelegate.x < root.width
1610 }
1611 PropertyChanges {
1612 target: appDelegate
1613 requestedWidth: appContainer.width
1614 requestedHeight: root.availableDesktopArea.height
1615 restoreEntryValues: false
1616 }
1617 PropertyChanges {
1618 target: decoratedWindow
1619 hasDecoration: false
1620 }
1621 PropertyChanges {
1622 target: resizeArea
1623 enabled: false
1624 }
1625 PropertyChanges {
1626 target: stageMaths
1627 animateX: !focusAnimation.running && !rightEdgeFocusAnimation.running && itemIndex !== spreadItem.highlightedIndex && !inhibitSlideAnimation
1628 }
1629 PropertyChanges {
1630 target: appDelegate.window
1631 allowClientResize: false
1632 }
1633 },
1634 State {
1635 name: "stagedWithSideStage"; when: root.state == "stagedWithSideStage"
1636 PropertyChanges {
1637 target: stageMaths
1638 itemIndex: index
1639 }
1640 PropertyChanges {
1641 target: appDelegate
1642 x: stageMaths.itemX
1643 y: root.availableDesktopArea.y
1644 z: stageMaths.itemZ
1645 visuallyMaximized: true
1646 visible: appDelegate.x < root.width
1647 }
1648 PropertyChanges {
1649 target: appDelegate
1650 requestedWidth: stageMaths.itemWidth
1651 requestedHeight: root.availableDesktopArea.height
1652 restoreEntryValues: false
1653 }
1654 PropertyChanges {
1655 target: decoratedWindow
1656 hasDecoration: false
1657 }
1658 PropertyChanges {
1659 target: resizeArea
1660 enabled: false
1661 }
1662 PropertyChanges {
1663 target: appDelegate.window
1664 allowClientResize: false
1665 }
1666 },
1667 State {
1668 name: "maximized"; when: appDelegate.maximized && !appDelegate.minimized
1669 PropertyChanges {
1670 target: appDelegate;
1671 requestedX: root.availableDesktopArea.x;
1672 requestedY: 0;
1673 visuallyMinimized: false;
1674 visuallyMaximized: true
1675 }
1676 PropertyChanges {
1677 target: appDelegate
1678 requestedWidth: root.availableDesktopArea.width;
1679 requestedHeight: appContainer.height;
1680 restoreEntryValues: false
1681 }
1682 PropertyChanges { target: touchControls; enabled: true }
1683 PropertyChanges { target: decoratedWindow; windowControlButtonsVisible: false }
1684 },
1685 State {
1686 name: "fullscreen"; when: appDelegate.fullscreen && !appDelegate.minimized
1687 PropertyChanges {
1688 target: appDelegate;
1689 requestedX: 0
1690 requestedY: 0
1691 }
1692 PropertyChanges {
1693 target: appDelegate
1694 requestedWidth: appContainer.width
1695 requestedHeight: appContainer.height
1696 restoreEntryValues: false
1697 }
1698 PropertyChanges { target: decoratedWindow; hasDecoration: false }
1699 },
1700 State {
1701 name: "normal";
1702 when: appDelegate.windowState == WindowStateStorage.WindowStateNormal
1703 PropertyChanges {
1704 target: appDelegate
1705 visuallyMinimized: false
1706 }
1707 PropertyChanges { target: touchControls; enabled: true }
1708 PropertyChanges { target: resizeArea; enabled: true }
1709 PropertyChanges { target: decoratedWindow; shadowOpacity: .3; windowControlButtonsVisible: true}
1710 PropertyChanges {
1711 target: appDelegate
1712 requestedWidth: windowedWidth
1713 requestedHeight: windowedHeight
1714 restoreEntryValues: false
1715 }
1716 },
1717 State {
1718 name: "restored";
1719 when: appDelegate.windowState == WindowStateStorage.WindowStateRestored
1720 extend: "normal"
1721 PropertyChanges {
1722 restoreEntryValues: false
1723 target: appDelegate;
1724 windowedX: restoredX;
1725 windowedY: restoredY;
1726 }
1727 },
1728 State {
1729 name: "maximizedLeft"; when: appDelegate.maximizedLeft && !appDelegate.minimized
1730 extend: "normal"
1731 PropertyChanges {
1732 target: appDelegate
1733 windowedX: root.availableDesktopArea.x
1734 windowedY: root.availableDesktopArea.y
1735 windowedWidth: root.availableDesktopArea.width / 2
1736 windowedHeight: root.availableDesktopArea.height
1737 }
1738 },
1739 State {
1740 name: "maximizedRight"; when: appDelegate.maximizedRight && !appDelegate.minimized
1741 extend: "maximizedLeft"
1742 PropertyChanges {
1743 target: appDelegate;
1744 windowedX: root.availableDesktopArea.x + (root.availableDesktopArea.width / 2)
1745 }
1746 },
1747 State {
1748 name: "maximizedTopLeft"; when: appDelegate.maximizedTopLeft && !appDelegate.minimized
1749 extend: "normal"
1750 PropertyChanges {
1751 target: appDelegate
1752 windowedX: root.availableDesktopArea.x
1753 windowedY: root.availableDesktopArea.y
1754 windowedWidth: root.availableDesktopArea.width / 2
1755 windowedHeight: root.availableDesktopArea.height / 2
1756 }
1757 },
1758 State {
1759 name: "maximizedTopRight"; when: appDelegate.maximizedTopRight && !appDelegate.minimized
1760 extend: "maximizedTopLeft"
1761 PropertyChanges {
1762 target: appDelegate
1763 windowedX: root.availableDesktopArea.x + (root.availableDesktopArea.width / 2)
1764 }
1765 },
1766 State {
1767 name: "maximizedBottomLeft"; when: appDelegate.maximizedBottomLeft && !appDelegate.minimized
1768 extend: "normal"
1769 PropertyChanges {
1770 target: appDelegate
1771 windowedX: root.availableDesktopArea.x
1772 windowedY: root.availableDesktopArea.y + (root.availableDesktopArea.height / 2)
1773 windowedWidth: root.availableDesktopArea.width / 2
1774 windowedHeight: root.availableDesktopArea.height / 2
1775 }
1776 },
1777 State {
1778 name: "maximizedBottomRight"; when: appDelegate.maximizedBottomRight && !appDelegate.minimized
1779 extend: "maximizedBottomLeft"
1780 PropertyChanges {
1781 target: appDelegate
1782 windowedX: root.availableDesktopArea.x + (root.availableDesktopArea.width / 2)
1783 }
1784 },
1785 State {
1786 name: "maximizedHorizontally"; when: appDelegate.maximizedHorizontally && !appDelegate.minimized
1787 extend: "normal"
1788 PropertyChanges {
1789 target: appDelegate
1790 windowedX: root.availableDesktopArea.x; windowedY: windowedY
1791 windowedWidth: root.availableDesktopArea.width; windowedHeight: windowedHeight
1792 }
1793 },
1794 State {
1795 name: "maximizedVertically"; when: appDelegate.maximizedVertically && !appDelegate.minimized
1796 extend: "normal"
1797 PropertyChanges {
1798 target: appDelegate
1799 windowedX: windowedX; windowedY: root.availableDesktopArea.y
1800 windowedWidth: windowedWidth; windowedHeight: root.availableDesktopArea.height
1801 }
1802 },
1803 State {
1804 name: "minimized"; when: appDelegate.minimized
1805 PropertyChanges {
1806 target: appDelegate
1807 scale: units.gu(5) / appDelegate.width
1808 opacity: 0;
1809 visuallyMinimized: true
1810 visuallyMaximized: false
1811 x: -appDelegate.width / 2
1812 y: root.height / 2
1813 }
1814 }
1815 ]
1816
1817 transitions: [
1818
1819 // These two animate applications into position from Staged to Desktop and back
1820 Transition {
1821 from: "staged,stagedWithSideStage"
1822 to: "normal,restored,maximized,maximizedHorizontally,maximizedVertically,maximizedLeft,maximizedRight,maximizedTopLeft,maximizedBottomLeft,maximizedTopRight,maximizedBottomRight"
1823 enabled: appDelegate.animationsEnabled
1824 PropertyAction { target: appDelegate; properties: "visuallyMinimized,visuallyMaximized" }
1825 LomiriNumberAnimation { target: appDelegate; properties: "x,y,requestedX,requestedY,opacity,requestedWidth,requestedHeight,scale"; duration: priv.animationDuration }
1826 },
1827 Transition {
1828 from: "normal,restored,maximized,maximizedHorizontally,maximizedVertically,maximizedLeft,maximizedRight,maximizedTopLeft,maximizedBottomLeft,maximizedTopRight,maximizedBottomRight"
1829 to: "staged,stagedWithSideStage"
1830 LomiriNumberAnimation { target: appDelegate; properties: "x,y,requestedX,requestedY,requestedWidth,requestedHeight"; duration: priv.animationDuration}
1831 },
1832
1833 Transition {
1834 from: "normal,restored,maximized,maximizedHorizontally,maximizedVertically,maximizedLeft,maximizedRight,maximizedTopLeft,maximizedBottomLeft,maximizedTopRight,maximizedBottomRight,staged,stagedWithSideStage,windowedRightEdge,stagedRightEdge";
1835 to: "spread"
1836 // DecoratedWindow wants the scaleToPreviewSize set before enabling scaleToPreview
1837 PropertyAction { target: appDelegate; properties: "z,visible" }
1838 PropertyAction { target: decoratedWindow; property: "scaleToPreviewSize" }
1839 LomiriNumberAnimation { target: appDelegate; properties: "x,y,height"; duration: priv.animationDuration }
1840 LomiriNumberAnimation { target: decoratedWindow; properties: "width,height,itemScale,angle,scaleToPreviewProgress"; duration: priv.animationDuration }
1841 LomiriNumberAnimation { target: windowInfoItem; properties: "opacity"; duration: priv.animationDuration }
1842 },
1843 Transition {
1844 from: "normal,staged"; to: "stagedWithSideStage"
1845 LomiriNumberAnimation { target: appDelegate; properties: "x,y,requestedWidth,requestedHeight"; duration: priv.animationDuration }
1846 },
1847 Transition {
1848 to: "windowedRightEdge"
1849 ScriptAction {
1850 script: {
1851 windowedRightEdgeMaths.startX = appDelegate.requestedX
1852 windowedRightEdgeMaths.startY = appDelegate.requestedY
1853
1854 if (index == 1) {
1855 var thisRect = { x: appDelegate.windowedX, y: appDelegate.windowedY, width: appDelegate.requestedWidth, height: appDelegate.requestedHeight }
1856 var otherDelegate = appRepeater.itemAt(0);
1857 var otherRect = { x: otherDelegate.windowedX, y: otherDelegate.windowedY, width: otherDelegate.requestedWidth, height: otherDelegate.requestedHeight }
1858 var intersectionRect = MathUtils.intersectionRect(thisRect, otherRect)
1859 var mappedInterSectionRect = appDelegate.mapFromItem(root, intersectionRect.x, intersectionRect.y)
1860 opacityEffect.maskX = mappedInterSectionRect.x
1861 opacityEffect.maskY = mappedInterSectionRect.y
1862 opacityEffect.maskWidth = intersectionRect.width
1863 opacityEffect.maskHeight = intersectionRect.height
1864 }
1865 }
1866 }
1867 },
1868 Transition {
1869 from: "stagedRightEdge"; to: "staged"
1870 enabled: rightEdgeDragArea.cancelled // only transition back to state if the gesture was cancelled, in the other cases we play the focusAnimations.
1871 SequentialAnimation {
1872 ParallelAnimation {
1873 LomiriNumberAnimation { target: appDelegate; properties: "x,y,height,width,scale"; duration: priv.animationDuration }
1874 LomiriNumberAnimation { target: decoratedWindow; properties: "width,height,itemScale,angle,scaleToPreviewProgress"; duration: priv.animationDuration }
1875 }
1876 // We need to release scaleToPreviewSize at last
1877 PropertyAction { target: decoratedWindow; property: "scaleToPreviewSize" }
1878 PropertyAction { target: appDelegate; property: "visible" }
1879 }
1880 },
1881 Transition {
1882 from: ",normal,restored,maximized,maximizedLeft,maximizedRight,maximizedTopLeft,maximizedTopRight,maximizedBottomLeft,maximizedBottomRight,maximizedHorizontally,maximizedVertically,fullscreen"
1883 to: "minimized"
1884 SequentialAnimation {
1885 ScriptAction { script: { fakeRectangle.stop(); } }
1886 PropertyAction { target: appDelegate; property: "visuallyMaximized" }
1887 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
1888 LomiriNumberAnimation { target: appDelegate; properties: "x,y,scale,opacity"; duration: priv.animationDuration }
1889 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
1890 }
1891 },
1892 Transition {
1893 from: "minimized"
1894 to: ",normal,restored,maximized,maximizedLeft,maximizedRight,maximizedTopLeft,maximizedTopRight,maximizedBottomLeft,maximizedBottomRight,maximizedHorizontally,maximizedVertically,fullscreen"
1895 SequentialAnimation {
1896 PropertyAction { target: appDelegate; property: "visuallyMinimized,z" }
1897 ParallelAnimation {
1898 LomiriNumberAnimation { target: appDelegate; properties: "x"; from: -appDelegate.width / 2; duration: priv.animationDuration }
1899 LomiriNumberAnimation { target: appDelegate; properties: "y,opacity"; duration: priv.animationDuration }
1900 LomiriNumberAnimation { target: appDelegate; properties: "scale"; from: 0; duration: priv.animationDuration }
1901 }
1902 PropertyAction { target: appDelegate; property: "visuallyMaximized" }
1903 }
1904 },
1905 Transition {
1906 id: windowedTransition
1907 from: ",normal,restored,maximized,maximizedLeft,maximizedRight,maximizedTopLeft,maximizedTopRight,maximizedBottomLeft,maximizedBottomRight,maximizedHorizontally,maximizedVertically,fullscreen,minimized"
1908 to: ",normal,restored,maximized,maximizedLeft,maximizedRight,maximizedTopLeft,maximizedTopRight,maximizedBottomLeft,maximizedBottomRight,maximizedHorizontally,maximizedVertically,fullscreen"
1909 enabled: appDelegate.animationsEnabled
1910 SequentialAnimation {
1911 ScriptAction { script: {
1912 if (appDelegate.visuallyMaximized) visuallyMaximized = false; // maximized before -> going to restored
1913 }
1914 }
1915 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
1916 LomiriNumberAnimation { target: appDelegate; properties: "requestedX,requestedY,windowedX,windowedY,opacity,scale,requestedWidth,requestedHeight,windowedWidth,windowedHeight";
1917 duration: priv.animationDuration }
1918 ScriptAction { script: {
1919 fakeRectangle.stop();
1920 appDelegate.visuallyMaximized = appDelegate.maximized; // reflect the target state
1921 }
1922 }
1923 }
1924 }
1925 ]
1926
1927 Binding {
1928 target: panelState
1929 property: "decorationsAlwaysVisible"
1930 value: appDelegate && appDelegate.maximized && touchControls.overlayShown
1931 }
1932
1933 WindowResizeArea {
1934 id: resizeArea
1935 objectName: "windowResizeArea"
1936
1937 anchors.fill: appDelegate
1938
1939 // workaround so that it chooses the correct resize borders when you drag from a corner ResizeGrip
1940 anchors.margins: touchControls.overlayShown ? borderThickness/2 : -borderThickness
1941
1942 target: appDelegate
1943 boundsItem: root.availableDesktopArea
1944 minWidth: units.gu(10)
1945 minHeight: units.gu(10)
1946 borderThickness: units.gu(2)
1947 enabled: false
1948 visible: enabled
1949 readyToAssesBounds: !appDelegate._constructing
1950
1951 onPressed: {
1952 appDelegate.activate();
1953 }
1954 }
1955
1956 DecoratedWindow {
1957 id: decoratedWindow
1958 objectName: "decoratedWindow"
1959 anchors.left: appDelegate.left
1960 anchors.top: appDelegate.top
1961 application: model.application
1962 surface: model.window.surface
1963 active: model.window.focused
1964 focus: true
1965 interactive: root.interactive
1966 showDecoration: 1
1967 decorationHeight: priv.windowDecorationHeight
1968 maximizeButtonShown: appDelegate.canBeMaximized
1969 overlayShown: touchControls.overlayShown
1970 width: implicitWidth
1971 height: implicitHeight
1972 highlightSize: windowInfoItem.iconMargin / 2
1973 boundsItem: root.availableDesktopArea
1974 panelState: root.panelState
1975 altDragEnabled: root.mode == "windowed"
1976
1977 requestedWidth: appDelegate.requestedWidth
1978 requestedHeight: appDelegate.requestedHeight
1979
1980 onCloseClicked: { appDelegate.close(); }
1981 onMaximizeClicked: {
1982 if (appDelegate.canBeMaximized) {
1983 appDelegate.anyMaximized ? appDelegate.requestRestore() : appDelegate.requestMaximize();
1984 }
1985 }
1986 onMaximizeHorizontallyClicked: {
1987 if (appDelegate.canBeMaximizedHorizontally) {
1988 appDelegate.maximizedHorizontally ? appDelegate.requestRestore() : appDelegate.requestMaximizeHorizontally()
1989 }
1990 }
1991 onMaximizeVerticallyClicked: {
1992 if (appDelegate.canBeMaximizedVertically) {
1993 appDelegate.maximizedVertically ? appDelegate.requestRestore() : appDelegate.requestMaximizeVertically()
1994 }
1995 }
1996 onMinimizeClicked: { appDelegate.requestMinimize(); }
1997 onDecorationPressed: { appDelegate.activate(); }
1998 onDecorationReleased: fakeRectangle.visible ? fakeRectangle.commit() : appDelegate.updateRestoredGeometry()
1999
2000 property real angle: 0
2001 Behavior on angle { enabled: priv.closingIndex >= 0; LomiriNumberAnimation {} }
2002 property real itemScale: 1
2003 Behavior on itemScale { enabled: priv.closingIndex >= 0; LomiriNumberAnimation {} }
2004
2005 transform: [
2006 Scale {
2007 origin.x: 0
2008 origin.y: decoratedWindow.implicitHeight / 2
2009 xScale: decoratedWindow.itemScale
2010 yScale: decoratedWindow.itemScale
2011 },
2012 Rotation {
2013 origin { x: 0; y: (decoratedWindow.height / 2) }
2014 axis { x: 0; y: 1; z: 0 }
2015 angle: decoratedWindow.angle
2016 }
2017 ]
2018 }
2019
2020 OpacityMask {
2021 id: opacityEffect
2022 anchors.fill: decoratedWindow
2023 }
2024
2025 WindowControlsOverlay {
2026 id: touchControls
2027 anchors.fill: appDelegate
2028 target: appDelegate
2029 resizeArea: resizeArea
2030 enabled: false
2031 visible: enabled
2032 boundsItem: root.availableDesktopArea
2033
2034 onFakeMaximizeAnimationRequested: if (!appDelegate.maximized) fakeRectangle.maximize(amount, true)
2035 onFakeMaximizeLeftAnimationRequested: if (!appDelegate.maximizedLeft) fakeRectangle.maximizeLeft(amount, true)
2036 onFakeMaximizeRightAnimationRequested: if (!appDelegate.maximizedRight) fakeRectangle.maximizeRight(amount, true)
2037 onFakeMaximizeTopLeftAnimationRequested: if (!appDelegate.maximizedTopLeft) fakeRectangle.maximizeTopLeft(amount, true);
2038 onFakeMaximizeTopRightAnimationRequested: if (!appDelegate.maximizedTopRight) fakeRectangle.maximizeTopRight(amount, true);
2039 onFakeMaximizeBottomLeftAnimationRequested: if (!appDelegate.maximizedBottomLeft) fakeRectangle.maximizeBottomLeft(amount, true);
2040 onFakeMaximizeBottomRightAnimationRequested: if (!appDelegate.maximizedBottomRight) fakeRectangle.maximizeBottomRight(amount, true);
2041 onStopFakeAnimation: fakeRectangle.stop();
2042 onDragReleased: fakeRectangle.visible ? fakeRectangle.commit() : appDelegate.updateRestoredGeometry()
2043 }
2044
2045 WindowedFullscreenPolicy {
2046 id: windowedFullscreenPolicy
2047 }
2048 StagedFullscreenPolicy {
2049 id: stagedFullscreenPolicy
2050 active: root.mode == "staged" || root.mode == "stagedWithSideStage"
2051 surface: model.window.surface
2052 }
2053
2054 SpreadDelegateInputArea {
2055 id: dragArea
2056 objectName: "dragArea"
2057 anchors.fill: decoratedWindow
2058 enabled: false
2059 closeable: true
2060 stage: root
2061 dragDelegate: fakeDragItem
2062
2063 onClicked: {
2064 spreadItem.highlightedIndex = index;
2065 if (distance == 0) {
2066 priv.goneToSpread = false;
2067 }
2068 }
2069 onClose: {
2070 priv.closingIndex = index
2071 appDelegate.close();
2072 }
2073 }
2074
2075 WindowInfoItem {
2076 id: windowInfoItem
2077 objectName: "windowInfoItem"
2078 anchors { left: parent.left; top: decoratedWindow.bottom; topMargin: units.gu(1) }
2079 title: model.application.name
2080 iconSource: model.application.icon
2081 height: spreadItem.appInfoHeight
2082 opacity: 0
2083 z: 1
2084 visible: opacity > 0
2085 maxWidth: {
2086 var nextApp = appRepeater.itemAt(index + 1);
2087 if (nextApp) {
2088 return Math.max(iconHeight, nextApp.x - appDelegate.x - units.gu(1))
2089 }
2090 return appDelegate.width;
2091 }
2092
2093 onClicked: {
2094 spreadItem.highlightedIndex = index;
2095 priv.goneToSpread = false;
2096 }
2097 }
2098
2099 MouseArea {
2100 id: closeMouseArea
2101 objectName: "closeMouseArea"
2102 anchors { left: parent.left; top: parent.top; leftMargin: -height / 2; topMargin: -height / 2 + spreadMaths.closeIconOffset }
2103 readonly property var mousePos: hoverMouseArea.mapToItem(appDelegate, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
2104 readonly property bool shown: dragArea.distance == 0
2105 && index == spreadItem.highlightedIndex
2106 && mousePos.y < (decoratedWindow.height / 3)
2107 && mousePos.y > -units.gu(4)
2108 && mousePos.x > -units.gu(4)
2109 && mousePos.x < (decoratedWindow.width * 2 / 3)
2110 opacity: shown ? 1 : 0
2111 visible: opacity > 0
2112 Behavior on opacity { LomiriNumberAnimation { duration: LomiriAnimation.SnapDuration } }
2113 height: units.gu(6)
2114 width: height
2115
2116 onClicked: {
2117 priv.closingIndex = index;
2118 appDelegate.close();
2119 }
2120 Image {
2121 id: closeImage
2122 source: "graphics/window-close.svg"
2123 anchors.fill: closeMouseArea
2124 anchors.margins: units.gu(2)
2125 sourceSize.width: width
2126 sourceSize.height: height
2127 }
2128 }
2129
2130 Item {
2131 // Group all child windows in this item so that we can fade them out together when going to the spread
2132 // (and fade them in back again when returning from it)
2133 readonly property bool stageOnProperState: root.state === "windowed"
2134 || root.state === "staged"
2135 || root.state === "stagedWithSideStage"
2136
2137 // TODO: Is it worth the extra cost of layering to avoid the opacity artifacts of intersecting children?
2138 // Btw, will involve more than uncommenting the line below as children won't necessarily fit this item's
2139 // geometry. This is just a reference.
2140 //layer.enabled: opacity !== 0.0 && opacity !== 1.0
2141
2142 opacity: stageOnProperState ? 1.0 : 0.0
2143 visible: opacity !== 0.0 // make it transparent to input as well
2144 Behavior on opacity { LomiriNumberAnimation {} }
2145
2146 Repeater {
2147 id: childWindowRepeater
2148 model: appDelegate.surface ? appDelegate.surface.childSurfaceList : null
2149
2150 delegate: ChildWindowTree {
2151 surface: model.surface
2152
2153 // Account for the displacement caused by window decoration in the top-level surface
2154 // Ie, the top-level surface is not positioned at (0,0) of this ChildWindow's parent (appDelegate)
2155 displacementX: appDelegate.clientAreaItem.x
2156 displacementY: appDelegate.clientAreaItem.y
2157
2158 boundsItem: root.availableDesktopArea
2159 decorationHeight: priv.windowDecorationHeight
2160
2161 z: childWindowRepeater.count - model.index
2162
2163 onFocusChanged: {
2164 if (focus) {
2165 // some child surface in this tree got focus.
2166 // Ensure we also have it at the top-level hierarchy
2167 appDelegate.claimFocus();
2168 }
2169 }
2170 }
2171 }
2172 }
2173 }
2174 }
2175 }
2176
2177 FakeMaximizeDelegate {
2178 id: fakeRectangle
2179 target: priv.focusedAppDelegate
2180 leftMargin: root.availableDesktopArea.x
2181 appContainerWidth: appContainer.width
2182 appContainerHeight: appContainer.height
2183 panelState: root.panelState
2184 }
2185
2186 WorkspaceSwitcher {
2187 id: workspaceSwitcher
2188 enabled: workspaceEnabled
2189 anchors.centerIn: parent
2190 height: units.gu(20)
2191 width: root.width - units.gu(8)
2192 background: root.background
2193 onActiveChanged: {
2194 if (!active) {
2195 appContainer.focus = true;
2196 }
2197 }
2198 }
2199
2200 PropertyAnimation {
2201 id: shortRightEdgeSwipeAnimation
2202 property: "x"
2203 to: 0
2204 duration: priv.animationDuration
2205 }
2206
2207 SwipeArea {
2208 id: rightEdgeDragArea
2209 objectName: "rightEdgeDragArea"
2210 direction: Direction.Leftwards
2211 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
2212 width: root.dragAreaWidth
2213 enabled: root.spreadEnabled
2214
2215 property var gesturePoints: []
2216 property bool cancelled: false
2217
2218 property real progress: -touchPosition.x / root.width
2219 onProgressChanged: {
2220 if (dragging) {
2221 draggedProgress = progress;
2222 }
2223 }
2224
2225 property real draggedProgress: 0
2226
2227 onTouchPositionChanged: {
2228 gesturePoints.push(touchPosition.x);
2229 if (gesturePoints.length > 10) {
2230 gesturePoints.splice(0, gesturePoints.length - 10)
2231 }
2232 }
2233
2234 onDraggingChanged: {
2235 if (dragging) {
2236 // A potential edge-drag gesture has started. Start recording it
2237 gesturePoints = [];
2238 cancelled = false;
2239 draggedProgress = 0;
2240 } else {
2241 // Ok. The user released. Did he drag far enough to go to full spread?
2242 if (gesturePoints[gesturePoints.length - 1] < -spreadItem.rightEdgeBreakPoint * spreadItem.width ) {
2243
2244 // He dragged far enough, but if the last movement was a flick to the right again, he wants to cancel the spread again.
2245 var oneWayFlickToRight = true;
2246 var smallestX = gesturePoints[0]-1;
2247 for (var i = 0; i < gesturePoints.length; i++) {
2248 if (gesturePoints[i] <= smallestX) {
2249 oneWayFlickToRight = false;
2250 break;
2251 }
2252 smallestX = gesturePoints[i];
2253 }
2254
2255 if (!oneWayFlickToRight) {
2256 // Ok, the user made it, let's go to spread!
2257 priv.goneToSpread = true;
2258 } else {
2259 cancelled = true;
2260 }
2261 } else {
2262 // Ok, the user didn't drag far enough to cross the breakPoint
2263 // Find out if it was a one-way movement to the left, in which case we just switch directly to next app.
2264 var oneWayFlick = true;
2265 var smallestX = rightEdgeDragArea.width;
2266 for (var i = 0; i < gesturePoints.length; i++) {
2267 if (gesturePoints[i] >= smallestX) {
2268 oneWayFlick = false;
2269 break;
2270 }
2271 smallestX = gesturePoints[i];
2272 }
2273
2274 if (appRepeater.count > 1 &&
2275 (oneWayFlick && rightEdgeDragArea.distance > units.gu(2) || rightEdgeDragArea.distance > spreadItem.rightEdgeBreakPoint * spreadItem.width)) {
2276 var nextStage = appRepeater.itemAt(priv.nextInStack).stage
2277 for (var i = 0; i < appRepeater.count; i++) {
2278 if (i != priv.nextInStack && appRepeater.itemAt(i).stage == nextStage) {
2279 appRepeater.itemAt(i).playHidingAnimation()
2280 break;
2281 }
2282 }
2283 appRepeater.itemAt(priv.nextInStack).playFocusAnimation()
2284 if (appRepeater.itemAt(priv.nextInStack).stage == ApplicationInfoInterface.SideStage && !sideStage.shown) {
2285 sideStage.show();
2286 }
2287
2288 } else {
2289 cancelled = true;
2290 }
2291
2292 gesturePoints = [];
2293 }
2294 }
2295 }
2296 }
2297
2298 TabletSideStageTouchGesture {
2299 id: triGestureArea
2300 objectName: "triGestureArea"
2301 anchors.fill: parent
2302 enabled: false
2303 property Item appDelegate
2304
2305 dragComponent: dragComponent
2306 dragComponentProperties: { "appDelegate": appDelegate }
2307
2308 onPressed: {
2309 function matchDelegate(obj) { return String(obj.objectName).indexOf("appDelegate") >= 0; }
2310
2311 var delegateAtCenter = Functions.itemAt(appContainer, x, y, matchDelegate);
2312 if (!delegateAtCenter) return;
2313
2314 appDelegate = delegateAtCenter;
2315 }
2316
2317 onClicked: {
2318 if (sideStage.shown) {
2319 sideStage.hide();
2320 } else {
2321 sideStage.show();
2322 priv.updateMainAndSideStageIndexes()
2323 }
2324 }
2325
2326 onDragStarted: {
2327 // If we're dragging to the sidestage.
2328 if (!sideStage.shown) {
2329 sideStage.show();
2330 }
2331 }
2332
2333 Component {
2334 id: dragComponent
2335 SurfaceContainer {
2336 property Item appDelegate
2337
2338 surface: appDelegate ? appDelegate.surface : null
2339
2340 consumesInput: false
2341 interactive: false
2342 focus: false
2343 requestedWidth: appDelegate ? appDelegate.requestedWidth : 0
2344 requestedHeight: appDelegate ? appDelegate.requestedHeight : 0
2345
2346 width: units.gu(40)
2347 height: units.gu(40)
2348
2349 Drag.hotSpot.x: width/2
2350 Drag.hotSpot.y: height/2
2351 // only accept opposite stage.
2352 Drag.keys: {
2353 if (!surface) return "Disabled";
2354
2355 if (appDelegate.stage === ApplicationInfo.MainStage) {
2356 if (appDelegate.application.supportedOrientations
2357 & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) {
2358 return "MainStage";
2359 }
2360 return "Disabled";
2361 }
2362 return "SideStage";
2363 }
2364 }
2365 }
2366 }
2367}