on 2ea8705 help sign in
unity8/Shell.qml
744 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 Unity.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 if (app != null) {
102 LauncherModel.applicationFocused(app.desktopFile);
103 } else {
104 LauncherModel.applicationFocused("");
105 }
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 GSettings {
130 id: backgroundSettings
131 schema.id: "org.gnome.desktop.background"
132 }
133 property url gSettingsPicture: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : shell.defaultBackground
134 onGSettingsPictureChanged: {
135 shell.background = gSettingsPicture
136 }
138 // This is a dummy image that is needed to determine if the picture url
139 // in backgroundSettings points to a valid picture file.
140 // We can't do this with the real background image because setting a
141 // new source in onStatusChanged triggers a binding loop detection
142 // inside Image, which causes it not to render even though a valid source
143 // would be set. We don't mind about this image staying black and just
144 // use it for verification to populate the source for the real
145 // background image.
146 Image {
147 source: shell.background
148 height: 0
149 width: 0
150 sourceSize.height: 0
151 sourceSize.width: 0
152 onStatusChanged: {
153 if (status == Image.Error && source != shell.defaultBackground) {
154 shell.background = defaultBackground
155 }
156 }
157 }
159 VolumeControl {
160 id: volumeControl
161 }
163 Keys.onVolumeUpPressed: volumeControl.volumeUp()
164 Keys.onVolumeDownPressed: volumeControl.volumeDown()
166 Item {
167 id: underlay
168 objectName: "underlay"
169 anchors.fill: parent
171 // Whether the underlay is fully covered by opaque UI elements.
172 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
174 readonly property bool applicationRunning: ((mainStage.applications && mainStage.applications.count > 0)
175 || (sideStage.applications && sideStage.applications.count > 0))
177 // Whether the user should see the topmost application surface (if there's one at all).
178 readonly property bool applicationSurfaceShouldBeSeen: applicationRunning && !stages.fullyHidden
179 && !mainStage.usingScreenshots // but want sideStage animating over app surface
183 // NB! Application surfaces are stacked behing the shell one. So they can only be seen by the user
184 // through the translucent parts of the shell surface.
185 visible: !fullyCovered && !applicationSurfaceShouldBeSeen
187 CrossFadeImage {
188 id: backgroundImage
189 objectName: "backgroundImage"
191 anchors.fill: parent
192 source: shell.background
193 }
195 Rectangle {
196 anchors.fill: parent
197 color: "black"
198 opacity: dash.disappearingAnimationProgress
199 }
201 Dash {
202 id: dash
203 objectName: "dash"
205 available: !greeter.shown && !lockscreen.shown
206 hides: [stages, launcher, panel.indicators]
207 shown: disappearingAnimationProgress !== 1.0
208 enabled: disappearingAnimationProgress === 0.0 && edgeDemo.dashEnabled
209 // FIXME: unfocus all applications when going back to the dash
210 onEnabledChanged: {
211 if (enabled) {
212 shell.applicationManager.unfocusCurrentApplication()
213 }
214 }
216 anchors {
217 fill: parent
218 topMargin: panel.panelHeight
219 }
221 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
222 opacity: 1.0 - disappearingAnimationProgress
223 property real disappearingAnimationProgress: {
224 if (greeter.shown) {
225 return greeter.showProgress;
226 } else {
227 return stagesOuterContainer.showProgress;
228 }
229 }
231 // FIXME: only necessary because stagesOuterContainer.showProgress and
232 // greeterRevealer.animatedProgress are not animated
233 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
234 }
235 }
237 Item {
238 id: stagesOuterContainer
240 width: parent.width
241 height: parent.height
242 x: launcher.progress
243 Behavior on x {SmoothedAnimation{velocity: 600}}
245 property real showProgress:
246 MathLocal.clamp(1 - (x + stages.x) / shell.width, 0, 1)
248 Showable {
249 id: stages
250 objectName: "stages"
252 x: width
254 property bool fullyShown: shown && x == 0 && parent.x == 0
255 property bool fullyHidden: !shown && x == width
256 available: !greeter.shown
257 hides: [launcher, panel.indicators]
258 shown: false
259 opacity: 1.0
260 showAnimation: StandardAnimation { property: "x"; duration: 350; to: 0; easing.type: Easing.OutCubic }
261 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: width; easing.type: Easing.OutCubic }
263 width: parent.width
264 height: parent.height
266 // close the stages when no focused application remains
267 Connections {
268 target: shell.applicationManager
269 onMainStageFocusedApplicationChanged: stages.closeIfNoApplications()
270 onSideStageFocusedApplicationChanged: stages.closeIfNoApplications()
271 ignoreUnknownSignals: true
272 }
274 function closeIfNoApplications() {
275 if (!shell.applicationManager.mainStageFocusedApplication
276 && !shell.applicationManager.sideStageFocusedApplication
277 && shell.applicationManager.mainStageApplications.count == 0
278 && shell.applicationManager.sideStageApplications.count == 0) {
279 stages.hide();
280 }
281 }
283 // show the stages when an application gets the focus
284 Connections {
285 target: shell.applicationManager
286 onMainStageFocusedApplicationChanged: {
287 if (shell.applicationManager.mainStageFocusedApplication) {
288 mainStage.show();
289 stages.show();
290 }
291 }
292 onSideStageFocusedApplicationChanged: {
293 if (shell.applicationManager.sideStageFocusedApplication) {
294 sideStage.show();
295 stages.show();
296 }
297 }
298 ignoreUnknownSignals: true
299 }
301 Stage {
302 id: mainStage
304 anchors.fill: parent
305 fullyShown: stages.fullyShown
306 fullyHidden: stages.fullyHidden
307 shouldUseScreenshots: !fullyShown
308 rightEdgeEnabled: !sideStage.enabled
310 applicationManager: shell.applicationManager
311 rightEdgeDraggingAreaWidth: shell.edgeSize
312 normalApplicationY: shell.panelHeight
314 shown: true
315 function show() {
316 stages.show();
317 }
318 function hide() {
319 }
321 // FIXME: workaround the fact that focusing a main stage application
322 // raises its surface on top of all other surfaces including the ones
323 // that belong to side stage applications.
324 onFocusedApplicationChanged: {
325 if (focusedApplication && sideStage.focusedApplication && sideStage.fullyShown) {
326 shell.applicationManager.focusApplication(sideStage.focusedApplication);
327 }
328 }
329 }
331 SideStage {
332 id: sideStage
334 applicationManager: shell.applicationManager
335 rightEdgeDraggingAreaWidth: shell.edgeSize
336 normalApplicationY: shell.panelHeight
338 onShownChanged: {
339 if (!shown && mainStage.applications.count == 0) {
340 stages.hide();
341 }
342 }
343 // FIXME: when hiding the side stage, refocus the main stage
344 // application so that it goes in front of the side stage
345 // application and hides it
346 onFullyShownChanged: {
347 if (!fullyShown && stages.fullyShown && sideStage.focusedApplication != null) {
348 shell.applicationManager.focusApplication(mainStage.focusedApplication);
349 }
350 }
352 enabled: shell.width >= units.gu(60)
353 visible: enabled
354 fullyShown: stages.fullyShown && shown
355 && sideStage[sideStageRevealer.boundProperty] == sideStageRevealer.openedValue
356 shouldUseScreenshots: !fullyShown || mainStage.usingScreenshots || sideStageRevealer.pressed
358 available: !greeter.shown && !lockscreen.shown && enabled
359 hides: [launcher, panel.indicators]
360 shown: false
361 showAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.openedValue; easing.type: Easing.OutQuint }
362 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.closedValue; easing.type: Easing.OutQuint }
364 width: units.gu(40)
365 height: stages.height
366 handleExpanded: sideStageRevealer.pressed
367 }
369 Revealer {
370 id: sideStageRevealer
372 enabled: mainStage.applications.count > 0 && sideStage.applications.count > 0
373 && sideStage.available
374 direction: Qt.RightToLeft
375 openedValue: parent.width - sideStage.width
376 hintDisplacement: units.gu(3)
377 /* The size of the sidestage handle needs to be bigger than the
378 typical size used for edge detection otherwise it is really
379 hard to grab.
380 */
381 handleSize: sideStage.shown ? units.gu(4) : shell.edgeSize
382 closedValue: parent.width + sideStage.handleSizeCollapsed
383 target: sideStage
384 x: parent.width - width
385 width: sideStage.width + handleSize * 0.7
386 height: sideStage.height
387 orientation: Qt.Horizontal
388 }
390 DragHandle {
391 id: stagesDragHandle
393 anchors.top: parent.top
394 anchors.bottom: parent.bottom
395 anchors.right: parent.left
397 width: shell.edgeSize
398 direction: Direction.Leftwards
399 enabled: edgeDemo.dashEnabled
400 property bool haveApps: mainStage.applications.count > 0 || sideStage.applications.count > 0
402 maxTotalDragDistance: haveApps ? parent.width : parent.width * 0.7
403 // Make autocompletion impossible when !haveApps
404 edgeDragEvaluator.minDragDistance: haveApps ? maxTotalDragDistance * 0.1 : Number.MAX_VALUE
405 }
406 }
407 }
409 Lockscreen {
410 id: lockscreen
411 hides: [launcher, panel.indicators, hud]
412 shown: false
413 enabled: true
414 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
415 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
416 y: panel.panelHeight
417 x: required ? 0 : - width
418 width: parent.width
419 height: parent.height - panel.panelHeight
420 background: shell.background
422 onUnlocked: lockscreen.hide()
423 onCancel: greeter.show()
425 Component.onCompleted: {
426 if (LightDM.Users.count == 1) {
427 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
428 greeter.selected(0)
429 }
430 }
431 }
433 Connections {
434 target: LightDM.Greeter
436 onShowPrompt: {
437 if (LightDM.Users.count == 1) {
438 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
439 if (text == "PIN") {
440 lockscreen.alphaNumeric = false
441 } else {
442 lockscreen.alphaNumeric = true
443 }
444 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
445 lockscreen.show();
446 }
447 }
448 }
450 Greeter {
451 id: greeter
452 objectName: "greeter"
454 available: true
455 hides: [launcher, panel.indicators, hud]
456 shown: true
458 y: panel.panelHeight
459 width: parent.width
460 height: parent.height - panel.panelHeight
462 dragHandleWidth: shell.edgeSize
464 onShownChanged: {
465 if (shown) {
466 lockscreen.reset();
467 // If there is only one user, we start authenticating with that one here.
468 // If there are more users, the Greeter will handle that
469 if (LightDM.Users.count == 1) {
470 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
471 greeter.selected(0);
472 }
473 greeter.forceActiveFocus();
474 }
475 }
477 onUnlocked: greeter.hide()
478 onSelected: {
479 // Update launcher items for new user
480 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
481 LauncherModel.setUser(user);
482 }
484 onLeftTeaserPressedChanged: {
485 if (leftTeaserPressed) {
486 launcher.tease();
487 }
488 }
489 }
491 InputFilterArea {
492 anchors.fill: parent
493 blockInput: greeter.shown || lockscreen.shown
494 }
496 Connections {
497 id: powerConnection
498 target: Powerd
500 property var previousMainApp: null
501 property var previousSideApp: null
503 function setFocused(focused) {
504 if (!focused) {
505 powerConnection.previousMainApp = applicationManager.mainStageFocusedApplication;
506 powerConnection.previousSideApp = applicationManager.sideStageFocusedApplication;
507 applicationManager.unfocusCurrentApplication();
508 } else {
509 if (powerConnection.previousMainApp) {
510 applicationManager.focusApplication(powerConnection.previousMainApp);
511 powerConnection.previousMainApp = null;
512 }
513 if (powerConnection.previousSideApp) {
514 applicationManager.focusApplication(powerConnection.previousSideApp);
515 powerConnection.previousSideApp = null;
516 }
517 }
518 }
520 onDisplayPowerStateChange: {
521 // We ignore any display-off signals when the proximity sensor
522 // is active. This usually indicates something like a phone call.
523 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
524 powerConnection.setFocused(false);
525 greeter.show();
526 } else if (status == Powerd.On) {
527 powerConnection.setFocused(true);
528 }
530 // No reason to chew demo CPU when user isn't watching
531 if (status == Powerd.Off) {
532 edgeDemo.paused = true;
533 } else if (status == Powerd.On) {
534 edgeDemo.paused = false;
535 }
536 }
537 }
539 Item {
540 id: overlay
542 anchors.fill: parent
544 Panel {
545 id: panel
546 anchors.fill: parent //because this draws indicator menus
547 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
548 indicators {
549 hides: [launcher]
550 available: edgeDemo.panelEnabled
551 contentEnabled: edgeDemo.panelContentEnabled
552 }
553 fullscreenMode: shell.fullscreenMode
554 searchVisible: !greeter.shown && !lockscreen.shown
556 InputFilterArea {
557 anchors.fill: parent
558 blockInput: panel.indicators.shown
559 }
560 }
562 Hud {
563 id: hud
565 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
566 height: parent.height
568 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown && edgeDemo.dashEnabled
569 shown: false
570 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
571 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
573 Connections {
574 target: shell.applicationManager
575 onMainStageFocusedApplicationChanged: hud.hide()
576 onSideStageFocusedApplicationChanged: hud.hide()
577 }
579 InputFilterArea {
580 anchors.fill: parent
581 blockInput: hud.shown
582 }
583 }
585 Revealer {
586 id: hudRevealer
588 enabled: hud.shown
589 width: hud.width
590 anchors.left: hud.left
591 height: parent.height
592 target: hud.revealerTarget
593 closedValue: height
594 openedValue: 0
595 direction: Qt.RightToLeft
596 orientation: Qt.Vertical
597 handleSize: hud.handleHeight
598 onCloseClicked: target.hide()
599 }
601 Bottombar {
602 theHud: hud
603 anchors.fill: parent
604 enabled: !panel.indicators.shown
605 applicationIsOnForeground: !!applicationManager.mainStageFocusedApplication
606 || !!applicationManager.sideStageFocusedApplication
607 }
609 InputFilterArea {
610 blockInput: launcher.shown
611 anchors {
612 top: parent.top
613 bottom: parent.bottom
614 left: parent.left
615 }
616 width: launcher.width
617 }
619 Launcher {
620 id: launcher
622 anchors.top: parent.top
623 anchors.bottom: parent.bottom
624 width: parent.width
625 dragAreaWidth: shell.edgeSize
626 available: (!greeter.shown || greeter.narrowMode) && edgeDemo.launcherEnabled
627 onDashItemSelected: {
628 greeter.hide()
629 // Animate if moving between application and dash
630 if (!stages.shown) {
631 dash.setCurrentScope("home.scope", true, false)
632 } else {
633 dash.setCurrentScope("home.scope", false, false)
634 }
635 stages.hide();
636 }
637 onDash: {
638 if (stages.shown) {
639 dash.setCurrentScope("applications.scope", true, false)
640 stages.hide();
641 launcher.hide();
642 }
643 }
644 onLauncherApplicationSelected:{
645 greeter.hide()
646 shell.activateApplication(desktopFile)
647 }
648 onShownChanged: {
649 if (shown) {
650 panel.indicators.hide()
651 hud.hide()
652 }
653 }
654 }
656 Notifications {
657 id: notifications
659 model: NotificationBackend.Model
660 anchors {
661 top: parent.top
662 right: parent.right
663 bottom: parent.bottom
664 leftMargin: units.gu(1)
665 rightMargin: units.gu(1)
666 topMargin: panel.panelHeight + units.gu(1)
667 }
668 states: [
669 State {
670 name: "narrow"
671 when: overlay.width <= units.gu(60)
672 AnchorChanges { target: notifications; anchors.left: parent.left }
673 },
674 State {
675 name: "wide"
676 when: overlay.width > units.gu(60)
677 AnchorChanges { target: notifications; anchors.left: undefined }
678 PropertyChanges { target: notifications; width: units.gu(38) }
679 }
680 ]
681 }
682 }
684 focus: true
686 InputFilterArea {
687 anchors {
688 top: parent.top
689 bottom: parent.bottom
690 left: parent.left
691 }
692 width: shell.edgeSize
693 blockInput: true
694 }
696 InputFilterArea {
697 anchors {
698 top: parent.top
699 bottom: parent.bottom
700 right: parent.right
701 }
702 width: shell.edgeSize
703 blockInput: true
704 }
706 Binding {
707 target: i18n
708 property: "domain"
709 value: "unity8"
710 }
712 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
713 MouseArea {
714 anchors.bottom: parent.bottom
715 anchors.left: parent.left
716 anchors.right: parent.right
717 height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
719 enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
720 }
722 Label {
723 anchors.centerIn: parent
724 visible: applicationManager.fake
725 text: "EARLY ALPHA\nNOT READY FOR USE"
726 color: "lightgrey"
727 opacity: 0.2
728 font.weight: Font.Black
729 horizontalAlignment: Text.AlignHCenter
730 verticalAlignment: Text.AlignVCenter
731 fontSizeMode: Text.Fit
732 rotation: -45
733 scale: Math.min(parent.width, parent.height) / width
734 }
736 EdgeDemo {
737 id: edgeDemo
738 greeter: greeter
739 launcher: launcher
740 dash: dash
741 indicators: panel.indicators
742 underlay: underlay
743 }