on ccf5cfe help sign in
unity8/qml/Shell.qml
818 lines 29 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 SessionBroadcast 0.1
27import "Dash"
28import "Greeter"
29import "Launcher"
30import "Panel"
31import "Hud"
32import "Components"
33import "Bottombar"
34import "SideStage"
35import "Notifications"
36import Unity.Notifications 1.0 as NotificationBackend
38FocusScope {
39 id: shell
40 objectName: "shell"
42 // this is only here to select the width / height of the window if not running fullscreen
43 property bool tablet: false
44 width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
45 height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
47 property real edgeSize: units.gu(2)
48 property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
49 property url background
50 readonly property real panelHeight: panel.panelHeight
52 property bool dashShown: dash.shown
53 property bool stageScreenshotsReady: {
54 if (sideStage.shown) {
55 if (mainStage.applications.count > 0) {
56 return mainStage.usingScreenshots && sideStage.usingScreenshots;
57 } else {
58 return sideStage.usingScreenshots;
59 }
60 } else {
61 return mainStage.usingScreenshots;
62 }
63 }
65 property var applicationManager: ApplicationManagerWrapper {}
67 Binding {
68 target: LauncherModel
69 property: "applicationManager"
70 value: ApplicationManager
71 }
73 Component.onCompleted: {
74 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
76 applicationManager.sideStageEnabled = Qt.binding(function() { return sideStage.enabled })
78 // FIXME: if application focused before shell starts, shell draws on top of it only.
79 // We should detect already running applications on shell start and bring them to the front.
80 applicationManager.unfocusCurrentApplication();
82 applicationManager.setMainStageAppRect(mainStageAppRect);
83 applicationManager.setSideStageAppRect(sideStageAppRect);
84 }
86 readonly property bool applicationFocused: !!applicationManager.mainStageFocusedApplication
87 || !!applicationManager.sideStageFocusedApplication
88 // Used for autopilot testing.
89 readonly property string currentFocusedAppId: ApplicationManager.focusedApplicationId
91 readonly property bool fullscreenMode: {
92 if (greeter.shown || lockscreen.shown) {
93 return false;
94 } else if (mainStage.usingScreenshots) { // Window Manager animating so want to re-evaluate fullscreen mode
95 return mainStage.switchingFromFullscreenToFullscreen;
96 } else if (applicationManager.mainStageFocusedApplication) {
97 return applicationManager.mainStageFocusedApplication.fullscreen;
98 } else {
99 return false;
100 }
101 }
103 function activateApplication(appId, argument) {
104 if (applicationManager) {
105 // For newly started applications, as it takes them time to draw their first frame
106 // we add a delay before we hide the animation screenshots to compensate.
107 var addDelay = !applicationManager.getApplicationFromDesktopFile(appId);
109 var application;
110 application = applicationManager.activateApplication(appId, argument);
111 if (application == null) {
112 return;
113 }
114 if (application.stage == ApplicationInfo.MainStage || !sideStage.enabled) {
115 mainStage.activateApplication(appId, addDelay);
116 } else {
117 sideStage.activateApplication(appId, addDelay);
118 }
119 stages.show();
120 }
121 }
123 GSettings {
124 id: backgroundSettings
125 schema.id: "org.gnome.desktop.background"
126 }
127 property url gSettingsPicture: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : shell.defaultBackground
128 onGSettingsPictureChanged: {
129 shell.background = gSettingsPicture
130 }
132 // This is a dummy image that is needed to determine if the picture url
133 // in backgroundSettings points to a valid picture file.
134 // We can't do this with the real background image because setting a
135 // new source in onStatusChanged triggers a binding loop detection
136 // inside Image, which causes it not to render even though a valid source
137 // would be set. We don't mind about this image staying black and just
138 // use it for verification to populate the source for the real
139 // background image.
140 Image {
141 source: shell.background
142 height: 0
143 width: 0
144 sourceSize.height: 0
145 sourceSize.width: 0
146 onStatusChanged: {
147 if (status == Image.Error && source != shell.defaultBackground) {
148 shell.background = defaultBackground
149 }
150 }
151 }
153 VolumeControl {
154 id: volumeControl
155 }
157 Keys.onVolumeUpPressed: volumeControl.volumeUp()
158 Keys.onVolumeDownPressed: volumeControl.volumeDown()
160 Item {
161 id: underlay
162 objectName: "underlay"
163 anchors.fill: parent
165 // Whether the underlay is fully covered by opaque UI elements.
166 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
168 readonly property bool applicationRunning: ((mainStage.applications && mainStage.applications.count > 0)
169 || (sideStage.applications && sideStage.applications.count > 0))
171 // Whether the user should see the topmost application surface (if there's one at all).
172 readonly property bool applicationSurfaceShouldBeSeen: applicationRunning && !stages.fullyHidden
173 && !mainStage.usingScreenshots // but want sideStage animating over app surface
177 // NB! Application surfaces are stacked behing the shell one. So they can only be seen by the user
178 // through the translucent parts of the shell surface.
179 visible: !fullyCovered && !applicationSurfaceShouldBeSeen
181 CrossFadeImage {
182 id: backgroundImage
183 objectName: "backgroundImage"
185 anchors.fill: parent
186 source: shell.background
187 fillMode: Image.PreserveAspectCrop
188 }
190 Rectangle {
191 anchors.fill: parent
192 color: "black"
193 opacity: dash.disappearingAnimationProgress
194 }
196 Dash {
197 id: dash
198 objectName: "dash"
200 available: !greeter.shown && !lockscreen.shown
201 hides: [stages, launcher, panel.indicators]
202 shown: disappearingAnimationProgress !== 1.0
203 enabled: disappearingAnimationProgress === 0.0 && edgeDemo.dashEnabled
204 // FIXME: unfocus all applications when going back to the dash
205 onEnabledChanged: {
206 if (enabled) {
207 shell.applicationManager.unfocusCurrentApplication()
208 }
209 }
211 anchors {
212 fill: parent
213 topMargin: panel.panelHeight
214 }
216 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
217 opacity: 1.0 - disappearingAnimationProgress
218 property real disappearingAnimationProgress: {
219 if (greeter.shown) {
220 return greeter.showProgress;
221 } else {
222 return stagesOuterContainer.showProgress;
223 }
224 }
226 // FIXME: only necessary because stagesOuterContainer.showProgress and
227 // greeterRevealer.animatedProgress are not animated
228 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
229 }
230 }
232 Item {
233 id: stagesOuterContainer
235 width: parent.width
236 height: parent.height
237 x: launcher.progress
238 Behavior on x {SmoothedAnimation{velocity: 600}}
240 property real showProgress:
241 MathUtils.clamp(1 - (x + stages.x) / shell.width, 0, 1)
243 Showable {
244 id: stages
245 objectName: "stages"
247 x: width
249 property bool fullyShown: shown && x == 0 && parent.x == 0
250 property bool fullyHidden: !shown && x == width
251 available: !greeter.shown
252 hides: [panel.indicators]
253 shown: false
254 opacity: 1.0
255 showAnimation: StandardAnimation { property: "x"; duration: 350; to: 0; easing.type: Easing.OutCubic }
256 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: width; easing.type: Easing.OutCubic }
258 width: parent.width
259 height: parent.height
261 // close the stages when no focused application remains
262 Connections {
263 target: shell.applicationManager
264 onMainStageFocusedApplicationChanged: stages.closeIfNoApplications()
265 onSideStageFocusedApplicationChanged: stages.closeIfNoApplications()
266 ignoreUnknownSignals: true
267 }
269 function closeIfNoApplications() {
270 if (!shell.applicationManager.mainStageFocusedApplication
271 && !shell.applicationManager.sideStageFocusedApplication
272 && shell.applicationManager.mainStageApplications.count == 0
273 && shell.applicationManager.sideStageApplications.count == 0) {
274 stages.hide();
275 }
276 }
278 // show the stages when an application gets the focus
279 Connections {
280 target: shell.applicationManager
281 onMainStageFocusedApplicationChanged: {
282 if (shell.applicationManager.mainStageFocusedApplication) {
283 mainStage.show();
284 stages.show();
285 }
286 }
287 onSideStageFocusedApplicationChanged: {
288 if (shell.applicationManager.sideStageFocusedApplication) {
289 sideStage.show();
290 stages.show();
291 }
292 }
293 ignoreUnknownSignals: true
294 }
296 Stage {
297 id: mainStage
299 anchors.fill: parent
300 fullyShown: stages.fullyShown
301 fullyHidden: stages.fullyHidden
302 shouldUseScreenshots: !fullyShown
303 rightEdgeEnabled: !sideStage.enabled
305 applicationManager: shell.applicationManager
306 rightEdgeDraggingAreaWidth: shell.edgeSize
307 normalApplicationY: shell.panelHeight
309 shown: true
310 function show() {
311 stages.show();
312 }
313 function hide() {
314 }
316 // FIXME: workaround the fact that focusing a main stage application
317 // raises its surface on top of all other surfaces including the ones
318 // that belong to side stage applications.
319 onFocusedApplicationChanged: {
320 if (focusedApplication && sideStage.focusedApplication && sideStage.fullyShown) {
321 shell.applicationManager.focusApplication(sideStage.focusedApplication);
322 }
323 }
324 }
326 SideStage {
327 id: sideStage
329 applicationManager: shell.applicationManager
330 rightEdgeDraggingAreaWidth: shell.edgeSize
331 normalApplicationY: shell.panelHeight
333 onShownChanged: {
334 if (!shown && mainStage.applications.count == 0) {
335 stages.hide();
336 }
337 }
338 // FIXME: when hiding the side stage, refocus the main stage
339 // application so that it goes in front of the side stage
340 // application and hides it
341 onFullyShownChanged: {
342 if (!fullyShown && stages.fullyShown && sideStage.focusedApplication != null) {
343 shell.applicationManager.focusApplication(mainStage.focusedApplication);
344 }
345 }
347 enabled: shell.width >= units.gu(60)
348 visible: enabled
349 fullyShown: stages.fullyShown && shown
350 && sideStage[sideStageRevealer.boundProperty] == sideStageRevealer.openedValue
351 shouldUseScreenshots: !fullyShown || mainStage.usingScreenshots || sideStageRevealer.pressed
353 available: !greeter.shown && !lockscreen.shown && enabled
354 hides: [launcher, panel.indicators]
355 shown: false
356 showAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.openedValue; easing.type: Easing.OutQuint }
357 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.closedValue; easing.type: Easing.OutQuint }
359 width: units.gu(40)
360 height: stages.height
361 handleExpanded: sideStageRevealer.pressed
362 }
364 Revealer {
365 id: sideStageRevealer
367 enabled: mainStage.applications.count > 0 && sideStage.applications.count > 0
368 && sideStage.available
369 direction: Qt.RightToLeft
370 openedValue: parent.width - sideStage.width
371 hintDisplacement: units.gu(3)
372 /* The size of the sidestage handle needs to be bigger than the
373 typical size used for edge detection otherwise it is really
374 hard to grab.
375 */
376 handleSize: sideStage.shown ? units.gu(4) : shell.edgeSize
377 closedValue: parent.width + sideStage.handleSizeCollapsed
378 target: sideStage
379 x: parent.width - width
380 width: sideStage.width + handleSize * 0.7
381 height: sideStage.height
382 orientation: Qt.Horizontal
383 }
385 // Defines the rectangle occupied by an application on the main or side stage.
386 // Used only to inform the ApplicationManager. Can be removed when the proper
387 // architecture is in place (unity8 as mir compositor).
388 Item {
389 id: mainStageAppRect
390 x: mainStage.x
391 y: mainStage.y + mainStage.normalApplicationY
392 width: mainStage.width
393 height: mainStage.height
394 visible: false; enabled: false
395 }
396 Item {
397 id: sideStageAppRect
398 x: sideStageRevealer.openedValue
399 y: sideStage.y + sideStage.normalApplicationY
400 width: sideStage.width
401 height: sideStage.height
402 visible: false; enabled: false
403 }
405 DragHandle {
406 id: stagesDragHandle
408 anchors.top: parent.top
409 anchors.bottom: parent.bottom
410 anchors.right: parent.left
412 width: shell.edgeSize
413 direction: Direction.Leftwards
414 enabled: greeter.showProgress == 0 && edgeDemo.dashEnabled
415 property bool haveApps: mainStage.applications.count > 0 || sideStage.applications.count > 0
417 maxTotalDragDistance: haveApps ? parent.width : parent.width * 0.7
418 // Make autocompletion impossible when !haveApps
419 edgeDragEvaluator.minDragDistance: haveApps ? maxTotalDragDistance * 0.1 : Number.MAX_VALUE
420 }
421 }
422 }
424 Lockscreen {
425 id: lockscreen
426 objectName: "lockscreen"
428 readonly property int backgroundTopMargin: -panel.panelHeight
430 hides: [launcher, panel.indicators, hud]
431 shown: false
432 enabled: true
433 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
434 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
435 y: panel.panelHeight
436 x: required ? 0 : - width
437 width: parent.width
438 height: parent.height - panel.panelHeight
439 background: shell.background
440 pinLength: 4
442 onEntered: LightDM.Greeter.respond(passphrase);
443 onCancel: greeter.show()
445 Component.onCompleted: {
446 if (LightDM.Users.count == 1) {
447 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
448 greeter.selected(0)
449 }
450 }
451 }
453 Connections {
454 target: LightDM.Greeter
456 onShowPrompt: {
457 if (LightDM.Users.count == 1) {
458 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
459 if (text == "PIN") {
460 lockscreen.alphaNumeric = false
461 } else {
462 lockscreen.alphaNumeric = true
463 }
464 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
465 lockscreen.show();
466 }
467 }
469 onAuthenticationComplete: {
470 if (LightDM.Greeter.promptless) {
471 return;
472 }
473 if (LightDM.Greeter.authenticated) {
474 lockscreen.hide();
475 } else {
476 lockscreen.clear(true);
477 }
478 }
479 }
481 Greeter {
482 id: greeter
483 objectName: "greeter"
485 available: true
486 hides: [launcher, panel.indicators, hud]
487 shown: true
489 defaultBackground: shell.background
491 y: panel.panelHeight
492 width: parent.width
493 height: parent.height - panel.panelHeight
495 dragHandleWidth: shell.edgeSize
497 property var previousMainApp: null
498 property var previousSideApp: null
500 function removeApplicationFocus() {
501 greeter.previousMainApp = applicationManager.mainStageFocusedApplication;
502 greeter.previousSideApp = applicationManager.sideStageFocusedApplication;
503 applicationManager.unfocusCurrentApplication();
504 }
506 function restoreApplicationFocus() {
507 if (greeter.previousMainApp) {
508 applicationManager.focusApplication(greeter.previousMainApp);
509 greeter.previousMainApp = null;
510 }
511 if (greeter.previousSideApp) {
512 applicationManager.focusApplication(greeter.previousSideApp);
513 greeter.previousSideApp = null;
514 }
515 }
517 onShownChanged: {
518 if (shown) {
519 lockscreen.reset();
520 // If there is only one user, we start authenticating with that one here.
521 // If there are more users, the Greeter will handle that
522 if (LightDM.Users.count == 1) {
523 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
524 greeter.selected(0);
525 }
526 greeter.forceActiveFocus();
527 removeApplicationFocus();
528 } else {
529 restoreApplicationFocus();
530 }
531 }
533 onUnlocked: greeter.hide()
534 onSelected: {
535 // Update launcher items for new user
536 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
537 AccountsService.user = user;
538 LauncherModel.setUser(user);
539 }
541 onLeftTeaserPressedChanged: {
542 if (leftTeaserPressed) {
543 launcher.tease();
544 }
545 }
547 Connections {
548 target: applicationManager
549 ignoreUnknownSignals: true
550 // If any app is focused when greeter is open, it's due to a user action
551 // like a snap decision (say, an incoming call).
552 // TODO: these should be protected to only unlock for certain applications / certain usecases
553 // potentially only in connection with a notification.
554 onMainStageFocusedApplicationChanged: {
555 if (greeter.shown && applicationManager.mainStageFocusedApplication) {
556 greeter.previousMainApp = null // make way for new focused app
557 greeter.previousSideApp = null
558 greeter.hide()
559 }
560 }
561 onSideStageFocusedApplicationChanged: {
562 if (greeter.shown && applicationManager.sideStageFocusedApplication) {
563 greeter.previousMainApp = null // make way for new focused app
564 greeter.previousSideApp = null
565 greeter.hide()
566 }
567 }
568 }
569 }
571 InputFilterArea {
572 anchors.fill: parent
573 blockInput: !applicationFocused || greeter.shown || lockscreen.shown || launcher.shown
574 || panel.indicators.shown || hud.shown
575 }
577 Connections {
578 id: powerConnection
579 target: Powerd
581 onDisplayPowerStateChange: {
582 // We ignore any display-off signals when the proximity sensor
583 // is active. This usually indicates something like a phone call.
584 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
585 greeter.showNow();
586 }
588 // No reason to chew demo CPU when user isn't watching
589 if (status == Powerd.Off) {
590 edgeDemo.paused = true;
591 } else if (status == Powerd.On) {
592 edgeDemo.paused = false;
593 }
594 }
595 }
597 function showHome() {
598 var animate = !greeter.shown && !stages.shown
599 greeter.hide()
600 dash.setCurrentScope("home.scope", animate, false)
601 stages.hide()
602 }
604 function hideIndicatorMenu(delay) {
605 panel.hideIndicatorMenu(delay);
606 }
608 Item {
609 id: overlay
611 anchors.fill: parent
613 Panel {
614 id: panel
615 anchors.fill: parent //because this draws indicator menus
616 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
617 indicators {
618 hides: [launcher]
619 available: edgeDemo.panelEnabled
620 contentEnabled: edgeDemo.panelContentEnabled
621 }
622 fullscreenMode: shell.fullscreenMode
623 searchVisible: !greeter.shown && !lockscreen.shown && dash.shown
625 InputFilterArea {
626 anchors {
627 top: parent.top
628 left: parent.left
629 right: parent.right
630 }
631 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
632 blockInput: true
633 }
634 }
636 Hud {
637 id: hud
639 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
640 height: parent.height
642 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown && edgeDemo.dashEnabled
643 shown: false
644 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
645 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
647 Connections {
648 target: shell.applicationManager
649 onMainStageFocusedApplicationChanged: hud.hide()
650 onSideStageFocusedApplicationChanged: hud.hide()
651 }
652 }
654 Revealer {
655 id: hudRevealer
657 enabled: hud.shown
658 width: hud.width
659 anchors.left: hud.left
660 height: parent.height
661 target: hud.revealerTarget
662 closedValue: height
663 openedValue: 0
664 direction: Qt.RightToLeft
665 orientation: Qt.Vertical
666 handleSize: hud.handleHeight
667 onCloseClicked: target.hide()
668 }
670 Bottombar {
671 id: bottombar
672 theHud: hud
673 anchors.fill: parent
674 enabled: hud.available
675 applicationIsOnForeground: applicationFocused
676 }
678 InputFilterArea {
679 blockInput: launcher.shown
680 anchors {
681 top: parent.top
682 bottom: parent.bottom
683 left: parent.left
684 }
685 width: launcher.width
686 }
688 Launcher {
689 id: launcher
691 readonly property bool dashSwipe: progress > 0
693 anchors.top: parent.top
694 anchors.bottom: parent.bottom
695 width: parent.width
696 dragAreaWidth: shell.edgeSize
697 available: (!greeter.shown || greeter.narrowMode) && edgeDemo.launcherEnabled
699 onShowDashHome: {
700 if (edgeDemo.running)
701 return;
703 showHome()
704 }
705 onDash: {
706 if (stages.shown) {
707 stages.hide();
708 launcher.hide();
709 }
710 }
711 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("applications.scope", false, true)
712 onLauncherApplicationSelected:{
713 if (edgeDemo.running)
714 return;
716 greeter.hide()
717 shell.activateApplication(appId)
718 }
719 onShownChanged: {
720 if (shown) {
721 panel.indicators.hide()
722 hud.hide()
723 bottombar.hide()
724 }
725 }
726 }
728 Notifications {
729 id: notifications
731 model: NotificationBackend.Model
732 margin: units.gu(1)
734 anchors {
735 top: parent.top
736 right: parent.right
737 bottom: parent.bottom
738 topMargin: panel.panelHeight
739 }
740 states: [
741 State {
742 name: "narrow"
743 when: overlay.width <= units.gu(60)
744 AnchorChanges { target: notifications; anchors.left: parent.left }
745 },
746 State {
747 name: "wide"
748 when: overlay.width > units.gu(60)
749 AnchorChanges { target: notifications; anchors.left: undefined }
750 PropertyChanges { target: notifications; width: units.gu(38) }
751 }
752 ]
754 InputFilterArea {
755 anchors { left: parent.left; right: parent.right }
756 height: parent.contentHeight
757 blockInput: height > 0
758 }
759 }
760 }
762 focus: true
763 onFocusChanged: if (!focus) forceActiveFocus();
765 InputFilterArea {
766 anchors {
767 top: parent.top
768 bottom: parent.bottom
769 left: parent.left
770 }
771 width: shell.edgeSize
772 blockInput: true
773 }
775 InputFilterArea {
776 anchors {
777 top: parent.top
778 bottom: parent.bottom
779 right: parent.right
780 }
781 width: shell.edgeSize
782 blockInput: true
783 }
785 Binding {
786 target: i18n
787 property: "domain"
788 value: "unity8"
789 }
791 Label {
792 anchors.centerIn: parent
793 visible: applicationManager.fake
794 text: "EARLY ALPHA\nNOT READY FOR USE"
795 color: "lightgrey"
796 opacity: 0.2
797 font.weight: Font.Black
798 horizontalAlignment: Text.AlignHCenter
799 verticalAlignment: Text.AlignVCenter
800 fontSizeMode: Text.Fit
801 rotation: -45
802 scale: Math.min(parent.width, parent.height) / width
803 }
805 EdgeDemo {
806 id: edgeDemo
807 greeter: greeter
808 launcher: launcher
809 dash: dash
810 indicators: panel.indicators
811 underlay: underlay
812 }
814 Connections {
815 target: SessionBroadcast
816 onShowHome: showHome()
817 }