on d851997 help sign in
unity8/Shell.qml
784 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 hides: [launcher, panel.indicators, hud]
406 shown: false
407 enabled: true
408 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
409 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
410 y: panel.panelHeight
411 x: required ? 0 : - width
412 width: parent.width
413 height: parent.height - panel.panelHeight
414 background: shell.background
416 onUnlocked: lockscreen.hide()
417 onCancel: greeter.show()
419 Component.onCompleted: {
420 if (LightDM.Users.count == 1) {
421 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
422 greeter.selected(0)
423 }
424 }
425 }
427 Connections {
428 target: LightDM.Greeter
430 onShowPrompt: {
431 if (LightDM.Users.count == 1) {
432 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
433 if (text == "PIN") {
434 lockscreen.alphaNumeric = false
435 } else {
436 lockscreen.alphaNumeric = true
437 }
438 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
439 lockscreen.show();
440 }
441 }
442 }
444 Greeter {
445 id: greeter
446 objectName: "greeter"
448 available: true
449 hides: [launcher, panel.indicators, hud]
450 shown: true
452 defaultBackground: shell.background
454 y: panel.panelHeight
455 width: parent.width
456 height: parent.height - panel.panelHeight
458 dragHandleWidth: shell.edgeSize
460 property var previousMainApp: null
461 property var previousSideApp: null
463 function removeApplicationFocus() {
464 greeter.previousMainApp = applicationManager.mainStageFocusedApplication;
465 greeter.previousSideApp = applicationManager.sideStageFocusedApplication;
466 applicationManager.unfocusCurrentApplication();
467 }
469 function restoreApplicationFocus() {
470 if (greeter.previousMainApp) {
471 applicationManager.focusApplication(greeter.previousMainApp);
472 greeter.previousMainApp = null;
473 }
474 if (greeter.previousSideApp) {
475 applicationManager.focusApplication(greeter.previousSideApp);
476 greeter.previousSideApp = null;
477 }
478 }
480 onShownChanged: {
481 if (shown) {
482 lockscreen.reset();
483 // If there is only one user, we start authenticating with that one here.
484 // If there are more users, the Greeter will handle that
485 if (LightDM.Users.count == 1) {
486 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
487 greeter.selected(0);
488 }
489 greeter.forceActiveFocus();
490 removeApplicationFocus();
491 } else {
492 restoreApplicationFocus();
493 }
494 }
496 onUnlocked: greeter.hide()
497 onSelected: {
498 // Update launcher items for new user
499 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
500 AccountsService.user = user;
501 LauncherModel.setUser(user);
502 }
504 onLeftTeaserPressedChanged: {
505 if (leftTeaserPressed) {
506 launcher.tease();
507 }
508 }
510 Connections {
511 target: applicationManager
512 ignoreUnknownSignals: true
513 // If any app is focused when greeter is open, it's due to a user action
514 // like a snap decision (say, an incoming call).
515 // TODO: these should be protected to only unlock for certain applications / certain usecases
516 // potentially only in connection with a notification.
517 onMainStageFocusedApplicationChanged: {
518 if (greeter.shown && applicationManager.mainStageFocusedApplication) {
519 greeter.previousMainApp = null // make way for new focused app
520 greeter.previousSideApp = null
521 greeter.hide()
522 }
523 }
524 onSideStageFocusedApplicationChanged: {
525 if (greeter.shown && applicationManager.sideStageFocusedApplication) {
526 greeter.previousMainApp = null // make way for new focused app
527 greeter.previousSideApp = null
528 greeter.hide()
529 }
530 }
531 }
532 }
534 InputFilterArea {
535 anchors.fill: parent
536 blockInput: !applicationFocused || greeter.shown || lockscreen.shown || launcher.shown
537 || panel.indicators.shown || hud.shown
538 }
540 Connections {
541 id: powerConnection
542 target: Powerd
544 onDisplayPowerStateChange: {
545 // We ignore any display-off signals when the proximity sensor
546 // is active. This usually indicates something like a phone call.
547 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
548 greeter.showNow();
549 }
551 // No reason to chew demo CPU when user isn't watching
552 if (status == Powerd.Off) {
553 edgeDemo.paused = true;
554 } else if (status == Powerd.On) {
555 edgeDemo.paused = false;
556 }
557 }
558 }
560 function showHome() {
561 greeter.hide()
562 // Animate if moving between application and dash
563 if (!stages.shown) {
564 dash.setCurrentScope("home.scope", true, false)
565 } else {
566 dash.setCurrentScope("home.scope", false, false)
567 }
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
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.fill: parent // as needs to know the geometry of the shell
745 }
747 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
748 MouseArea {
749 anchors.bottom: parent.bottom
750 anchors.left: parent.left
751 anchors.right: parent.right
752 height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
754 enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
755 }
757 Label {
758 anchors.centerIn: parent
759 visible: applicationManager.fake
760 text: "EARLY ALPHA\nNOT READY FOR USE"
761 color: "lightgrey"
762 opacity: 0.2
763 font.weight: Font.Black
764 horizontalAlignment: Text.AlignHCenter
765 verticalAlignment: Text.AlignVCenter
766 fontSizeMode: Text.Fit
767 rotation: -45
768 scale: Math.min(parent.width, parent.height) / width
769 }
771 EdgeDemo {
772 id: edgeDemo
773 greeter: greeter
774 launcher: launcher
775 dash: dash
776 indicators: panel.indicators
777 underlay: underlay
778 }
780 Connections {
781 target: SessionBroadcast
782 onShowHome: showHome()
783 }