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