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