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