on 3769f12 help sign in
unity8/Shell.qml
785 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 anchors.top: parent.top
655 anchors.bottom: parent.bottom
656 width: parent.width
657 dragAreaWidth: shell.edgeSize
658 available: (!greeter.shown || greeter.narrowMode) && edgeDemo.launcherEnabled
659 onDashItemSelected: showHome()
660 onDash: {
661 if (stages.shown) {
662 dash.setCurrentScope("applications.scope", true /* animate */, true /* reset */)
663 stages.hide();
664 launcher.hide();
665 }
666 }
667 onLauncherApplicationSelected:{
668 greeter.hide()
669 shell.activateApplication(appId)
670 }
671 onShownChanged: {
672 if (shown) {
673 panel.indicators.hide()
674 hud.hide()
675 bottombar.hide()
676 }
677 }
678 }
680 Notifications {
681 id: notifications
683 model: NotificationBackend.Model
684 anchors {
685 top: parent.top
686 right: parent.right
687 bottom: parent.bottom
688 leftMargin: units.gu(1)
689 rightMargin: units.gu(1)
690 topMargin: panel.panelHeight + units.gu(1)
691 }
692 states: [
693 State {
694 name: "narrow"
695 when: overlay.width <= units.gu(60)
696 AnchorChanges { target: notifications; anchors.left: parent.left }
697 },
698 State {
699 name: "wide"
700 when: overlay.width > units.gu(60)
701 AnchorChanges { target: notifications; anchors.left: undefined }
702 PropertyChanges { target: notifications; width: units.gu(38) }
703 }
704 ]
706 InputFilterArea {
707 anchors { left: parent.left; right: parent.right }
708 height: parent.contentHeight
709 blockInput: height > 0
710 }
711 }
712 }
714 focus: true
715 onFocusChanged: if (!focus) forceActiveFocus();
717 InputFilterArea {
718 anchors {
719 top: parent.top
720 bottom: parent.bottom
721 left: parent.left
722 }
723 width: shell.edgeSize
724 blockInput: true
725 }
727 InputFilterArea {
728 anchors {
729 top: parent.top
730 bottom: parent.bottom
731 right: parent.right
732 }
733 width: shell.edgeSize
734 blockInput: true
735 }
737 Binding {
738 target: i18n
739 property: "domain"
740 value: "unity8"
741 }
743 OSKController {
744 anchors.topMargin: panel.panelHeight
745 anchors.fill: parent // as needs to know the geometry of the shell
746 }
748 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
749 MouseArea {
750 anchors.bottom: parent.bottom
751 anchors.left: parent.left
752 anchors.right: parent.right
753 height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
755 enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
756 }
758 Label {
759 anchors.centerIn: parent
760 visible: applicationManager.fake
761 text: "EARLY ALPHA\nNOT READY FOR USE"
762 color: "lightgrey"
763 opacity: 0.2
764 font.weight: Font.Black
765 horizontalAlignment: Text.AlignHCenter
766 verticalAlignment: Text.AlignVCenter
767 fontSizeMode: Text.Fit
768 rotation: -45
769 scale: Math.min(parent.width, parent.height) / width
770 }
772 EdgeDemo {
773 id: edgeDemo
774 greeter: greeter
775 launcher: launcher
776 dash: dash
777 indicators: panel.indicators
778 underlay: underlay
779 }
781 Connections {
782 target: SessionBroadcast
783 onShowHome: showHome()
784 }