on 508d51d help sign in
unity8/Shell.qml
734 lines 26 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 property bool haveApps: mainStage.applications.count > 0 || sideStage.applications.count > 0
392 maxTotalDragDistance: haveApps ? parent.width : parent.width * 0.7
393 // Make autocompletion impossible when !haveApps
394 edgeDragEvaluator.minDragDistance: haveApps ? maxTotalDragDistance * 0.1 : Number.MAX_VALUE
395 }
396 }
397 }
399 Lockscreen {
400 id: lockscreen
401 hides: [launcher, panel.indicators, hud]
402 shown: false
403 enabled: true
404 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
405 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
406 y: panel.panelHeight
407 x: required ? 0 : - width
408 width: parent.width
409 height: parent.height - panel.panelHeight
410 background: shell.background
412 onUnlocked: lockscreen.hide()
413 onCancel: greeter.show()
415 Component.onCompleted: {
416 if (LightDM.Users.count == 1) {
417 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
418 greeter.selected(0)
419 }
420 }
421 }
423 Connections {
424 target: LightDM.Greeter
426 onShowPrompt: {
427 if (LightDM.Users.count == 1) {
428 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
429 if (text == "PIN") {
430 lockscreen.alphaNumeric = false
431 } else {
432 lockscreen.alphaNumeric = true
433 }
434 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
435 lockscreen.show();
436 }
437 }
438 }
440 Greeter {
441 id: greeter
442 objectName: "greeter"
444 available: true
445 hides: [launcher, panel.indicators, hud]
446 shown: true
448 y: panel.panelHeight
449 width: parent.width
450 height: parent.height - panel.panelHeight
452 dragHandleWidth: shell.edgeSize
454 onShownChanged: {
455 if (shown) {
456 lockscreen.reset();
457 // If there is only one user, we start authenticating with that one here.
458 // If there are more users, the Greeter will handle that
459 if (LightDM.Users.count == 1) {
460 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
461 greeter.selected(0);
462 }
463 greeter.forceActiveFocus();
464 }
465 }
467 onUnlocked: greeter.hide()
468 onSelected: {
469 // Update launcher items for new user
470 LauncherModel.setUser(user);
471 }
473 onLeftTeaserPressedChanged: {
474 if (leftTeaserPressed) {
475 launcher.tease();
476 }
477 }
478 }
480 InputFilterArea {
481 anchors.fill: parent
482 blockInput: greeter.shown || lockscreen.shown
483 }
485 Connections {
486 id: powerConnection
487 target: Powerd
489 property var previousMainApp: null
490 property var previousSideApp: null
492 function setFocused(focused) {
493 if (!focused) {
494 // FIXME: *FocusedApplication are not updated when unfocused, hence the need to check whether
495 // the stage was actually shown
496 if (mainStage.fullyShown) powerConnection.previousMainApp = applicationManager.mainStageFocusedApplication;
497 if (sideStage.fullyShown) powerConnection.previousSideApp = applicationManager.sideStageFocusedApplication;
498 applicationManager.unfocusCurrentApplication();
499 } else {
500 if (powerConnection.previousMainApp) {
501 applicationManager.focusApplication(powerConnection.previousMainApp);
502 powerConnection.previousMainApp = null;
503 }
504 if (powerConnection.previousSideApp) {
505 applicationManager.focusApplication(powerConnection.previousSideApp);
506 powerConnection.previousSideApp = null;
507 }
508 }
509 }
511 onDisplayPowerStateChange: {
512 // We ignore any display-off signals when the proximity sensor
513 // is active. This usually indicates something like a phone call.
514 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
515 powerConnection.setFocused(false);
516 greeter.show();
517 } else if (status == Powerd.On) {
518 powerConnection.setFocused(true);
519 }
521 // No reason to chew demo CPU when user isn't watching
522 if (status == Powerd.Off) {
523 edgeDemo.paused = true;
524 } else if (status == Powerd.On) {
525 edgeDemo.paused = false;
526 }
527 }
528 }
530 Item {
531 id: overlay
533 anchors.fill: parent
535 Panel {
536 id: panel
537 anchors.fill: parent //because this draws indicator menus
538 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
539 indicators {
540 hides: [launcher]
541 available: edgeDemo.panelEnabled
542 }
543 fullscreenMode: shell.fullscreenMode
544 searchVisible: !greeter.shown && !lockscreen.shown
546 InputFilterArea {
547 anchors.fill: parent
548 blockInput: panel.indicators.shown
549 }
550 }
552 Hud {
553 id: hud
555 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
556 height: parent.height
558 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown && edgeDemo.dashEnabled
559 shown: false
560 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
561 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
563 Connections {
564 target: shell.applicationManager
565 onMainStageFocusedApplicationChanged: hud.hide()
566 onSideStageFocusedApplicationChanged: hud.hide()
567 }
569 InputFilterArea {
570 anchors.fill: parent
571 blockInput: hud.shown
572 }
573 }
575 Revealer {
576 id: hudRevealer
578 enabled: hud.shown
579 width: hud.width
580 anchors.left: hud.left
581 height: parent.height
582 target: hud.revealerTarget
583 closedValue: height
584 openedValue: 0
585 direction: Qt.RightToLeft
586 orientation: Qt.Vertical
587 handleSize: hud.handleHeight
588 onCloseClicked: target.hide()
589 }
591 Bottombar {
592 theHud: hud
593 anchors.fill: parent
594 enabled: !panel.indicators.shown
595 applicationIsOnForeground: applicationManager.mainStageFocusedApplication
596 || applicationManager.sideStageFocusedApplication
597 }
599 InputFilterArea {
600 blockInput: launcher.shown
601 anchors {
602 top: parent.top
603 bottom: parent.bottom
604 left: parent.left
605 }
606 width: launcher.width
607 }
609 Launcher {
610 id: launcher
612 anchors.top: parent.top
613 anchors.bottom: parent.bottom
614 width: parent.width
615 dragAreaWidth: shell.edgeSize
616 available: (!greeter.shown || greeter.narrowMode) && edgeDemo.launcherEnabled
617 onDashItemSelected: {
618 greeter.hide()
619 // Animate if moving between application and dash
620 if (!stages.shown) {
621 dash.setCurrentScope("home.scope", true, false)
622 } else {
623 dash.setCurrentScope("home.scope", false, false)
624 }
625 stages.hide();
626 }
627 onDash: {
628 if (stages.shown) {
629 dash.setCurrentScope("applications.scope", true, false)
630 stages.hide();
631 launcher.hide();
632 }
633 }
634 onLauncherApplicationSelected:{
635 greeter.hide()
636 shell.activateApplication(desktopFile)
637 }
638 onShownChanged: {
639 if (shown) {
640 panel.indicators.hide()
641 hud.hide()
642 }
643 }
644 }
646 Notifications {
647 id: notifications
649 model: NotificationBackend.Model
650 anchors {
651 top: parent.top
652 right: parent.right
653 bottom: parent.bottom
654 leftMargin: units.gu(1)
655 rightMargin: units.gu(1)
656 topMargin: panel.panelHeight + units.gu(1)
657 }
658 states: [
659 State {
660 name: "narrow"
661 when: overlay.width <= units.gu(60)
662 AnchorChanges { target: notifications; anchors.left: parent.left }
663 },
664 State {
665 name: "wide"
666 when: overlay.width > units.gu(60)
667 AnchorChanges { target: notifications; anchors.left: undefined }
668 PropertyChanges { target: notifications; width: units.gu(38) }
669 }
670 ]
671 }
672 }
674 focus: true
676 InputFilterArea {
677 anchors {
678 top: parent.top
679 bottom: parent.bottom
680 left: parent.left
681 }
682 width: shell.edgeSize
683 blockInput: true
684 }
686 InputFilterArea {
687 anchors {
688 top: parent.top
689 bottom: parent.bottom
690 right: parent.right
691 }
692 width: shell.edgeSize
693 blockInput: true
694 }
696 Binding {
697 target: i18n
698 property: "domain"
699 value: "unity8"
700 }
702 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
703 MouseArea {
704 anchors.bottom: parent.bottom
705 anchors.left: parent.left
706 anchors.right: parent.right
707 height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
709 enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
710 }
712 Label {
713 anchors.centerIn: parent
714 visible: applicationManager.fake
715 text: "EARLY ALPHA\nNOT READY FOR USE"
716 color: "lightgrey"
717 opacity: 0.2
718 font.weight: Font.Black
719 horizontalAlignment: Text.AlignHCenter
720 verticalAlignment: Text.AlignVCenter
721 fontSizeMode: Text.Fit
722 rotation: -45
723 scale: Math.min(parent.width, parent.height) / width
724 }
726 EdgeDemo {
727 id: edgeDemo
728 greeter: greeter
729 launcher: launcher
730 dash: dash
731 indicators: panel.indicators
732 overlay: overlay
733 }