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