on cedbc3f help sign in
unity8/Shell.qml
745 lines 26 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) : units.gu(40)
41 height: tablet ? units.gu(100) : 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 applicationManager.sideStageEnabled = Qt.binding(function() { return sideStage.enabled })
81 // FIXME: if application focused before shell starts, shell draws on top of it only.
82 // We should detect already running applications on shell start and bring them to the front.
83 applicationManager.unfocusCurrentApplication();
84 }
86 readonly property bool fullscreenMode: {
87 if (greeter.shown || lockscreen.shown) {
88 return false;
89 } else if (mainStage.usingScreenshots) { // Window Manager animating so want to re-evaluate fullscreen mode
90 return mainStage.switchingFromFullscreenToFullscreen;
91 } else if (applicationManager.mainStageFocusedApplication) {
92 return applicationManager.mainStageFocusedApplication.fullscreen;
93 } else {
94 return false;
95 }
96 }
98 Connections {
99 target: applicationManager
100 ignoreUnknownSignals: true
101 onFocusRequested: {
102 // TODO: this should be protected to only unlock for certain applications / certain usecases
103 // potentially only in connection with a notification
104 greeter.hide();
105 shell.activateApplication(desktopFile);
106 }
107 }
109 function activateApplication(desktopFile, argument) {
110 if (applicationManager) {
111 // For newly started applications, as it takes them time to draw their first frame
112 // we add a delay before we hide the animation screenshots to compensate.
113 var addDelay = !applicationManager.getApplicationFromDesktopFile(desktopFile);
115 var application;
116 application = applicationManager.activateApplication(desktopFile, argument);
117 if (application == null) {
118 return;
119 }
120 if (application.stage == ApplicationInfo.MainStage || !sideStage.enabled) {
121 mainStage.activateApplication(desktopFile, addDelay);
122 } else {
123 sideStage.activateApplication(desktopFile, addDelay);
124 }
125 stages.show();
126 }
127 }
129 VolumeControl {
130 id: volumeControl
131 }
133 Keys.onVolumeUpPressed: volumeControl.volumeUp()
134 Keys.onVolumeDownPressed: volumeControl.volumeDown()
136 Keys.onReleased: {
137 if (event.key == Qt.Key_PowerOff) {
138 greeter.show()
139 }
140 }
142 Item {
143 id: underlay
144 anchors.fill: parent
145 visible: !(panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth)
146 && (stages.fullyHidden
147 || (stages.fullyShown && mainStage.usingScreenshots)
148 || !stages.fullyShown && (mainStage.usingScreenshots || (sideStage.shown && sideStage.usingScreenshots)))
150 Image {
151 id: backgroundImage
152 source: shell.background
153 sourceSize.width: parent.width
154 sourceSize.height: parent.height
155 anchors.fill: parent
156 }
158 Rectangle {
159 anchors.fill: parent
160 color: "black"
161 opacity: dash.disappearingAnimationProgress
162 }
164 Dash {
165 id: dash
167 available: !greeter.shown && !lockscreen.shown
168 hides: [stages, launcher, panel.indicators]
169 shown: disappearingAnimationProgress !== 1.0
170 enabled: disappearingAnimationProgress === 0.0 && !leftEdgeDemo.visible && !topEdgeDemo.visible
171 // FIXME: unfocus all applications when going back to the dash
172 onEnabledChanged: {
173 if (enabled) {
174 shell.applicationManager.unfocusCurrentApplication()
175 }
176 }
178 anchors {
179 fill: parent
180 topMargin: panel.panelHeight
181 }
183 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
184 opacity: 1.0 - disappearingAnimationProgress
185 property real disappearingAnimationProgress: {
186 if (greeter.shown) {
187 return greeter.showProgress;
188 } else {
189 return stagesOuterContainer.showProgress;
190 }
191 }
193 // FIXME: only necessary because stagesRevealer.animatedProgress and
194 // greeterRevealer.animatedProgress are not animated
195 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
196 }
198 DemoOverlay {
199 id: topEdgeDemo
201 edge: "top"
202 title: i18n.tr("Top edge")
203 text: i18n.tr("Try swiping from the top edge to access the indicators")
204 anchors.fill: dash
205 visible: false
206 enabled: shell.showEdgeDemo
207 onSkip: shell.hideEdgeDemo()
209 Connections {
210 target: greeter
211 onShownChanged: if (!greeter.shown) topEdgeDemo.visible = true
212 }
214 Connections {
215 target: panel.indicators
216 onFullyOpenedChanged: if (panel.indicators.fullyOpened) topEdgeDemo.visible = false
217 }
218 }
220 DemoOverlay {
221 id: leftEdgeDemo
223 edge: "left"
224 title: i18n.tr("Left edge")
225 text: i18n.tr("Swipe from the left to reveal the launcher for quick access to apps")
226 anchors.fill: dash
227 visible: false
228 enabled: shell.showEdgeDemo
229 onSkip: shell.hideEdgeDemo()
231 Connections {
232 target: bottomEdgeDemo
233 onVisibleChanged: if (!bottomEdgeDemo.visible) leftEdgeDemo.visible = true
234 }
236 Connections {
237 target: launcher
238 onProgressChanged: {
239 if (launcher.progress >= 1.0) {
240 leftEdgeDemo.visible = false;
241 shell.hideEdgeDemo();
242 }
243 }
244 }
245 }
246 }
249 Item {
250 id: stagesOuterContainer
252 width: parent.width
253 height: parent.height
254 x: launcher.progress
255 Behavior on x {SmoothedAnimation{velocity: 600}}
257 property real showProgress:
258 MathLocal.clamp(1 - (x + stages.x) / shell.width, 0, 1)
260 Showable {
261 id: stages
263 x: width
265 property bool fullyShown: shown && x == 0 && parent.x == 0
266 property bool fullyHidden: !shown && x == width
267 available: !greeter.shown
268 hides: [launcher, panel.indicators]
269 shown: false
270 opacity: 1.0
271 showAnimation: StandardAnimation { property: "x"; duration: 350; to: 0; easing.type: Easing.OutCubic }
272 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: width; easing.type: Easing.OutCubic }
274 width: parent.width
275 height: parent.height
277 // close the stages when no focused application remains
278 Connections {
279 target: shell.applicationManager
280 onMainStageFocusedApplicationChanged: stages.closeIfNoApplications()
281 onSideStageFocusedApplicationChanged: stages.closeIfNoApplications()
282 ignoreUnknownSignals: true
283 }
285 function closeIfNoApplications() {
286 if (!shell.applicationManager.mainStageFocusedApplication
287 && !shell.applicationManager.sideStageFocusedApplication
288 && shell.applicationManager.mainStageApplications.count == 0
289 && shell.applicationManager.sideStageApplications.count == 0) {
290 stages.hide();
291 }
292 }
294 // show the stages when an application gets the focus
295 Connections {
296 target: shell.applicationManager
297 onMainStageFocusedApplicationChanged: {
298 if (shell.applicationManager.mainStageFocusedApplication) {
299 mainStage.show();
300 stages.show();
301 }
302 }
303 onSideStageFocusedApplicationChanged: {
304 if (shell.applicationManager.sideStageFocusedApplication) {
305 sideStage.show();
306 stages.show();
307 }
308 }
309 ignoreUnknownSignals: true
310 }
313 Stage {
314 id: mainStage
316 anchors.fill: parent
317 fullyShown: stages.fullyShown
318 shouldUseScreenshots: !fullyShown
319 rightEdgeEnabled: !sideStage.enabled
321 applicationManager: shell.applicationManager
322 rightEdgeDraggingAreaWidth: shell.edgeSize
323 normalApplicationY: shell.panelHeight
325 shown: true
326 function show() {
327 stages.show();
328 }
329 function hide() {
330 }
332 // FIXME: workaround the fact that focusing a main stage application
333 // raises its surface on top of all other surfaces including the ones
334 // that belong to side stage applications.
335 onFocusedApplicationChanged: {
336 if (focusedApplication && sideStage.focusedApplication && sideStage.fullyShown) {
337 shell.applicationManager.focusApplication(sideStage.focusedApplication);
338 }
339 }
340 }
342 SideStage {
343 id: sideStage
345 applicationManager: shell.applicationManager
346 rightEdgeDraggingAreaWidth: shell.edgeSize
347 normalApplicationY: shell.panelHeight
349 onShownChanged: {
350 if (!shown && mainStage.applications.count == 0) {
351 stages.hide();
352 }
353 }
354 // FIXME: when hiding the side stage, refocus the main stage
355 // application so that it goes in front of the side stage
356 // application and hides it
357 onFullyShownChanged: {
358 if (!fullyShown && stages.fullyShown && sideStage.focusedApplication != null) {
359 shell.applicationManager.focusApplication(mainStage.focusedApplication);
360 }
361 }
363 enabled: shell.width >= units.gu(60)
364 visible: enabled
365 fullyShown: stages.fullyShown && shown
366 && sideStage[sideStageRevealer.boundProperty] == sideStageRevealer.openedValue
367 shouldUseScreenshots: !fullyShown || mainStage.usingScreenshots || sideStageRevealer.pressed
369 available: !greeter.shown && !lockscreen.shown && enabled
370 hides: [launcher, panel.indicators]
371 shown: false
372 showAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.openedValue; easing.type: Easing.OutQuint }
373 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.closedValue; easing.type: Easing.OutQuint }
375 width: units.gu(40)
376 height: stages.height
377 handleExpanded: sideStageRevealer.pressed
378 }
380 Revealer {
381 id: sideStageRevealer
383 enabled: mainStage.applications.count > 0 && sideStage.applications.count > 0
384 && sideStage.available
385 direction: Qt.RightToLeft
386 openedValue: parent.width - sideStage.width
387 hintDisplacement: units.gu(3)
388 /* The size of the sidestage handle needs to be bigger than the
389 typical size used for edge detection otherwise it is really
390 hard to grab.
391 */
392 handleSize: sideStage.shown ? units.gu(4) : shell.edgeSize
393 closedValue: parent.width + sideStage.handleSizeCollapsed
394 target: sideStage
395 x: parent.width - width
396 width: sideStage.width + handleSize * 0.7
397 height: sideStage.height
398 orientation: Qt.Horizontal
399 }
401 DragHandle {
402 id: stagesRevealer
404 anchors.top: parent.top
405 anchors.bottom: parent.bottom
406 anchors.right: parent.left
408 width: shell.edgeSize
409 direction: Direction.Leftwards
410 enabled: mainStage.applications.count > 0 || sideStage.applications.count > 0
411 }
412 }
413 }
415 Lockscreen {
416 id: lockscreen
417 hides: [launcher, panel.indicators, hud]
418 shown: false
419 enabled: true
420 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
421 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
422 y: panel.panelHeight
423 x: required ? 0 : - width
424 width: parent.width
425 height: parent.height - panel.panelHeight
426 background: shell.background
428 onUnlocked: lockscreen.hide()
429 onCancel: greeter.show()
431 Component.onCompleted: {
432 if (LightDM.Users.count == 1) {
433 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
434 greeter.selected(0)
435 }
436 }
437 }
439 Connections {
440 target: LightDM.Greeter
442 onShowPrompt: {
443 if (LightDM.Users.count == 1) {
444 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
445 if (text == "PIN") {
446 lockscreen.alphaNumeric = false
447 } else {
448 lockscreen.alphaNumeric = true
449 }
450 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
451 lockscreen.show();
452 }
453 }
454 }
456 Greeter {
457 id: 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 property var previousMainApp: null
470 property var previousSideApp: null
472 onShownChanged: {
473 if (shown) {
474 lockscreen.reset();
475 // If there is only one user, we start authenticating with that one here.
476 // If there are more users, the Greeter will handle that
477 if (LightDM.Users.count == 1) {
478 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
479 greeter.selected(0);
480 }
481 greeter.forceActiveFocus();
482 // FIXME: *FocusedApplication are not updated when unfocused, hence the need to check whether
483 // the stage was actually shown
484 if (mainStage.fullyShown) greeter.previousMainApp = applicationManager.mainStageFocusedApplication;
485 if (sideStage.fullyShown) greeter.previousSideApp = applicationManager.sideStageFocusedApplication;
486 applicationManager.unfocusCurrentApplication();
487 } else {
488 if (greeter.previousMainApp) {
489 applicationManager.focusApplication(greeter.previousMainApp);
490 greeter.previousMainApp = null;
491 }
492 if (greeter.previousSideApp) {
493 applicationManager.focusApplication(greeter.previousSideApp);
494 greeter.previousSideApp = null;
495 }
496 }
497 }
499 onUnlocked: greeter.hide()
500 onSelected: {
501 // Update background
502 var bgPath = greeter.model.data(uid, LightDM.UserRoles.BackgroundPathRole)
503 shell.background = bgPath ? bgPath : default_background
504 // Update edge demo hint
505 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole)
506 shell.showEdgeDemo = AccountsService.getUserProperty(user, "demo-edges")
507 }
509 onLeftTeaserPressedChanged: {
510 if (leftTeaserPressed) {
511 launcher.tease();
512 }
513 }
515 DemoOverlay {
516 id: rightEdgeDemo
518 edge: "right"
519 title: i18n.tr("Right edge")
520 text: i18n.tr("Try swiping from the right edge to unlock the phone")
521 anchors.fill: parent
522 enabled: shell.showEdgeDemoInGreeter
523 onSkip: {
524 shell.hideEdgeDemoInGreeter()
525 shell.hideEdgeDemo()
526 }
528 Connections {
529 target: greeter
530 onUnlocked: rightEdgeDemo.visible = false
531 onShownChanged: {
532 if (!greeter.shown) {
533 rightEdgeDemo.visible = false
534 shell.hideEdgeDemoInGreeter()
535 }
536 }
537 }
538 }
539 }
541 InputFilterArea {
542 blockInput: greeter.shown || lockscreen.shown
543 anchors.fill: parent
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: !rightEdgeDemo.visible && !leftEdgeDemo.visible
558 }
559 fullscreenMode: shell.fullscreenMode
560 searchVisible: !greeter.shown && !lockscreen.shown
562 InputFilterArea {
563 anchors.fill: parent
564 blockInput: panel.indicators.shown
565 }
567 DemoOverlay {
568 id: bottomEdgeDemo
570 edge: "bottom"
571 title: i18n.tr("Close")
572 text: i18n.tr("Swipe up again to close the settings screen")
573 anchors.fill: panel.indicators
574 visible: false
575 enabled: shell.showEdgeDemo
576 onSkip: shell.hideEdgeDemo()
578 Connections {
579 target: panel.indicators
580 onFullyOpenedChanged: if (panel.indicators.fullyOpened) bottomEdgeDemo.visible = true
581 onPartiallyOpenedChanged: if (!panel.indicators.partiallyOpened && !panel.indicators.fullyOpened) bottomEdgeDemo.visible = false
582 }
583 }
584 }
586 Hud {
587 id: hud
589 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
590 height: parent.height
592 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown && !topEdgeDemo.visible
593 shown: false
594 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
595 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
597 Connections {
598 target: shell.applicationManager
599 onMainStageFocusedApplicationChanged: hud.hide()
600 onSideStageFocusedApplicationChanged: hud.hide()
601 }
603 InputFilterArea {
604 anchors.fill: parent
605 blockInput: hud.shown
606 }
607 }
609 Revealer {
610 id: hudRevealer
612 enabled: hud.shown
613 width: hud.width
614 anchors.left: hud.left
615 height: parent.height
616 target: hud.revealerTarget
617 closedValue: height
618 openedValue: 0
619 direction: Qt.RightToLeft
620 orientation: Qt.Vertical
621 handleSize: hud.handleHeight
622 onCloseClicked: target.hide()
623 }
625 Bottombar {
626 theHud: hud
627 anchors.fill: parent
628 enabled: !panel.indicators.shown
629 applicationIsOnForeground: applicationManager.mainStageFocusedApplication
630 || applicationManager.sideStageFocusedApplication
631 }
633 InputFilterArea {
634 blockInput: launcher.shown
635 anchors {
636 top: parent.top
637 bottom: parent.bottom
638 left: parent.left
639 }
640 width: launcher.width
641 }
643 Launcher {
644 id: launcher
646 anchors.top: parent.top
647 anchors.bottom: parent.bottom
648 width: parent.width
649 dragAreaWidth: shell.edgeSize
650 available: !greeter.locked && !rightEdgeDemo.visible && !topEdgeDemo.visible && !bottomEdgeDemo.visible
651 onDashItemSelected: {
652 greeter.hide()
653 // Animate if moving between application and dash
654 if (!stages.shown) {
655 dash.setCurrentScope("home.scope", true, false)
656 } else {
657 dash.setCurrentScope("home.scope", false, false)
658 }
659 stages.hide();
660 }
661 onDash: {
662 if (stages.shown) {
663 dash.setCurrentScope("applications.scope", true, false)
664 stages.hide();
665 launcher.hide();
666 }
667 }
668 onLauncherApplicationSelected:{
669 greeter.hide()
670 shell.activateApplication(desktopFile)
671 }
672 onShownChanged: {
673 if (shown) {
674 panel.indicators.hide()
675 hud.hide()
676 }
677 }
678 }
680 Notifications {
681 id: notifications
683 model: NotificationBackend.Model
684 anchors {
685 top: parent.top
686 right: parent.right
687 bottom: parent.bottom
688 leftMargin: units.gu(1)
689 rightMargin: units.gu(1)
690 topMargin: panel.panelHeight + units.gu(1)
691 }
692 states: [
693 State {
694 name: "narrow"
695 when: overlay.width <= units.gu(60)
696 AnchorChanges { target: notifications; anchors.left: parent.left }
697 },
698 State {
699 name: "wide"
700 when: overlay.width > units.gu(60)
701 AnchorChanges { target: notifications; anchors.left: undefined }
702 PropertyChanges { target: notifications; width: units.gu(38) }
703 }
704 ]
705 }
706 }
708 focus: true
710 InputFilterArea {
711 anchors {
712 top: parent.top
713 bottom: parent.bottom
714 left: parent.left
715 }
716 width: shell.edgeSize
717 blockInput: true
718 }
720 InputFilterArea {
721 anchors {
722 top: parent.top
723 bottom: parent.bottom
724 right: parent.right
725 }
726 width: shell.edgeSize
727 blockInput: true
728 }
730 Binding {
731 target: i18n
732 property: "domain"
733 value: "unity8"
734 }
736 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
737 MouseArea {
738 anchors.bottom: parent.bottom
739 anchors.left: parent.left
740 anchors.right: parent.right
741 height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
743 enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
744 }