on 331dca1 help sign in
unity8/Shell.qml
748 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 Ubuntu.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 "Dash"
27import "Greeter"
28import "Launcher"
29import "Panel"
30import "Hud"
31import "Components"
32import "Components/Math.js" as MathLocal
33import "Bottombar"
34import "SideStage"
35import "Notifications"
36import Unity.Notifications 1.0 as NotificationBackend
38FocusScope {
39 id: shell
41 // this is only here to select the width / height of the window if not running fullscreen
42 property bool tablet: false
43 width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
44 height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
46 property real edgeSize: units.gu(2)
47 property url defaultBackground: shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg"
48 property url background
49 readonly property real panelHeight: panel.panelHeight
51 property bool dashShown: dash.shown
52 property bool stageScreenshotsReady: {
53 if (sideStage.shown) {
54 if (mainStage.applications.count > 0) {
55 return mainStage.usingScreenshots && sideStage.usingScreenshots;
56 } else {
57 return sideStage.usingScreenshots;
58 }
59 } else {
60 return mainStage.usingScreenshots;
61 }
62 }
64 property ListModel searchHistory: SearchHistoryModel {}
66 property var applicationManager: ApplicationManagerWrapper {}
68 Component.onCompleted: {
69 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
71 applicationManager.sideStageEnabled = Qt.binding(function() { return sideStage.enabled })
73 // FIXME: if application focused before shell starts, shell draws on top of it only.
74 // We should detect already running applications on shell start and bring them to the front.
75 applicationManager.unfocusCurrentApplication();
76 }
78 readonly property bool fullscreenMode: {
79 if (greeter.shown || lockscreen.shown) {
80 return false;
81 } else if (mainStage.usingScreenshots) { // Window Manager animating so want to re-evaluate fullscreen mode
82 return mainStage.switchingFromFullscreenToFullscreen;
83 } else if (applicationManager.mainStageFocusedApplication) {
84 return applicationManager.mainStageFocusedApplication.fullscreen;
85 } else {
86 return false;
87 }
88 }
90 Connections {
91 target: applicationManager
92 ignoreUnknownSignals: true
93 onFocusRequested: {
94 // TODO: this should be protected to only unlock for certain applications / certain usecases
95 // potentially only in connection with a notification
96 greeter.hide();
97 shell.activateApplication(desktopFile);
98 }
100 onMainStageFocusedApplicationChanged: {
101 var app = applicationManager.mainStageFocusedApplication
102 if (app != null) {
103 LauncherModel.applicationFocused(app.desktopFile);
104 } else {
105 LauncherModel.applicationFocused("");
106 }
107 }
108 }
110 function activateApplication(desktopFile, argument) {
111 if (applicationManager) {
112 // For newly started applications, as it takes them time to draw their first frame
113 // we add a delay before we hide the animation screenshots to compensate.
114 var addDelay = !applicationManager.getApplicationFromDesktopFile(desktopFile);
116 var application;
117 application = applicationManager.activateApplication(desktopFile, argument);
118 if (application == null) {
119 return;
120 }
121 if (application.stage == ApplicationInfo.MainStage || !sideStage.enabled) {
122 mainStage.activateApplication(desktopFile, addDelay);
123 } else {
124 sideStage.activateApplication(desktopFile, addDelay);
125 }
126 stages.show();
127 }
128 }
130 GSettings {
131 id: backgroundSettings
132 schema.id: "org.gnome.desktop.background"
133 }
134 property url gSettingsPicture: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : shell.defaultBackground
135 onGSettingsPictureChanged: {
136 shell.background = gSettingsPicture
137 }
139 // This is a dummy image that is needed to determine if the picture url
140 // in backgroundSettings points to a valid picture file.
141 // We can't do this with the real background image because setting a
142 // new source in onStatusChanged triggers a binding loop detection
143 // inside Image, which causes it not to render even though a valid source
144 // would be set. We don't mind about this image staying black and just
145 // use it for verification to populate the source for the real
146 // background image.
147 Image {
148 source: shell.background
149 height: 0
150 width: 0
151 sourceSize.height: 0
152 sourceSize.width: 0
153 onStatusChanged: {
154 if (status == Image.Error && source != shell.defaultBackground) {
155 shell.background = defaultBackground
156 }
157 }
158 }
160 VolumeControl {
161 id: volumeControl
162 }
164 Keys.onVolumeUpPressed: volumeControl.volumeUp()
165 Keys.onVolumeDownPressed: volumeControl.volumeDown()
167 Item {
168 id: underlay
169 objectName: "underlay"
170 anchors.fill: parent
172 // Whether the underlay is fully covered by opaque UI elements.
173 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
175 readonly property bool applicationRunning: ((mainStage.applications && mainStage.applications.count > 0)
176 || (sideStage.applications && sideStage.applications.count > 0))
178 // Whether the user should see the topmost application surface (if there's one at all).
179 readonly property bool applicationSurfaceShouldBeSeen: applicationRunning && !stages.fullyHidden
180 && !mainStage.usingScreenshots // but want sideStage animating over app surface
184 // NB! Application surfaces are stacked behing the shell one. So they can only be seen by the user
185 // through the translucent parts of the shell surface.
186 visible: !fullyCovered && !applicationSurfaceShouldBeSeen
188 CrossFadeImage {
189 id: backgroundImage
190 objectName: "backgroundImage"
192 anchors.fill: parent
193 source: shell.background
194 }
196 Rectangle {
197 anchors.fill: parent
198 color: "black"
199 opacity: dash.disappearingAnimationProgress
200 }
202 Dash {
203 id: dash
204 objectName: "dash"
206 available: !greeter.shown && !lockscreen.shown
207 hides: [stages, launcher, panel.indicators]
208 shown: disappearingAnimationProgress !== 1.0
209 enabled: disappearingAnimationProgress === 0.0 && edgeDemo.dashEnabled
210 // FIXME: unfocus all applications when going back to the dash
211 onEnabledChanged: {
212 if (enabled) {
213 shell.applicationManager.unfocusCurrentApplication()
214 }
215 }
217 anchors {
218 fill: parent
219 topMargin: panel.panelHeight
220 }
222 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
223 opacity: 1.0 - disappearingAnimationProgress
224 property real disappearingAnimationProgress: {
225 if (greeter.shown) {
226 return greeter.showProgress;
227 } else {
228 return stagesOuterContainer.showProgress;
229 }
230 }
232 // FIXME: only necessary because stagesOuterContainer.showProgress and
233 // greeterRevealer.animatedProgress are not animated
234 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
235 }
236 }
238 Item {
239 id: stagesOuterContainer
241 width: parent.width
242 height: parent.height
243 x: launcher.progress
244 Behavior on x {SmoothedAnimation{velocity: 600}}
246 property real showProgress:
247 MathLocal.clamp(1 - (x + stages.x) / shell.width, 0, 1)
249 Showable {
250 id: stages
251 objectName: "stages"
253 x: width
255 property bool fullyShown: shown && x == 0 && parent.x == 0
256 property bool fullyHidden: !shown && x == width
257 available: !greeter.shown
258 hides: [launcher, panel.indicators]
259 shown: false
260 opacity: 1.0
261 showAnimation: StandardAnimation { property: "x"; duration: 350; to: 0; easing.type: Easing.OutCubic }
262 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: width; easing.type: Easing.OutCubic }
264 width: parent.width
265 height: parent.height
267 // close the stages when no focused application remains
268 Connections {
269 target: shell.applicationManager
270 onMainStageFocusedApplicationChanged: stages.closeIfNoApplications()
271 onSideStageFocusedApplicationChanged: stages.closeIfNoApplications()
272 ignoreUnknownSignals: true
273 }
275 function closeIfNoApplications() {
276 if (!shell.applicationManager.mainStageFocusedApplication
277 && !shell.applicationManager.sideStageFocusedApplication
278 && shell.applicationManager.mainStageApplications.count == 0
279 && shell.applicationManager.sideStageApplications.count == 0) {
280 stages.hide();
281 }
282 }
284 // show the stages when an application gets the focus
285 Connections {
286 target: shell.applicationManager
287 onMainStageFocusedApplicationChanged: {
288 if (shell.applicationManager.mainStageFocusedApplication) {
289 mainStage.show();
290 stages.show();
291 }
292 }
293 onSideStageFocusedApplicationChanged: {
294 if (shell.applicationManager.sideStageFocusedApplication) {
295 sideStage.show();
296 stages.show();
297 }
298 }
299 ignoreUnknownSignals: true
300 }
302 Stage {
303 id: mainStage
305 anchors.fill: parent
306 fullyShown: stages.fullyShown
307 fullyHidden: stages.fullyHidden
308 shouldUseScreenshots: !fullyShown
309 rightEdgeEnabled: !sideStage.enabled
311 applicationManager: shell.applicationManager
312 rightEdgeDraggingAreaWidth: shell.edgeSize
313 normalApplicationY: shell.panelHeight
315 shown: true
316 function show() {
317 stages.show();
318 }
319 function hide() {
320 }
322 // FIXME: workaround the fact that focusing a main stage application
323 // raises its surface on top of all other surfaces including the ones
324 // that belong to side stage applications.
325 onFocusedApplicationChanged: {
326 if (focusedApplication && sideStage.focusedApplication && sideStage.fullyShown) {
327 shell.applicationManager.focusApplication(sideStage.focusedApplication);
328 }
329 }
330 }
332 SideStage {
333 id: sideStage
335 applicationManager: shell.applicationManager
336 rightEdgeDraggingAreaWidth: shell.edgeSize
337 normalApplicationY: shell.panelHeight
339 onShownChanged: {
340 if (!shown && mainStage.applications.count == 0) {
341 stages.hide();
342 }
343 }
344 // FIXME: when hiding the side stage, refocus the main stage
345 // application so that it goes in front of the side stage
346 // application and hides it
347 onFullyShownChanged: {
348 if (!fullyShown && stages.fullyShown && sideStage.focusedApplication != null) {
349 shell.applicationManager.focusApplication(mainStage.focusedApplication);
350 }
351 }
353 enabled: shell.width >= units.gu(60)
354 visible: enabled
355 fullyShown: stages.fullyShown && shown
356 && sideStage[sideStageRevealer.boundProperty] == sideStageRevealer.openedValue
357 shouldUseScreenshots: !fullyShown || mainStage.usingScreenshots || sideStageRevealer.pressed
359 available: !greeter.shown && !lockscreen.shown && enabled
360 hides: [launcher, panel.indicators]
361 shown: false
362 showAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.openedValue; easing.type: Easing.OutQuint }
363 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.closedValue; easing.type: Easing.OutQuint }
365 width: units.gu(40)
366 height: stages.height
367 handleExpanded: sideStageRevealer.pressed
368 }
370 Revealer {
371 id: sideStageRevealer
373 enabled: mainStage.applications.count > 0 && sideStage.applications.count > 0
374 && sideStage.available
375 direction: Qt.RightToLeft
376 openedValue: parent.width - sideStage.width
377 hintDisplacement: units.gu(3)
378 /* The size of the sidestage handle needs to be bigger than the
379 typical size used for edge detection otherwise it is really
380 hard to grab.
381 */
382 handleSize: sideStage.shown ? units.gu(4) : shell.edgeSize
383 closedValue: parent.width + sideStage.handleSizeCollapsed
384 target: sideStage
385 x: parent.width - width
386 width: sideStage.width + handleSize * 0.7
387 height: sideStage.height
388 orientation: Qt.Horizontal
389 }
391 DragHandle {
392 id: stagesDragHandle
394 anchors.top: parent.top
395 anchors.bottom: parent.bottom
396 anchors.right: parent.left
398 width: shell.edgeSize
399 direction: Direction.Leftwards
400 enabled: edgeDemo.dashEnabled
401 property bool haveApps: mainStage.applications.count > 0 || sideStage.applications.count > 0
403 maxTotalDragDistance: haveApps ? parent.width : parent.width * 0.7
404 // Make autocompletion impossible when !haveApps
405 edgeDragEvaluator.minDragDistance: haveApps ? maxTotalDragDistance * 0.1 : Number.MAX_VALUE
406 }
407 }
408 }
410 Lockscreen {
411 id: lockscreen
412 hides: [launcher, panel.indicators, hud]
413 shown: false
414 enabled: true
415 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
416 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
417 y: panel.panelHeight
418 x: required ? 0 : - width
419 width: parent.width
420 height: parent.height - panel.panelHeight
421 background: shell.background
423 onUnlocked: lockscreen.hide()
424 onCancel: greeter.show()
426 Component.onCompleted: {
427 if (LightDM.Users.count == 1) {
428 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
429 greeter.selected(0)
430 }
431 }
432 }
434 Connections {
435 target: LightDM.Greeter
437 onShowPrompt: {
438 if (LightDM.Users.count == 1) {
439 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
440 if (text == "PIN") {
441 lockscreen.alphaNumeric = false
442 } else {
443 lockscreen.alphaNumeric = true
444 }
445 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
446 lockscreen.show();
447 }
448 }
449 }
451 Greeter {
452 id: greeter
453 objectName: "greeter"
455 available: true
456 hides: [launcher, panel.indicators, hud]
457 shown: true
459 y: panel.panelHeight
460 width: parent.width
461 height: parent.height - panel.panelHeight
463 dragHandleWidth: shell.edgeSize
465 onShownChanged: {
466 if (shown) {
467 lockscreen.reset();
468 // If there is only one user, we start authenticating with that one here.
469 // If there are more users, the Greeter will handle that
470 if (LightDM.Users.count == 1) {
471 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
472 greeter.selected(0);
473 }
474 greeter.forceActiveFocus();
475 }
476 }
478 onUnlocked: greeter.hide()
479 onSelected: {
480 // Update launcher items for new user
481 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
482 AccountsService.user = user;
483 LauncherModel.setUser(user);
484 }
486 onLeftTeaserPressedChanged: {
487 if (leftTeaserPressed) {
488 launcher.tease();
489 }
490 }
491 }
493 InputFilterArea {
494 anchors.fill: parent
495 blockInput: greeter.shown || lockscreen.shown
496 }
498 Connections {
499 id: powerConnection
500 target: Powerd
502 property var previousMainApp: null
503 property var previousSideApp: null
505 function setFocused(focused) {
506 if (!focused) {
507 // FIXME: *FocusedApplication are not updated when unfocused, hence the need to check whether
508 // the stage was actually shown
509 if (mainStage.fullyShown) powerConnection.previousMainApp = applicationManager.mainStageFocusedApplication;
510 if (sideStage.fullyShown) powerConnection.previousSideApp = applicationManager.sideStageFocusedApplication;
511 applicationManager.unfocusCurrentApplication();
512 } else {
513 if (powerConnection.previousMainApp) {
514 applicationManager.focusApplication(powerConnection.previousMainApp);
515 powerConnection.previousMainApp = null;
516 }
517 if (powerConnection.previousSideApp) {
518 applicationManager.focusApplication(powerConnection.previousSideApp);
519 powerConnection.previousSideApp = null;
520 }
521 }
522 }
524 onDisplayPowerStateChange: {
525 // We ignore any display-off signals when the proximity sensor
526 // is active. This usually indicates something like a phone call.
527 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
528 powerConnection.setFocused(false);
529 greeter.show();
530 } else if (status == Powerd.On) {
531 powerConnection.setFocused(true);
532 }
534 // No reason to chew demo CPU when user isn't watching
535 if (status == Powerd.Off) {
536 edgeDemo.paused = true;
537 } else if (status == Powerd.On) {
538 edgeDemo.paused = false;
539 }
540 }
541 }
543 Item {
544 id: overlay
546 anchors.fill: parent
548 Panel {
549 id: panel
550 anchors.fill: parent //because this draws indicator menus
551 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
552 indicators {
553 hides: [launcher]
554 available: edgeDemo.panelEnabled
555 contentEnabled: edgeDemo.panelContentEnabled
556 }
557 fullscreenMode: shell.fullscreenMode
558 searchVisible: !greeter.shown && !lockscreen.shown
560 InputFilterArea {
561 anchors.fill: parent
562 blockInput: panel.indicators.shown
563 }
564 }
566 Hud {
567 id: hud
569 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
570 height: parent.height
572 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown && edgeDemo.dashEnabled
573 shown: false
574 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
575 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
577 Connections {
578 target: shell.applicationManager
579 onMainStageFocusedApplicationChanged: hud.hide()
580 onSideStageFocusedApplicationChanged: hud.hide()
581 }
583 InputFilterArea {
584 anchors.fill: parent
585 blockInput: hud.shown
586 }
587 }
589 Revealer {
590 id: hudRevealer
592 enabled: hud.shown
593 width: hud.width
594 anchors.left: hud.left
595 height: parent.height
596 target: hud.revealerTarget
597 closedValue: height
598 openedValue: 0
599 direction: Qt.RightToLeft
600 orientation: Qt.Vertical
601 handleSize: hud.handleHeight
602 onCloseClicked: target.hide()
603 }
605 Bottombar {
606 theHud: hud
607 anchors.fill: parent
608 enabled: !panel.indicators.shown
609 applicationIsOnForeground: applicationManager.mainStageFocusedApplication
610 || applicationManager.sideStageFocusedApplication
611 }
613 InputFilterArea {
614 blockInput: launcher.shown
615 anchors {
616 top: parent.top
617 bottom: parent.bottom
618 left: parent.left
619 }
620 width: launcher.width
621 }
623 Launcher {
624 id: launcher
626 anchors.top: parent.top
627 anchors.bottom: parent.bottom
628 width: parent.width
629 dragAreaWidth: shell.edgeSize
630 available: (!greeter.shown || greeter.narrowMode) && edgeDemo.launcherEnabled
631 onDashItemSelected: {
632 greeter.hide()
633 // Animate if moving between application and dash
634 if (!stages.shown) {
635 dash.setCurrentScope("home.scope", true, false)
636 } else {
637 dash.setCurrentScope("home.scope", false, false)
638 }
639 stages.hide();
640 }
641 onDash: {
642 if (stages.shown) {
643 dash.setCurrentScope("applications.scope", true, false)
644 stages.hide();
645 launcher.hide();
646 }
647 }
648 onLauncherApplicationSelected:{
649 greeter.hide()
650 shell.activateApplication(desktopFile)
651 }
652 onShownChanged: {
653 if (shown) {
654 panel.indicators.hide()
655 hud.hide()
656 }
657 }
658 }
660 Notifications {
661 id: notifications
663 model: NotificationBackend.Model
664 anchors {
665 top: parent.top
666 right: parent.right
667 bottom: parent.bottom
668 leftMargin: units.gu(1)
669 rightMargin: units.gu(1)
670 topMargin: panel.panelHeight + units.gu(1)
671 }
672 states: [
673 State {
674 name: "narrow"
675 when: overlay.width <= units.gu(60)
676 AnchorChanges { target: notifications; anchors.left: parent.left }
677 },
678 State {
679 name: "wide"
680 when: overlay.width > units.gu(60)
681 AnchorChanges { target: notifications; anchors.left: undefined }
682 PropertyChanges { target: notifications; width: units.gu(38) }
683 }
684 ]
685 }
686 }
688 focus: true
690 InputFilterArea {
691 anchors {
692 top: parent.top
693 bottom: parent.bottom
694 left: parent.left
695 }
696 width: shell.edgeSize
697 blockInput: true
698 }
700 InputFilterArea {
701 anchors {
702 top: parent.top
703 bottom: parent.bottom
704 right: parent.right
705 }
706 width: shell.edgeSize
707 blockInput: true
708 }
710 Binding {
711 target: i18n
712 property: "domain"
713 value: "unity8"
714 }
716 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
717 MouseArea {
718 anchors.bottom: parent.bottom
719 anchors.left: parent.left
720 anchors.right: parent.right
721 height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
723 enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
724 }
726 Label {
727 anchors.centerIn: parent
728 visible: applicationManager.fake
729 text: "EARLY ALPHA\nNOT READY FOR USE"
730 color: "lightgrey"
731 opacity: 0.2
732 font.weight: Font.Black
733 horizontalAlignment: Text.AlignHCenter
734 verticalAlignment: Text.AlignVCenter
735 fontSizeMode: Text.Fit
736 rotation: -45
737 scale: Math.min(parent.width, parent.height) / width
738 }
740 EdgeDemo {
741 id: edgeDemo
742 greeter: greeter
743 launcher: launcher
744 dash: dash
745 indicators: panel.indicators
746 underlay: underlay
747 }