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