on b778dca help sign in
unity8/Shell.qml
781 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 var animate = !greeter.shown && !stages.shown
562 greeter.hide()
563 dash.setCurrentScope("home.scope", animate, false)
564 stages.hide()
565 }
567 Item {
568 id: overlay
570 anchors.fill: parent
572 Panel {
573 id: panel
574 anchors.fill: parent //because this draws indicator menus
575 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
576 indicators {
577 hides: [launcher]
578 available: edgeDemo.panelEnabled
579 contentEnabled: edgeDemo.panelContentEnabled
580 }
581 fullscreenMode: shell.fullscreenMode
582 searchVisible: !greeter.shown && !lockscreen.shown && dash.shown
584 InputFilterArea {
585 anchors {
586 top: parent.top
587 left: parent.left
588 right: parent.right
589 }
590 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
591 blockInput: true
592 }
593 }
595 Hud {
596 id: hud
598 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
599 height: parent.height
601 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown && edgeDemo.dashEnabled
602 shown: false
603 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
604 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
606 Connections {
607 target: shell.applicationManager
608 onMainStageFocusedApplicationChanged: hud.hide()
609 onSideStageFocusedApplicationChanged: hud.hide()
610 }
611 }
613 Revealer {
614 id: hudRevealer
616 enabled: hud.shown
617 width: hud.width
618 anchors.left: hud.left
619 height: parent.height
620 target: hud.revealerTarget
621 closedValue: height
622 openedValue: 0
623 direction: Qt.RightToLeft
624 orientation: Qt.Vertical
625 handleSize: hud.handleHeight
626 onCloseClicked: target.hide()
627 }
629 Bottombar {
630 id: bottombar
631 theHud: hud
632 anchors.fill: parent
633 enabled: hud.available
634 applicationIsOnForeground: applicationFocused
635 }
637 InputFilterArea {
638 blockInput: launcher.shown
639 anchors {
640 top: parent.top
641 bottom: parent.bottom
642 left: parent.left
643 }
644 width: launcher.width
645 }
647 Launcher {
648 id: launcher
650 anchors.top: parent.top
651 anchors.bottom: parent.bottom
652 width: parent.width
653 dragAreaWidth: shell.edgeSize
654 available: (!greeter.shown || greeter.narrowMode) && edgeDemo.launcherEnabled
655 onDashItemSelected: showHome()
656 onDash: {
657 if (stages.shown) {
658 dash.setCurrentScope("applications.scope", true /* animate */, true /* reset */)
659 stages.hide();
660 launcher.hide();
661 }
662 }
663 onLauncherApplicationSelected:{
664 greeter.hide()
665 shell.activateApplication(appId)
666 }
667 onShownChanged: {
668 if (shown) {
669 panel.indicators.hide()
670 hud.hide()
671 bottombar.hide()
672 }
673 }
674 }
676 Notifications {
677 id: notifications
679 model: NotificationBackend.Model
680 anchors {
681 top: parent.top
682 right: parent.right
683 bottom: parent.bottom
684 leftMargin: units.gu(1)
685 rightMargin: units.gu(1)
686 topMargin: panel.panelHeight + units.gu(1)
687 }
688 states: [
689 State {
690 name: "narrow"
691 when: overlay.width <= units.gu(60)
692 AnchorChanges { target: notifications; anchors.left: parent.left }
693 },
694 State {
695 name: "wide"
696 when: overlay.width > units.gu(60)
697 AnchorChanges { target: notifications; anchors.left: undefined }
698 PropertyChanges { target: notifications; width: units.gu(38) }
699 }
700 ]
702 InputFilterArea {
703 anchors { left: parent.left; right: parent.right }
704 height: parent.contentHeight
705 blockInput: height > 0
706 }
707 }
708 }
710 focus: true
711 onFocusChanged: if (!focus) forceActiveFocus();
713 InputFilterArea {
714 anchors {
715 top: parent.top
716 bottom: parent.bottom
717 left: parent.left
718 }
719 width: shell.edgeSize
720 blockInput: true
721 }
723 InputFilterArea {
724 anchors {
725 top: parent.top
726 bottom: parent.bottom
727 right: parent.right
728 }
729 width: shell.edgeSize
730 blockInput: true
731 }
733 Binding {
734 target: i18n
735 property: "domain"
736 value: "unity8"
737 }
739 OSKController {
740 anchors.topMargin: panel.panelHeight
741 anchors.fill: parent // as needs to know the geometry of the shell
742 }
744 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
745 MouseArea {
746 anchors.bottom: parent.bottom
747 anchors.left: parent.left
748 anchors.right: parent.right
749 height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
751 enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
752 }
754 Label {
755 anchors.centerIn: parent
756 visible: applicationManager.fake
757 text: "EARLY ALPHA\nNOT READY FOR USE"
758 color: "lightgrey"
759 opacity: 0.2
760 font.weight: Font.Black
761 horizontalAlignment: Text.AlignHCenter
762 verticalAlignment: Text.AlignVCenter
763 fontSizeMode: Text.Fit
764 rotation: -45
765 scale: Math.min(parent.width, parent.height) / width
766 }
768 EdgeDemo {
769 id: edgeDemo
770 greeter: greeter
771 launcher: launcher
772 dash: dash
773 indicators: panel.indicators
774 underlay: underlay
775 }
777 Connections {
778 target: SessionBroadcast
779 onShowHome: showHome()
780 }