on d08666f help sign in
unity8/Shell.qml
764 lines 28 kB history raw
1/*
2 * Copyright (C) 2013 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
17import QtQuick 2.0
18import Ubuntu.Application 0.1
19import Ubuntu.Components 0.1
20import Ubuntu.Gestures 0.1
21import LightDM 0.1 as LightDM
22import AccountsService 0.1
23import "Dash"
24import "Greeter"
25import "Launcher"
26import "Panel"
27import "Hud"
28import "Components"
29import "Components/Math.js" as MathLocal
30import "Bottombar"
31import "SideStage"
32import "Notifications"
33import Unity.Notifications 1.0 as NotificationBackend
35FocusScope {
36 id: shell
38 // this is only here to select the width / height of the window if not running fullscreen
39 property bool tablet: false
40 width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
41 height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
43 property real edgeSize: units.gu(2)
44 property url default_background: shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg"
45 property url background: default_background
46 readonly property real panelHeight: panel.panelHeight
48 property bool dashShown: dash.shown
49 property bool stageScreenshotsReady: {
50 if (sideStage.shown) {
51 if (mainStage.applications.count > 0) {
52 return mainStage.usingScreenshots && sideStage.usingScreenshots;
53 } else {
54 return sideStage.usingScreenshots;
55 }
56 } else {
57 return mainStage.usingScreenshots;
58 }
59 }
61 property bool showEdgeDemo: true
62 property bool showEdgeDemoInGreeter: showEdgeDemo //AccountsService.getUserProperty("lightdm", "demo-edges")
64 function hideEdgeDemo() {
65 var user = LightDM.Users.data(greeter.currentIndex, LightDM.UserRoles.NameRole);
66 AccountsService.setUserProperty(user, "demo-edges", false);
67 shell.showEdgeDemo = false;
68 }
69 function hideEdgeDemoInGreeter() {
70 //AccountsService.setUserProperty("lightdm", "demo-edges", false);
71 shell.showEdgeDemoInGreeter = false;
72 }
74 property ListModel searchHistory: SearchHistoryModel {}
76 property var applicationManager: ApplicationManagerWrapper {}
78 Component.onCompleted: {
79 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
81 applicationManager.sideStageEnabled = Qt.binding(function() { return sideStage.enabled })
83 // FIXME: if application focused before shell starts, shell draws on top of it only.
84 // We should detect already running applications on shell start and bring them to the front.
85 applicationManager.unfocusCurrentApplication();
86 }
88 readonly property bool fullscreenMode: {
89 if (greeter.shown || lockscreen.shown) {
90 return false;
91 } else if (mainStage.usingScreenshots) { // Window Manager animating so want to re-evaluate fullscreen mode
92 return mainStage.switchingFromFullscreenToFullscreen;
93 } else if (applicationManager.mainStageFocusedApplication) {
94 return applicationManager.mainStageFocusedApplication.fullscreen;
95 } else {
96 return false;
97 }
98 }
100 Connections {
101 target: applicationManager
102 ignoreUnknownSignals: true
103 onFocusRequested: {
104 // TODO: this should be protected to only unlock for certain applications / certain usecases
105 // potentially only in connection with a notification
106 greeter.hide();
107 shell.activateApplication(desktopFile);
108 }
109 }
111 function activateApplication(desktopFile, argument) {
112 if (applicationManager) {
113 // For newly started applications, as it takes them time to draw their first frame
114 // we add a delay before we hide the animation screenshots to compensate.
115 var addDelay = !applicationManager.getApplicationFromDesktopFile(desktopFile);
117 var application;
118 application = applicationManager.activateApplication(desktopFile, argument);
119 if (application == null) {
120 return;
121 }
122 if (application.stage == ApplicationInfo.MainStage || !sideStage.enabled) {
123 mainStage.activateApplication(desktopFile, addDelay);
124 } else {
125 sideStage.activateApplication(desktopFile, addDelay);
126 }
127 stages.show();
128 }
129 }
131 VolumeControl {
132 id: volumeControl
133 }
135 Keys.onVolumeUpPressed: volumeControl.volumeUp()
136 Keys.onVolumeDownPressed: volumeControl.volumeDown()
138 Keys.onReleased: {
139 if (event.key == Qt.Key_PowerOff) {
140 greeter.show()
141 }
142 }
144 Item {
145 id: underlay
146 objectName: "underlay"
147 anchors.fill: parent
149 // Whether the underlay is fully covered by opaque UI elements.
150 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
152 // Whether the user should see the topmost application surface (if there's one at all).
153 property bool applicationSurfaceShouldBeSeen:
154 (mainStage.applications && mainStage.applications.count > 0)
155 && (!stages.fullyHidden && !mainStage.usingScreenshots)
157 // NB! Application surfaces are stacked behing the shell one. So they can only be seen by the user
158 // through the translucent parts of the shell surface.
159 visible: !fullyCovered && !applicationSurfaceShouldBeSeen
161 Image {
162 id: backgroundImage
163 source: shell.background
164 sourceSize.width: parent.width
165 sourceSize.height: parent.height
166 anchors.fill: parent
167 }
169 Rectangle {
170 anchors.fill: parent
171 color: "black"
172 opacity: dash.disappearingAnimationProgress
173 }
175 Dash {
176 id: dash
177 objectName: "dash"
179 available: !greeter.shown && !lockscreen.shown
180 hides: [stages, launcher, panel.indicators]
181 shown: disappearingAnimationProgress !== 1.0
182 enabled: disappearingAnimationProgress === 0.0 && !leftEdgeDemo.visible && !topEdgeDemo.visible
183 // FIXME: unfocus all applications when going back to the dash
184 onEnabledChanged: {
185 if (enabled) {
186 shell.applicationManager.unfocusCurrentApplication()
187 }
188 }
190 anchors {
191 fill: parent
192 topMargin: panel.panelHeight
193 }
195 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
196 opacity: 1.0 - disappearingAnimationProgress
197 property real disappearingAnimationProgress: {
198 if (greeter.shown) {
199 return greeter.showProgress;
200 } else {
201 return stagesOuterContainer.showProgress;
202 }
203 }
205 // FIXME: only necessary because stagesOuterContainer.showProgress and
206 // greeterRevealer.animatedProgress are not animated
207 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
208 }
210 DemoOverlay {
211 id: topEdgeDemo
213 edge: "top"
214 title: i18n.tr("Top edge")
215 text: i18n.tr("Try swiping from the top edge to access the indicators")
216 anchors.fill: dash
217 visible: false
218 enabled: shell.showEdgeDemo
219 onSkip: shell.hideEdgeDemo()
221 Connections {
222 target: greeter
223 onShownChanged: if (topEdgeDemo.enabled && !greeter.shown) topEdgeDemo.visible = true
224 }
226 Connections {
227 target: panel.indicators
228 onFullyOpenedChanged: if (topEdgeDemo.enabled && panel.indicators.fullyOpened) topEdgeDemo.visible = false
229 }
230 }
232 DemoOverlay {
233 id: leftEdgeDemo
235 edge: "left"
236 title: i18n.tr("Left edge")
237 text: i18n.tr("Swipe from the left to reveal the launcher for quick access to apps")
238 anchors.fill: dash
239 visible: false
240 enabled: shell.showEdgeDemo
241 onSkip: shell.hideEdgeDemo()
243 Connections {
244 target: bottomEdgeDemo
245 onVisibleChanged: if (leftEdgeDemo.enabled && !bottomEdgeDemo.visible) leftEdgeDemo.visible = true
246 }
248 Connections {
249 target: launcher
250 onProgressChanged: {
251 if (leftEdgeDemo.enabled && launcher.progress >= 1.0) {
252 leftEdgeDemo.visible = false;
253 shell.hideEdgeDemo();
254 }
255 }
256 }
257 }
258 }
261 Item {
262 id: stagesOuterContainer
264 width: parent.width
265 height: parent.height
266 x: launcher.progress
267 Behavior on x {SmoothedAnimation{velocity: 600}}
269 property real showProgress:
270 MathLocal.clamp(1 - (x + stages.x) / shell.width, 0, 1)
272 Showable {
273 id: stages
274 objectName: "stages"
276 x: width
278 property bool fullyShown: shown && x == 0 && parent.x == 0
279 property bool fullyHidden: !shown && x == width
280 available: !greeter.shown
281 hides: [launcher, panel.indicators]
282 shown: false
283 opacity: 1.0
284 showAnimation: StandardAnimation { property: "x"; duration: 350; to: 0; easing.type: Easing.OutCubic }
285 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: width; easing.type: Easing.OutCubic }
287 width: parent.width
288 height: parent.height
290 // close the stages when no focused application remains
291 Connections {
292 target: shell.applicationManager
293 onMainStageFocusedApplicationChanged: stages.closeIfNoApplications()
294 onSideStageFocusedApplicationChanged: stages.closeIfNoApplications()
295 ignoreUnknownSignals: true
296 }
298 function closeIfNoApplications() {
299 if (!shell.applicationManager.mainStageFocusedApplication
300 && !shell.applicationManager.sideStageFocusedApplication
301 && shell.applicationManager.mainStageApplications.count == 0
302 && shell.applicationManager.sideStageApplications.count == 0) {
303 stages.hide();
304 }
305 }
307 // show the stages when an application gets the focus
308 Connections {
309 target: shell.applicationManager
310 onMainStageFocusedApplicationChanged: {
311 if (shell.applicationManager.mainStageFocusedApplication) {
312 mainStage.show();
313 stages.show();
314 }
315 }
316 onSideStageFocusedApplicationChanged: {
317 if (shell.applicationManager.sideStageFocusedApplication) {
318 sideStage.show();
319 stages.show();
320 }
321 }
322 ignoreUnknownSignals: true
323 }
326 Stage {
327 id: mainStage
329 anchors.fill: parent
330 fullyShown: stages.fullyShown
331 fullyHidden: stages.fullyHidden
332 shouldUseScreenshots: !fullyShown
333 rightEdgeEnabled: !sideStage.enabled
335 applicationManager: shell.applicationManager
336 rightEdgeDraggingAreaWidth: shell.edgeSize
337 normalApplicationY: shell.panelHeight
339 shown: true
340 function show() {
341 stages.show();
342 }
343 function hide() {
344 }
346 // FIXME: workaround the fact that focusing a main stage application
347 // raises its surface on top of all other surfaces including the ones
348 // that belong to side stage applications.
349 onFocusedApplicationChanged: {
350 if (focusedApplication && sideStage.focusedApplication && sideStage.fullyShown) {
351 shell.applicationManager.focusApplication(sideStage.focusedApplication);
352 }
353 }
354 }
356 SideStage {
357 id: sideStage
359 applicationManager: shell.applicationManager
360 rightEdgeDraggingAreaWidth: shell.edgeSize
361 normalApplicationY: shell.panelHeight
363 onShownChanged: {
364 if (!shown && mainStage.applications.count == 0) {
365 stages.hide();
366 }
367 }
368 // FIXME: when hiding the side stage, refocus the main stage
369 // application so that it goes in front of the side stage
370 // application and hides it
371 onFullyShownChanged: {
372 if (!fullyShown && stages.fullyShown && sideStage.focusedApplication != null) {
373 shell.applicationManager.focusApplication(mainStage.focusedApplication);
374 }
375 }
377 enabled: shell.width >= units.gu(60)
378 visible: enabled
379 fullyShown: stages.fullyShown && shown
380 && sideStage[sideStageRevealer.boundProperty] == sideStageRevealer.openedValue
381 shouldUseScreenshots: !fullyShown || mainStage.usingScreenshots || sideStageRevealer.pressed
383 available: !greeter.shown && !lockscreen.shown && enabled
384 hides: [launcher, panel.indicators]
385 shown: false
386 showAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.openedValue; easing.type: Easing.OutQuint }
387 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.closedValue; easing.type: Easing.OutQuint }
389 width: units.gu(40)
390 height: stages.height
391 handleExpanded: sideStageRevealer.pressed
392 }
394 Revealer {
395 id: sideStageRevealer
397 enabled: mainStage.applications.count > 0 && sideStage.applications.count > 0
398 && sideStage.available
399 direction: Qt.RightToLeft
400 openedValue: parent.width - sideStage.width
401 hintDisplacement: units.gu(3)
402 /* The size of the sidestage handle needs to be bigger than the
403 typical size used for edge detection otherwise it is really
404 hard to grab.
405 */
406 handleSize: sideStage.shown ? units.gu(4) : shell.edgeSize
407 closedValue: parent.width + sideStage.handleSizeCollapsed
408 target: sideStage
409 x: parent.width - width
410 width: sideStage.width + handleSize * 0.7
411 height: sideStage.height
412 orientation: Qt.Horizontal
413 }
415 DragHandle {
416 id: stagesDragHandle
418 anchors.top: parent.top
419 anchors.bottom: parent.bottom
420 anchors.right: parent.left
422 width: shell.edgeSize
423 direction: Direction.Leftwards
424 property bool haveApps: mainStage.applications.count > 0 || sideStage.applications.count > 0
426 maxTotalDragDistance: haveApps ? parent.width : parent.width * 0.7
427 // Make autocompletion impossible when !haveApps
428 edgeDragEvaluator.minDragDistance: haveApps ? maxTotalDragDistance * 0.1 : Number.MAX_VALUE
429 }
430 }
431 }
433 Lockscreen {
434 id: lockscreen
435 hides: [launcher, panel.indicators, hud]
436 shown: false
437 enabled: true
438 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
439 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
440 y: panel.panelHeight
441 x: required ? 0 : - width
442 width: parent.width
443 height: parent.height - panel.panelHeight
444 background: shell.background
446 onUnlocked: lockscreen.hide()
447 onCancel: greeter.show()
449 Component.onCompleted: {
450 if (LightDM.Users.count == 1) {
451 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
452 greeter.selected(0)
453 }
454 }
455 }
457 Connections {
458 target: LightDM.Greeter
460 onShowPrompt: {
461 if (LightDM.Users.count == 1) {
462 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
463 if (text == "PIN") {
464 lockscreen.alphaNumeric = false
465 } else {
466 lockscreen.alphaNumeric = true
467 }
468 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
469 lockscreen.show();
470 }
471 }
472 }
474 Greeter {
475 id: greeter
477 available: true
478 hides: [launcher, panel.indicators, hud]
479 shown: true
481 y: panel.panelHeight
482 width: parent.width
483 height: parent.height - panel.panelHeight
485 dragHandleWidth: shell.edgeSize
487 property var previousMainApp: null
488 property var previousSideApp: null
490 onShownChanged: {
491 if (shown) {
492 lockscreen.reset();
493 // If there is only one user, we start authenticating with that one here.
494 // If there are more users, the Greeter will handle that
495 if (LightDM.Users.count == 1) {
496 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
497 greeter.selected(0);
498 }
499 greeter.forceActiveFocus();
500 // FIXME: *FocusedApplication are not updated when unfocused, hence the need to check whether
501 // the stage was actually shown
502 if (mainStage.fullyShown) greeter.previousMainApp = applicationManager.mainStageFocusedApplication;
503 if (sideStage.fullyShown) greeter.previousSideApp = applicationManager.sideStageFocusedApplication;
504 applicationManager.unfocusCurrentApplication();
505 } else {
506 if (greeter.previousMainApp) {
507 applicationManager.focusApplication(greeter.previousMainApp);
508 greeter.previousMainApp = null;
509 }
510 if (greeter.previousSideApp) {
511 applicationManager.focusApplication(greeter.previousSideApp);
512 greeter.previousSideApp = null;
513 }
514 }
515 }
517 onUnlocked: greeter.hide()
518 onSelected: {
519 // Update background
520 var bgPath = greeter.model.data(uid, LightDM.UserRoles.BackgroundPathRole)
521 shell.background = bgPath ? bgPath : default_background
522 // Update edge demo hint
523 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole)
524 shell.showEdgeDemo = AccountsService.getUserProperty(user, "demo-edges")
525 }
527 onLeftTeaserPressedChanged: {
528 if (leftTeaserPressed) {
529 launcher.tease();
530 }
531 }
533 DemoOverlay {
534 id: rightEdgeDemo
536 edge: "right"
537 title: i18n.tr("Right edge")
538 text: i18n.tr("Try swiping from the right edge to unlock the phone")
539 anchors.fill: parent
540 visible: shell.showEdgeDemoInGreeter
541 enabled: shell.showEdgeDemoInGreeter
542 onSkip: {
543 shell.hideEdgeDemoInGreeter()
544 shell.hideEdgeDemo()
545 }
547 Connections {
548 target: greeter
549 onUnlocked: if (rightEdgeDemo.enabled) rightEdgeDemo.visible = false
550 onShownChanged: {
551 if (rightEdgeDemo.enabled && !greeter.shown) {
552 rightEdgeDemo.visible = false
553 shell.hideEdgeDemoInGreeter()
554 }
555 }
556 }
557 }
558 }
560 InputFilterArea {
561 blockInput: greeter.shown || lockscreen.shown
562 anchors.fill: parent
563 }
565 Item {
566 id: overlay
568 anchors.fill: parent
570 Panel {
571 id: panel
572 anchors.fill: parent //because this draws indicator menus
573 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
574 indicators {
575 hides: [launcher]
576 available: !rightEdgeDemo.visible && !leftEdgeDemo.visible
577 }
578 fullscreenMode: shell.fullscreenMode
579 searchVisible: !greeter.shown && !lockscreen.shown
581 InputFilterArea {
582 anchors.fill: parent
583 blockInput: panel.indicators.shown
584 }
586 DemoOverlay {
587 id: bottomEdgeDemo
589 edge: "bottom"
590 title: i18n.tr("Close")
591 text: i18n.tr("Swipe up again to close the settings screen")
592 anchors.fill: panel.indicators
593 visible: false
594 enabled: shell.showEdgeDemo
595 onSkip: shell.hideEdgeDemo()
597 Connections {
598 target: panel.indicators
599 onFullyOpenedChanged: if (bottomEdgeDemo.enabled && panel.indicators.fullyOpened) bottomEdgeDemo.visible = true
600 onPartiallyOpenedChanged: if (bottomEdgeDemo.enabled && !panel.indicators.partiallyOpened && !panel.indicators.fullyOpened) bottomEdgeDemo.visible = false
601 }
602 }
603 }
605 Hud {
606 id: hud
608 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
609 height: parent.height
611 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown && !topEdgeDemo.visible
612 shown: false
613 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
614 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
616 Connections {
617 target: shell.applicationManager
618 onMainStageFocusedApplicationChanged: hud.hide()
619 onSideStageFocusedApplicationChanged: hud.hide()
620 }
622 InputFilterArea {
623 anchors.fill: parent
624 blockInput: hud.shown
625 }
626 }
628 Revealer {
629 id: hudRevealer
631 enabled: hud.shown
632 width: hud.width
633 anchors.left: hud.left
634 height: parent.height
635 target: hud.revealerTarget
636 closedValue: height
637 openedValue: 0
638 direction: Qt.RightToLeft
639 orientation: Qt.Vertical
640 handleSize: hud.handleHeight
641 onCloseClicked: target.hide()
642 }
644 Bottombar {
645 theHud: hud
646 anchors.fill: parent
647 enabled: !panel.indicators.shown
648 applicationIsOnForeground: applicationManager.mainStageFocusedApplication
649 || applicationManager.sideStageFocusedApplication
650 }
652 InputFilterArea {
653 blockInput: launcher.shown
654 anchors {
655 top: parent.top
656 bottom: parent.bottom
657 left: parent.left
658 }
659 width: launcher.width
660 }
662 Launcher {
663 id: launcher
665 anchors.top: parent.top
666 anchors.bottom: parent.bottom
667 width: parent.width
668 dragAreaWidth: shell.edgeSize
669 available: !greeter.locked && !rightEdgeDemo.visible && !topEdgeDemo.visible && !bottomEdgeDemo.visible
670 onDashItemSelected: {
671 greeter.hide()
672 // Animate if moving between application and dash
673 if (!stages.shown) {
674 dash.setCurrentScope("home.scope", true, false)
675 } else {
676 dash.setCurrentScope("home.scope", false, false)
677 }
678 stages.hide();
679 }
680 onDash: {
681 if (stages.shown) {
682 dash.setCurrentScope("applications.scope", true, false)
683 stages.hide();
684 launcher.hide();
685 }
686 }
687 onLauncherApplicationSelected:{
688 greeter.hide()
689 shell.activateApplication(desktopFile)
690 }
691 onShownChanged: {
692 if (shown) {
693 panel.indicators.hide()
694 hud.hide()
695 }
696 }
697 }
699 Notifications {
700 id: notifications
702 model: NotificationBackend.Model
703 anchors {
704 top: parent.top
705 right: parent.right
706 bottom: parent.bottom
707 leftMargin: units.gu(1)
708 rightMargin: units.gu(1)
709 topMargin: panel.panelHeight + units.gu(1)
710 }
711 states: [
712 State {
713 name: "narrow"
714 when: overlay.width <= units.gu(60)
715 AnchorChanges { target: notifications; anchors.left: parent.left }
716 },
717 State {
718 name: "wide"
719 when: overlay.width > units.gu(60)
720 AnchorChanges { target: notifications; anchors.left: undefined }
721 PropertyChanges { target: notifications; width: units.gu(38) }
722 }
723 ]
724 }
725 }
727 focus: true
729 InputFilterArea {
730 anchors {
731 top: parent.top
732 bottom: parent.bottom
733 left: parent.left
734 }
735 width: shell.edgeSize
736 blockInput: true
737 }
739 InputFilterArea {
740 anchors {
741 top: parent.top
742 bottom: parent.bottom
743 right: parent.right
744 }
745 width: shell.edgeSize
746 blockInput: true
747 }
749 Binding {
750 target: i18n
751 property: "domain"
752 value: "unity8"
753 }
755 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
756 MouseArea {
757 anchors.bottom: parent.bottom
758 anchors.left: parent.left
759 anchors.right: parent.right
760 height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
762 enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
763 }