on 5997e63 help sign in
unity8/Shell.qml
718 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 }
511 }
512 }
514 Item {
515 id: overlay
517 anchors.fill: parent
519 Panel {
520 id: panel
521 anchors.fill: parent //because this draws indicator menus
522 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
523 indicators {
524 hides: [launcher]
525 available: edgeDemo.panelEnabled
526 }
527 fullscreenMode: shell.fullscreenMode
528 searchVisible: !greeter.shown && !lockscreen.shown
530 InputFilterArea {
531 anchors.fill: parent
532 blockInput: panel.indicators.shown
533 }
534 }
536 Hud {
537 id: hud
539 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
540 height: parent.height
542 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown && edgeDemo.dashEnabled
543 shown: false
544 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
545 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
547 Connections {
548 target: shell.applicationManager
549 onMainStageFocusedApplicationChanged: hud.hide()
550 onSideStageFocusedApplicationChanged: hud.hide()
551 }
553 InputFilterArea {
554 anchors.fill: parent
555 blockInput: hud.shown
556 }
557 }
559 Revealer {
560 id: hudRevealer
562 enabled: hud.shown
563 width: hud.width
564 anchors.left: hud.left
565 height: parent.height
566 target: hud.revealerTarget
567 closedValue: height
568 openedValue: 0
569 direction: Qt.RightToLeft
570 orientation: Qt.Vertical
571 handleSize: hud.handleHeight
572 onCloseClicked: target.hide()
573 }
575 Bottombar {
576 theHud: hud
577 anchors.fill: parent
578 enabled: !panel.indicators.shown
579 applicationIsOnForeground: applicationManager.mainStageFocusedApplication
580 || applicationManager.sideStageFocusedApplication
581 }
583 InputFilterArea {
584 blockInput: launcher.shown
585 anchors {
586 top: parent.top
587 bottom: parent.bottom
588 left: parent.left
589 }
590 width: launcher.width
591 }
593 Launcher {
594 id: launcher
596 anchors.top: parent.top
597 anchors.bottom: parent.bottom
598 width: parent.width
599 dragAreaWidth: shell.edgeSize
600 available: (!greeter.shown || greeter.narrowMode) && edgeDemo.launcherEnabled
601 onDashItemSelected: {
602 greeter.hide()
603 // Animate if moving between application and dash
604 if (!stages.shown) {
605 dash.setCurrentScope("home.scope", true, false)
606 } else {
607 dash.setCurrentScope("home.scope", false, false)
608 }
609 stages.hide();
610 }
611 onDash: {
612 if (stages.shown) {
613 dash.setCurrentScope("applications.scope", true, false)
614 stages.hide();
615 launcher.hide();
616 }
617 }
618 onLauncherApplicationSelected:{
619 greeter.hide()
620 shell.activateApplication(desktopFile)
621 }
622 onShownChanged: {
623 if (shown) {
624 panel.indicators.hide()
625 hud.hide()
626 }
627 }
628 }
630 Notifications {
631 id: notifications
633 model: NotificationBackend.Model
634 anchors {
635 top: parent.top
636 right: parent.right
637 bottom: parent.bottom
638 leftMargin: units.gu(1)
639 rightMargin: units.gu(1)
640 topMargin: panel.panelHeight + units.gu(1)
641 }
642 states: [
643 State {
644 name: "narrow"
645 when: overlay.width <= units.gu(60)
646 AnchorChanges { target: notifications; anchors.left: parent.left }
647 },
648 State {
649 name: "wide"
650 when: overlay.width > units.gu(60)
651 AnchorChanges { target: notifications; anchors.left: undefined }
652 PropertyChanges { target: notifications; width: units.gu(38) }
653 }
654 ]
655 }
656 }
658 focus: true
660 InputFilterArea {
661 anchors {
662 top: parent.top
663 bottom: parent.bottom
664 left: parent.left
665 }
666 width: shell.edgeSize
667 blockInput: true
668 }
670 InputFilterArea {
671 anchors {
672 top: parent.top
673 bottom: parent.bottom
674 right: parent.right
675 }
676 width: shell.edgeSize
677 blockInput: true
678 }
680 Binding {
681 target: i18n
682 property: "domain"
683 value: "unity8"
684 }
686 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
687 MouseArea {
688 anchors.bottom: parent.bottom
689 anchors.left: parent.left
690 anchors.right: parent.right
691 height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
693 enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
694 }
696 Label {
697 anchors.centerIn: parent
698 visible: applicationManager.fake
699 text: "EARLY ALPHA\nNOT READY FOR USE"
700 color: "lightgrey"
701 opacity: 0.2
702 font.weight: Font.Black
703 horizontalAlignment: Text.AlignHCenter
704 verticalAlignment: Text.AlignVCenter
705 fontSizeMode: Text.Fit
706 rotation: -45
707 scale: Math.min(parent.width, parent.height) / width
708 }
710 EdgeDemo {
711 id: edgeDemo
712 greeter: greeter
713 launcher: launcher
714 dash: dash
715 indicators: panel.indicators
716 overlay: overlay
717 }