on fa82bac help sign in
unity8/Shell.qml
706 lines 25 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
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 }
414 }
415 }
417 Connections {
418 target: LightDM.Greeter
420 onShowPrompt: {
421 if (LightDM.Users.count == 1) {
422 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
423 if (text == "PIN") {
424 lockscreen.alphaNumeric = false
425 } else {
426 lockscreen.alphaNumeric = true
427 }
428 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
429 lockscreen.show();
430 }
431 }
432 }
434 Greeter {
435 id: greeter
436 objectName: "greeter"
438 available: true
439 hides: [launcher, panel.indicators, hud]
440 shown: true
442 y: panel.panelHeight
443 width: parent.width
444 height: parent.height - panel.panelHeight
446 dragHandleWidth: shell.edgeSize
448 onShownChanged: {
449 if (shown) {
450 lockscreen.reset();
451 // If there is only one user, we start authenticating with that one here.
452 // If there are more users, the Greeter will handle that
453 if (LightDM.Users.count == 1) {
454 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
455 }
456 greeter.forceActiveFocus();
457 }
458 }
460 onUnlocked: greeter.hide()
462 onLeftTeaserPressedChanged: {
463 if (leftTeaserPressed) {
464 launcher.tease();
465 }
466 }
467 }
469 InputFilterArea {
470 anchors.fill: parent
471 blockInput: greeter.shown || lockscreen.shown
472 }
474 Connections {
475 id: powerConnection
476 target: Powerd
478 property var previousMainApp: null
479 property var previousSideApp: null
481 function setFocused(focused) {
482 if (!focused) {
483 // FIXME: *FocusedApplication are not updated when unfocused, hence the need to check whether
484 // the stage was actually shown
485 if (mainStage.fullyShown) powerConnection.previousMainApp = applicationManager.mainStageFocusedApplication;
486 if (sideStage.fullyShown) powerConnection.previousSideApp = applicationManager.sideStageFocusedApplication;
487 applicationManager.unfocusCurrentApplication();
488 } else {
489 if (powerConnection.previousMainApp) {
490 applicationManager.focusApplication(powerConnection.previousMainApp);
491 powerConnection.previousMainApp = null;
492 }
493 if (powerConnection.previousSideApp) {
494 applicationManager.focusApplication(powerConnection.previousSideApp);
495 powerConnection.previousSideApp = null;
496 }
497 }
498 }
500 onDisplayPowerStateChange: {
501 // We ignore any display-off signals when the proximity sensor
502 // is active. This usually indicates something like a phone call.
503 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
504 powerConnection.setFocused(false);
505 greeter.show();
506 } else if (status == Powerd.On) {
507 powerConnection.setFocused(true);
508 }
509 }
510 }
512 Item {
513 id: overlay
515 anchors.fill: parent
517 Panel {
518 id: panel
519 anchors.fill: parent //because this draws indicator menus
520 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
521 indicators {
522 hides: [launcher]
523 }
524 fullscreenMode: shell.fullscreenMode
525 searchVisible: !greeter.shown && !lockscreen.shown
527 InputFilterArea {
528 anchors.fill: parent
529 blockInput: panel.indicators.shown
530 }
531 }
533 Hud {
534 id: hud
536 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
537 height: parent.height
539 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown
540 shown: false
541 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
542 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
544 Connections {
545 target: shell.applicationManager
546 onMainStageFocusedApplicationChanged: hud.hide()
547 onSideStageFocusedApplicationChanged: hud.hide()
548 }
550 InputFilterArea {
551 anchors.fill: parent
552 blockInput: hud.shown
553 }
554 }
556 Revealer {
557 id: hudRevealer
559 enabled: hud.shown
560 width: hud.width
561 anchors.left: hud.left
562 height: parent.height
563 target: hud.revealerTarget
564 closedValue: height
565 openedValue: 0
566 direction: Qt.RightToLeft
567 orientation: Qt.Vertical
568 handleSize: hud.handleHeight
569 onCloseClicked: target.hide()
570 }
572 Bottombar {
573 theHud: hud
574 anchors.fill: parent
575 enabled: !panel.indicators.shown
576 applicationIsOnForeground: applicationManager.mainStageFocusedApplication
577 || applicationManager.sideStageFocusedApplication
578 }
580 InputFilterArea {
581 blockInput: launcher.shown
582 anchors {
583 top: parent.top
584 bottom: parent.bottom
585 left: parent.left
586 }
587 width: launcher.width
588 }
590 Launcher {
591 id: launcher
593 anchors.top: parent.top
594 anchors.bottom: parent.bottom
595 width: parent.width
596 dragAreaWidth: shell.edgeSize
597 available: !greeter.shown || greeter.narrowMode
598 onDashItemSelected: {
599 greeter.hide()
600 // Animate if moving between application and dash
601 if (!stages.shown) {
602 dash.setCurrentScope("home.scope", true, false)
603 } else {
604 dash.setCurrentScope("home.scope", false, false)
605 }
606 stages.hide();
607 }
608 onDash: {
609 if (stages.shown) {
610 dash.setCurrentScope("applications.scope", true, false)
611 stages.hide();
612 launcher.hide();
613 }
614 }
615 onLauncherApplicationSelected:{
616 greeter.hide()
617 shell.activateApplication(desktopFile)
618 }
619 onShownChanged: {
620 if (shown) {
621 panel.indicators.hide()
622 hud.hide()
623 }
624 }
625 }
627 Notifications {
628 id: notifications
630 model: NotificationBackend.Model
631 anchors {
632 top: parent.top
633 right: parent.right
634 bottom: parent.bottom
635 leftMargin: units.gu(1)
636 rightMargin: units.gu(1)
637 topMargin: panel.panelHeight + units.gu(1)
638 }
639 states: [
640 State {
641 name: "narrow"
642 when: overlay.width <= units.gu(60)
643 AnchorChanges { target: notifications; anchors.left: parent.left }
644 },
645 State {
646 name: "wide"
647 when: overlay.width > units.gu(60)
648 AnchorChanges { target: notifications; anchors.left: undefined }
649 PropertyChanges { target: notifications; width: units.gu(38) }
650 }
651 ]
652 }
653 }
655 focus: true
657 InputFilterArea {
658 anchors {
659 top: parent.top
660 bottom: parent.bottom
661 left: parent.left
662 }
663 width: shell.edgeSize
664 blockInput: true
665 }
667 InputFilterArea {
668 anchors {
669 top: parent.top
670 bottom: parent.bottom
671 right: parent.right
672 }
673 width: shell.edgeSize
674 blockInput: true
675 }
677 Binding {
678 target: i18n
679 property: "domain"
680 value: "unity8"
681 }
683 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
684 MouseArea {
685 anchors.bottom: parent.bottom
686 anchors.left: parent.left
687 anchors.right: parent.right
688 height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
690 enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
691 }
693 Label {
694 anchors.centerIn: parent
695 visible: applicationManager.fake
696 text: "EARLY ALPHA\nNOT READY FOR USE"
697 color: "lightgrey"
698 opacity: 0.2
699 font.weight: Font.Black
700 horizontalAlignment: Text.AlignHCenter
701 verticalAlignment: Text.AlignVCenter
702 fontSizeMode: Text.Fit
703 rotation: -45
704 scale: Math.min(parent.width, parent.height) / width
705 }