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