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