on 80acc01 help sign in
unity8/Shell.qml
763 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 Ubuntu.Application 0.1
19import Ubuntu.Components 0.1
20import Ubuntu.Gestures 0.1
21import LightDM 0.1 as LightDM
22import AccountsService 0.1
23import "Dash"
24import "Greeter"
25import "Launcher"
26import "Panel"
27import "Hud"
28import "Components"
29import "Components/Math.js" as MathLocal
30import "Bottombar"
31import "SideStage"
32import "Notifications"
33import Unity.Notifications 1.0 as NotificationBackend
35FocusScope {
36 id: shell
38 // this is only here to select the width / height of the window if not running fullscreen
39 property bool tablet: false
40 width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
41 height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
43 property real edgeSize: units.gu(2)
44 property url default_background: shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg"
45 property url background: default_background
46 readonly property real panelHeight: panel.panelHeight
48 property bool dashShown: dash.shown
49 property bool stageScreenshotsReady: {
50 if (sideStage.shown) {
51 if (mainStage.applications.count > 0) {
52 return mainStage.usingScreenshots && sideStage.usingScreenshots;
53 } else {
54 return sideStage.usingScreenshots;
55 }
56 } else {
57 return mainStage.usingScreenshots;
58 }
59 }
61 property bool showEdgeDemo: true
62 property bool showEdgeDemoInGreeter: showEdgeDemo //AccountsService.getUserProperty("lightdm", "demo-edges")
64 function hideEdgeDemo() {
65 var user = LightDM.Users.data(greeter.currentIndex, LightDM.UserRoles.NameRole);
66 AccountsService.setUserProperty(user, "demo-edges", false);
67 shell.showEdgeDemo = false;
68 }
69 function hideEdgeDemoInGreeter() {
70 //AccountsService.setUserProperty("lightdm", "demo-edges", false);
71 shell.showEdgeDemoInGreeter = false;
72 }
74 property ListModel searchHistory: SearchHistoryModel {}
76 property var applicationManager: ApplicationManagerWrapper {}
78 Component.onCompleted: {
79 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
81 applicationManager.sideStageEnabled = Qt.binding(function() { return sideStage.enabled })
83 // FIXME: if application focused before shell starts, shell draws on top of it only.
84 // We should detect already running applications on shell start and bring them to the front.
85 applicationManager.unfocusCurrentApplication();
86 }
88 readonly property bool fullscreenMode: {
89 if (greeter.shown || lockscreen.shown) {
90 return false;
91 } else if (mainStage.usingScreenshots) { // Window Manager animating so want to re-evaluate fullscreen mode
92 return mainStage.switchingFromFullscreenToFullscreen;
93 } else if (applicationManager.mainStageFocusedApplication) {
94 return applicationManager.mainStageFocusedApplication.fullscreen;
95 } else {
96 return false;
97 }
98 }
100 Connections {
101 target: applicationManager
102 ignoreUnknownSignals: true
103 onFocusRequested: {
104 // TODO: this should be protected to only unlock for certain applications / certain usecases
105 // potentially only in connection with a notification
106 greeter.hide();
107 shell.activateApplication(desktopFile);
108 }
109 }
111 function activateApplication(desktopFile, argument) {
112 if (applicationManager) {
113 // For newly started applications, as it takes them time to draw their first frame
114 // we add a delay before we hide the animation screenshots to compensate.
115 var addDelay = !applicationManager.getApplicationFromDesktopFile(desktopFile);
117 var application;
118 application = applicationManager.activateApplication(desktopFile, argument);
119 if (application == null) {
120 return;
121 }
122 if (application.stage == ApplicationInfo.MainStage || !sideStage.enabled) {
123 mainStage.activateApplication(desktopFile, addDelay);
124 } else {
125 sideStage.activateApplication(desktopFile, addDelay);
126 }
127 stages.show();
128 }
129 }
131 VolumeControl {
132 id: volumeControl
133 }
135 Keys.onVolumeUpPressed: volumeControl.volumeUp()
136 Keys.onVolumeDownPressed: volumeControl.volumeDown()
138 Keys.onReleased: {
139 if (event.key == Qt.Key_PowerOff) {
140 greeter.show()
141 }
142 }
144 Item {
145 id: underlay
146 objectName: "underlay"
147 anchors.fill: parent
149 // Whether the underlay is fully covered by opaque UI elements.
150 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
152 // Whether the user should see the topmost application surface (if there's one at all).
153 property bool applicationSurfaceShouldBeSeen:
154 (mainStage.applications && mainStage.applications.count > 0)
155 && (!stages.fullyHidden && !mainStage.usingScreenshots)
157 // NB! Application surfaces are stacked behing the shell one. So they can only be seen by the user
158 // through the translucent parts of the shell surface.
159 visible: !fullyCovered && !applicationSurfaceShouldBeSeen
161 Image {
162 id: backgroundImage
163 source: shell.background
164 sourceSize.width: parent.width
165 sourceSize.height: parent.height
166 anchors.fill: parent
167 }
169 Rectangle {
170 anchors.fill: parent
171 color: "black"
172 opacity: dash.disappearingAnimationProgress
173 }
175 Dash {
176 id: dash
177 objectName: "dash"
179 available: !greeter.shown && !lockscreen.shown
180 hides: [stages, launcher, panel.indicators]
181 shown: disappearingAnimationProgress !== 1.0
182 enabled: disappearingAnimationProgress === 0.0 && !leftEdgeDemo.visible && !topEdgeDemo.visible
183 // FIXME: unfocus all applications when going back to the dash
184 onEnabledChanged: {
185 if (enabled) {
186 shell.applicationManager.unfocusCurrentApplication()
187 }
188 }
190 anchors {
191 fill: parent
192 topMargin: panel.panelHeight
193 }
195 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
196 opacity: 1.0 - disappearingAnimationProgress
197 property real disappearingAnimationProgress: {
198 if (greeter.shown) {
199 return greeter.showProgress;
200 } else {
201 return stagesOuterContainer.showProgress;
202 }
203 }
205 // FIXME: only necessary because stagesOuterContainer.showProgress and
206 // greeterRevealer.animatedProgress are not animated
207 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
208 }
210 DemoOverlay {
211 id: topEdgeDemo
213 edge: "top"
214 title: i18n.tr("Top edge")
215 text: i18n.tr("Try swiping from the top edge to access the indicators")
216 anchors.fill: dash
217 visible: false
218 enabled: shell.showEdgeDemo
219 onSkip: shell.hideEdgeDemo()
221 Connections {
222 target: greeter
223 onShownChanged: if (!greeter.shown) topEdgeDemo.visible = true
224 }
226 Connections {
227 target: panel.indicators
228 onFullyOpenedChanged: if (panel.indicators.fullyOpened) topEdgeDemo.visible = false
229 }
230 }
232 DemoOverlay {
233 id: leftEdgeDemo
235 edge: "left"
236 title: i18n.tr("Left edge")
237 text: i18n.tr("Swipe from the left to reveal the launcher for quick access to apps")
238 anchors.fill: dash
239 visible: false
240 enabled: shell.showEdgeDemo
241 onSkip: shell.hideEdgeDemo()
243 Connections {
244 target: bottomEdgeDemo
245 onVisibleChanged: if (!bottomEdgeDemo.visible) leftEdgeDemo.visible = true
246 }
248 Connections {
249 target: launcher
250 onProgressChanged: {
251 if (launcher.progress >= 1.0) {
252 leftEdgeDemo.visible = false;
253 shell.hideEdgeDemo();
254 }
255 }
256 }
257 }
258 }
261 Item {
262 id: stagesOuterContainer
264 width: parent.width
265 height: parent.height
266 x: launcher.progress
267 Behavior on x {SmoothedAnimation{velocity: 600}}
269 property real showProgress:
270 MathLocal.clamp(1 - (x + stages.x) / shell.width, 0, 1)
272 Showable {
273 id: stages
274 objectName: "stages"
276 x: width
278 property bool fullyShown: shown && x == 0 && parent.x == 0
279 property bool fullyHidden: !shown && x == width
280 available: !greeter.shown
281 hides: [launcher, panel.indicators]
282 shown: false
283 opacity: 1.0
284 showAnimation: StandardAnimation { property: "x"; duration: 350; to: 0; easing.type: Easing.OutCubic }
285 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: width; easing.type: Easing.OutCubic }
287 width: parent.width
288 height: parent.height
290 // close the stages when no focused application remains
291 Connections {
292 target: shell.applicationManager
293 onMainStageFocusedApplicationChanged: stages.closeIfNoApplications()
294 onSideStageFocusedApplicationChanged: stages.closeIfNoApplications()
295 ignoreUnknownSignals: true
296 }
298 function closeIfNoApplications() {
299 if (!shell.applicationManager.mainStageFocusedApplication
300 && !shell.applicationManager.sideStageFocusedApplication
301 && shell.applicationManager.mainStageApplications.count == 0
302 && shell.applicationManager.sideStageApplications.count == 0) {
303 stages.hide();
304 }
305 }
307 // show the stages when an application gets the focus
308 Connections {
309 target: shell.applicationManager
310 onMainStageFocusedApplicationChanged: {
311 if (shell.applicationManager.mainStageFocusedApplication) {
312 mainStage.show();
313 stages.show();
314 }
315 }
316 onSideStageFocusedApplicationChanged: {
317 if (shell.applicationManager.sideStageFocusedApplication) {
318 sideStage.show();
319 stages.show();
320 }
321 }
322 ignoreUnknownSignals: true
323 }
326 Stage {
327 id: mainStage
329 anchors.fill: parent
330 fullyShown: stages.fullyShown
331 fullyHidden: stages.fullyHidden
332 shouldUseScreenshots: !fullyShown
333 rightEdgeEnabled: !sideStage.enabled
335 applicationManager: shell.applicationManager
336 rightEdgeDraggingAreaWidth: shell.edgeSize
337 normalApplicationY: shell.panelHeight
339 shown: true
340 function show() {
341 stages.show();
342 }
343 function hide() {
344 }
346 // FIXME: workaround the fact that focusing a main stage application
347 // raises its surface on top of all other surfaces including the ones
348 // that belong to side stage applications.
349 onFocusedApplicationChanged: {
350 if (focusedApplication && sideStage.focusedApplication && sideStage.fullyShown) {
351 shell.applicationManager.focusApplication(sideStage.focusedApplication);
352 }
353 }
354 }
356 SideStage {
357 id: sideStage
359 applicationManager: shell.applicationManager
360 rightEdgeDraggingAreaWidth: shell.edgeSize
361 normalApplicationY: shell.panelHeight
363 onShownChanged: {
364 if (!shown && mainStage.applications.count == 0) {
365 stages.hide();
366 }
367 }
368 // FIXME: when hiding the side stage, refocus the main stage
369 // application so that it goes in front of the side stage
370 // application and hides it
371 onFullyShownChanged: {
372 if (!fullyShown && stages.fullyShown && sideStage.focusedApplication != null) {
373 shell.applicationManager.focusApplication(mainStage.focusedApplication);
374 }
375 }
377 enabled: shell.width >= units.gu(60)
378 visible: enabled
379 fullyShown: stages.fullyShown && shown
380 && sideStage[sideStageRevealer.boundProperty] == sideStageRevealer.openedValue
381 shouldUseScreenshots: !fullyShown || mainStage.usingScreenshots || sideStageRevealer.pressed
383 available: !greeter.shown && !lockscreen.shown && enabled
384 hides: [launcher, panel.indicators]
385 shown: false
386 showAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.openedValue; easing.type: Easing.OutQuint }
387 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.closedValue; easing.type: Easing.OutQuint }
389 width: units.gu(40)
390 height: stages.height
391 handleExpanded: sideStageRevealer.pressed
392 }
394 Revealer {
395 id: sideStageRevealer
397 enabled: mainStage.applications.count > 0 && sideStage.applications.count > 0
398 && sideStage.available
399 direction: Qt.RightToLeft
400 openedValue: parent.width - sideStage.width
401 hintDisplacement: units.gu(3)
402 /* The size of the sidestage handle needs to be bigger than the
403 typical size used for edge detection otherwise it is really
404 hard to grab.
405 */
406 handleSize: sideStage.shown ? units.gu(4) : shell.edgeSize
407 closedValue: parent.width + sideStage.handleSizeCollapsed
408 target: sideStage
409 x: parent.width - width
410 width: sideStage.width + handleSize * 0.7
411 height: sideStage.height
412 orientation: Qt.Horizontal
413 }
415 DragHandle {
416 id: stagesDragHandle
418 anchors.top: parent.top
419 anchors.bottom: parent.bottom
420 anchors.right: parent.left
422 width: shell.edgeSize
423 direction: Direction.Leftwards
424 property bool haveApps: mainStage.applications.count > 0 || sideStage.applications.count > 0
426 maxTotalDragDistance: haveApps ? parent.width : parent.width * 0.7
427 // Make autocompletion impossible when !haveApps
428 edgeDragEvaluator.minDragDistance: haveApps ? maxTotalDragDistance * 0.1 : Number.MAX_VALUE
429 }
430 }
431 }
433 Lockscreen {
434 id: lockscreen
435 hides: [launcher, panel.indicators, hud]
436 shown: false
437 enabled: true
438 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
439 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
440 y: panel.panelHeight
441 x: required ? 0 : - width
442 width: parent.width
443 height: parent.height - panel.panelHeight
444 background: shell.background
446 onUnlocked: lockscreen.hide()
447 onCancel: greeter.show()
449 Component.onCompleted: {
450 if (LightDM.Users.count == 1) {
451 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
452 greeter.selected(0)
453 }
454 }
455 }
457 Connections {
458 target: LightDM.Greeter
460 onShowPrompt: {
461 if (LightDM.Users.count == 1) {
462 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
463 if (text == "PIN") {
464 lockscreen.alphaNumeric = false
465 } else {
466 lockscreen.alphaNumeric = true
467 }
468 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
469 lockscreen.show();
470 }
471 }
472 }
474 Greeter {
475 id: greeter
477 available: true
478 hides: [launcher, panel.indicators, hud]
479 shown: true
481 y: panel.panelHeight
482 width: parent.width
483 height: parent.height - panel.panelHeight
485 dragHandleWidth: shell.edgeSize
487 property var previousMainApp: null
488 property var previousSideApp: null
490 onShownChanged: {
491 if (shown) {
492 lockscreen.reset();
493 // If there is only one user, we start authenticating with that one here.
494 // If there are more users, the Greeter will handle that
495 if (LightDM.Users.count == 1) {
496 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
497 greeter.selected(0);
498 }
499 greeter.forceActiveFocus();
500 // FIXME: *FocusedApplication are not updated when unfocused, hence the need to check whether
501 // the stage was actually shown
502 if (mainStage.fullyShown) greeter.previousMainApp = applicationManager.mainStageFocusedApplication;
503 if (sideStage.fullyShown) greeter.previousSideApp = applicationManager.sideStageFocusedApplication;
504 applicationManager.unfocusCurrentApplication();
505 } else {
506 if (greeter.previousMainApp) {
507 applicationManager.focusApplication(greeter.previousMainApp);
508 greeter.previousMainApp = null;
509 }
510 if (greeter.previousSideApp) {
511 applicationManager.focusApplication(greeter.previousSideApp);
512 greeter.previousSideApp = null;
513 }
514 }
515 }
517 onUnlocked: greeter.hide()
518 onSelected: {
519 // Update background
520 var bgPath = greeter.model.data(uid, LightDM.UserRoles.BackgroundPathRole)
521 shell.background = bgPath ? bgPath : default_background
522 // Update edge demo hint
523 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole)
524 shell.showEdgeDemo = AccountsService.getUserProperty(user, "demo-edges")
525 }
527 onLeftTeaserPressedChanged: {
528 if (leftTeaserPressed) {
529 launcher.tease();
530 }
531 }
533 DemoOverlay {
534 id: rightEdgeDemo
536 edge: "right"
537 title: i18n.tr("Right edge")
538 text: i18n.tr("Try swiping from the right edge to unlock the phone")
539 anchors.fill: parent
540 enabled: shell.showEdgeDemoInGreeter
541 onSkip: {
542 shell.hideEdgeDemoInGreeter()
543 shell.hideEdgeDemo()
544 }
546 Connections {
547 target: greeter
548 onUnlocked: rightEdgeDemo.visible = false
549 onShownChanged: {
550 if (!greeter.shown) {
551 rightEdgeDemo.visible = false
552 shell.hideEdgeDemoInGreeter()
553 }
554 }
555 }
556 }
557 }
559 InputFilterArea {
560 blockInput: greeter.shown || lockscreen.shown
561 anchors.fill: parent
562 }
564 Item {
565 id: overlay
567 anchors.fill: parent
569 Panel {
570 id: panel
571 anchors.fill: parent //because this draws indicator menus
572 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
573 indicators {
574 hides: [launcher]
575 available: !rightEdgeDemo.visible && !leftEdgeDemo.visible
576 }
577 fullscreenMode: shell.fullscreenMode
578 searchVisible: !greeter.shown && !lockscreen.shown
580 InputFilterArea {
581 anchors.fill: parent
582 blockInput: panel.indicators.shown
583 }
585 DemoOverlay {
586 id: bottomEdgeDemo
588 edge: "bottom"
589 title: i18n.tr("Close")
590 text: i18n.tr("Swipe up again to close the settings screen")
591 anchors.fill: panel.indicators
592 visible: false
593 enabled: shell.showEdgeDemo
594 onSkip: shell.hideEdgeDemo()
596 Connections {
597 target: panel.indicators
598 onFullyOpenedChanged: if (panel.indicators.fullyOpened) bottomEdgeDemo.visible = true
599 onPartiallyOpenedChanged: if (!panel.indicators.partiallyOpened && !panel.indicators.fullyOpened) bottomEdgeDemo.visible = false
600 }
601 }
602 }
604 Hud {
605 id: hud
607 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
608 height: parent.height
610 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown && !topEdgeDemo.visible
611 shown: false
612 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
613 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
615 Connections {
616 target: shell.applicationManager
617 onMainStageFocusedApplicationChanged: hud.hide()
618 onSideStageFocusedApplicationChanged: hud.hide()
619 }
621 InputFilterArea {
622 anchors.fill: parent
623 blockInput: hud.shown
624 }
625 }
627 Revealer {
628 id: hudRevealer
630 enabled: hud.shown
631 width: hud.width
632 anchors.left: hud.left
633 height: parent.height
634 target: hud.revealerTarget
635 closedValue: height
636 openedValue: 0
637 direction: Qt.RightToLeft
638 orientation: Qt.Vertical
639 handleSize: hud.handleHeight
640 onCloseClicked: target.hide()
641 }
643 Bottombar {
644 theHud: hud
645 anchors.fill: parent
646 enabled: !panel.indicators.shown
647 applicationIsOnForeground: applicationManager.mainStageFocusedApplication
648 || applicationManager.sideStageFocusedApplication
649 }
651 InputFilterArea {
652 blockInput: launcher.shown
653 anchors {
654 top: parent.top
655 bottom: parent.bottom
656 left: parent.left
657 }
658 width: launcher.width
659 }
661 Launcher {
662 id: launcher
664 anchors.top: parent.top
665 anchors.bottom: parent.bottom
666 width: parent.width
667 dragAreaWidth: shell.edgeSize
668 available: !greeter.locked && !rightEdgeDemo.visible && !topEdgeDemo.visible && !bottomEdgeDemo.visible
669 onDashItemSelected: {
670 greeter.hide()
671 // Animate if moving between application and dash
672 if (!stages.shown) {
673 dash.setCurrentScope("home.scope", true, false)
674 } else {
675 dash.setCurrentScope("home.scope", false, false)
676 }
677 stages.hide();
678 }
679 onDash: {
680 if (stages.shown) {
681 dash.setCurrentScope("applications.scope", true, false)
682 stages.hide();
683 launcher.hide();
684 }
685 }
686 onLauncherApplicationSelected:{
687 greeter.hide()
688 shell.activateApplication(desktopFile)
689 }
690 onShownChanged: {
691 if (shown) {
692 panel.indicators.hide()
693 hud.hide()
694 }
695 }
696 }
698 Notifications {
699 id: notifications
701 model: NotificationBackend.Model
702 anchors {
703 top: parent.top
704 right: parent.right
705 bottom: parent.bottom
706 leftMargin: units.gu(1)
707 rightMargin: units.gu(1)
708 topMargin: panel.panelHeight + units.gu(1)
709 }
710 states: [
711 State {
712 name: "narrow"
713 when: overlay.width <= units.gu(60)
714 AnchorChanges { target: notifications; anchors.left: parent.left }
715 },
716 State {
717 name: "wide"
718 when: overlay.width > units.gu(60)
719 AnchorChanges { target: notifications; anchors.left: undefined }
720 PropertyChanges { target: notifications; width: units.gu(38) }
721 }
722 ]
723 }
724 }
726 focus: true
728 InputFilterArea {
729 anchors {
730 top: parent.top
731 bottom: parent.bottom
732 left: parent.left
733 }
734 width: shell.edgeSize
735 blockInput: true
736 }
738 InputFilterArea {
739 anchors {
740 top: parent.top
741 bottom: parent.bottom
742 right: parent.right
743 }
744 width: shell.edgeSize
745 blockInput: true
746 }
748 Binding {
749 target: i18n
750 property: "domain"
751 value: "unity8"
752 }
754 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
755 MouseArea {
756 anchors.bottom: parent.bottom
757 anchors.left: parent.left
758 anchors.right: parent.right
759 height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
761 enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
762 }