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