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