on 47a67e9 help sign in
unity8/Shell.qml
788 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 AccountsService 0.1
19import GSettings 1.0
20import Unity.Application 0.1
21import Ubuntu.Components 0.1
22import Ubuntu.Gestures 0.1
23import Unity.Launcher 0.1
24import LightDM 0.1 as LightDM
25import Powerd 0.1
26import SessionBroadcast 0.1
27import "Dash"
28import "Greeter"
29import "Launcher"
30import "Panel"
31import "Hud"
32import "Components"
33import "Components/Math.js" as MathLocal
34import "Bottombar"
35import "SideStage"
36import "Notifications"
37import Unity.Notifications 1.0 as NotificationBackend
39FocusScope {
40 id: shell
42 // this is only here to select the width / height of the window if not running fullscreen
43 property bool tablet: false
44 width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
45 height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
47 property real edgeSize: units.gu(2)
48 property url defaultBackground: shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg"
49 property url background
50 readonly property real panelHeight: panel.panelHeight
52 property bool dashShown: dash.shown
53 property bool stageScreenshotsReady: {
54 if (sideStage.shown) {
55 if (mainStage.applications.count > 0) {
56 return mainStage.usingScreenshots && sideStage.usingScreenshots;
57 } else {
58 return sideStage.usingScreenshots;
59 }
60 } else {
61 return mainStage.usingScreenshots;
62 }
63 }
65 property ListModel searchHistory: SearchHistoryModel {}
67 property var applicationManager: ApplicationManagerWrapper {}
69 Binding {
70 target: LauncherModel
71 property: "applicationManager"
72 value: ApplicationManager
73 }
75 Component.onCompleted: {
76 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
78 applicationManager.sideStageEnabled = Qt.binding(function() { return sideStage.enabled })
80 // FIXME: if application focused before shell starts, shell draws on top of it only.
81 // We should detect already running applications on shell start and bring them to the front.
82 applicationManager.unfocusCurrentApplication();
83 }
85 readonly property bool applicationFocused: !!applicationManager.mainStageFocusedApplication
86 || !!applicationManager.sideStageFocusedApplication
87 // Used for autopilot testing.
88 readonly property string currentFocusedAppId: ApplicationManager.focusedApplicationId
90 readonly property bool fullscreenMode: {
91 if (greeter.shown || lockscreen.shown) {
92 return false;
93 } else if (mainStage.usingScreenshots) { // Window Manager animating so want to re-evaluate fullscreen mode
94 return mainStage.switchingFromFullscreenToFullscreen;
95 } else if (applicationManager.mainStageFocusedApplication) {
96 return applicationManager.mainStageFocusedApplication.fullscreen;
97 } else {
98 return false;
99 }
100 }
102 function activateApplication(desktopFile, argument) {
103 if (applicationManager) {
104 // For newly started applications, as it takes them time to draw their first frame
105 // we add a delay before we hide the animation screenshots to compensate.
106 var addDelay = !applicationManager.getApplicationFromDesktopFile(desktopFile);
108 var application;
109 application = applicationManager.activateApplication(desktopFile, argument);
110 if (application == null) {
111 return;
112 }
113 if (application.stage == ApplicationInfo.MainStage || !sideStage.enabled) {
114 mainStage.activateApplication(desktopFile, addDelay);
115 } else {
116 sideStage.activateApplication(desktopFile, addDelay);
117 }
118 stages.show();
119 }
120 }
122 GSettings {
123 id: backgroundSettings
124 schema.id: "org.gnome.desktop.background"
125 }
126 property url gSettingsPicture: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : shell.defaultBackground
127 onGSettingsPictureChanged: {
128 shell.background = gSettingsPicture
129 }
131 // This is a dummy image that is needed to determine if the picture url
132 // in backgroundSettings points to a valid picture file.
133 // We can't do this with the real background image because setting a
134 // new source in onStatusChanged triggers a binding loop detection
135 // inside Image, which causes it not to render even though a valid source
136 // would be set. We don't mind about this image staying black and just
137 // use it for verification to populate the source for the real
138 // background image.
139 Image {
140 source: shell.background
141 height: 0
142 width: 0
143 sourceSize.height: 0
144 sourceSize.width: 0
145 onStatusChanged: {
146 if (status == Image.Error && source != shell.defaultBackground) {
147 shell.background = defaultBackground
148 }
149 }
150 }
152 VolumeControl {
153 id: volumeControl
154 }
156 Keys.onVolumeUpPressed: volumeControl.volumeUp()
157 Keys.onVolumeDownPressed: volumeControl.volumeDown()
159 Item {
160 id: underlay
161 objectName: "underlay"
162 anchors.fill: parent
164 // Whether the underlay is fully covered by opaque UI elements.
165 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
167 readonly property bool applicationRunning: ((mainStage.applications && mainStage.applications.count > 0)
168 || (sideStage.applications && sideStage.applications.count > 0))
170 // Whether the user should see the topmost application surface (if there's one at all).
171 readonly property bool applicationSurfaceShouldBeSeen: applicationRunning && !stages.fullyHidden
172 && !mainStage.usingScreenshots // but want sideStage animating over app surface
176 // NB! Application surfaces are stacked behing the shell one. So they can only be seen by the user
177 // through the translucent parts of the shell surface.
178 visible: !fullyCovered && !applicationSurfaceShouldBeSeen
180 CrossFadeImage {
181 id: backgroundImage
182 objectName: "backgroundImage"
184 anchors.fill: parent
185 source: shell.background
186 fillMode: Image.PreserveAspectCrop
187 }
189 Rectangle {
190 anchors.fill: parent
191 color: "black"
192 opacity: dash.disappearingAnimationProgress
193 }
195 Dash {
196 id: dash
197 objectName: "dash"
199 available: !greeter.shown && !lockscreen.shown
200 hides: [stages, launcher, panel.indicators]
201 shown: disappearingAnimationProgress !== 1.0
202 enabled: disappearingAnimationProgress === 0.0 && edgeDemo.dashEnabled
203 // FIXME: unfocus all applications when going back to the dash
204 onEnabledChanged: {
205 if (enabled) {
206 shell.applicationManager.unfocusCurrentApplication()
207 }
208 }
210 anchors {
211 fill: parent
212 topMargin: panel.panelHeight
213 }
215 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
216 opacity: 1.0 - disappearingAnimationProgress
217 property real disappearingAnimationProgress: {
218 if (greeter.shown) {
219 return greeter.showProgress;
220 } else {
221 return stagesOuterContainer.showProgress;
222 }
223 }
225 // FIXME: only necessary because stagesOuterContainer.showProgress and
226 // greeterRevealer.animatedProgress are not animated
227 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
228 }
229 }
231 Item {
232 id: stagesOuterContainer
234 width: parent.width
235 height: parent.height
236 x: launcher.progress
237 Behavior on x {SmoothedAnimation{velocity: 600}}
239 property real showProgress:
240 MathLocal.clamp(1 - (x + stages.x) / shell.width, 0, 1)
242 Showable {
243 id: stages
244 objectName: "stages"
246 x: width
248 property bool fullyShown: shown && x == 0 && parent.x == 0
249 property bool fullyHidden: !shown && x == width
250 available: !greeter.shown
251 hides: [panel.indicators]
252 shown: false
253 opacity: 1.0
254 showAnimation: StandardAnimation { property: "x"; duration: 350; to: 0; easing.type: Easing.OutCubic }
255 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: width; easing.type: Easing.OutCubic }
257 width: parent.width
258 height: parent.height
260 // close the stages when no focused application remains
261 Connections {
262 target: shell.applicationManager
263 onMainStageFocusedApplicationChanged: stages.closeIfNoApplications()
264 onSideStageFocusedApplicationChanged: stages.closeIfNoApplications()
265 ignoreUnknownSignals: true
266 }
268 function closeIfNoApplications() {
269 if (!shell.applicationManager.mainStageFocusedApplication
270 && !shell.applicationManager.sideStageFocusedApplication
271 && shell.applicationManager.mainStageApplications.count == 0
272 && shell.applicationManager.sideStageApplications.count == 0) {
273 stages.hide();
274 }
275 }
277 // show the stages when an application gets the focus
278 Connections {
279 target: shell.applicationManager
280 onMainStageFocusedApplicationChanged: {
281 if (shell.applicationManager.mainStageFocusedApplication) {
282 mainStage.show();
283 stages.show();
284 }
285 }
286 onSideStageFocusedApplicationChanged: {
287 if (shell.applicationManager.sideStageFocusedApplication) {
288 sideStage.show();
289 stages.show();
290 }
291 }
292 ignoreUnknownSignals: true
293 }
295 Stage {
296 id: mainStage
298 anchors.fill: parent
299 fullyShown: stages.fullyShown
300 fullyHidden: stages.fullyHidden
301 shouldUseScreenshots: !fullyShown
302 rightEdgeEnabled: !sideStage.enabled
304 applicationManager: shell.applicationManager
305 rightEdgeDraggingAreaWidth: shell.edgeSize
306 normalApplicationY: shell.panelHeight
308 shown: true
309 function show() {
310 stages.show();
311 }
312 function hide() {
313 }
315 // FIXME: workaround the fact that focusing a main stage application
316 // raises its surface on top of all other surfaces including the ones
317 // that belong to side stage applications.
318 onFocusedApplicationChanged: {
319 if (focusedApplication && sideStage.focusedApplication && sideStage.fullyShown) {
320 shell.applicationManager.focusApplication(sideStage.focusedApplication);
321 }
322 }
323 }
325 SideStage {
326 id: sideStage
328 applicationManager: shell.applicationManager
329 rightEdgeDraggingAreaWidth: shell.edgeSize
330 normalApplicationY: shell.panelHeight
332 onShownChanged: {
333 if (!shown && mainStage.applications.count == 0) {
334 stages.hide();
335 }
336 }
337 // FIXME: when hiding the side stage, refocus the main stage
338 // application so that it goes in front of the side stage
339 // application and hides it
340 onFullyShownChanged: {
341 if (!fullyShown && stages.fullyShown && sideStage.focusedApplication != null) {
342 shell.applicationManager.focusApplication(mainStage.focusedApplication);
343 }
344 }
346 enabled: shell.width >= units.gu(60)
347 visible: enabled
348 fullyShown: stages.fullyShown && shown
349 && sideStage[sideStageRevealer.boundProperty] == sideStageRevealer.openedValue
350 shouldUseScreenshots: !fullyShown || mainStage.usingScreenshots || sideStageRevealer.pressed
352 available: !greeter.shown && !lockscreen.shown && enabled
353 hides: [launcher, panel.indicators]
354 shown: false
355 showAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.openedValue; easing.type: Easing.OutQuint }
356 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.closedValue; easing.type: Easing.OutQuint }
358 width: units.gu(40)
359 height: stages.height
360 handleExpanded: sideStageRevealer.pressed
361 }
363 Revealer {
364 id: sideStageRevealer
366 enabled: mainStage.applications.count > 0 && sideStage.applications.count > 0
367 && sideStage.available
368 direction: Qt.RightToLeft
369 openedValue: parent.width - sideStage.width
370 hintDisplacement: units.gu(3)
371 /* The size of the sidestage handle needs to be bigger than the
372 typical size used for edge detection otherwise it is really
373 hard to grab.
374 */
375 handleSize: sideStage.shown ? units.gu(4) : shell.edgeSize
376 closedValue: parent.width + sideStage.handleSizeCollapsed
377 target: sideStage
378 x: parent.width - width
379 width: sideStage.width + handleSize * 0.7
380 height: sideStage.height
381 orientation: Qt.Horizontal
382 }
384 DragHandle {
385 id: stagesDragHandle
387 anchors.top: parent.top
388 anchors.bottom: parent.bottom
389 anchors.right: parent.left
391 width: shell.edgeSize
392 direction: Direction.Leftwards
393 enabled: greeter.showProgress == 0 && edgeDemo.dashEnabled
394 property bool haveApps: mainStage.applications.count > 0 || sideStage.applications.count > 0
396 maxTotalDragDistance: haveApps ? parent.width : parent.width * 0.7
397 // Make autocompletion impossible when !haveApps
398 edgeDragEvaluator.minDragDistance: haveApps ? maxTotalDragDistance * 0.1 : Number.MAX_VALUE
399 }
400 }
401 }
403 Lockscreen {
404 id: lockscreen
405 objectName: "lockscreen"
407 readonly property int backgroundTopMargin: -panel.panelHeight
409 hides: [launcher, panel.indicators, hud]
410 shown: false
411 enabled: true
412 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
413 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
414 y: panel.panelHeight
415 x: required ? 0 : - width
416 width: parent.width
417 height: parent.height - panel.panelHeight
418 background: shell.background
420 onUnlocked: lockscreen.hide()
421 onCancel: greeter.show()
423 Component.onCompleted: {
424 if (LightDM.Users.count == 1) {
425 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
426 greeter.selected(0)
427 }
428 }
429 }
431 Connections {
432 target: LightDM.Greeter
434 onShowPrompt: {
435 if (LightDM.Users.count == 1) {
436 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
437 if (text == "PIN") {
438 lockscreen.alphaNumeric = false
439 } else {
440 lockscreen.alphaNumeric = true
441 }
442 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
443 lockscreen.show();
444 }
445 }
446 }
448 Greeter {
449 id: greeter
450 objectName: "greeter"
452 available: true
453 hides: [launcher, panel.indicators, hud]
454 shown: true
456 defaultBackground: shell.background
458 y: panel.panelHeight
459 width: parent.width
460 height: parent.height - panel.panelHeight
462 dragHandleWidth: shell.edgeSize
464 property var previousMainApp: null
465 property var previousSideApp: null
467 function removeApplicationFocus() {
468 greeter.previousMainApp = applicationManager.mainStageFocusedApplication;
469 greeter.previousSideApp = applicationManager.sideStageFocusedApplication;
470 applicationManager.unfocusCurrentApplication();
471 }
473 function restoreApplicationFocus() {
474 if (greeter.previousMainApp) {
475 applicationManager.focusApplication(greeter.previousMainApp);
476 greeter.previousMainApp = null;
477 }
478 if (greeter.previousSideApp) {
479 applicationManager.focusApplication(greeter.previousSideApp);
480 greeter.previousSideApp = null;
481 }
482 }
484 onShownChanged: {
485 if (shown) {
486 lockscreen.reset();
487 // If there is only one user, we start authenticating with that one here.
488 // If there are more users, the Greeter will handle that
489 if (LightDM.Users.count == 1) {
490 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
491 greeter.selected(0);
492 }
493 greeter.forceActiveFocus();
494 removeApplicationFocus();
495 } else {
496 restoreApplicationFocus();
497 }
498 }
500 onUnlocked: greeter.hide()
501 onSelected: {
502 // Update launcher items for new user
503 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
504 AccountsService.user = user;
505 LauncherModel.setUser(user);
506 }
508 onLeftTeaserPressedChanged: {
509 if (leftTeaserPressed) {
510 launcher.tease();
511 }
512 }
514 Connections {
515 target: applicationManager
516 ignoreUnknownSignals: true
517 // If any app is focused when greeter is open, it's due to a user action
518 // like a snap decision (say, an incoming call).
519 // TODO: these should be protected to only unlock for certain applications / certain usecases
520 // potentially only in connection with a notification.
521 onMainStageFocusedApplicationChanged: {
522 if (greeter.shown && applicationManager.mainStageFocusedApplication) {
523 greeter.previousMainApp = null // make way for new focused app
524 greeter.previousSideApp = null
525 greeter.hide()
526 }
527 }
528 onSideStageFocusedApplicationChanged: {
529 if (greeter.shown && applicationManager.sideStageFocusedApplication) {
530 greeter.previousMainApp = null // make way for new focused app
531 greeter.previousSideApp = null
532 greeter.hide()
533 }
534 }
535 }
536 }
538 InputFilterArea {
539 anchors.fill: parent
540 blockInput: !applicationFocused || greeter.shown || lockscreen.shown || launcher.shown
541 || panel.indicators.shown || hud.shown
542 }
544 Connections {
545 id: powerConnection
546 target: Powerd
548 onDisplayPowerStateChange: {
549 // We ignore any display-off signals when the proximity sensor
550 // is active. This usually indicates something like a phone call.
551 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
552 greeter.showNow();
553 }
555 // No reason to chew demo CPU when user isn't watching
556 if (status == Powerd.Off) {
557 edgeDemo.paused = true;
558 } else if (status == Powerd.On) {
559 edgeDemo.paused = false;
560 }
561 }
562 }
564 function showHome() {
565 var animate = !greeter.shown && !stages.shown
566 greeter.hide()
567 dash.setCurrentScope("home.scope", animate, false)
568 stages.hide()
569 }
571 Item {
572 id: overlay
574 anchors.fill: parent
576 Panel {
577 id: panel
578 anchors.fill: parent //because this draws indicator menus
579 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
580 indicators {
581 hides: [launcher]
582 available: edgeDemo.panelEnabled
583 contentEnabled: edgeDemo.panelContentEnabled
584 }
585 fullscreenMode: shell.fullscreenMode
586 searchVisible: !greeter.shown && !lockscreen.shown && dash.shown
588 InputFilterArea {
589 anchors {
590 top: parent.top
591 left: parent.left
592 right: parent.right
593 }
594 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
595 blockInput: true
596 }
597 }
599 Hud {
600 id: hud
602 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
603 height: parent.height
605 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown && edgeDemo.dashEnabled
606 shown: false
607 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
608 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
610 Connections {
611 target: shell.applicationManager
612 onMainStageFocusedApplicationChanged: hud.hide()
613 onSideStageFocusedApplicationChanged: hud.hide()
614 }
615 }
617 Revealer {
618 id: hudRevealer
620 enabled: hud.shown
621 width: hud.width
622 anchors.left: hud.left
623 height: parent.height
624 target: hud.revealerTarget
625 closedValue: height
626 openedValue: 0
627 direction: Qt.RightToLeft
628 orientation: Qt.Vertical
629 handleSize: hud.handleHeight
630 onCloseClicked: target.hide()
631 }
633 Bottombar {
634 id: bottombar
635 theHud: hud
636 anchors.fill: parent
637 enabled: hud.available
638 applicationIsOnForeground: applicationFocused
639 }
641 InputFilterArea {
642 blockInput: launcher.shown
643 anchors {
644 top: parent.top
645 bottom: parent.bottom
646 left: parent.left
647 }
648 width: launcher.width
649 }
651 Launcher {
652 id: launcher
654 readonly property bool dashSwipe: progress > 0
656 anchors.top: parent.top
657 anchors.bottom: parent.bottom
658 width: parent.width
659 dragAreaWidth: shell.edgeSize
660 available: (!greeter.shown || greeter.narrowMode) && edgeDemo.launcherEnabled
662 onDashItemSelected: showHome()
663 onDash: {
664 if (stages.shown) {
665 stages.hide();
666 launcher.hide();
667 }
668 }
669 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("applications.scope", false, true)
670 onLauncherApplicationSelected:{
671 greeter.hide()
672 shell.activateApplication(appId)
673 }
674 onShownChanged: {
675 if (shown) {
676 panel.indicators.hide()
677 hud.hide()
678 bottombar.hide()
679 }
680 }
681 }
683 Notifications {
684 id: notifications
686 model: NotificationBackend.Model
687 anchors {
688 top: parent.top
689 right: parent.right
690 bottom: parent.bottom
691 leftMargin: units.gu(1)
692 rightMargin: units.gu(1)
693 topMargin: panel.panelHeight + units.gu(1)
694 }
695 states: [
696 State {
697 name: "narrow"
698 when: overlay.width <= units.gu(60)
699 AnchorChanges { target: notifications; anchors.left: parent.left }
700 },
701 State {
702 name: "wide"
703 when: overlay.width > units.gu(60)
704 AnchorChanges { target: notifications; anchors.left: undefined }
705 PropertyChanges { target: notifications; width: units.gu(38) }
706 }
707 ]
709 InputFilterArea {
710 anchors { left: parent.left; right: parent.right }
711 height: parent.contentHeight
712 blockInput: height > 0
713 }
714 }
715 }
717 focus: true
718 onFocusChanged: if (!focus) forceActiveFocus();
720 InputFilterArea {
721 anchors {
722 top: parent.top
723 bottom: parent.bottom
724 left: parent.left
725 }
726 width: shell.edgeSize
727 blockInput: true
728 }
730 InputFilterArea {
731 anchors {
732 top: parent.top
733 bottom: parent.bottom
734 right: parent.right
735 }
736 width: shell.edgeSize
737 blockInput: true
738 }
740 Binding {
741 target: i18n
742 property: "domain"
743 value: "unity8"
744 }
746 OSKController {
747 anchors.topMargin: panel.panelHeight
748 anchors.fill: parent // as needs to know the geometry of the shell
749 }
751 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
752 MouseArea {
753 anchors.bottom: parent.bottom
754 anchors.left: parent.left
755 anchors.right: parent.right
756 height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
758 enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
759 }
761 Label {
762 anchors.centerIn: parent
763 visible: applicationManager.fake
764 text: "EARLY ALPHA\nNOT READY FOR USE"
765 color: "lightgrey"
766 opacity: 0.2
767 font.weight: Font.Black
768 horizontalAlignment: Text.AlignHCenter
769 verticalAlignment: Text.AlignVCenter
770 fontSizeMode: Text.Fit
771 rotation: -45
772 scale: Math.min(parent.width, parent.height) / width
773 }
775 EdgeDemo {
776 id: edgeDemo
777 greeter: greeter
778 launcher: launcher
779 dash: dash
780 indicators: panel.indicators
781 underlay: underlay
782 }
784 Connections {
785 target: SessionBroadcast
786 onShowHome: showHome()
787 }