on eaeb180 help sign in
unity8/Shell.qml
750 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: [launcher, 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 y: panel.panelHeight
464 width: parent.width
465 height: parent.height - panel.panelHeight
467 dragHandleWidth: shell.edgeSize
469 onShownChanged: {
470 if (shown) {
471 lockscreen.reset();
472 // If there is only one user, we start authenticating with that one here.
473 // If there are more users, the Greeter will handle that
474 if (LightDM.Users.count == 1) {
475 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
476 greeter.selected(0);
477 }
478 greeter.forceActiveFocus();
479 }
480 }
482 onUnlocked: greeter.hide()
483 onSelected: {
484 // Update launcher items for new user
485 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
486 AccountsService.user = user;
487 LauncherModel.setUser(user);
488 }
490 onLeftTeaserPressedChanged: {
491 if (leftTeaserPressed) {
492 launcher.tease();
493 }
494 }
495 }
497 InputFilterArea {
498 anchors.fill: parent
499 blockInput: !applicationFocused || greeter.shown || lockscreen.shown || launcher.shown
500 || panel.indicators.shown || hud.shown
501 }
503 Connections {
504 id: powerConnection
505 target: Powerd
507 property var previousMainApp: null
508 property var previousSideApp: null
510 function setFocused(focused) {
511 if (!focused) {
512 powerConnection.previousMainApp = applicationManager.mainStageFocusedApplication;
513 powerConnection.previousSideApp = applicationManager.sideStageFocusedApplication;
514 applicationManager.unfocusCurrentApplication();
515 } else {
516 if (powerConnection.previousMainApp) {
517 applicationManager.focusApplication(powerConnection.previousMainApp);
518 powerConnection.previousMainApp = null;
519 }
520 if (powerConnection.previousSideApp) {
521 applicationManager.focusApplication(powerConnection.previousSideApp);
522 powerConnection.previousSideApp = null;
523 }
524 }
525 }
527 onDisplayPowerStateChange: {
528 // We ignore any display-off signals when the proximity sensor
529 // is active. This usually indicates something like a phone call.
530 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
531 powerConnection.setFocused(false);
532 greeter.show();
533 } else if (status == Powerd.On) {
534 powerConnection.setFocused(true);
535 }
537 // No reason to chew demo CPU when user isn't watching
538 if (status == Powerd.Off) {
539 edgeDemo.paused = true;
540 } else if (status == Powerd.On) {
541 edgeDemo.paused = false;
542 }
543 }
544 }
546 Item {
547 id: overlay
549 anchors.fill: parent
551 Panel {
552 id: panel
553 anchors.fill: parent //because this draws indicator menus
554 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
555 indicators {
556 hides: [launcher]
557 available: edgeDemo.panelEnabled
558 contentEnabled: edgeDemo.panelContentEnabled
559 }
560 fullscreenMode: shell.fullscreenMode
561 searchVisible: !greeter.shown && !lockscreen.shown
563 InputFilterArea {
564 anchors {
565 top: parent.top
566 left: parent.left
567 right: parent.right
568 }
569 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
570 blockInput: true
571 }
572 }
574 Hud {
575 id: hud
577 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
578 height: parent.height
580 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown && edgeDemo.dashEnabled
581 shown: false
582 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
583 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
585 Connections {
586 target: shell.applicationManager
587 onMainStageFocusedApplicationChanged: hud.hide()
588 onSideStageFocusedApplicationChanged: hud.hide()
589 }
590 }
592 Revealer {
593 id: hudRevealer
595 enabled: hud.shown
596 width: hud.width
597 anchors.left: hud.left
598 height: parent.height
599 target: hud.revealerTarget
600 closedValue: height
601 openedValue: 0
602 direction: Qt.RightToLeft
603 orientation: Qt.Vertical
604 handleSize: hud.handleHeight
605 onCloseClicked: target.hide()
606 }
608 Bottombar {
609 theHud: hud
610 anchors.fill: parent
611 enabled: !panel.indicators.shown
612 applicationIsOnForeground: applicationFocused
613 }
615 InputFilterArea {
616 blockInput: launcher.shown
617 anchors {
618 top: parent.top
619 bottom: parent.bottom
620 left: parent.left
621 }
622 width: launcher.width
623 }
625 Launcher {
626 id: launcher
628 anchors.top: parent.top
629 anchors.bottom: parent.bottom
630 width: parent.width
631 dragAreaWidth: shell.edgeSize
632 available: (!greeter.shown || greeter.narrowMode) && edgeDemo.launcherEnabled
633 onDashItemSelected: {
634 greeter.hide()
635 // Animate if moving between application and dash
636 if (!stages.shown) {
637 dash.setCurrentScope("home.scope", true, false)
638 } else {
639 dash.setCurrentScope("home.scope", false, false)
640 }
641 stages.hide();
642 }
643 onDash: {
644 if (stages.shown) {
645 dash.setCurrentScope("applications.scope", true, false)
646 stages.hide();
647 launcher.hide();
648 }
649 }
650 onLauncherApplicationSelected:{
651 greeter.hide()
652 shell.activateApplication(appId)
653 }
654 onShownChanged: {
655 if (shown) {
656 panel.indicators.hide()
657 hud.hide()
658 }
659 }
660 }
662 Notifications {
663 id: notifications
665 model: NotificationBackend.Model
666 anchors {
667 top: parent.top
668 right: parent.right
669 bottom: parent.bottom
670 leftMargin: units.gu(1)
671 rightMargin: units.gu(1)
672 topMargin: panel.panelHeight + units.gu(1)
673 }
674 states: [
675 State {
676 name: "narrow"
677 when: overlay.width <= units.gu(60)
678 AnchorChanges { target: notifications; anchors.left: parent.left }
679 },
680 State {
681 name: "wide"
682 when: overlay.width > units.gu(60)
683 AnchorChanges { target: notifications; anchors.left: undefined }
684 PropertyChanges { target: notifications; width: units.gu(38) }
685 }
686 ]
687 }
688 }
690 focus: true
692 InputFilterArea {
693 anchors {
694 top: parent.top
695 bottom: parent.bottom
696 left: parent.left
697 }
698 width: shell.edgeSize
699 blockInput: true
700 }
702 InputFilterArea {
703 anchors {
704 top: parent.top
705 bottom: parent.bottom
706 right: parent.right
707 }
708 width: shell.edgeSize
709 blockInput: true
710 }
712 Binding {
713 target: i18n
714 property: "domain"
715 value: "unity8"
716 }
718 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
719 MouseArea {
720 anchors.bottom: parent.bottom
721 anchors.left: parent.left
722 anchors.right: parent.right
723 height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
725 enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
726 }
728 Label {
729 anchors.centerIn: parent
730 visible: applicationManager.fake
731 text: "EARLY ALPHA\nNOT READY FOR USE"
732 color: "lightgrey"
733 opacity: 0.2
734 font.weight: Font.Black
735 horizontalAlignment: Text.AlignHCenter
736 verticalAlignment: Text.AlignVCenter
737 fontSizeMode: Text.Fit
738 rotation: -45
739 scale: Math.min(parent.width, parent.height) / width
740 }
742 EdgeDemo {
743 id: edgeDemo
744 greeter: greeter
745 launcher: launcher
746 dash: dash
747 indicators: panel.indicators
748 underlay: underlay
749 }