on 16fd5bf help sign in
unity8/Shell.qml
763 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 theHud: hud
627 anchors.fill: parent
628 enabled: hud.available
629 applicationIsOnForeground: applicationFocused
630 }
632 InputFilterArea {
633 blockInput: launcher.shown
634 anchors {
635 top: parent.top
636 bottom: parent.bottom
637 left: parent.left
638 }
639 width: launcher.width
640 }
642 Launcher {
643 id: launcher
645 anchors.top: parent.top
646 anchors.bottom: parent.bottom
647 width: parent.width
648 dragAreaWidth: shell.edgeSize
649 available: (!greeter.shown || greeter.narrowMode) && edgeDemo.launcherEnabled
650 onDashItemSelected: showHome()
651 onDash: {
652 if (stages.shown) {
653 dash.setCurrentScope("applications.scope", true, false)
654 stages.hide();
655 launcher.hide();
656 }
657 }
658 onLauncherApplicationSelected:{
659 greeter.hide()
660 shell.activateApplication(appId)
661 }
662 onShownChanged: {
663 if (shown) {
664 panel.indicators.hide()
665 hud.hide()
666 }
667 }
668 }
670 Notifications {
671 id: notifications
673 model: NotificationBackend.Model
674 anchors {
675 top: parent.top
676 right: parent.right
677 bottom: parent.bottom
678 leftMargin: units.gu(1)
679 rightMargin: units.gu(1)
680 topMargin: panel.panelHeight + units.gu(1)
681 }
682 states: [
683 State {
684 name: "narrow"
685 when: overlay.width <= units.gu(60)
686 AnchorChanges { target: notifications; anchors.left: parent.left }
687 },
688 State {
689 name: "wide"
690 when: overlay.width > units.gu(60)
691 AnchorChanges { target: notifications; anchors.left: undefined }
692 PropertyChanges { target: notifications; width: units.gu(38) }
693 }
694 ]
695 }
696 }
698 focus: true
700 InputFilterArea {
701 anchors {
702 top: parent.top
703 bottom: parent.bottom
704 left: parent.left
705 }
706 width: shell.edgeSize
707 blockInput: true
708 }
710 InputFilterArea {
711 anchors {
712 top: parent.top
713 bottom: parent.bottom
714 right: parent.right
715 }
716 width: shell.edgeSize
717 blockInput: true
718 }
720 Binding {
721 target: i18n
722 property: "domain"
723 value: "unity8"
724 }
726 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
727 MouseArea {
728 anchors.bottom: parent.bottom
729 anchors.left: parent.left
730 anchors.right: parent.right
731 height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
733 enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
734 }
736 Label {
737 anchors.centerIn: parent
738 visible: applicationManager.fake
739 text: "EARLY ALPHA\nNOT READY FOR USE"
740 color: "lightgrey"
741 opacity: 0.2
742 font.weight: Font.Black
743 horizontalAlignment: Text.AlignHCenter
744 verticalAlignment: Text.AlignVCenter
745 fontSizeMode: Text.Fit
746 rotation: -45
747 scale: Math.min(parent.width, parent.height) / width
748 }
750 EdgeDemo {
751 id: edgeDemo
752 greeter: greeter
753 launcher: launcher
754 dash: dash
755 indicators: panel.indicators
756 underlay: underlay
757 }
759 Connections {
760 target: SessionBroadcast
761 onShowHome: showHome()
762 }