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