on 1832ace help sign in
unity8/Shell.qml
710 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 readonly property bool applicationRunning: ((mainStage.applications && mainStage.applications.count > 0)
165 || (sideStage.applications && sideStage.applications.count > 0))
167 // Whether the user should see the topmost application surface (if there's one at all).
168 readonly property bool applicationSurfaceShouldBeSeen: applicationRunning && !stages.fullyHidden
169 && !mainStage.usingScreenshots // but want sideStage animating over app surface
173 // NB! Application surfaces are stacked behing the shell one. So they can only be seen by the user
174 // through the translucent parts of the shell surface.
175 visible: !fullyCovered && !applicationSurfaceShouldBeSeen
177 CrossFadeImage {
178 id: backgroundImage
179 objectName: "backgroundImage"
181 anchors.fill: parent
182 source: shell.background
183 }
185 Rectangle {
186 anchors.fill: parent
187 color: "black"
188 opacity: dash.disappearingAnimationProgress
189 }
191 Dash {
192 id: dash
193 objectName: "dash"
195 available: !greeter.shown && !lockscreen.shown
196 hides: [stages, launcher, panel.indicators]
197 shown: disappearingAnimationProgress !== 1.0
198 enabled: disappearingAnimationProgress === 0.0
199 // FIXME: unfocus all applications when going back to the dash
200 onEnabledChanged: {
201 if (enabled) {
202 shell.applicationManager.unfocusCurrentApplication()
203 }
204 }
206 anchors {
207 fill: parent
208 topMargin: panel.panelHeight
209 }
211 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
212 opacity: 1.0 - disappearingAnimationProgress
213 property real disappearingAnimationProgress: {
214 if (greeter.shown) {
215 return greeter.showProgress;
216 } else {
217 return stagesOuterContainer.showProgress;
218 }
219 }
221 // FIXME: only necessary because stagesOuterContainer.showProgress and
222 // greeterRevealer.animatedProgress are not animated
223 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
224 }
225 }
227 Item {
228 id: stagesOuterContainer
230 width: parent.width
231 height: parent.height
232 x: launcher.progress
233 Behavior on x {SmoothedAnimation{velocity: 600}}
235 property real showProgress:
236 MathLocal.clamp(1 - (x + stages.x) / shell.width, 0, 1)
238 Showable {
239 id: stages
240 objectName: "stages"
242 x: width
244 property bool fullyShown: shown && x == 0 && parent.x == 0
245 property bool fullyHidden: !shown && x == width
246 available: !greeter.shown
247 hides: [launcher, panel.indicators]
248 shown: false
249 opacity: 1.0
250 showAnimation: StandardAnimation { property: "x"; duration: 350; to: 0; easing.type: Easing.OutCubic }
251 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: width; easing.type: Easing.OutCubic }
253 width: parent.width
254 height: parent.height
256 // close the stages when no focused application remains
257 Connections {
258 target: shell.applicationManager
259 onMainStageFocusedApplicationChanged: stages.closeIfNoApplications()
260 onSideStageFocusedApplicationChanged: stages.closeIfNoApplications()
261 ignoreUnknownSignals: true
262 }
264 function closeIfNoApplications() {
265 if (!shell.applicationManager.mainStageFocusedApplication
266 && !shell.applicationManager.sideStageFocusedApplication
267 && shell.applicationManager.mainStageApplications.count == 0
268 && shell.applicationManager.sideStageApplications.count == 0) {
269 stages.hide();
270 }
271 }
273 // show the stages when an application gets the focus
274 Connections {
275 target: shell.applicationManager
276 onMainStageFocusedApplicationChanged: {
277 if (shell.applicationManager.mainStageFocusedApplication) {
278 mainStage.show();
279 stages.show();
280 }
281 }
282 onSideStageFocusedApplicationChanged: {
283 if (shell.applicationManager.sideStageFocusedApplication) {
284 sideStage.show();
285 stages.show();
286 }
287 }
288 ignoreUnknownSignals: true
289 }
291 Stage {
292 id: mainStage
294 anchors.fill: parent
295 fullyShown: stages.fullyShown
296 fullyHidden: stages.fullyHidden
297 shouldUseScreenshots: !fullyShown
298 rightEdgeEnabled: !sideStage.enabled
300 applicationManager: shell.applicationManager
301 rightEdgeDraggingAreaWidth: shell.edgeSize
302 normalApplicationY: shell.panelHeight
304 shown: true
305 function show() {
306 stages.show();
307 }
308 function hide() {
309 }
311 // FIXME: workaround the fact that focusing a main stage application
312 // raises its surface on top of all other surfaces including the ones
313 // that belong to side stage applications.
314 onFocusedApplicationChanged: {
315 if (focusedApplication && sideStage.focusedApplication && sideStage.fullyShown) {
316 shell.applicationManager.focusApplication(sideStage.focusedApplication);
317 }
318 }
319 }
321 SideStage {
322 id: sideStage
324 applicationManager: shell.applicationManager
325 rightEdgeDraggingAreaWidth: shell.edgeSize
326 normalApplicationY: shell.panelHeight
328 onShownChanged: {
329 if (!shown && mainStage.applications.count == 0) {
330 stages.hide();
331 }
332 }
333 // FIXME: when hiding the side stage, refocus the main stage
334 // application so that it goes in front of the side stage
335 // application and hides it
336 onFullyShownChanged: {
337 if (!fullyShown && stages.fullyShown && sideStage.focusedApplication != null) {
338 shell.applicationManager.focusApplication(mainStage.focusedApplication);
339 }
340 }
342 enabled: shell.width >= units.gu(60)
343 visible: enabled
344 fullyShown: stages.fullyShown && shown
345 && sideStage[sideStageRevealer.boundProperty] == sideStageRevealer.openedValue
346 shouldUseScreenshots: !fullyShown || mainStage.usingScreenshots || sideStageRevealer.pressed
348 available: !greeter.shown && !lockscreen.shown && enabled
349 hides: [launcher, panel.indicators]
350 shown: false
351 showAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.openedValue; easing.type: Easing.OutQuint }
352 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.closedValue; easing.type: Easing.OutQuint }
354 width: units.gu(40)
355 height: stages.height
356 handleExpanded: sideStageRevealer.pressed
357 }
359 Revealer {
360 id: sideStageRevealer
362 enabled: mainStage.applications.count > 0 && sideStage.applications.count > 0
363 && sideStage.available
364 direction: Qt.RightToLeft
365 openedValue: parent.width - sideStage.width
366 hintDisplacement: units.gu(3)
367 /* The size of the sidestage handle needs to be bigger than the
368 typical size used for edge detection otherwise it is really
369 hard to grab.
370 */
371 handleSize: sideStage.shown ? units.gu(4) : shell.edgeSize
372 closedValue: parent.width + sideStage.handleSizeCollapsed
373 target: sideStage
374 x: parent.width - width
375 width: sideStage.width + handleSize * 0.7
376 height: sideStage.height
377 orientation: Qt.Horizontal
378 }
380 DragHandle {
381 id: stagesDragHandle
383 anchors.top: parent.top
384 anchors.bottom: parent.bottom
385 anchors.right: parent.left
387 width: shell.edgeSize
388 direction: Direction.Leftwards
389 property bool haveApps: mainStage.applications.count > 0 || sideStage.applications.count > 0
391 maxTotalDragDistance: haveApps ? parent.width : parent.width * 0.7
392 // Make autocompletion impossible when !haveApps
393 edgeDragEvaluator.minDragDistance: haveApps ? maxTotalDragDistance * 0.1 : Number.MAX_VALUE
394 }
395 }
396 }
398 Lockscreen {
399 id: lockscreen
400 hides: [launcher, panel.indicators, hud]
401 shown: false
402 enabled: true
403 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
404 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
405 y: panel.panelHeight
406 x: required ? 0 : - width
407 width: parent.width
408 height: parent.height - panel.panelHeight
409 background: shell.background
411 onUnlocked: lockscreen.hide()
412 onCancel: greeter.show()
414 Component.onCompleted: {
415 if (LightDM.Users.count == 1) {
416 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
417 }
418 }
419 }
421 Connections {
422 target: LightDM.Greeter
424 onShowPrompt: {
425 if (LightDM.Users.count == 1) {
426 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
427 if (text == "PIN") {
428 lockscreen.alphaNumeric = false
429 } else {
430 lockscreen.alphaNumeric = true
431 }
432 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
433 lockscreen.show();
434 }
435 }
436 }
438 Greeter {
439 id: greeter
440 objectName: "greeter"
442 available: true
443 hides: [launcher, panel.indicators, hud]
444 shown: true
446 y: panel.panelHeight
447 width: parent.width
448 height: parent.height - panel.panelHeight
450 dragHandleWidth: shell.edgeSize
452 onShownChanged: {
453 if (shown) {
454 lockscreen.reset();
455 // If there is only one user, we start authenticating with that one here.
456 // If there are more users, the Greeter will handle that
457 if (LightDM.Users.count == 1) {
458 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
459 }
460 greeter.forceActiveFocus();
461 }
462 }
464 onUnlocked: greeter.hide()
466 onLeftTeaserPressedChanged: {
467 if (leftTeaserPressed) {
468 launcher.tease();
469 }
470 }
471 }
473 InputFilterArea {
474 anchors.fill: parent
475 blockInput: greeter.shown || lockscreen.shown
476 }
478 Connections {
479 id: powerConnection
480 target: Powerd
482 property var previousMainApp: null
483 property var previousSideApp: null
485 function setFocused(focused) {
486 if (!focused) {
487 // FIXME: *FocusedApplication are not updated when unfocused, hence the need to check whether
488 // the stage was actually shown
489 if (mainStage.fullyShown) powerConnection.previousMainApp = applicationManager.mainStageFocusedApplication;
490 if (sideStage.fullyShown) powerConnection.previousSideApp = applicationManager.sideStageFocusedApplication;
491 applicationManager.unfocusCurrentApplication();
492 } else {
493 if (powerConnection.previousMainApp) {
494 applicationManager.focusApplication(powerConnection.previousMainApp);
495 powerConnection.previousMainApp = null;
496 }
497 if (powerConnection.previousSideApp) {
498 applicationManager.focusApplication(powerConnection.previousSideApp);
499 powerConnection.previousSideApp = null;
500 }
501 }
502 }
504 onDisplayPowerStateChange: {
505 // We ignore any display-off signals when the proximity sensor
506 // is active. This usually indicates something like a phone call.
507 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
508 powerConnection.setFocused(false);
509 greeter.show();
510 } else if (status == Powerd.On) {
511 powerConnection.setFocused(true);
512 }
513 }
514 }
516 Item {
517 id: overlay
519 anchors.fill: parent
521 Panel {
522 id: panel
523 anchors.fill: parent //because this draws indicator menus
524 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
525 indicators {
526 hides: [launcher]
527 }
528 fullscreenMode: shell.fullscreenMode
529 searchVisible: !greeter.shown && !lockscreen.shown
531 InputFilterArea {
532 anchors.fill: parent
533 blockInput: panel.indicators.shown
534 }
535 }
537 Hud {
538 id: hud
540 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
541 height: parent.height
543 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown
544 shown: false
545 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
546 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
548 Connections {
549 target: shell.applicationManager
550 onMainStageFocusedApplicationChanged: hud.hide()
551 onSideStageFocusedApplicationChanged: hud.hide()
552 }
554 InputFilterArea {
555 anchors.fill: parent
556 blockInput: hud.shown
557 }
558 }
560 Revealer {
561 id: hudRevealer
563 enabled: hud.shown
564 width: hud.width
565 anchors.left: hud.left
566 height: parent.height
567 target: hud.revealerTarget
568 closedValue: height
569 openedValue: 0
570 direction: Qt.RightToLeft
571 orientation: Qt.Vertical
572 handleSize: hud.handleHeight
573 onCloseClicked: target.hide()
574 }
576 Bottombar {
577 theHud: hud
578 anchors.fill: parent
579 enabled: !panel.indicators.shown
580 applicationIsOnForeground: applicationManager.mainStageFocusedApplication
581 || applicationManager.sideStageFocusedApplication
582 }
584 InputFilterArea {
585 blockInput: launcher.shown
586 anchors {
587 top: parent.top
588 bottom: parent.bottom
589 left: parent.left
590 }
591 width: launcher.width
592 }
594 Launcher {
595 id: launcher
597 anchors.top: parent.top
598 anchors.bottom: parent.bottom
599 width: parent.width
600 dragAreaWidth: shell.edgeSize
601 available: !greeter.shown || greeter.narrowMode
602 onDashItemSelected: {
603 greeter.hide()
604 // Animate if moving between application and dash
605 if (!stages.shown) {
606 dash.setCurrentScope("home.scope", true, false)
607 } else {
608 dash.setCurrentScope("home.scope", false, false)
609 }
610 stages.hide();
611 }
612 onDash: {
613 if (stages.shown) {
614 dash.setCurrentScope("applications.scope", true, false)
615 stages.hide();
616 launcher.hide();
617 }
618 }
619 onLauncherApplicationSelected:{
620 greeter.hide()
621 shell.activateApplication(desktopFile)
622 }
623 onShownChanged: {
624 if (shown) {
625 panel.indicators.hide()
626 hud.hide()
627 }
628 }
629 }
631 Notifications {
632 id: notifications
634 model: NotificationBackend.Model
635 anchors {
636 top: parent.top
637 right: parent.right
638 bottom: parent.bottom
639 leftMargin: units.gu(1)
640 rightMargin: units.gu(1)
641 topMargin: panel.panelHeight + units.gu(1)
642 }
643 states: [
644 State {
645 name: "narrow"
646 when: overlay.width <= units.gu(60)
647 AnchorChanges { target: notifications; anchors.left: parent.left }
648 },
649 State {
650 name: "wide"
651 when: overlay.width > units.gu(60)
652 AnchorChanges { target: notifications; anchors.left: undefined }
653 PropertyChanges { target: notifications; width: units.gu(38) }
654 }
655 ]
656 }
657 }
659 focus: true
661 InputFilterArea {
662 anchors {
663 top: parent.top
664 bottom: parent.bottom
665 left: parent.left
666 }
667 width: shell.edgeSize
668 blockInput: true
669 }
671 InputFilterArea {
672 anchors {
673 top: parent.top
674 bottom: parent.bottom
675 right: parent.right
676 }
677 width: shell.edgeSize
678 blockInput: true
679 }
681 Binding {
682 target: i18n
683 property: "domain"
684 value: "unity8"
685 }
687 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
688 MouseArea {
689 anchors.bottom: parent.bottom
690 anchors.left: parent.left
691 anchors.right: parent.right
692 height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
694 enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
695 }
697 Label {
698 anchors.centerIn: parent
699 visible: applicationManager.fake
700 text: "EARLY ALPHA\nNOT READY FOR USE"
701 color: "lightgrey"
702 opacity: 0.2
703 font.weight: Font.Black
704 horizontalAlignment: Text.AlignHCenter
705 verticalAlignment: Text.AlignVCenter
706 fontSizeMode: Text.Fit
707 rotation: -45
708 scale: Math.min(parent.width, parent.height) / width
709 }