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