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