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