on 83fd0fa help sign in
unity8/Shell.qml
691 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: backgroundImage.source
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 }
124 VolumeControl {
125 id: volumeControl
126 }
128 Keys.onVolumeUpPressed: volumeControl.volumeUp()
129 Keys.onVolumeDownPressed: volumeControl.volumeDown()
131 Item {
132 id: underlay
133 objectName: "underlay"
134 anchors.fill: parent
136 // Whether the underlay is fully covered by opaque UI elements.
137 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
139 // Whether the user should see the topmost application surface (if there's one at all).
140 property bool applicationSurfaceShouldBeSeen:
141 (mainStage.applications && mainStage.applications.count > 0)
142 && (!stages.fullyHidden && !mainStage.usingScreenshots)
144 // NB! Application surfaces are stacked behing the shell one. So they can only be seen by the user
145 // through the translucent parts of the shell surface.
146 visible: !fullyCovered && !applicationSurfaceShouldBeSeen
148 CrossFadeImage {
149 id: backgroundImage
150 objectName: "backgroundImage"
152 property url gSettingsPicture: backgroundSettings.pictureUri
154 anchors.fill: parent
156 onGSettingsPictureChanged: source = gSettingsPicture
158 onStatusChanged: {
159 if (status == Image.Error && source != defaultBackground) {
160 source = defaultBackground
161 }
162 }
163 }
165 Rectangle {
166 anchors.fill: parent
167 color: "black"
168 opacity: dash.disappearingAnimationProgress
169 }
171 Dash {
172 id: dash
173 objectName: "dash"
175 available: !greeter.shown && !lockscreen.shown
176 hides: [stages, launcher, panel.indicators]
177 shown: disappearingAnimationProgress !== 1.0
178 enabled: disappearingAnimationProgress === 0.0
179 // FIXME: unfocus all applications when going back to the dash
180 onEnabledChanged: {
181 if (enabled) {
182 shell.applicationManager.unfocusCurrentApplication()
183 }
184 }
186 anchors {
187 fill: parent
188 topMargin: panel.panelHeight
189 }
191 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
192 opacity: 1.0 - disappearingAnimationProgress
193 property real disappearingAnimationProgress: {
194 if (greeter.shown) {
195 return greeter.showProgress;
196 } else {
197 return stagesOuterContainer.showProgress;
198 }
199 }
201 // FIXME: only necessary because stagesOuterContainer.showProgress and
202 // greeterRevealer.animatedProgress are not animated
203 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
204 }
205 }
207 Item {
208 id: stagesOuterContainer
210 width: parent.width
211 height: parent.height
212 x: launcher.progress
213 Behavior on x {SmoothedAnimation{velocity: 600}}
215 property real showProgress:
216 MathLocal.clamp(1 - (x + stages.x) / shell.width, 0, 1)
218 Showable {
219 id: stages
220 objectName: "stages"
222 x: width
224 property bool fullyShown: shown && x == 0 && parent.x == 0
225 property bool fullyHidden: !shown && x == width
226 available: !greeter.shown
227 hides: [launcher, panel.indicators]
228 shown: false
229 opacity: 1.0
230 showAnimation: StandardAnimation { property: "x"; duration: 350; to: 0; easing.type: Easing.OutCubic }
231 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: width; easing.type: Easing.OutCubic }
233 width: parent.width
234 height: parent.height
236 // close the stages when no focused application remains
237 Connections {
238 target: shell.applicationManager
239 onMainStageFocusedApplicationChanged: stages.closeIfNoApplications()
240 onSideStageFocusedApplicationChanged: stages.closeIfNoApplications()
241 ignoreUnknownSignals: true
242 }
244 function closeIfNoApplications() {
245 if (!shell.applicationManager.mainStageFocusedApplication
246 && !shell.applicationManager.sideStageFocusedApplication
247 && shell.applicationManager.mainStageApplications.count == 0
248 && shell.applicationManager.sideStageApplications.count == 0) {
249 stages.hide();
250 }
251 }
253 // show the stages when an application gets the focus
254 Connections {
255 target: shell.applicationManager
256 onMainStageFocusedApplicationChanged: {
257 if (shell.applicationManager.mainStageFocusedApplication) {
258 mainStage.show();
259 stages.show();
260 }
261 }
262 onSideStageFocusedApplicationChanged: {
263 if (shell.applicationManager.sideStageFocusedApplication) {
264 sideStage.show();
265 stages.show();
266 }
267 }
268 ignoreUnknownSignals: true
269 }
271 Stage {
272 id: mainStage
274 anchors.fill: parent
275 fullyShown: stages.fullyShown
276 fullyHidden: stages.fullyHidden
277 shouldUseScreenshots: !fullyShown
278 rightEdgeEnabled: !sideStage.enabled
280 applicationManager: shell.applicationManager
281 rightEdgeDraggingAreaWidth: shell.edgeSize
282 normalApplicationY: shell.panelHeight
284 shown: true
285 function show() {
286 stages.show();
287 }
288 function hide() {
289 }
291 // FIXME: workaround the fact that focusing a main stage application
292 // raises its surface on top of all other surfaces including the ones
293 // that belong to side stage applications.
294 onFocusedApplicationChanged: {
295 if (focusedApplication && sideStage.focusedApplication && sideStage.fullyShown) {
296 shell.applicationManager.focusApplication(sideStage.focusedApplication);
297 }
298 }
299 }
301 SideStage {
302 id: sideStage
304 applicationManager: shell.applicationManager
305 rightEdgeDraggingAreaWidth: shell.edgeSize
306 normalApplicationY: shell.panelHeight
308 onShownChanged: {
309 if (!shown && mainStage.applications.count == 0) {
310 stages.hide();
311 }
312 }
313 // FIXME: when hiding the side stage, refocus the main stage
314 // application so that it goes in front of the side stage
315 // application and hides it
316 onFullyShownChanged: {
317 if (!fullyShown && stages.fullyShown && sideStage.focusedApplication != null) {
318 shell.applicationManager.focusApplication(mainStage.focusedApplication);
319 }
320 }
322 enabled: shell.width >= units.gu(60)
323 visible: enabled
324 fullyShown: stages.fullyShown && shown
325 && sideStage[sideStageRevealer.boundProperty] == sideStageRevealer.openedValue
326 shouldUseScreenshots: !fullyShown || mainStage.usingScreenshots || sideStageRevealer.pressed
328 available: !greeter.shown && !lockscreen.shown && enabled
329 hides: [launcher, panel.indicators]
330 shown: false
331 showAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.openedValue; easing.type: Easing.OutQuint }
332 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.closedValue; easing.type: Easing.OutQuint }
334 width: units.gu(40)
335 height: stages.height
336 handleExpanded: sideStageRevealer.pressed
337 }
339 Revealer {
340 id: sideStageRevealer
342 enabled: mainStage.applications.count > 0 && sideStage.applications.count > 0
343 && sideStage.available
344 direction: Qt.RightToLeft
345 openedValue: parent.width - sideStage.width
346 hintDisplacement: units.gu(3)
347 /* The size of the sidestage handle needs to be bigger than the
348 typical size used for edge detection otherwise it is really
349 hard to grab.
350 */
351 handleSize: sideStage.shown ? units.gu(4) : shell.edgeSize
352 closedValue: parent.width + sideStage.handleSizeCollapsed
353 target: sideStage
354 x: parent.width - width
355 width: sideStage.width + handleSize * 0.7
356 height: sideStage.height
357 orientation: Qt.Horizontal
358 }
360 DragHandle {
361 id: stagesDragHandle
363 anchors.top: parent.top
364 anchors.bottom: parent.bottom
365 anchors.right: parent.left
367 width: shell.edgeSize
368 direction: Direction.Leftwards
369 property bool haveApps: mainStage.applications.count > 0 || sideStage.applications.count > 0
371 maxTotalDragDistance: haveApps ? parent.width : parent.width * 0.7
372 // Make autocompletion impossible when !haveApps
373 edgeDragEvaluator.minDragDistance: haveApps ? maxTotalDragDistance * 0.1 : Number.MAX_VALUE
374 }
375 }
376 }
378 Lockscreen {
379 id: lockscreen
380 hides: [launcher, panel.indicators, hud]
381 shown: false
382 enabled: true
383 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
384 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
385 y: panel.panelHeight
386 x: required ? 0 : - width
387 width: parent.width
388 height: parent.height - panel.panelHeight
389 background: shell.background
391 onUnlocked: lockscreen.hide()
392 onCancel: greeter.show()
394 Component.onCompleted: {
395 if (LightDM.Users.count == 1) {
396 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
397 }
398 }
399 }
401 Connections {
402 target: LightDM.Greeter
404 onShowPrompt: {
405 if (LightDM.Users.count == 1) {
406 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
407 if (text == "PIN") {
408 lockscreen.alphaNumeric = false
409 } else {
410 lockscreen.alphaNumeric = true
411 }
412 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
413 lockscreen.show();
414 }
415 }
416 }
418 Greeter {
419 id: greeter
421 available: true
422 hides: [launcher, panel.indicators, hud]
423 shown: true
425 y: panel.panelHeight
426 width: parent.width
427 height: parent.height - panel.panelHeight
429 dragHandleWidth: shell.edgeSize
431 onShownChanged: {
432 if (shown) {
433 lockscreen.reset();
434 // If there is only one user, we start authenticating with that one here.
435 // If there are more users, the Greeter will handle that
436 if (LightDM.Users.count == 1) {
437 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
438 }
439 greeter.forceActiveFocus();
440 }
441 }
443 onUnlocked: greeter.hide()
444 onSelected: {
445 var bgPath = greeter.model.data(uid, LightDM.UserRoles.BackgroundPathRole)
446 shell.background = bgPath ? bgPath : defaultBackground
447 }
449 onLeftTeaserPressedChanged: {
450 if (leftTeaserPressed) {
451 launcher.tease();
452 }
453 }
454 }
456 InputFilterArea {
457 anchors.fill: parent
458 blockInput: greeter.shown || lockscreen.shown
459 }
461 Connections {
462 id: powerConnection
463 target: Powerd
465 property var previousMainApp: null
466 property var previousSideApp: null
468 function setFocused(focused) {
469 if (!focused) {
470 // FIXME: *FocusedApplication are not updated when unfocused, hence the need to check whether
471 // the stage was actually shown
472 if (mainStage.fullyShown) powerConnection.previousMainApp = applicationManager.mainStageFocusedApplication;
473 if (sideStage.fullyShown) powerConnection.previousSideApp = applicationManager.sideStageFocusedApplication;
474 applicationManager.unfocusCurrentApplication();
475 } else {
476 if (powerConnection.previousMainApp) {
477 applicationManager.focusApplication(powerConnection.previousMainApp);
478 powerConnection.previousMainApp = null;
479 }
480 if (powerConnection.previousSideApp) {
481 applicationManager.focusApplication(powerConnection.previousSideApp);
482 powerConnection.previousSideApp = null;
483 }
484 }
485 }
487 onPowerStateChange: {
488 if (state == 0) { // suspend
489 powerConnection.setFocused(false);
490 greeter.show();
491 } else if (state == 1) { // active
492 powerConnection.setFocused(true);
493 }
494 }
495 }
497 Item {
498 id: overlay
500 anchors.fill: parent
502 Panel {
503 id: panel
504 anchors.fill: parent //because this draws indicator menus
505 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
506 indicators {
507 hides: [launcher]
508 }
509 fullscreenMode: shell.fullscreenMode
510 searchVisible: !greeter.shown && !lockscreen.shown
512 InputFilterArea {
513 anchors.fill: parent
514 blockInput: panel.indicators.shown
515 }
516 }
518 Hud {
519 id: hud
521 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
522 height: parent.height
524 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown
525 shown: false
526 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
527 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
529 Connections {
530 target: shell.applicationManager
531 onMainStageFocusedApplicationChanged: hud.hide()
532 onSideStageFocusedApplicationChanged: hud.hide()
533 }
535 InputFilterArea {
536 anchors.fill: parent
537 blockInput: hud.shown
538 }
539 }
541 Revealer {
542 id: hudRevealer
544 enabled: hud.shown
545 width: hud.width
546 anchors.left: hud.left
547 height: parent.height
548 target: hud.revealerTarget
549 closedValue: height
550 openedValue: 0
551 direction: Qt.RightToLeft
552 orientation: Qt.Vertical
553 handleSize: hud.handleHeight
554 onCloseClicked: target.hide()
555 }
557 Bottombar {
558 theHud: hud
559 anchors.fill: parent
560 enabled: !panel.indicators.shown
561 applicationIsOnForeground: applicationManager.mainStageFocusedApplication
562 || applicationManager.sideStageFocusedApplication
563 }
565 InputFilterArea {
566 blockInput: launcher.shown
567 anchors {
568 top: parent.top
569 bottom: parent.bottom
570 left: parent.left
571 }
572 width: launcher.width
573 }
575 Launcher {
576 id: launcher
578 anchors.top: parent.top
579 anchors.bottom: parent.bottom
580 width: parent.width
581 dragAreaWidth: shell.edgeSize
582 available: !greeter.shown || greeter.narrowMode
583 onDashItemSelected: {
584 greeter.hide()
585 // Animate if moving between application and dash
586 if (!stages.shown) {
587 dash.setCurrentScope("home.scope", true, false)
588 } else {
589 dash.setCurrentScope("home.scope", false, false)
590 }
591 stages.hide();
592 }
593 onDash: {
594 if (stages.shown) {
595 dash.setCurrentScope("applications.scope", true, false)
596 stages.hide();
597 launcher.hide();
598 }
599 }
600 onLauncherApplicationSelected:{
601 greeter.hide()
602 shell.activateApplication(desktopFile)
603 }
604 onShownChanged: {
605 if (shown) {
606 panel.indicators.hide()
607 hud.hide()
608 }
609 }
610 }
612 Notifications {
613 id: notifications
615 model: NotificationBackend.Model
616 anchors {
617 top: parent.top
618 right: parent.right
619 bottom: parent.bottom
620 leftMargin: units.gu(1)
621 rightMargin: units.gu(1)
622 topMargin: panel.panelHeight + units.gu(1)
623 }
624 states: [
625 State {
626 name: "narrow"
627 when: overlay.width <= units.gu(60)
628 AnchorChanges { target: notifications; anchors.left: parent.left }
629 },
630 State {
631 name: "wide"
632 when: overlay.width > units.gu(60)
633 AnchorChanges { target: notifications; anchors.left: undefined }
634 PropertyChanges { target: notifications; width: units.gu(38) }
635 }
636 ]
637 }
638 }
640 focus: true
642 InputFilterArea {
643 anchors {
644 top: parent.top
645 bottom: parent.bottom
646 left: parent.left
647 }
648 width: shell.edgeSize
649 blockInput: true
650 }
652 InputFilterArea {
653 anchors {
654 top: parent.top
655 bottom: parent.bottom
656 right: parent.right
657 }
658 width: shell.edgeSize
659 blockInput: true
660 }
662 Binding {
663 target: i18n
664 property: "domain"
665 value: "unity8"
666 }
668 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
669 MouseArea {
670 anchors.bottom: parent.bottom
671 anchors.left: parent.left
672 anchors.right: parent.right
673 height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
675 enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
676 }
678 Label {
679 anchors.fill: parent
680 visible: applicationManager.fake
681 text: "EARLY ALPHA\nNOT READY FOR USE"
682 color: "lightgrey"
683 opacity: 0.2
684 font.weight: Font.Black
685 horizontalAlignment: Text.AlignHCenter
686 verticalAlignment: Text.AlignVCenter
687 fontSizeMode: Text.Fit
688 font.pixelSize: height/2
689 rotation: -45
690 }