on b64ae7d help sign in
unity8/Shell.qml
742 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 GSettings 1.0
19import Ubuntu.Application 0.1
20import Ubuntu.Components 0.1
21import Ubuntu.Gestures 0.1
22import Unity.Launcher 0.1
23import LightDM 0.1 as LightDM
24import Powerd 0.1
25import "Dash"
26import "Greeter"
27import "Launcher"
28import "Panel"
29import "Hud"
30import "Components"
31import "Components/Math.js" as MathLocal
32import "Bottombar"
33import "SideStage"
34import "Notifications"
35import Unity.Notifications 1.0 as NotificationBackend
37FocusScope {
38 id: shell
40 // this is only here to select the width / height of the window if not running fullscreen
41 property bool tablet: false
42 width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
43 height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
45 property real edgeSize: units.gu(2)
46 property url defaultBackground: shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg"
47 property url background
48 readonly property real panelHeight: panel.panelHeight
50 property bool dashShown: dash.shown
51 property bool stageScreenshotsReady: {
52 if (sideStage.shown) {
53 if (mainStage.applications.count > 0) {
54 return mainStage.usingScreenshots && sideStage.usingScreenshots;
55 } else {
56 return sideStage.usingScreenshots;
57 }
58 } else {
59 return mainStage.usingScreenshots;
60 }
61 }
63 property ListModel searchHistory: SearchHistoryModel {}
65 property var applicationManager: ApplicationManagerWrapper {}
67 Component.onCompleted: {
68 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
70 applicationManager.sideStageEnabled = Qt.binding(function() { return sideStage.enabled })
72 // FIXME: if application focused before shell starts, shell draws on top of it only.
73 // We should detect already running applications on shell start and bring them to the front.
74 applicationManager.unfocusCurrentApplication();
75 }
77 readonly property bool fullscreenMode: {
78 if (greeter.shown || lockscreen.shown) {
79 return false;
80 } else if (mainStage.usingScreenshots) { // Window Manager animating so want to re-evaluate fullscreen mode
81 return mainStage.switchingFromFullscreenToFullscreen;
82 } else if (applicationManager.mainStageFocusedApplication) {
83 return applicationManager.mainStageFocusedApplication.fullscreen;
84 } else {
85 return false;
86 }
87 }
89 Connections {
90 target: applicationManager
91 ignoreUnknownSignals: true
92 onFocusRequested: {
93 // TODO: this should be protected to only unlock for certain applications / certain usecases
94 // potentially only in connection with a notification
95 greeter.hide();
96 shell.activateApplication(desktopFile);
97 }
99 onMainStageFocusedApplicationChanged: {
100 var app = applicationManager.mainStageFocusedApplication
101 LauncherModel.applicationFocused(app.desktopFile);
102 }
103 }
105 function activateApplication(desktopFile, argument) {
106 if (applicationManager) {
107 // For newly started applications, as it takes them time to draw their first frame
108 // we add a delay before we hide the animation screenshots to compensate.
109 var addDelay = !applicationManager.getApplicationFromDesktopFile(desktopFile);
111 var application;
112 application = applicationManager.activateApplication(desktopFile, argument);
113 if (application == null) {
114 return;
115 }
116 if (application.stage == ApplicationInfo.MainStage || !sideStage.enabled) {
117 mainStage.activateApplication(desktopFile, addDelay);
118 } else {
119 sideStage.activateApplication(desktopFile, addDelay);
120 }
121 stages.show();
122 }
123 }
125 GSettings {
126 id: backgroundSettings
127 schema.id: "org.gnome.desktop.background"
128 }
129 property url gSettingsPicture: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : shell.defaultBackground
130 onGSettingsPictureChanged: {
131 shell.background = gSettingsPicture
132 }
134 // This is a dummy image that is needed to determine if the picture url
135 // in backgroundSettings points to a valid picture file.
136 // We can't do this with the real background image because setting a
137 // new source in onStatusChanged triggers a binding loop detection
138 // inside Image, which causes it not to render even though a valid source
139 // would be set. We don't mind about this image staying black and just
140 // use it for verification to populate the source for the real
141 // background image.
142 Image {
143 source: shell.background
144 height: 0
145 width: 0
146 sourceSize.height: 0
147 sourceSize.width: 0
148 onStatusChanged: {
149 if (status == Image.Error && source != shell.defaultBackground) {
150 shell.background = defaultBackground
151 }
152 }
153 }
155 VolumeControl {
156 id: volumeControl
157 }
159 Keys.onVolumeUpPressed: volumeControl.volumeUp()
160 Keys.onVolumeDownPressed: volumeControl.volumeDown()
162 Item {
163 id: underlay
164 objectName: "underlay"
165 anchors.fill: parent
167 // Whether the underlay is fully covered by opaque UI elements.
168 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
170 readonly property bool applicationRunning: ((mainStage.applications && mainStage.applications.count > 0)
171 || (sideStage.applications && sideStage.applications.count > 0))
173 // Whether the user should see the topmost application surface (if there's one at all).
174 readonly property bool applicationSurfaceShouldBeSeen: applicationRunning && !stages.fullyHidden
175 && !mainStage.usingScreenshots // but want sideStage animating over app surface
179 // NB! Application surfaces are stacked behing the shell one. So they can only be seen by the user
180 // through the translucent parts of the shell surface.
181 visible: !fullyCovered && !applicationSurfaceShouldBeSeen
183 CrossFadeImage {
184 id: backgroundImage
185 objectName: "backgroundImage"
187 anchors.fill: parent
188 source: shell.background
189 }
191 Rectangle {
192 anchors.fill: parent
193 color: "black"
194 opacity: dash.disappearingAnimationProgress
195 }
197 Dash {
198 id: dash
199 objectName: "dash"
201 available: !greeter.shown && !lockscreen.shown
202 hides: [stages, launcher, panel.indicators]
203 shown: disappearingAnimationProgress !== 1.0
204 enabled: disappearingAnimationProgress === 0.0 && edgeDemo.dashEnabled
205 // FIXME: unfocus all applications when going back to the dash
206 onEnabledChanged: {
207 if (enabled) {
208 shell.applicationManager.unfocusCurrentApplication()
209 }
210 }
212 anchors {
213 fill: parent
214 topMargin: panel.panelHeight
215 }
217 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
218 opacity: 1.0 - disappearingAnimationProgress
219 property real disappearingAnimationProgress: {
220 if (greeter.shown) {
221 return greeter.showProgress;
222 } else {
223 return stagesOuterContainer.showProgress;
224 }
225 }
227 // FIXME: only necessary because stagesOuterContainer.showProgress and
228 // greeterRevealer.animatedProgress are not animated
229 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
230 }
231 }
233 Item {
234 id: stagesOuterContainer
236 width: parent.width
237 height: parent.height
238 x: launcher.progress
239 Behavior on x {SmoothedAnimation{velocity: 600}}
241 property real showProgress:
242 MathLocal.clamp(1 - (x + stages.x) / shell.width, 0, 1)
244 Showable {
245 id: stages
246 objectName: "stages"
248 x: width
250 property bool fullyShown: shown && x == 0 && parent.x == 0
251 property bool fullyHidden: !shown && x == width
252 available: !greeter.shown
253 hides: [launcher, panel.indicators]
254 shown: false
255 opacity: 1.0
256 showAnimation: StandardAnimation { property: "x"; duration: 350; to: 0; easing.type: Easing.OutCubic }
257 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: width; easing.type: Easing.OutCubic }
259 width: parent.width
260 height: parent.height
262 // close the stages when no focused application remains
263 Connections {
264 target: shell.applicationManager
265 onMainStageFocusedApplicationChanged: stages.closeIfNoApplications()
266 onSideStageFocusedApplicationChanged: stages.closeIfNoApplications()
267 ignoreUnknownSignals: true
268 }
270 function closeIfNoApplications() {
271 if (!shell.applicationManager.mainStageFocusedApplication
272 && !shell.applicationManager.sideStageFocusedApplication
273 && shell.applicationManager.mainStageApplications.count == 0
274 && shell.applicationManager.sideStageApplications.count == 0) {
275 stages.hide();
276 }
277 }
279 // show the stages when an application gets the focus
280 Connections {
281 target: shell.applicationManager
282 onMainStageFocusedApplicationChanged: {
283 if (shell.applicationManager.mainStageFocusedApplication) {
284 mainStage.show();
285 stages.show();
286 }
287 }
288 onSideStageFocusedApplicationChanged: {
289 if (shell.applicationManager.sideStageFocusedApplication) {
290 sideStage.show();
291 stages.show();
292 }
293 }
294 ignoreUnknownSignals: true
295 }
297 Stage {
298 id: mainStage
300 anchors.fill: parent
301 fullyShown: stages.fullyShown
302 fullyHidden: stages.fullyHidden
303 shouldUseScreenshots: !fullyShown
304 rightEdgeEnabled: !sideStage.enabled
306 applicationManager: shell.applicationManager
307 rightEdgeDraggingAreaWidth: shell.edgeSize
308 normalApplicationY: shell.panelHeight
310 shown: true
311 function show() {
312 stages.show();
313 }
314 function hide() {
315 }
317 // FIXME: workaround the fact that focusing a main stage application
318 // raises its surface on top of all other surfaces including the ones
319 // that belong to side stage applications.
320 onFocusedApplicationChanged: {
321 if (focusedApplication && sideStage.focusedApplication && sideStage.fullyShown) {
322 shell.applicationManager.focusApplication(sideStage.focusedApplication);
323 }
324 }
325 }
327 SideStage {
328 id: sideStage
330 applicationManager: shell.applicationManager
331 rightEdgeDraggingAreaWidth: shell.edgeSize
332 normalApplicationY: shell.panelHeight
334 onShownChanged: {
335 if (!shown && mainStage.applications.count == 0) {
336 stages.hide();
337 }
338 }
339 // FIXME: when hiding the side stage, refocus the main stage
340 // application so that it goes in front of the side stage
341 // application and hides it
342 onFullyShownChanged: {
343 if (!fullyShown && stages.fullyShown && sideStage.focusedApplication != null) {
344 shell.applicationManager.focusApplication(mainStage.focusedApplication);
345 }
346 }
348 enabled: shell.width >= units.gu(60)
349 visible: enabled
350 fullyShown: stages.fullyShown && shown
351 && sideStage[sideStageRevealer.boundProperty] == sideStageRevealer.openedValue
352 shouldUseScreenshots: !fullyShown || mainStage.usingScreenshots || sideStageRevealer.pressed
354 available: !greeter.shown && !lockscreen.shown && enabled
355 hides: [launcher, panel.indicators]
356 shown: false
357 showAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.openedValue; easing.type: Easing.OutQuint }
358 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.closedValue; easing.type: Easing.OutQuint }
360 width: units.gu(40)
361 height: stages.height
362 handleExpanded: sideStageRevealer.pressed
363 }
365 Revealer {
366 id: sideStageRevealer
368 enabled: mainStage.applications.count > 0 && sideStage.applications.count > 0
369 && sideStage.available
370 direction: Qt.RightToLeft
371 openedValue: parent.width - sideStage.width
372 hintDisplacement: units.gu(3)
373 /* The size of the sidestage handle needs to be bigger than the
374 typical size used for edge detection otherwise it is really
375 hard to grab.
376 */
377 handleSize: sideStage.shown ? units.gu(4) : shell.edgeSize
378 closedValue: parent.width + sideStage.handleSizeCollapsed
379 target: sideStage
380 x: parent.width - width
381 width: sideStage.width + handleSize * 0.7
382 height: sideStage.height
383 orientation: Qt.Horizontal
384 }
386 DragHandle {
387 id: stagesDragHandle
389 anchors.top: parent.top
390 anchors.bottom: parent.bottom
391 anchors.right: parent.left
393 width: shell.edgeSize
394 direction: Direction.Leftwards
395 enabled: edgeDemo.dashEnabled
396 property bool haveApps: mainStage.applications.count > 0 || sideStage.applications.count > 0
398 maxTotalDragDistance: haveApps ? parent.width : parent.width * 0.7
399 // Make autocompletion impossible when !haveApps
400 edgeDragEvaluator.minDragDistance: haveApps ? maxTotalDragDistance * 0.1 : Number.MAX_VALUE
401 }
402 }
403 }
405 Lockscreen {
406 id: lockscreen
407 hides: [launcher, panel.indicators, hud]
408 shown: false
409 enabled: true
410 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
411 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
412 y: panel.panelHeight
413 x: required ? 0 : - width
414 width: parent.width
415 height: parent.height - panel.panelHeight
416 background: shell.background
418 onUnlocked: lockscreen.hide()
419 onCancel: greeter.show()
421 Component.onCompleted: {
422 if (LightDM.Users.count == 1) {
423 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
424 greeter.selected(0)
425 }
426 }
427 }
429 Connections {
430 target: LightDM.Greeter
432 onShowPrompt: {
433 if (LightDM.Users.count == 1) {
434 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
435 if (text == "PIN") {
436 lockscreen.alphaNumeric = false
437 } else {
438 lockscreen.alphaNumeric = true
439 }
440 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
441 lockscreen.show();
442 }
443 }
444 }
446 Greeter {
447 id: greeter
448 objectName: "greeter"
450 available: true
451 hides: [launcher, panel.indicators, hud]
452 shown: true
454 y: panel.panelHeight
455 width: parent.width
456 height: parent.height - panel.panelHeight
458 dragHandleWidth: shell.edgeSize
460 onShownChanged: {
461 if (shown) {
462 lockscreen.reset();
463 // If there is only one user, we start authenticating with that one here.
464 // If there are more users, the Greeter will handle that
465 if (LightDM.Users.count == 1) {
466 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
467 greeter.selected(0);
468 }
469 greeter.forceActiveFocus();
470 }
471 }
473 onUnlocked: greeter.hide()
474 onSelected: {
475 // Update launcher items for new user
476 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
477 LauncherModel.setUser(user);
478 }
480 onLeftTeaserPressedChanged: {
481 if (leftTeaserPressed) {
482 launcher.tease();
483 }
484 }
485 }
487 InputFilterArea {
488 anchors.fill: parent
489 blockInput: greeter.shown || lockscreen.shown
490 }
492 Connections {
493 id: powerConnection
494 target: Powerd
496 property var previousMainApp: null
497 property var previousSideApp: null
499 function setFocused(focused) {
500 if (!focused) {
501 // FIXME: *FocusedApplication are not updated when unfocused, hence the need to check whether
502 // the stage was actually shown
503 if (mainStage.fullyShown) powerConnection.previousMainApp = applicationManager.mainStageFocusedApplication;
504 if (sideStage.fullyShown) powerConnection.previousSideApp = applicationManager.sideStageFocusedApplication;
505 applicationManager.unfocusCurrentApplication();
506 } else {
507 if (powerConnection.previousMainApp) {
508 applicationManager.focusApplication(powerConnection.previousMainApp);
509 powerConnection.previousMainApp = null;
510 }
511 if (powerConnection.previousSideApp) {
512 applicationManager.focusApplication(powerConnection.previousSideApp);
513 powerConnection.previousSideApp = null;
514 }
515 }
516 }
518 onDisplayPowerStateChange: {
519 // We ignore any display-off signals when the proximity sensor
520 // is active. This usually indicates something like a phone call.
521 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
522 powerConnection.setFocused(false);
523 greeter.show();
524 } else if (status == Powerd.On) {
525 powerConnection.setFocused(true);
526 }
528 // No reason to chew demo CPU when user isn't watching
529 if (status == Powerd.Off) {
530 edgeDemo.paused = true;
531 } else if (status == Powerd.On) {
532 edgeDemo.paused = false;
533 }
534 }
535 }
537 Item {
538 id: overlay
540 anchors.fill: parent
542 Panel {
543 id: panel
544 anchors.fill: parent //because this draws indicator menus
545 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
546 indicators {
547 hides: [launcher]
548 available: edgeDemo.panelEnabled
549 contentEnabled: edgeDemo.panelContentEnabled
550 }
551 fullscreenMode: shell.fullscreenMode
552 searchVisible: !greeter.shown && !lockscreen.shown
554 InputFilterArea {
555 anchors.fill: parent
556 blockInput: panel.indicators.shown
557 }
558 }
560 Hud {
561 id: hud
563 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
564 height: parent.height
566 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown && edgeDemo.dashEnabled
567 shown: false
568 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
569 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
571 Connections {
572 target: shell.applicationManager
573 onMainStageFocusedApplicationChanged: hud.hide()
574 onSideStageFocusedApplicationChanged: hud.hide()
575 }
577 InputFilterArea {
578 anchors.fill: parent
579 blockInput: hud.shown
580 }
581 }
583 Revealer {
584 id: hudRevealer
586 enabled: hud.shown
587 width: hud.width
588 anchors.left: hud.left
589 height: parent.height
590 target: hud.revealerTarget
591 closedValue: height
592 openedValue: 0
593 direction: Qt.RightToLeft
594 orientation: Qt.Vertical
595 handleSize: hud.handleHeight
596 onCloseClicked: target.hide()
597 }
599 Bottombar {
600 theHud: hud
601 anchors.fill: parent
602 enabled: !panel.indicators.shown
603 applicationIsOnForeground: applicationManager.mainStageFocusedApplication
604 || applicationManager.sideStageFocusedApplication
605 }
607 InputFilterArea {
608 blockInput: launcher.shown
609 anchors {
610 top: parent.top
611 bottom: parent.bottom
612 left: parent.left
613 }
614 width: launcher.width
615 }
617 Launcher {
618 id: launcher
620 anchors.top: parent.top
621 anchors.bottom: parent.bottom
622 width: parent.width
623 dragAreaWidth: shell.edgeSize
624 available: (!greeter.shown || greeter.narrowMode) && edgeDemo.launcherEnabled
625 onDashItemSelected: {
626 greeter.hide()
627 // Animate if moving between application and dash
628 if (!stages.shown) {
629 dash.setCurrentScope("home.scope", true, false)
630 } else {
631 dash.setCurrentScope("home.scope", false, false)
632 }
633 stages.hide();
634 }
635 onDash: {
636 if (stages.shown) {
637 dash.setCurrentScope("applications.scope", true, false)
638 stages.hide();
639 launcher.hide();
640 }
641 }
642 onLauncherApplicationSelected:{
643 greeter.hide()
644 shell.activateApplication(desktopFile)
645 }
646 onShownChanged: {
647 if (shown) {
648 panel.indicators.hide()
649 hud.hide()
650 }
651 }
652 }
654 Notifications {
655 id: notifications
657 model: NotificationBackend.Model
658 anchors {
659 top: parent.top
660 right: parent.right
661 bottom: parent.bottom
662 leftMargin: units.gu(1)
663 rightMargin: units.gu(1)
664 topMargin: panel.panelHeight + units.gu(1)
665 }
666 states: [
667 State {
668 name: "narrow"
669 when: overlay.width <= units.gu(60)
670 AnchorChanges { target: notifications; anchors.left: parent.left }
671 },
672 State {
673 name: "wide"
674 when: overlay.width > units.gu(60)
675 AnchorChanges { target: notifications; anchors.left: undefined }
676 PropertyChanges { target: notifications; width: units.gu(38) }
677 }
678 ]
679 }
680 }
682 focus: true
684 InputFilterArea {
685 anchors {
686 top: parent.top
687 bottom: parent.bottom
688 left: parent.left
689 }
690 width: shell.edgeSize
691 blockInput: true
692 }
694 InputFilterArea {
695 anchors {
696 top: parent.top
697 bottom: parent.bottom
698 right: parent.right
699 }
700 width: shell.edgeSize
701 blockInput: true
702 }
704 Binding {
705 target: i18n
706 property: "domain"
707 value: "unity8"
708 }
710 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
711 MouseArea {
712 anchors.bottom: parent.bottom
713 anchors.left: parent.left
714 anchors.right: parent.right
715 height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
717 enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
718 }
720 Label {
721 anchors.centerIn: parent
722 visible: applicationManager.fake
723 text: "EARLY ALPHA\nNOT READY FOR USE"
724 color: "lightgrey"
725 opacity: 0.2
726 font.weight: Font.Black
727 horizontalAlignment: Text.AlignHCenter
728 verticalAlignment: Text.AlignVCenter
729 fontSizeMode: Text.Fit
730 rotation: -45
731 scale: Math.min(parent.width, parent.height) / width
732 }
734 EdgeDemo {
735 id: edgeDemo
736 greeter: greeter
737 launcher: launcher
738 dash: dash
739 indicators: panel.indicators
740 underlay: underlay
741 }