on ce999dc help sign in
unity8/Shell.qml
756 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.shellStageFocusedApplication
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 VolumeControl {
161 id: volumeControl
162 }
164 Keys.onVolumeUpPressed: volumeControl.volumeUp()
165 Keys.onVolumeDownPressed: volumeControl.volumeDown()
167 Item {
168 id: underlay
169 objectName: "underlay"
170 anchors.fill: parent
172 // Whether the underlay is fully covered by opaque UI elements.
173 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
175 readonly property bool applicationRunning: ((mainStage.applications && mainStage.applications.count > 0)
176 || (sideStage.applications && sideStage.applications.count > 0))
178 // Whether the user should see the topmost application surface (if there's one at all).
179 readonly property bool applicationSurfaceShouldBeSeen: applicationRunning && !stages.fullyHidden
180 && !mainStage.usingScreenshots // but want sideStage animating over app surface
184 // NB! Application surfaces are stacked behing the shell one. So they can only be seen by the user
185 // through the translucent parts of the shell surface.
186 visible: !fullyCovered && !applicationSurfaceShouldBeSeen
188 CrossFadeImage {
189 id: backgroundImage
190 objectName: "backgroundImage"
192 anchors.fill: parent
193 source: shell.background
194 }
196 Rectangle {
197 anchors.fill: parent
198 color: "black"
199 opacity: dash.disappearingAnimationProgress
200 }
202 Dash {
203 id: dash
204 objectName: "dash"
206 available: !greeter.shown && !lockscreen.shown
207 hides: [stages, launcher, panel.indicators]
208 shown: disappearingAnimationProgress !== 1.0
209 enabled: disappearingAnimationProgress === 0.0 && edgeDemo.dashEnabled
210 // FIXME: unfocus all applications when going back to the dash
211 onEnabledChanged: {
212 if (enabled) {
213 shell.applicationManager.unfocusCurrentApplication()
214 }
215 }
217 anchors {
218 fill: parent
219 topMargin: panel.panelHeight
220 }
222 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
223 opacity: 1.0 - disappearingAnimationProgress
224 property real disappearingAnimationProgress: {
225 if (greeter.shown) {
226 return greeter.showProgress;
227 } else {
228 return stagesOuterContainer.showProgress;
229 }
230 }
232 // FIXME: only necessary because stagesOuterContainer.showProgress and
233 // greeterRevealer.animatedProgress are not animated
234 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
235 }
236 }
238 Item {
239 id: stagesOuterContainer
241 width: parent.width
242 height: parent.height
243 x: launcher.progress
244 Behavior on x {SmoothedAnimation{velocity: 600}}
246 property real showProgress:
247 MathLocal.clamp(1 - (x + stages.x) / shell.width, 0, 1)
249 Showable {
250 id: stages
251 objectName: "stages"
253 x: width
255 property bool fullyShown: shown && x == 0 && parent.x == 0
256 property bool fullyHidden: !shown && x == width
257 available: !greeter.shown
258 hides: [launcher, panel.indicators]
259 shown: false
260 opacity: 1.0
261 showAnimation: StandardAnimation { property: "x"; duration: 350; to: 0; easing.type: Easing.OutCubic }
262 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: width; easing.type: Easing.OutCubic }
264 width: parent.width
265 height: parent.height
267 // close the stages when no focused application remains
268 Connections {
269 target: shell.applicationManager
270 onMainStageFocusedApplicationChanged: stages.closeIfNoApplications()
271 onSideStageFocusedApplicationChanged: stages.closeIfNoApplications()
272 ignoreUnknownSignals: true
273 }
275 function closeIfNoApplications() {
276 if (!shell.applicationManager.mainStageFocusedApplication
277 && !shell.applicationManager.sideStageFocusedApplication
278 && shell.applicationManager.mainStageApplications.count == 0
279 && shell.applicationManager.sideStageApplications.count == 0) {
280 stages.hide();
281 }
282 }
284 // show the stages when an application gets the focus
285 Connections {
286 target: shell.applicationManager
287 onMainStageFocusedApplicationChanged: {
288 if (shell.applicationManager.mainStageFocusedApplication) {
289 mainStage.show();
290 stages.show();
291 }
292 }
293 onSideStageFocusedApplicationChanged: {
294 if (shell.applicationManager.sideStageFocusedApplication) {
295 sideStage.show();
296 stages.show();
297 }
298 }
299 ignoreUnknownSignals: true
300 }
302 Stage {
303 id: mainStage
305 anchors.fill: parent
306 fullyShown: stages.fullyShown
307 fullyHidden: stages.fullyHidden
308 shouldUseScreenshots: !fullyShown
309 rightEdgeEnabled: !sideStage.enabled
311 applicationManager: shell.applicationManager
312 rightEdgeDraggingAreaWidth: shell.edgeSize
313 normalApplicationY: shell.panelHeight
315 shown: true
316 function show() {
317 stages.show();
318 }
319 function hide() {
320 }
322 // FIXME: workaround the fact that focusing a main stage application
323 // raises its surface on top of all other surfaces including the ones
324 // that belong to side stage applications.
325 onFocusedApplicationChanged: {
326 if (focusedApplication && sideStage.focusedApplication && sideStage.fullyShown) {
327 shell.applicationManager.focusApplication(sideStage.focusedApplication);
328 }
329 }
330 }
332 SideStage {
333 id: sideStage
335 applicationManager: shell.applicationManager
336 rightEdgeDraggingAreaWidth: shell.edgeSize
337 normalApplicationY: shell.panelHeight
339 onShownChanged: {
340 if (!shown && mainStage.applications.count == 0) {
341 stages.hide();
342 }
343 }
344 // FIXME: when hiding the side stage, refocus the main stage
345 // application so that it goes in front of the side stage
346 // application and hides it
347 onFullyShownChanged: {
348 if (!fullyShown && stages.fullyShown && sideStage.focusedApplication != null) {
349 shell.applicationManager.focusApplication(mainStage.focusedApplication);
350 }
351 }
353 enabled: shell.width >= units.gu(60)
354 visible: enabled
355 fullyShown: stages.fullyShown && shown
356 && sideStage[sideStageRevealer.boundProperty] == sideStageRevealer.openedValue
357 shouldUseScreenshots: !fullyShown || mainStage.usingScreenshots || sideStageRevealer.pressed
359 available: !greeter.shown && !lockscreen.shown && enabled
360 hides: [launcher, panel.indicators]
361 shown: false
362 showAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.openedValue; easing.type: Easing.OutQuint }
363 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.closedValue; easing.type: Easing.OutQuint }
365 width: units.gu(40)
366 height: stages.height
367 handleExpanded: sideStageRevealer.pressed
368 }
370 Revealer {
371 id: sideStageRevealer
373 enabled: mainStage.applications.count > 0 && sideStage.applications.count > 0
374 && sideStage.available
375 direction: Qt.RightToLeft
376 openedValue: parent.width - sideStage.width
377 hintDisplacement: units.gu(3)
378 /* The size of the sidestage handle needs to be bigger than the
379 typical size used for edge detection otherwise it is really
380 hard to grab.
381 */
382 handleSize: sideStage.shown ? units.gu(4) : shell.edgeSize
383 closedValue: parent.width + sideStage.handleSizeCollapsed
384 target: sideStage
385 x: parent.width - width
386 width: sideStage.width + handleSize * 0.7
387 height: sideStage.height
388 orientation: Qt.Horizontal
389 }
391 DragHandle {
392 id: stagesDragHandle
394 anchors.top: parent.top
395 anchors.bottom: parent.bottom
396 anchors.right: parent.left
398 width: shell.edgeSize
399 direction: Direction.Leftwards
400 enabled: edgeDemo.dashEnabled
401 property bool haveApps: mainStage.applications.count > 0 || sideStage.applications.count > 0
403 maxTotalDragDistance: haveApps ? parent.width : parent.width * 0.7
404 // Make autocompletion impossible when !haveApps
405 edgeDragEvaluator.minDragDistance: haveApps ? maxTotalDragDistance * 0.1 : Number.MAX_VALUE
406 }
407 }
408 }
410 Lockscreen {
411 id: lockscreen
412 hides: [launcher, panel.indicators, hud]
413 shown: false
414 enabled: true
415 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
416 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
417 y: panel.panelHeight
418 x: required ? 0 : - width
419 width: parent.width
420 height: parent.height - panel.panelHeight
421 background: shell.background
423 onUnlocked: lockscreen.hide()
424 onCancel: greeter.show()
426 Component.onCompleted: {
427 if (LightDM.Users.count == 1) {
428 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
429 greeter.selected(0)
430 }
431 }
432 }
434 Connections {
435 target: LightDM.Greeter
437 onShowPrompt: {
438 if (LightDM.Users.count == 1) {
439 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
440 if (text == "PIN") {
441 lockscreen.alphaNumeric = false
442 } else {
443 lockscreen.alphaNumeric = true
444 }
445 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
446 lockscreen.show();
447 }
448 }
449 }
451 Greeter {
452 id: greeter
453 objectName: "greeter"
455 available: true
456 hides: [launcher, panel.indicators, hud]
457 shown: true
459 y: panel.panelHeight
460 width: parent.width
461 height: parent.height - panel.panelHeight
463 dragHandleWidth: shell.edgeSize
465 onShownChanged: {
466 if (shown) {
467 lockscreen.reset();
468 // If there is only one user, we start authenticating with that one here.
469 // If there are more users, the Greeter will handle that
470 if (LightDM.Users.count == 1) {
471 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
472 greeter.selected(0);
473 }
474 greeter.forceActiveFocus();
475 }
476 }
478 onUnlocked: greeter.hide()
479 onSelected: {
480 // Update launcher items for new user
481 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
482 AccountsService.user = user;
483 LauncherModel.setUser(user);
484 }
486 onLeftTeaserPressedChanged: {
487 if (leftTeaserPressed) {
488 launcher.tease();
489 }
490 }
491 }
493 InputFilterArea {
494 anchors.fill: parent
495 blockInput: !applicationFocused || greeter.shown || lockscreen.shown || launcher.shown
496 || panel.indicators.shown
497 }
499 Connections {
500 id: powerConnection
501 target: Powerd
503 property var previousMainApp: null
504 property var previousSideApp: null
506 function setFocused(focused) {
507 if (!focused) {
508 powerConnection.previousMainApp = applicationManager.mainStageFocusedApplication;
509 powerConnection.previousSideApp = applicationManager.sideStageFocusedApplication;
510 applicationManager.unfocusCurrentApplication();
511 } else {
512 if (powerConnection.previousMainApp) {
513 applicationManager.focusApplication(powerConnection.previousMainApp);
514 powerConnection.previousMainApp = null;
515 }
516 if (powerConnection.previousSideApp) {
517 applicationManager.focusApplication(powerConnection.previousSideApp);
518 powerConnection.previousSideApp = null;
519 }
520 }
521 }
523 onDisplayPowerStateChange: {
524 // We ignore any display-off signals when the proximity sensor
525 // is active. This usually indicates something like a phone call.
526 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
527 powerConnection.setFocused(false);
528 greeter.show();
529 } else if (status == Powerd.On) {
530 powerConnection.setFocused(true);
531 }
533 // No reason to chew demo CPU when user isn't watching
534 if (status == Powerd.Off) {
535 edgeDemo.paused = true;
536 } else if (status == Powerd.On) {
537 edgeDemo.paused = false;
538 }
539 }
540 }
542 Item {
543 id: overlay
545 anchors.fill: parent
547 Panel {
548 id: panel
549 anchors.fill: parent //because this draws indicator menus
550 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
551 indicators {
552 hides: [launcher]
553 available: edgeDemo.panelEnabled
554 contentEnabled: edgeDemo.panelContentEnabled
555 }
556 fullscreenMode: shell.fullscreenMode
557 searchVisible: !greeter.shown && !lockscreen.shown
559 InputFilterArea {
560 anchors {
561 top: parent.top
562 left: parent.left
563 right: parent.right
564 }
565 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
566 blockInput: true
567 }
568 }
570 Hud {
571 id: hud
573 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
574 height: parent.height
576 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown && edgeDemo.dashEnabled
577 shown: false
578 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
579 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
581 Connections {
582 target: shell.applicationManager
583 onMainStageFocusedApplicationChanged: hud.hide()
584 onSideStageFocusedApplicationChanged: hud.hide()
585 }
587 InputFilterArea {
588 anchors {
589 bottom: parent.bottom
590 left: parent.left
591 }
592 width: parent.width
593 height: (!hud.shown) ? shell.edgeSize : parent.height
594 blockInput: true
595 }
596 }
598 Revealer {
599 id: hudRevealer
601 enabled: hud.shown
602 width: hud.width
603 anchors.left: hud.left
604 height: parent.height
605 target: hud.revealerTarget
606 closedValue: height
607 openedValue: 0
608 direction: Qt.RightToLeft
609 orientation: Qt.Vertical
610 handleSize: hud.handleHeight
611 onCloseClicked: target.hide()
612 }
614 Bottombar {
615 theHud: hud
616 anchors.fill: parent
617 enabled: !panel.indicators.shown
618 applicationIsOnForeground: applicationFocused
619 }
621 InputFilterArea {
622 blockInput: launcher.shown
623 anchors {
624 top: parent.top
625 bottom: parent.bottom
626 left: parent.left
627 }
628 width: launcher.width
629 }
631 Launcher {
632 id: launcher
634 anchors.top: parent.top
635 anchors.bottom: parent.bottom
636 width: parent.width
637 dragAreaWidth: shell.edgeSize
638 available: (!greeter.shown || greeter.narrowMode) && edgeDemo.launcherEnabled
639 onDashItemSelected: {
640 greeter.hide()
641 // Animate if moving between application and dash
642 if (!stages.shown) {
643 dash.setCurrentScope("home.scope", true, false)
644 } else {
645 dash.setCurrentScope("home.scope", false, false)
646 }
647 stages.hide();
648 }
649 onDash: {
650 if (stages.shown) {
651 dash.setCurrentScope("applications.scope", true, false)
652 stages.hide();
653 launcher.hide();
654 }
655 }
656 onLauncherApplicationSelected:{
657 greeter.hide()
658 shell.activateApplication(appId)
659 }
660 onShownChanged: {
661 if (shown) {
662 panel.indicators.hide()
663 hud.hide()
664 }
665 }
666 }
668 Notifications {
669 id: notifications
671 model: NotificationBackend.Model
672 anchors {
673 top: parent.top
674 right: parent.right
675 bottom: parent.bottom
676 leftMargin: units.gu(1)
677 rightMargin: units.gu(1)
678 topMargin: panel.panelHeight + units.gu(1)
679 }
680 states: [
681 State {
682 name: "narrow"
683 when: overlay.width <= units.gu(60)
684 AnchorChanges { target: notifications; anchors.left: parent.left }
685 },
686 State {
687 name: "wide"
688 when: overlay.width > units.gu(60)
689 AnchorChanges { target: notifications; anchors.left: undefined }
690 PropertyChanges { target: notifications; width: units.gu(38) }
691 }
692 ]
693 }
694 }
696 focus: true
698 InputFilterArea {
699 anchors {
700 top: parent.top
701 bottom: parent.bottom
702 left: parent.left
703 }
704 width: shell.edgeSize
705 blockInput: true
706 }
708 InputFilterArea {
709 anchors {
710 top: parent.top
711 bottom: parent.bottom
712 right: parent.right
713 }
714 width: shell.edgeSize
715 blockInput: true
716 }
718 Binding {
719 target: i18n
720 property: "domain"
721 value: "unity8"
722 }
724 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
725 MouseArea {
726 anchors.bottom: parent.bottom
727 anchors.left: parent.left
728 anchors.right: parent.right
729 height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
731 enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
732 }
734 Label {
735 anchors.centerIn: parent
736 visible: applicationManager.fake
737 text: "EARLY ALPHA\nNOT READY FOR USE"
738 color: "lightgrey"
739 opacity: 0.2
740 font.weight: Font.Black
741 horizontalAlignment: Text.AlignHCenter
742 verticalAlignment: Text.AlignVCenter
743 fontSizeMode: Text.Fit
744 rotation: -45
745 scale: Math.min(parent.width, parent.height) / width
746 }
748 EdgeDemo {
749 id: edgeDemo
750 greeter: greeter
751 launcher: launcher
752 dash: dash
753 indicators: panel.indicators
754 underlay: underlay
755 }