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