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