on 04462d0 help sign in
unity8/Shell.qml
772 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 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 VolumeControl {
164 id: volumeControl
165 }
167 Keys.onVolumeUpPressed: volumeControl.volumeUp()
168 Keys.onVolumeDownPressed: volumeControl.volumeDown()
170 Item {
171 id: underlay
172 objectName: "underlay"
173 anchors.fill: parent
175 // Whether the underlay is fully covered by opaque UI elements.
176 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
178 readonly property bool applicationRunning: ((mainStage.applications && mainStage.applications.count > 0)
179 || (sideStage.applications && sideStage.applications.count > 0))
181 // Whether the user should see the topmost application surface (if there's one at all).
182 readonly property bool applicationSurfaceShouldBeSeen: applicationRunning && !stages.fullyHidden
183 && !mainStage.usingScreenshots // but want sideStage animating over app surface
187 // NB! Application surfaces are stacked behing the shell one. So they can only be seen by the user
188 // through the translucent parts of the shell surface.
189 visible: !fullyCovered && !applicationSurfaceShouldBeSeen
191 CrossFadeImage {
192 id: backgroundImage
193 objectName: "backgroundImage"
195 anchors.fill: parent
196 source: shell.background
197 fillMode: Image.PreserveAspectCrop
198 }
200 Rectangle {
201 anchors.fill: parent
202 color: "black"
203 opacity: dash.disappearingAnimationProgress
204 }
206 Dash {
207 id: dash
208 objectName: "dash"
210 available: !greeter.shown && !lockscreen.shown
211 hides: [stages, launcher, panel.indicators]
212 shown: disappearingAnimationProgress !== 1.0
213 enabled: disappearingAnimationProgress === 0.0 && edgeDemo.dashEnabled
214 // FIXME: unfocus all applications when going back to the dash
215 onEnabledChanged: {
216 if (enabled) {
217 shell.applicationManager.unfocusCurrentApplication()
218 }
219 }
221 anchors {
222 fill: parent
223 topMargin: panel.panelHeight
224 }
226 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
227 opacity: 1.0 - disappearingAnimationProgress
228 property real disappearingAnimationProgress: {
229 if (greeter.shown) {
230 return greeter.showProgress;
231 } else {
232 return stagesOuterContainer.showProgress;
233 }
234 }
236 // FIXME: only necessary because stagesOuterContainer.showProgress and
237 // greeterRevealer.animatedProgress are not animated
238 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
239 }
240 }
242 Item {
243 id: stagesOuterContainer
245 width: parent.width
246 height: parent.height
247 x: launcher.progress
248 Behavior on x {SmoothedAnimation{velocity: 600}}
250 property real showProgress:
251 MathLocal.clamp(1 - (x + stages.x) / shell.width, 0, 1)
253 Showable {
254 id: stages
255 objectName: "stages"
257 x: width
259 property bool fullyShown: shown && x == 0 && parent.x == 0
260 property bool fullyHidden: !shown && x == width
261 available: !greeter.shown
262 hides: [panel.indicators]
263 shown: false
264 opacity: 1.0
265 showAnimation: StandardAnimation { property: "x"; duration: 350; to: 0; easing.type: Easing.OutCubic }
266 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: width; easing.type: Easing.OutCubic }
268 width: parent.width
269 height: parent.height
271 // close the stages when no focused application remains
272 Connections {
273 target: shell.applicationManager
274 onMainStageFocusedApplicationChanged: stages.closeIfNoApplications()
275 onSideStageFocusedApplicationChanged: stages.closeIfNoApplications()
276 ignoreUnknownSignals: true
277 }
279 function closeIfNoApplications() {
280 if (!shell.applicationManager.mainStageFocusedApplication
281 && !shell.applicationManager.sideStageFocusedApplication
282 && shell.applicationManager.mainStageApplications.count == 0
283 && shell.applicationManager.sideStageApplications.count == 0) {
284 stages.hide();
285 }
286 }
288 // show the stages when an application gets the focus
289 Connections {
290 target: shell.applicationManager
291 onMainStageFocusedApplicationChanged: {
292 if (shell.applicationManager.mainStageFocusedApplication) {
293 mainStage.show();
294 stages.show();
295 }
296 }
297 onSideStageFocusedApplicationChanged: {
298 if (shell.applicationManager.sideStageFocusedApplication) {
299 sideStage.show();
300 stages.show();
301 }
302 }
303 ignoreUnknownSignals: true
304 }
306 Stage {
307 id: mainStage
309 anchors.fill: parent
310 fullyShown: stages.fullyShown
311 fullyHidden: stages.fullyHidden
312 shouldUseScreenshots: !fullyShown
313 rightEdgeEnabled: !sideStage.enabled
315 applicationManager: shell.applicationManager
316 rightEdgeDraggingAreaWidth: shell.edgeSize
317 normalApplicationY: shell.panelHeight
319 shown: true
320 function show() {
321 stages.show();
322 }
323 function hide() {
324 }
326 // FIXME: workaround the fact that focusing a main stage application
327 // raises its surface on top of all other surfaces including the ones
328 // that belong to side stage applications.
329 onFocusedApplicationChanged: {
330 if (focusedApplication && sideStage.focusedApplication && sideStage.fullyShown) {
331 shell.applicationManager.focusApplication(sideStage.focusedApplication);
332 }
333 }
334 }
336 SideStage {
337 id: sideStage
339 applicationManager: shell.applicationManager
340 rightEdgeDraggingAreaWidth: shell.edgeSize
341 normalApplicationY: shell.panelHeight
343 onShownChanged: {
344 if (!shown && mainStage.applications.count == 0) {
345 stages.hide();
346 }
347 }
348 // FIXME: when hiding the side stage, refocus the main stage
349 // application so that it goes in front of the side stage
350 // application and hides it
351 onFullyShownChanged: {
352 if (!fullyShown && stages.fullyShown && sideStage.focusedApplication != null) {
353 shell.applicationManager.focusApplication(mainStage.focusedApplication);
354 }
355 }
357 enabled: shell.width >= units.gu(60)
358 visible: enabled
359 fullyShown: stages.fullyShown && shown
360 && sideStage[sideStageRevealer.boundProperty] == sideStageRevealer.openedValue
361 shouldUseScreenshots: !fullyShown || mainStage.usingScreenshots || sideStageRevealer.pressed
363 available: !greeter.shown && !lockscreen.shown && enabled
364 hides: [launcher, panel.indicators]
365 shown: false
366 showAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.openedValue; easing.type: Easing.OutQuint }
367 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.closedValue; easing.type: Easing.OutQuint }
369 width: units.gu(40)
370 height: stages.height
371 handleExpanded: sideStageRevealer.pressed
372 }
374 Revealer {
375 id: sideStageRevealer
377 enabled: mainStage.applications.count > 0 && sideStage.applications.count > 0
378 && sideStage.available
379 direction: Qt.RightToLeft
380 openedValue: parent.width - sideStage.width
381 hintDisplacement: units.gu(3)
382 /* The size of the sidestage handle needs to be bigger than the
383 typical size used for edge detection otherwise it is really
384 hard to grab.
385 */
386 handleSize: sideStage.shown ? units.gu(4) : shell.edgeSize
387 closedValue: parent.width + sideStage.handleSizeCollapsed
388 target: sideStage
389 x: parent.width - width
390 width: sideStage.width + handleSize * 0.7
391 height: sideStage.height
392 orientation: Qt.Horizontal
393 }
395 DragHandle {
396 id: stagesDragHandle
398 anchors.top: parent.top
399 anchors.bottom: parent.bottom
400 anchors.right: parent.left
402 width: shell.edgeSize
403 direction: Direction.Leftwards
404 enabled: edgeDemo.dashEnabled
405 property bool haveApps: mainStage.applications.count > 0 || sideStage.applications.count > 0
407 maxTotalDragDistance: haveApps ? parent.width : parent.width * 0.7
408 // Make autocompletion impossible when !haveApps
409 edgeDragEvaluator.minDragDistance: haveApps ? maxTotalDragDistance * 0.1 : Number.MAX_VALUE
410 }
411 }
412 }
414 Lockscreen {
415 id: lockscreen
416 hides: [launcher, panel.indicators, hud]
417 shown: false
418 enabled: true
419 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
420 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
421 y: panel.panelHeight
422 x: required ? 0 : - width
423 width: parent.width
424 height: parent.height - panel.panelHeight
425 background: shell.background
427 onUnlocked: lockscreen.hide()
428 onCancel: greeter.show()
430 Component.onCompleted: {
431 if (LightDM.Users.count == 1) {
432 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
433 greeter.selected(0)
434 }
435 }
436 }
438 Connections {
439 target: LightDM.Greeter
441 onShowPrompt: {
442 if (LightDM.Users.count == 1) {
443 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
444 if (text == "PIN") {
445 lockscreen.alphaNumeric = false
446 } else {
447 lockscreen.alphaNumeric = true
448 }
449 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
450 lockscreen.show();
451 }
452 }
453 }
455 Greeter {
456 id: greeter
457 objectName: "greeter"
459 available: true
460 hides: [launcher, panel.indicators, hud]
461 shown: true
463 defaultBackground: shell.background
465 y: panel.panelHeight
466 width: parent.width
467 height: parent.height - panel.panelHeight
469 dragHandleWidth: shell.edgeSize
471 onShownChanged: {
472 if (shown) {
473 lockscreen.reset();
474 // If there is only one user, we start authenticating with that one here.
475 // If there are more users, the Greeter will handle that
476 if (LightDM.Users.count == 1) {
477 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
478 greeter.selected(0);
479 }
480 greeter.forceActiveFocus();
481 }
482 }
484 onUnlocked: greeter.hide()
485 onSelected: {
486 // Update launcher items for new user
487 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
488 AccountsService.user = user;
489 LauncherModel.setUser(user);
490 }
492 onLeftTeaserPressedChanged: {
493 if (leftTeaserPressed) {
494 launcher.tease();
495 }
496 }
497 }
499 InputFilterArea {
500 anchors.fill: parent
501 blockInput: !applicationFocused || greeter.shown || lockscreen.shown || launcher.shown
502 || panel.indicators.shown || hud.shown
503 }
505 Connections {
506 id: powerConnection
507 target: Powerd
509 property var previousMainApp: null
510 property var previousSideApp: null
512 function setFocused(focused) {
513 if (!focused) {
514 powerConnection.previousMainApp = applicationManager.mainStageFocusedApplication;
515 powerConnection.previousSideApp = applicationManager.sideStageFocusedApplication;
516 applicationManager.unfocusCurrentApplication();
517 } else {
518 if (powerConnection.previousMainApp) {
519 applicationManager.focusApplication(powerConnection.previousMainApp);
520 powerConnection.previousMainApp = null;
521 }
522 if (powerConnection.previousSideApp) {
523 applicationManager.focusApplication(powerConnection.previousSideApp);
524 powerConnection.previousSideApp = null;
525 }
526 }
527 }
529 onDisplayPowerStateChange: {
530 // We ignore any display-off signals when the proximity sensor
531 // is active. This usually indicates something like a phone call.
532 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
533 powerConnection.setFocused(false);
534 greeter.showNow();
535 } else if (status == Powerd.On) {
536 powerConnection.setFocused(true);
537 }
539 // No reason to chew demo CPU when user isn't watching
540 if (status == Powerd.Off) {
541 edgeDemo.paused = true;
542 } else if (status == Powerd.On) {
543 edgeDemo.paused = false;
544 }
545 }
546 }
548 function showHome() {
549 greeter.hide()
550 // Animate if moving between application and dash
551 if (!stages.shown) {
552 dash.setCurrentScope("home.scope", true, false)
553 } else {
554 dash.setCurrentScope("home.scope", false, false)
555 }
556 stages.hide()
557 }
559 Item {
560 id: overlay
562 anchors.fill: parent
564 Panel {
565 id: panel
566 anchors.fill: parent //because this draws indicator menus
567 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
568 indicators {
569 hides: [launcher]
570 available: edgeDemo.panelEnabled
571 contentEnabled: edgeDemo.panelContentEnabled
572 }
573 fullscreenMode: shell.fullscreenMode
574 searchVisible: !greeter.shown && !lockscreen.shown
576 InputFilterArea {
577 anchors {
578 top: parent.top
579 left: parent.left
580 right: parent.right
581 }
582 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
583 blockInput: true
584 }
585 }
587 Hud {
588 id: hud
590 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
591 height: parent.height
593 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown && edgeDemo.dashEnabled
594 shown: false
595 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
596 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
598 Connections {
599 target: shell.applicationManager
600 onMainStageFocusedApplicationChanged: hud.hide()
601 onSideStageFocusedApplicationChanged: hud.hide()
602 }
603 }
605 Revealer {
606 id: hudRevealer
608 enabled: hud.shown
609 width: hud.width
610 anchors.left: hud.left
611 height: parent.height
612 target: hud.revealerTarget
613 closedValue: height
614 openedValue: 0
615 direction: Qt.RightToLeft
616 orientation: Qt.Vertical
617 handleSize: hud.handleHeight
618 onCloseClicked: target.hide()
619 }
621 Bottombar {
622 id: bottombar
623 theHud: hud
624 anchors.fill: parent
625 enabled: hud.available
626 applicationIsOnForeground: applicationFocused
627 }
629 InputFilterArea {
630 blockInput: launcher.shown
631 anchors {
632 top: parent.top
633 bottom: parent.bottom
634 left: parent.left
635 }
636 width: launcher.width
637 }
639 Launcher {
640 id: launcher
642 anchors.top: parent.top
643 anchors.bottom: parent.bottom
644 width: parent.width
645 dragAreaWidth: shell.edgeSize
646 available: (!greeter.shown || greeter.narrowMode) && edgeDemo.launcherEnabled
647 onDashItemSelected: showHome()
648 onDash: {
649 if (stages.shown) {
650 dash.setCurrentScope("applications.scope", true /* animate */, true /* reset */)
651 stages.hide();
652 launcher.hide();
653 }
654 }
655 onLauncherApplicationSelected:{
656 greeter.hide()
657 shell.activateApplication(appId)
658 }
659 onShownChanged: {
660 if (shown) {
661 panel.indicators.hide()
662 hud.hide()
663 bottombar.hide()
664 }
665 }
666 }
668 Notifications {
669 id: notifications
671 model: NotificationBackend.Model
672 anchors {
673 top: parent.top
674 right: parent.right
675 bottom: parent.bottom
676 leftMargin: units.gu(1)
677 rightMargin: units.gu(1)
678 topMargin: panel.panelHeight + units.gu(1)
679 }
680 states: [
681 State {
682 name: "narrow"
683 when: overlay.width <= units.gu(60)
684 AnchorChanges { target: notifications; anchors.left: parent.left }
685 },
686 State {
687 name: "wide"
688 when: overlay.width > units.gu(60)
689 AnchorChanges { target: notifications; anchors.left: undefined }
690 PropertyChanges { target: notifications; width: units.gu(38) }
691 }
692 ]
694 InputFilterArea {
695 anchors { left: parent.left; right: parent.right }
696 height: parent.contentHeight
697 blockInput: height > 0
698 }
699 }
700 }
702 focus: true
703 onFocusChanged: if (!focus) forceActiveFocus();
705 InputFilterArea {
706 anchors {
707 top: parent.top
708 bottom: parent.bottom
709 left: parent.left
710 }
711 width: shell.edgeSize
712 blockInput: true
713 }
715 InputFilterArea {
716 anchors {
717 top: parent.top
718 bottom: parent.bottom
719 right: parent.right
720 }
721 width: shell.edgeSize
722 blockInput: true
723 }
725 Binding {
726 target: i18n
727 property: "domain"
728 value: "unity8"
729 }
731 OSKController {
732 anchors.fill: parent // as needs to know the geometry of the shell
733 }
735 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
736 MouseArea {
737 anchors.bottom: parent.bottom
738 anchors.left: parent.left
739 anchors.right: parent.right
740 height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
742 enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
743 }
745 Label {
746 anchors.centerIn: parent
747 visible: applicationManager.fake
748 text: "EARLY ALPHA\nNOT READY FOR USE"
749 color: "lightgrey"
750 opacity: 0.2
751 font.weight: Font.Black
752 horizontalAlignment: Text.AlignHCenter
753 verticalAlignment: Text.AlignVCenter
754 fontSizeMode: Text.Fit
755 rotation: -45
756 scale: Math.min(parent.width, parent.height) / width
757 }
759 EdgeDemo {
760 id: edgeDemo
761 greeter: greeter
762 launcher: launcher
763 dash: dash
764 indicators: panel.indicators
765 underlay: underlay
766 }
768 Connections {
769 target: SessionBroadcast
770 onShowHome: showHome()
771 }