on deafa96 help sign in
unity8/Shell.qml
731 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 LightDM 0.1 as LightDM
23import Powerd 0.1
24import "Dash"
25import "Greeter"
26import "Launcher"
27import "Panel"
28import "Hud"
29import "Components"
30import "Components/Math.js" as MathLocal
31import "Bottombar"
32import "SideStage"
33import "Notifications"
34import Unity.Notifications 1.0 as NotificationBackend
36FocusScope {
37 id: shell
39 // this is only here to select the width / height of the window if not running fullscreen
40 property bool tablet: false
41 width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
42 height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
44 property real edgeSize: units.gu(2)
45 property url defaultBackground: shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg"
46 property url background
47 readonly property real panelHeight: panel.panelHeight
49 property bool dashShown: dash.shown
50 property bool stageScreenshotsReady: {
51 if (sideStage.shown) {
52 if (mainStage.applications.count > 0) {
53 return mainStage.usingScreenshots && sideStage.usingScreenshots;
54 } else {
55 return sideStage.usingScreenshots;
56 }
57 } else {
58 return mainStage.usingScreenshots;
59 }
60 }
62 property ListModel searchHistory: SearchHistoryModel {}
64 property var applicationManager: ApplicationManagerWrapper {}
66 Component.onCompleted: {
67 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
69 applicationManager.sideStageEnabled = Qt.binding(function() { return sideStage.enabled })
71 // FIXME: if application focused before shell starts, shell draws on top of it only.
72 // We should detect already running applications on shell start and bring them to the front.
73 applicationManager.unfocusCurrentApplication();
74 }
76 readonly property bool fullscreenMode: {
77 if (greeter.shown || lockscreen.shown) {
78 return false;
79 } else if (mainStage.usingScreenshots) { // Window Manager animating so want to re-evaluate fullscreen mode
80 return mainStage.switchingFromFullscreenToFullscreen;
81 } else if (applicationManager.mainStageFocusedApplication) {
82 return applicationManager.mainStageFocusedApplication.fullscreen;
83 } else {
84 return false;
85 }
86 }
88 Connections {
89 target: applicationManager
90 ignoreUnknownSignals: true
91 onFocusRequested: {
92 // TODO: this should be protected to only unlock for certain applications / certain usecases
93 // potentially only in connection with a notification
94 greeter.hide();
95 shell.activateApplication(desktopFile);
96 }
97 }
99 function activateApplication(desktopFile, argument) {
100 if (applicationManager) {
101 // For newly started applications, as it takes them time to draw their first frame
102 // we add a delay before we hide the animation screenshots to compensate.
103 var addDelay = !applicationManager.getApplicationFromDesktopFile(desktopFile);
105 var application;
106 application = applicationManager.activateApplication(desktopFile, argument);
107 if (application == null) {
108 return;
109 }
110 if (application.stage == ApplicationInfo.MainStage || !sideStage.enabled) {
111 mainStage.activateApplication(desktopFile, addDelay);
112 } else {
113 sideStage.activateApplication(desktopFile, addDelay);
114 }
115 stages.show();
116 }
117 }
119 GSettings {
120 id: backgroundSettings
121 schema.id: "org.gnome.desktop.background"
122 }
123 property url gSettingsPicture: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : shell.defaultBackground
124 onGSettingsPictureChanged: {
125 shell.background = gSettingsPicture
126 }
128 // This is a dummy image that is needed to determine if the picture url
129 // in backgroundSettings points to a valid picture file.
130 // We can't do this with the real background image because setting a
131 // new source in onStatusChanged triggers a binding loop detection
132 // inside Image, which causes it not to render even though a valid source
133 // would be set. We don't mind about this image staying black and just
134 // use it for verification to populate the source for the real
135 // background image.
136 Image {
137 source: shell.background
138 height: 0
139 width: 0
140 sourceSize.height: 0
141 sourceSize.width: 0
142 onStatusChanged: {
143 if (status == Image.Error && source != shell.defaultBackground) {
144 shell.background = defaultBackground
145 }
146 }
147 }
149 VolumeControl {
150 id: volumeControl
151 }
153 Keys.onVolumeUpPressed: volumeControl.volumeUp()
154 Keys.onVolumeDownPressed: volumeControl.volumeDown()
156 Item {
157 id: underlay
158 objectName: "underlay"
159 anchors.fill: parent
161 // Whether the underlay is fully covered by opaque UI elements.
162 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
164 readonly property bool applicationRunning: ((mainStage.applications && mainStage.applications.count > 0)
165 || (sideStage.applications && sideStage.applications.count > 0))
167 // Whether the user should see the topmost application surface (if there's one at all).
168 readonly property bool applicationSurfaceShouldBeSeen: applicationRunning && !stages.fullyHidden
169 && !mainStage.usingScreenshots // but want sideStage animating over app surface
173 // NB! Application surfaces are stacked behing the shell one. So they can only be seen by the user
174 // through the translucent parts of the shell surface.
175 visible: !fullyCovered && !applicationSurfaceShouldBeSeen
177 CrossFadeImage {
178 id: backgroundImage
179 objectName: "backgroundImage"
181 anchors.fill: parent
182 source: shell.background
183 }
185 Rectangle {
186 anchors.fill: parent
187 color: "black"
188 opacity: dash.disappearingAnimationProgress
189 }
191 Dash {
192 id: dash
193 objectName: "dash"
195 available: !greeter.shown && !lockscreen.shown
196 hides: [stages, launcher, panel.indicators]
197 shown: disappearingAnimationProgress !== 1.0
198 enabled: disappearingAnimationProgress === 0.0 && edgeDemo.dashEnabled
199 // FIXME: unfocus all applications when going back to the dash
200 onEnabledChanged: {
201 if (enabled) {
202 shell.applicationManager.unfocusCurrentApplication()
203 }
204 }
206 anchors {
207 fill: parent
208 topMargin: panel.panelHeight
209 }
211 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
212 opacity: 1.0 - disappearingAnimationProgress
213 property real disappearingAnimationProgress: {
214 if (greeter.shown) {
215 return greeter.showProgress;
216 } else {
217 return stagesOuterContainer.showProgress;
218 }
219 }
221 // FIXME: only necessary because stagesOuterContainer.showProgress and
222 // greeterRevealer.animatedProgress are not animated
223 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
224 }
225 }
227 Item {
228 id: stagesOuterContainer
230 width: parent.width
231 height: parent.height
232 x: launcher.progress
233 Behavior on x {SmoothedAnimation{velocity: 600}}
235 property real showProgress:
236 MathLocal.clamp(1 - (x + stages.x) / shell.width, 0, 1)
238 Showable {
239 id: stages
240 objectName: "stages"
242 x: width
244 property bool fullyShown: shown && x == 0 && parent.x == 0
245 property bool fullyHidden: !shown && x == width
246 available: !greeter.shown
247 hides: [launcher, panel.indicators]
248 shown: false
249 opacity: 1.0
250 showAnimation: StandardAnimation { property: "x"; duration: 350; to: 0; easing.type: Easing.OutCubic }
251 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: width; easing.type: Easing.OutCubic }
253 width: parent.width
254 height: parent.height
256 // close the stages when no focused application remains
257 Connections {
258 target: shell.applicationManager
259 onMainStageFocusedApplicationChanged: stages.closeIfNoApplications()
260 onSideStageFocusedApplicationChanged: stages.closeIfNoApplications()
261 ignoreUnknownSignals: true
262 }
264 function closeIfNoApplications() {
265 if (!shell.applicationManager.mainStageFocusedApplication
266 && !shell.applicationManager.sideStageFocusedApplication
267 && shell.applicationManager.mainStageApplications.count == 0
268 && shell.applicationManager.sideStageApplications.count == 0) {
269 stages.hide();
270 }
271 }
273 // show the stages when an application gets the focus
274 Connections {
275 target: shell.applicationManager
276 onMainStageFocusedApplicationChanged: {
277 if (shell.applicationManager.mainStageFocusedApplication) {
278 mainStage.show();
279 stages.show();
280 }
281 }
282 onSideStageFocusedApplicationChanged: {
283 if (shell.applicationManager.sideStageFocusedApplication) {
284 sideStage.show();
285 stages.show();
286 }
287 }
288 ignoreUnknownSignals: true
289 }
291 Stage {
292 id: mainStage
294 anchors.fill: parent
295 fullyShown: stages.fullyShown
296 fullyHidden: stages.fullyHidden
297 shouldUseScreenshots: !fullyShown
298 rightEdgeEnabled: !sideStage.enabled
300 applicationManager: shell.applicationManager
301 rightEdgeDraggingAreaWidth: shell.edgeSize
302 normalApplicationY: shell.panelHeight
304 shown: true
305 function show() {
306 stages.show();
307 }
308 function hide() {
309 }
311 // FIXME: workaround the fact that focusing a main stage application
312 // raises its surface on top of all other surfaces including the ones
313 // that belong to side stage applications.
314 onFocusedApplicationChanged: {
315 if (focusedApplication && sideStage.focusedApplication && sideStage.fullyShown) {
316 shell.applicationManager.focusApplication(sideStage.focusedApplication);
317 }
318 }
319 }
321 SideStage {
322 id: sideStage
324 applicationManager: shell.applicationManager
325 rightEdgeDraggingAreaWidth: shell.edgeSize
326 normalApplicationY: shell.panelHeight
328 onShownChanged: {
329 if (!shown && mainStage.applications.count == 0) {
330 stages.hide();
331 }
332 }
333 // FIXME: when hiding the side stage, refocus the main stage
334 // application so that it goes in front of the side stage
335 // application and hides it
336 onFullyShownChanged: {
337 if (!fullyShown && stages.fullyShown && sideStage.focusedApplication != null) {
338 shell.applicationManager.focusApplication(mainStage.focusedApplication);
339 }
340 }
342 enabled: shell.width >= units.gu(60)
343 visible: enabled
344 fullyShown: stages.fullyShown && shown
345 && sideStage[sideStageRevealer.boundProperty] == sideStageRevealer.openedValue
346 shouldUseScreenshots: !fullyShown || mainStage.usingScreenshots || sideStageRevealer.pressed
348 available: !greeter.shown && !lockscreen.shown && enabled
349 hides: [launcher, panel.indicators]
350 shown: false
351 showAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.openedValue; easing.type: Easing.OutQuint }
352 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.closedValue; easing.type: Easing.OutQuint }
354 width: units.gu(40)
355 height: stages.height
356 handleExpanded: sideStageRevealer.pressed
357 }
359 Revealer {
360 id: sideStageRevealer
362 enabled: mainStage.applications.count > 0 && sideStage.applications.count > 0
363 && sideStage.available
364 direction: Qt.RightToLeft
365 openedValue: parent.width - sideStage.width
366 hintDisplacement: units.gu(3)
367 /* The size of the sidestage handle needs to be bigger than the
368 typical size used for edge detection otherwise it is really
369 hard to grab.
370 */
371 handleSize: sideStage.shown ? units.gu(4) : shell.edgeSize
372 closedValue: parent.width + sideStage.handleSizeCollapsed
373 target: sideStage
374 x: parent.width - width
375 width: sideStage.width + handleSize * 0.7
376 height: sideStage.height
377 orientation: Qt.Horizontal
378 }
380 DragHandle {
381 id: stagesDragHandle
383 anchors.top: parent.top
384 anchors.bottom: parent.bottom
385 anchors.right: parent.left
387 width: shell.edgeSize
388 direction: Direction.Leftwards
389 enabled: edgeDemo.dashEnabled
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()
469 onLeftTeaserPressedChanged: {
470 if (leftTeaserPressed) {
471 launcher.tease();
472 }
473 }
474 }
476 InputFilterArea {
477 anchors.fill: parent
478 blockInput: greeter.shown || lockscreen.shown
479 }
481 Connections {
482 id: powerConnection
483 target: Powerd
485 property var previousMainApp: null
486 property var previousSideApp: null
488 function setFocused(focused) {
489 if (!focused) {
490 // FIXME: *FocusedApplication are not updated when unfocused, hence the need to check whether
491 // the stage was actually shown
492 if (mainStage.fullyShown) powerConnection.previousMainApp = applicationManager.mainStageFocusedApplication;
493 if (sideStage.fullyShown) powerConnection.previousSideApp = applicationManager.sideStageFocusedApplication;
494 applicationManager.unfocusCurrentApplication();
495 } else {
496 if (powerConnection.previousMainApp) {
497 applicationManager.focusApplication(powerConnection.previousMainApp);
498 powerConnection.previousMainApp = null;
499 }
500 if (powerConnection.previousSideApp) {
501 applicationManager.focusApplication(powerConnection.previousSideApp);
502 powerConnection.previousSideApp = null;
503 }
504 }
505 }
507 onDisplayPowerStateChange: {
508 // We ignore any display-off signals when the proximity sensor
509 // is active. This usually indicates something like a phone call.
510 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
511 powerConnection.setFocused(false);
512 greeter.show();
513 } else if (status == Powerd.On) {
514 powerConnection.setFocused(true);
515 }
517 // No reason to chew demo CPU when user isn't watching
518 if (status == Powerd.Off) {
519 edgeDemo.paused = true;
520 } else if (status == Powerd.On) {
521 edgeDemo.paused = false;
522 }
523 }
524 }
526 Item {
527 id: overlay
529 anchors.fill: parent
531 Panel {
532 id: panel
533 anchors.fill: parent //because this draws indicator menus
534 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
535 indicators {
536 hides: [launcher]
537 available: edgeDemo.panelEnabled
538 contentEnabled: edgeDemo.panelContentEnabled
539 }
540 fullscreenMode: shell.fullscreenMode
541 searchVisible: !greeter.shown && !lockscreen.shown
543 InputFilterArea {
544 anchors.fill: parent
545 blockInput: panel.indicators.shown
546 }
547 }
549 Hud {
550 id: hud
552 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
553 height: parent.height
555 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown && edgeDemo.dashEnabled
556 shown: false
557 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
558 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
560 Connections {
561 target: shell.applicationManager
562 onMainStageFocusedApplicationChanged: hud.hide()
563 onSideStageFocusedApplicationChanged: hud.hide()
564 }
566 InputFilterArea {
567 anchors.fill: parent
568 blockInput: hud.shown
569 }
570 }
572 Revealer {
573 id: hudRevealer
575 enabled: hud.shown
576 width: hud.width
577 anchors.left: hud.left
578 height: parent.height
579 target: hud.revealerTarget
580 closedValue: height
581 openedValue: 0
582 direction: Qt.RightToLeft
583 orientation: Qt.Vertical
584 handleSize: hud.handleHeight
585 onCloseClicked: target.hide()
586 }
588 Bottombar {
589 theHud: hud
590 anchors.fill: parent
591 enabled: !panel.indicators.shown
592 applicationIsOnForeground: applicationManager.mainStageFocusedApplication
593 || applicationManager.sideStageFocusedApplication
594 }
596 InputFilterArea {
597 blockInput: launcher.shown
598 anchors {
599 top: parent.top
600 bottom: parent.bottom
601 left: parent.left
602 }
603 width: launcher.width
604 }
606 Launcher {
607 id: launcher
609 anchors.top: parent.top
610 anchors.bottom: parent.bottom
611 width: parent.width
612 dragAreaWidth: shell.edgeSize
613 available: (!greeter.shown || greeter.narrowMode) && edgeDemo.launcherEnabled
614 onDashItemSelected: {
615 greeter.hide()
616 // Animate if moving between application and dash
617 if (!stages.shown) {
618 dash.setCurrentScope("home.scope", true, false)
619 } else {
620 dash.setCurrentScope("home.scope", false, false)
621 }
622 stages.hide();
623 }
624 onDash: {
625 if (stages.shown) {
626 dash.setCurrentScope("applications.scope", true, false)
627 stages.hide();
628 launcher.hide();
629 }
630 }
631 onLauncherApplicationSelected:{
632 greeter.hide()
633 shell.activateApplication(desktopFile)
634 }
635 onShownChanged: {
636 if (shown) {
637 panel.indicators.hide()
638 hud.hide()
639 }
640 }
641 }
643 Notifications {
644 id: notifications
646 model: NotificationBackend.Model
647 anchors {
648 top: parent.top
649 right: parent.right
650 bottom: parent.bottom
651 leftMargin: units.gu(1)
652 rightMargin: units.gu(1)
653 topMargin: panel.panelHeight + units.gu(1)
654 }
655 states: [
656 State {
657 name: "narrow"
658 when: overlay.width <= units.gu(60)
659 AnchorChanges { target: notifications; anchors.left: parent.left }
660 },
661 State {
662 name: "wide"
663 when: overlay.width > units.gu(60)
664 AnchorChanges { target: notifications; anchors.left: undefined }
665 PropertyChanges { target: notifications; width: units.gu(38) }
666 }
667 ]
668 }
669 }
671 focus: true
673 InputFilterArea {
674 anchors {
675 top: parent.top
676 bottom: parent.bottom
677 left: parent.left
678 }
679 width: shell.edgeSize
680 blockInput: true
681 }
683 InputFilterArea {
684 anchors {
685 top: parent.top
686 bottom: parent.bottom
687 right: parent.right
688 }
689 width: shell.edgeSize
690 blockInput: true
691 }
693 Binding {
694 target: i18n
695 property: "domain"
696 value: "unity8"
697 }
699 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
700 MouseArea {
701 anchors.bottom: parent.bottom
702 anchors.left: parent.left
703 anchors.right: parent.right
704 height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
706 enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
707 }
709 Label {
710 anchors.centerIn: parent
711 visible: applicationManager.fake
712 text: "EARLY ALPHA\nNOT READY FOR USE"
713 color: "lightgrey"
714 opacity: 0.2
715 font.weight: Font.Black
716 horizontalAlignment: Text.AlignHCenter
717 verticalAlignment: Text.AlignVCenter
718 fontSizeMode: Text.Fit
719 rotation: -45
720 scale: Math.min(parent.width, parent.height) / width
721 }
723 EdgeDemo {
724 id: edgeDemo
725 greeter: greeter
726 launcher: launcher
727 dash: dash
728 indicators: panel.indicators
729 underlay: underlay
730 }