on e83ce71 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
87 readonly property bool fullscreenMode: {
88 if (greeter.shown || lockscreen.shown) {
89 return false;
90 } else if (mainStage.usingScreenshots) { // Window Manager animating so want to re-evaluate fullscreen mode
91 return mainStage.switchingFromFullscreenToFullscreen;
92 } else if (applicationManager.mainStageFocusedApplication) {
93 return applicationManager.mainStageFocusedApplication.fullscreen;
94 } else {
95 return false;
96 }
97 }
99 Connections {
100 target: applicationManager
101 ignoreUnknownSignals: true
102 onFocusRequested: {
103 // TODO: this should be protected to only unlock for certain applications / certain usecases
104 // potentially only in connection with a notification
105 greeter.hide();
106 shell.activateApplication(desktopFile);
107 }
108 }
110 function activateApplication(desktopFile, argument) {
111 if (applicationManager) {
112 // For newly started applications, as it takes them time to draw their first frame
113 // we add a delay before we hide the animation screenshots to compensate.
114 var addDelay = !applicationManager.getApplicationFromDesktopFile(desktopFile);
116 var application;
117 application = applicationManager.activateApplication(desktopFile, argument);
118 if (application == null) {
119 return;
120 }
121 if (application.stage == ApplicationInfo.MainStage || !sideStage.enabled) {
122 mainStage.activateApplication(desktopFile, addDelay);
123 } else {
124 sideStage.activateApplication(desktopFile, addDelay);
125 }
126 stages.show();
127 }
128 }
130 GSettings {
131 id: backgroundSettings
132 schema.id: "org.gnome.desktop.background"
133 }
134 property url gSettingsPicture: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : shell.defaultBackground
135 onGSettingsPictureChanged: {
136 shell.background = gSettingsPicture
137 }
139 // This is a dummy image that is needed to determine if the picture url
140 // in backgroundSettings points to a valid picture file.
141 // We can't do this with the real background image because setting a
142 // new source in onStatusChanged triggers a binding loop detection
143 // inside Image, which causes it not to render even though a valid source
144 // would be set. We don't mind about this image staying black and just
145 // use it for verification to populate the source for the real
146 // background image.
147 Image {
148 source: shell.background
149 height: 0
150 width: 0
151 sourceSize.height: 0
152 sourceSize.width: 0
153 onStatusChanged: {
154 if (status == Image.Error && source != shell.defaultBackground) {
155 shell.background = defaultBackground
156 }
157 }
158 }
160 OSKController {
161 anchors.fill: parent // as needs to know the geometry of the shell
162 }
164 VolumeControl {
165 id: volumeControl
166 }
168 Keys.onVolumeUpPressed: volumeControl.volumeUp()
169 Keys.onVolumeDownPressed: volumeControl.volumeDown()
171 Item {
172 id: underlay
173 objectName: "underlay"
174 anchors.fill: parent
176 // Whether the underlay is fully covered by opaque UI elements.
177 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
179 readonly property bool applicationRunning: ((mainStage.applications && mainStage.applications.count > 0)
180 || (sideStage.applications && sideStage.applications.count > 0))
182 // Whether the user should see the topmost application surface (if there's one at all).
183 readonly property bool applicationSurfaceShouldBeSeen: applicationRunning && !stages.fullyHidden
184 && !mainStage.usingScreenshots // but want sideStage animating over app surface
188 // NB! Application surfaces are stacked behing the shell one. So they can only be seen by the user
189 // through the translucent parts of the shell surface.
190 visible: !fullyCovered && !applicationSurfaceShouldBeSeen
192 CrossFadeImage {
193 id: backgroundImage
194 objectName: "backgroundImage"
196 anchors.fill: parent
197 source: shell.background
198 }
200 Rectangle {
201 anchors.fill: parent
202 color: "black"
203 opacity: dash.disappearingAnimationProgress
204 }
206 Dash {
207 id: dash
208 objectName: "dash"
210 available: !greeter.shown && !lockscreen.shown
211 hides: [stages, launcher, panel.indicators]
212 shown: disappearingAnimationProgress !== 1.0
213 enabled: disappearingAnimationProgress === 0.0 && edgeDemo.dashEnabled
214 // FIXME: unfocus all applications when going back to the dash
215 onEnabledChanged: {
216 if (enabled) {
217 shell.applicationManager.unfocusCurrentApplication()
218 }
219 }
221 anchors {
222 fill: parent
223 topMargin: panel.panelHeight
224 }
226 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
227 opacity: 1.0 - disappearingAnimationProgress
228 property real disappearingAnimationProgress: {
229 if (greeter.shown) {
230 return greeter.showProgress;
231 } else {
232 return stagesOuterContainer.showProgress;
233 }
234 }
236 // FIXME: only necessary because stagesOuterContainer.showProgress and
237 // greeterRevealer.animatedProgress are not animated
238 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
239 }
240 }
242 Item {
243 id: stagesOuterContainer
245 width: parent.width
246 height: parent.height
247 x: launcher.progress
248 Behavior on x {SmoothedAnimation{velocity: 600}}
250 property real showProgress:
251 MathLocal.clamp(1 - (x + stages.x) / shell.width, 0, 1)
253 Showable {
254 id: stages
255 objectName: "stages"
257 x: width
259 property bool fullyShown: shown && x == 0 && parent.x == 0
260 property bool fullyHidden: !shown && x == width
261 available: !greeter.shown
262 hides: [panel.indicators]
263 shown: false
264 opacity: 1.0
265 showAnimation: StandardAnimation { property: "x"; duration: 350; to: 0; easing.type: Easing.OutCubic }
266 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: width; easing.type: Easing.OutCubic }
268 width: parent.width
269 height: parent.height
271 // close the stages when no focused application remains
272 Connections {
273 target: shell.applicationManager
274 onMainStageFocusedApplicationChanged: stages.closeIfNoApplications()
275 onSideStageFocusedApplicationChanged: stages.closeIfNoApplications()
276 ignoreUnknownSignals: true
277 }
279 function closeIfNoApplications() {
280 if (!shell.applicationManager.mainStageFocusedApplication
281 && !shell.applicationManager.sideStageFocusedApplication
282 && shell.applicationManager.mainStageApplications.count == 0
283 && shell.applicationManager.sideStageApplications.count == 0) {
284 stages.hide();
285 }
286 }
288 // show the stages when an application gets the focus
289 Connections {
290 target: shell.applicationManager
291 onMainStageFocusedApplicationChanged: {
292 if (shell.applicationManager.mainStageFocusedApplication) {
293 mainStage.show();
294 stages.show();
295 }
296 }
297 onSideStageFocusedApplicationChanged: {
298 if (shell.applicationManager.sideStageFocusedApplication) {
299 sideStage.show();
300 stages.show();
301 }
302 }
303 ignoreUnknownSignals: true
304 }
306 Stage {
307 id: mainStage
309 anchors.fill: parent
310 fullyShown: stages.fullyShown
311 fullyHidden: stages.fullyHidden
312 shouldUseScreenshots: !fullyShown
313 rightEdgeEnabled: !sideStage.enabled
315 applicationManager: shell.applicationManager
316 rightEdgeDraggingAreaWidth: shell.edgeSize
317 normalApplicationY: shell.panelHeight
319 shown: true
320 function show() {
321 stages.show();
322 }
323 function hide() {
324 }
326 // FIXME: workaround the fact that focusing a main stage application
327 // raises its surface on top of all other surfaces including the ones
328 // that belong to side stage applications.
329 onFocusedApplicationChanged: {
330 if (focusedApplication && sideStage.focusedApplication && sideStage.fullyShown) {
331 shell.applicationManager.focusApplication(sideStage.focusedApplication);
332 }
333 }
334 }
336 SideStage {
337 id: sideStage
339 applicationManager: shell.applicationManager
340 rightEdgeDraggingAreaWidth: shell.edgeSize
341 normalApplicationY: shell.panelHeight
343 onShownChanged: {
344 if (!shown && mainStage.applications.count == 0) {
345 stages.hide();
346 }
347 }
348 // FIXME: when hiding the side stage, refocus the main stage
349 // application so that it goes in front of the side stage
350 // application and hides it
351 onFullyShownChanged: {
352 if (!fullyShown && stages.fullyShown && sideStage.focusedApplication != null) {
353 shell.applicationManager.focusApplication(mainStage.focusedApplication);
354 }
355 }
357 enabled: shell.width >= units.gu(60)
358 visible: enabled
359 fullyShown: stages.fullyShown && shown
360 && sideStage[sideStageRevealer.boundProperty] == sideStageRevealer.openedValue
361 shouldUseScreenshots: !fullyShown || mainStage.usingScreenshots || sideStageRevealer.pressed
363 available: !greeter.shown && !lockscreen.shown && enabled
364 hides: [launcher, panel.indicators]
365 shown: false
366 showAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.openedValue; easing.type: Easing.OutQuint }
367 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.closedValue; easing.type: Easing.OutQuint }
369 width: units.gu(40)
370 height: stages.height
371 handleExpanded: sideStageRevealer.pressed
372 }
374 Revealer {
375 id: sideStageRevealer
377 enabled: mainStage.applications.count > 0 && sideStage.applications.count > 0
378 && sideStage.available
379 direction: Qt.RightToLeft
380 openedValue: parent.width - sideStage.width
381 hintDisplacement: units.gu(3)
382 /* The size of the sidestage handle needs to be bigger than the
383 typical size used for edge detection otherwise it is really
384 hard to grab.
385 */
386 handleSize: sideStage.shown ? units.gu(4) : shell.edgeSize
387 closedValue: parent.width + sideStage.handleSizeCollapsed
388 target: sideStage
389 x: parent.width - width
390 width: sideStage.width + handleSize * 0.7
391 height: sideStage.height
392 orientation: Qt.Horizontal
393 }
395 DragHandle {
396 id: stagesDragHandle
398 anchors.top: parent.top
399 anchors.bottom: parent.bottom
400 anchors.right: parent.left
402 width: shell.edgeSize
403 direction: Direction.Leftwards
404 enabled: edgeDemo.dashEnabled
405 property bool haveApps: mainStage.applications.count > 0 || sideStage.applications.count > 0
407 maxTotalDragDistance: haveApps ? parent.width : parent.width * 0.7
408 // Make autocompletion impossible when !haveApps
409 edgeDragEvaluator.minDragDistance: haveApps ? maxTotalDragDistance * 0.1 : Number.MAX_VALUE
410 }
411 }
412 }
414 Lockscreen {
415 id: lockscreen
416 hides: [launcher, panel.indicators, hud]
417 shown: false
418 enabled: true
419 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
420 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
421 y: panel.panelHeight
422 x: required ? 0 : - width
423 width: parent.width
424 height: parent.height - panel.panelHeight
425 background: shell.background
427 onUnlocked: lockscreen.hide()
428 onCancel: greeter.show()
430 Component.onCompleted: {
431 if (LightDM.Users.count == 1) {
432 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
433 greeter.selected(0)
434 }
435 }
436 }
438 Connections {
439 target: LightDM.Greeter
441 onShowPrompt: {
442 if (LightDM.Users.count == 1) {
443 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
444 if (text == "PIN") {
445 lockscreen.alphaNumeric = false
446 } else {
447 lockscreen.alphaNumeric = true
448 }
449 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
450 lockscreen.show();
451 }
452 }
453 }
455 Greeter {
456 id: greeter
457 objectName: "greeter"
459 available: true
460 hides: [launcher, panel.indicators, hud]
461 shown: true
463 defaultBackground: shell.background
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 }