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