on ec2e174 help sign in
unity8/Shell.qml
765 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 fillMode: Image.PreserveAspectCrop
202 }
204 Rectangle {
205 anchors.fill: parent
206 color: "black"
207 opacity: dash.disappearingAnimationProgress
208 }
210 Dash {
211 id: dash
212 objectName: "dash"
214 available: !greeter.shown && !lockscreen.shown
215 hides: [stages, launcher, panel.indicators]
216 shown: disappearingAnimationProgress !== 1.0
217 enabled: disappearingAnimationProgress === 0.0 && edgeDemo.dashEnabled
218 // FIXME: unfocus all applications when going back to the dash
219 onEnabledChanged: {
220 if (enabled) {
221 shell.applicationManager.unfocusCurrentApplication()
222 }
223 }
225 anchors {
226 fill: parent
227 topMargin: panel.panelHeight
228 }
230 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
231 opacity: 1.0 - disappearingAnimationProgress
232 property real disappearingAnimationProgress: {
233 if (greeter.shown) {
234 return greeter.showProgress;
235 } else {
236 return stagesOuterContainer.showProgress;
237 }
238 }
240 // FIXME: only necessary because stagesOuterContainer.showProgress and
241 // greeterRevealer.animatedProgress are not animated
242 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
243 }
244 }
246 Item {
247 id: stagesOuterContainer
249 width: parent.width
250 height: parent.height
251 x: launcher.progress
252 Behavior on x {SmoothedAnimation{velocity: 600}}
254 property real showProgress:
255 MathLocal.clamp(1 - (x + stages.x) / shell.width, 0, 1)
257 Showable {
258 id: stages
259 objectName: "stages"
261 x: width
263 property bool fullyShown: shown && x == 0 && parent.x == 0
264 property bool fullyHidden: !shown && x == width
265 available: !greeter.shown
266 hides: [panel.indicators]
267 shown: false
268 opacity: 1.0
269 showAnimation: StandardAnimation { property: "x"; duration: 350; to: 0; easing.type: Easing.OutCubic }
270 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: width; easing.type: Easing.OutCubic }
272 width: parent.width
273 height: parent.height
275 // close the stages when no focused application remains
276 Connections {
277 target: shell.applicationManager
278 onMainStageFocusedApplicationChanged: stages.closeIfNoApplications()
279 onSideStageFocusedApplicationChanged: stages.closeIfNoApplications()
280 ignoreUnknownSignals: true
281 }
283 function closeIfNoApplications() {
284 if (!shell.applicationManager.mainStageFocusedApplication
285 && !shell.applicationManager.sideStageFocusedApplication
286 && shell.applicationManager.mainStageApplications.count == 0
287 && shell.applicationManager.sideStageApplications.count == 0) {
288 stages.hide();
289 }
290 }
292 // show the stages when an application gets the focus
293 Connections {
294 target: shell.applicationManager
295 onMainStageFocusedApplicationChanged: {
296 if (shell.applicationManager.mainStageFocusedApplication) {
297 mainStage.show();
298 stages.show();
299 }
300 }
301 onSideStageFocusedApplicationChanged: {
302 if (shell.applicationManager.sideStageFocusedApplication) {
303 sideStage.show();
304 stages.show();
305 }
306 }
307 ignoreUnknownSignals: true
308 }
310 Stage {
311 id: mainStage
313 anchors.fill: parent
314 fullyShown: stages.fullyShown
315 fullyHidden: stages.fullyHidden
316 shouldUseScreenshots: !fullyShown
317 rightEdgeEnabled: !sideStage.enabled
319 applicationManager: shell.applicationManager
320 rightEdgeDraggingAreaWidth: shell.edgeSize
321 normalApplicationY: shell.panelHeight
323 shown: true
324 function show() {
325 stages.show();
326 }
327 function hide() {
328 }
330 // FIXME: workaround the fact that focusing a main stage application
331 // raises its surface on top of all other surfaces including the ones
332 // that belong to side stage applications.
333 onFocusedApplicationChanged: {
334 if (focusedApplication && sideStage.focusedApplication && sideStage.fullyShown) {
335 shell.applicationManager.focusApplication(sideStage.focusedApplication);
336 }
337 }
338 }
340 SideStage {
341 id: sideStage
343 applicationManager: shell.applicationManager
344 rightEdgeDraggingAreaWidth: shell.edgeSize
345 normalApplicationY: shell.panelHeight
347 onShownChanged: {
348 if (!shown && mainStage.applications.count == 0) {
349 stages.hide();
350 }
351 }
352 // FIXME: when hiding the side stage, refocus the main stage
353 // application so that it goes in front of the side stage
354 // application and hides it
355 onFullyShownChanged: {
356 if (!fullyShown && stages.fullyShown && sideStage.focusedApplication != null) {
357 shell.applicationManager.focusApplication(mainStage.focusedApplication);
358 }
359 }
361 enabled: shell.width >= units.gu(60)
362 visible: enabled
363 fullyShown: stages.fullyShown && shown
364 && sideStage[sideStageRevealer.boundProperty] == sideStageRevealer.openedValue
365 shouldUseScreenshots: !fullyShown || mainStage.usingScreenshots || sideStageRevealer.pressed
367 available: !greeter.shown && !lockscreen.shown && enabled
368 hides: [launcher, panel.indicators]
369 shown: false
370 showAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.openedValue; easing.type: Easing.OutQuint }
371 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.closedValue; easing.type: Easing.OutQuint }
373 width: units.gu(40)
374 height: stages.height
375 handleExpanded: sideStageRevealer.pressed
376 }
378 Revealer {
379 id: sideStageRevealer
381 enabled: mainStage.applications.count > 0 && sideStage.applications.count > 0
382 && sideStage.available
383 direction: Qt.RightToLeft
384 openedValue: parent.width - sideStage.width
385 hintDisplacement: units.gu(3)
386 /* The size of the sidestage handle needs to be bigger than the
387 typical size used for edge detection otherwise it is really
388 hard to grab.
389 */
390 handleSize: sideStage.shown ? units.gu(4) : shell.edgeSize
391 closedValue: parent.width + sideStage.handleSizeCollapsed
392 target: sideStage
393 x: parent.width - width
394 width: sideStage.width + handleSize * 0.7
395 height: sideStage.height
396 orientation: Qt.Horizontal
397 }
399 DragHandle {
400 id: stagesDragHandle
402 anchors.top: parent.top
403 anchors.bottom: parent.bottom
404 anchors.right: parent.left
406 width: shell.edgeSize
407 direction: Direction.Leftwards
408 enabled: edgeDemo.dashEnabled
409 property bool haveApps: mainStage.applications.count > 0 || sideStage.applications.count > 0
411 maxTotalDragDistance: haveApps ? parent.width : parent.width * 0.7
412 // Make autocompletion impossible when !haveApps
413 edgeDragEvaluator.minDragDistance: haveApps ? maxTotalDragDistance * 0.1 : Number.MAX_VALUE
414 }
415 }
416 }
418 Lockscreen {
419 id: lockscreen
420 hides: [launcher, panel.indicators, hud]
421 shown: false
422 enabled: true
423 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
424 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
425 y: panel.panelHeight
426 x: required ? 0 : - width
427 width: parent.width
428 height: parent.height - panel.panelHeight
429 background: shell.background
431 onUnlocked: lockscreen.hide()
432 onCancel: greeter.show()
434 Component.onCompleted: {
435 if (LightDM.Users.count == 1) {
436 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
437 greeter.selected(0)
438 }
439 }
440 }
442 Connections {
443 target: LightDM.Greeter
445 onShowPrompt: {
446 if (LightDM.Users.count == 1) {
447 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
448 if (text == "PIN") {
449 lockscreen.alphaNumeric = false
450 } else {
451 lockscreen.alphaNumeric = true
452 }
453 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
454 lockscreen.show();
455 }
456 }
457 }
459 Greeter {
460 id: greeter
461 objectName: "greeter"
463 available: true
464 hides: [launcher, panel.indicators, hud]
465 shown: true
467 defaultBackground: shell.background
469 y: panel.panelHeight
470 width: parent.width
471 height: parent.height - panel.panelHeight
473 dragHandleWidth: shell.edgeSize
475 onShownChanged: {
476 if (shown) {
477 lockscreen.reset();
478 // If there is only one user, we start authenticating with that one here.
479 // If there are more users, the Greeter will handle that
480 if (LightDM.Users.count == 1) {
481 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
482 greeter.selected(0);
483 }
484 greeter.forceActiveFocus();
485 }
486 }
488 onUnlocked: greeter.hide()
489 onSelected: {
490 // Update launcher items for new user
491 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
492 AccountsService.user = user;
493 LauncherModel.setUser(user);
494 }
496 onLeftTeaserPressedChanged: {
497 if (leftTeaserPressed) {
498 launcher.tease();
499 }
500 }
501 }
503 InputFilterArea {
504 anchors.fill: parent
505 blockInput: !applicationFocused || greeter.shown || lockscreen.shown || launcher.shown
506 || panel.indicators.shown || hud.shown
507 }
509 Connections {
510 id: powerConnection
511 target: Powerd
513 property var previousMainApp: null
514 property var previousSideApp: null
516 function setFocused(focused) {
517 if (!focused) {
518 powerConnection.previousMainApp = applicationManager.mainStageFocusedApplication;
519 powerConnection.previousSideApp = applicationManager.sideStageFocusedApplication;
520 applicationManager.unfocusCurrentApplication();
521 } else {
522 if (powerConnection.previousMainApp) {
523 applicationManager.focusApplication(powerConnection.previousMainApp);
524 powerConnection.previousMainApp = null;
525 }
526 if (powerConnection.previousSideApp) {
527 applicationManager.focusApplication(powerConnection.previousSideApp);
528 powerConnection.previousSideApp = null;
529 }
530 }
531 }
533 onDisplayPowerStateChange: {
534 // We ignore any display-off signals when the proximity sensor
535 // is active. This usually indicates something like a phone call.
536 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
537 powerConnection.setFocused(false);
538 greeter.show();
539 } else if (status == Powerd.On) {
540 powerConnection.setFocused(true);
541 }
543 // No reason to chew demo CPU when user isn't watching
544 if (status == Powerd.Off) {
545 edgeDemo.paused = true;
546 } else if (status == Powerd.On) {
547 edgeDemo.paused = false;
548 }
549 }
550 }
552 function showHome() {
553 greeter.hide()
554 // Animate if moving between application and dash
555 if (!stages.shown) {
556 dash.setCurrentScope("home.scope", true, false)
557 } else {
558 dash.setCurrentScope("home.scope", false, false)
559 }
560 stages.hide()
561 }
563 Item {
564 id: overlay
566 anchors.fill: parent
568 Panel {
569 id: panel
570 anchors.fill: parent //because this draws indicator menus
571 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
572 indicators {
573 hides: [launcher]
574 available: edgeDemo.panelEnabled
575 contentEnabled: edgeDemo.panelContentEnabled
576 }
577 fullscreenMode: shell.fullscreenMode
578 searchVisible: !greeter.shown && !lockscreen.shown
580 InputFilterArea {
581 anchors {
582 top: parent.top
583 left: parent.left
584 right: parent.right
585 }
586 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
587 blockInput: true
588 }
589 }
591 Hud {
592 id: hud
594 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
595 height: parent.height
597 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown && edgeDemo.dashEnabled
598 shown: false
599 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
600 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
602 Connections {
603 target: shell.applicationManager
604 onMainStageFocusedApplicationChanged: hud.hide()
605 onSideStageFocusedApplicationChanged: hud.hide()
606 }
607 }
609 Revealer {
610 id: hudRevealer
612 enabled: hud.shown
613 width: hud.width
614 anchors.left: hud.left
615 height: parent.height
616 target: hud.revealerTarget
617 closedValue: height
618 openedValue: 0
619 direction: Qt.RightToLeft
620 orientation: Qt.Vertical
621 handleSize: hud.handleHeight
622 onCloseClicked: target.hide()
623 }
625 Bottombar {
626 id: bottombar
627 theHud: hud
628 anchors.fill: parent
629 enabled: hud.available
630 applicationIsOnForeground: applicationFocused
631 }
633 InputFilterArea {
634 blockInput: launcher.shown
635 anchors {
636 top: parent.top
637 bottom: parent.bottom
638 left: parent.left
639 }
640 width: launcher.width
641 }
643 Launcher {
644 id: launcher
646 anchors.top: parent.top
647 anchors.bottom: parent.bottom
648 width: parent.width
649 dragAreaWidth: shell.edgeSize
650 available: (!greeter.shown || greeter.narrowMode) && edgeDemo.launcherEnabled
651 onDashItemSelected: showHome()
652 onDash: {
653 if (stages.shown) {
654 dash.setCurrentScope("applications.scope", true /* animate */, true /* reset */)
655 stages.hide();
656 launcher.hide();
657 }
658 }
659 onLauncherApplicationSelected:{
660 greeter.hide()
661 shell.activateApplication(appId)
662 }
663 onShownChanged: {
664 if (shown) {
665 panel.indicators.hide()
666 hud.hide()
667 bottombar.hide()
668 }
669 }
670 }
672 Notifications {
673 id: notifications
675 model: NotificationBackend.Model
676 anchors {
677 top: parent.top
678 right: parent.right
679 bottom: parent.bottom
680 leftMargin: units.gu(1)
681 rightMargin: units.gu(1)
682 topMargin: panel.panelHeight + units.gu(1)
683 }
684 states: [
685 State {
686 name: "narrow"
687 when: overlay.width <= units.gu(60)
688 AnchorChanges { target: notifications; anchors.left: parent.left }
689 },
690 State {
691 name: "wide"
692 when: overlay.width > units.gu(60)
693 AnchorChanges { target: notifications; anchors.left: undefined }
694 PropertyChanges { target: notifications; width: units.gu(38) }
695 }
696 ]
697 }
698 }
700 focus: true
702 InputFilterArea {
703 anchors {
704 top: parent.top
705 bottom: parent.bottom
706 left: parent.left
707 }
708 width: shell.edgeSize
709 blockInput: true
710 }
712 InputFilterArea {
713 anchors {
714 top: parent.top
715 bottom: parent.bottom
716 right: parent.right
717 }
718 width: shell.edgeSize
719 blockInput: true
720 }
722 Binding {
723 target: i18n
724 property: "domain"
725 value: "unity8"
726 }
728 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
729 MouseArea {
730 anchors.bottom: parent.bottom
731 anchors.left: parent.left
732 anchors.right: parent.right
733 height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
735 enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
736 }
738 Label {
739 anchors.centerIn: parent
740 visible: applicationManager.fake
741 text: "EARLY ALPHA\nNOT READY FOR USE"
742 color: "lightgrey"
743 opacity: 0.2
744 font.weight: Font.Black
745 horizontalAlignment: Text.AlignHCenter
746 verticalAlignment: Text.AlignVCenter
747 fontSizeMode: Text.Fit
748 rotation: -45
749 scale: Math.min(parent.width, parent.height) / width
750 }
752 EdgeDemo {
753 id: edgeDemo
754 greeter: greeter
755 launcher: launcher
756 dash: dash
757 indicators: panel.indicators
758 underlay: underlay
759 }
761 Connections {
762 target: SessionBroadcast
763 onShowHome: showHome()
764 }