on fa641c4 help sign in
unity8/Shell.qml
691 lines 24 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 "Dash"
24import "Greeter"
25import "Launcher"
26import "Panel"
27import "Hud"
28import "Components"
29import "Components/Math.js" as MathLocal
30import "Bottombar"
31import "SideStage"
32import "Notifications"
33import Unity.Notifications 1.0 as NotificationBackend
35FocusScope {
36 id: shell
38 // this is only here to select the width / height of the window if not running fullscreen
39 property bool tablet: false
40 width: tablet ? units.gu(160) : units.gu(40)
41 height: tablet ? units.gu(100) : units.gu(71)
43 property real edgeSize: units.gu(2)
44 property url default_background: shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg"
45 property url background: default_background
46 readonly property real panelHeight: panel.panelHeight
48 property bool dashShown: dash.shown
49 property bool stageScreenshotsReady: {
50 if (sideStage.shown) {
51 if (mainStage.applications.count > 0) {
52 return mainStage.usingScreenshots && sideStage.usingScreenshots;
53 } else {
54 return sideStage.usingScreenshots;
55 }
56 } else {
57 return mainStage.usingScreenshots;
58 }
59 }
61 property ListModel searchHistory: SearchHistoryModel {}
63 property var applicationManager: ApplicationManagerWrapper {}
65 Component.onCompleted: {
66 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
68 applicationManager.sideStageEnabled = Qt.binding(function() { return sideStage.enabled })
70 // FIXME: if application focused before shell starts, shell draws on top of it only.
71 // We should detect already running applications on shell start and bring them to the front.
72 applicationManager.unfocusCurrentApplication();
73 }
75 readonly property bool fullscreenMode: {
76 if (greeter.shown || lockscreen.shown) {
77 return false;
78 } else if (mainStage.usingScreenshots) { // Window Manager animating so want to re-evaluate fullscreen mode
79 return mainStage.switchingFromFullscreenToFullscreen;
80 } else if (applicationManager.mainStageFocusedApplication) {
81 return applicationManager.mainStageFocusedApplication.fullscreen;
82 } else {
83 return false;
84 }
85 }
87 Connections {
88 target: applicationManager
89 ignoreUnknownSignals: true
90 onFocusRequested: {
91 // TODO: this should be protected to only unlock for certain applications / certain usecases
92 // potentially only in connection with a notification
93 greeter.hide();
94 shell.activateApplication(desktopFile);
95 }
96 }
98 function activateApplication(desktopFile, argument) {
99 if (applicationManager) {
100 // For newly started applications, as it takes them time to draw their first frame
101 // we add a delay before we hide the animation screenshots to compensate.
102 var addDelay = !applicationManager.getApplicationFromDesktopFile(desktopFile);
104 var application;
105 application = applicationManager.activateApplication(desktopFile, argument);
106 if (application == null) {
107 return;
108 }
109 if (application.stage == ApplicationInfo.MainStage || !sideStage.enabled) {
110 mainStage.activateApplication(desktopFile, addDelay);
111 } else {
112 sideStage.activateApplication(desktopFile, addDelay);
113 }
114 stages.show();
115 }
116 }
118 GSettings {
119 id: backgroundSettings
120 schema.id: "org.gnome.desktop.background"
121 }
123 function updateImage() {
124 if (testImage.source == "")
125 return
127 if (testImage.status == Image.Ready) {
128 if (shell.width / shell.height <= testImage.sourceSize.width / testImage.sourceSize.height) {
129 backgroundImage.sourceSize.width = 0
130 backgroundImage.sourceSize.height = Qt.binding(function() { return shell.height })
131 } else {
132 backgroundImage.sourceSize.height = 0
133 backgroundImage.sourceSize.width = Qt.binding(function() { return shell.width })
134 }
136 shell.background = testImage.source
137 } else if (testImage.status == Image.Error) {
138 shell.background = shell.default_background
139 }
141 // unload testImage source to save memory
142 testImage.source = ""
143 }
145 Image {
146 id: testImage
148 property url gsettingsImage: backgroundSettings.pictureUri
150 source: gsettingsImage
151 visible: false
153 onGsettingsImageChanged: source = gsettingsImage
154 onStatusChanged: updateImage()
155 }
157 VolumeControl {
158 id: volumeControl
159 }
161 Keys.onVolumeUpPressed: volumeControl.volumeUp()
162 Keys.onVolumeDownPressed: volumeControl.volumeDown()
164 Keys.onReleased: {
165 if (event.key == Qt.Key_PowerOff) {
166 greeter.show()
167 }
168 }
170 Item {
171 id: underlay
172 objectName: "underlay"
173 anchors.fill: parent
175 // Whether the underlay is fully covered by opaque UI elements.
176 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
178 // Whether the user should see the topmost application surface (if there's one at all).
179 property bool applicationSurfaceShouldBeSeen:
180 (mainStage.applications && mainStage.applications.count > 0)
181 && (!stages.fullyHidden && !mainStage.usingScreenshots)
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 Image {
188 id: backgroundImage
189 source: shell.background
190 anchors.fill: parent
191 fillMode: Image.PreserveAspectCrop
192 }
194 Rectangle {
195 anchors.fill: parent
196 color: "black"
197 opacity: dash.disappearingAnimationProgress
198 }
200 Dash {
201 id: dash
202 objectName: "dash"
204 available: !greeter.shown && !lockscreen.shown
205 hides: [stages, launcher, panel.indicators]
206 shown: disappearingAnimationProgress !== 1.0
207 enabled: disappearingAnimationProgress === 0.0
208 // FIXME: unfocus all applications when going back to the dash
209 onEnabledChanged: {
210 if (enabled) {
211 shell.applicationManager.unfocusCurrentApplication()
212 }
213 }
215 anchors {
216 fill: parent
217 topMargin: panel.panelHeight
218 }
220 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
221 opacity: 1.0 - disappearingAnimationProgress
222 property real disappearingAnimationProgress: {
223 if (greeter.shown) {
224 return greeter.showProgress;
225 } else {
226 return stagesOuterContainer.showProgress;
227 }
228 }
230 // FIXME: only necessary because stagesOuterContainer.showProgress and
231 // greeterRevealer.animatedProgress are not animated
232 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
233 }
234 }
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 }
302 Stage {
303 id: mainStage
305 anchors.fill: parent
306 fullyShown: stages.fullyShown
307 fullyHidden: stages.fullyHidden
308 shouldUseScreenshots: !fullyShown
309 rightEdgeEnabled: !sideStage.enabled
311 applicationManager: shell.applicationManager
312 rightEdgeDraggingAreaWidth: shell.edgeSize
313 normalApplicationY: shell.panelHeight
315 shown: true
316 function show() {
317 stages.show();
318 }
319 function hide() {
320 }
322 // FIXME: workaround the fact that focusing a main stage application
323 // raises its surface on top of all other surfaces including the ones
324 // that belong to side stage applications.
325 onFocusedApplicationChanged: {
326 if (focusedApplication && sideStage.focusedApplication && sideStage.fullyShown) {
327 shell.applicationManager.focusApplication(sideStage.focusedApplication);
328 }
329 }
330 }
332 SideStage {
333 id: sideStage
335 applicationManager: shell.applicationManager
336 rightEdgeDraggingAreaWidth: shell.edgeSize
337 normalApplicationY: shell.panelHeight
339 onShownChanged: {
340 if (!shown && mainStage.applications.count == 0) {
341 stages.hide();
342 }
343 }
344 // FIXME: when hiding the side stage, refocus the main stage
345 // application so that it goes in front of the side stage
346 // application and hides it
347 onFullyShownChanged: {
348 if (!fullyShown && stages.fullyShown && sideStage.focusedApplication != null) {
349 shell.applicationManager.focusApplication(mainStage.focusedApplication);
350 }
351 }
353 enabled: shell.width >= units.gu(60)
354 visible: enabled
355 fullyShown: stages.fullyShown && shown
356 && sideStage[sideStageRevealer.boundProperty] == sideStageRevealer.openedValue
357 shouldUseScreenshots: !fullyShown || mainStage.usingScreenshots || sideStageRevealer.pressed
359 available: !greeter.shown && !lockscreen.shown && enabled
360 hides: [launcher, panel.indicators]
361 shown: false
362 showAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.openedValue; easing.type: Easing.OutQuint }
363 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.closedValue; easing.type: Easing.OutQuint }
365 width: units.gu(40)
366 height: stages.height
367 handleExpanded: sideStageRevealer.pressed
368 }
370 Revealer {
371 id: sideStageRevealer
373 enabled: mainStage.applications.count > 0 && sideStage.applications.count > 0
374 && sideStage.available
375 direction: Qt.RightToLeft
376 openedValue: parent.width - sideStage.width
377 hintDisplacement: units.gu(3)
378 /* The size of the sidestage handle needs to be bigger than the
379 typical size used for edge detection otherwise it is really
380 hard to grab.
381 */
382 handleSize: sideStage.shown ? units.gu(4) : shell.edgeSize
383 closedValue: parent.width + sideStage.handleSizeCollapsed
384 target: sideStage
385 x: parent.width - width
386 width: sideStage.width + handleSize * 0.7
387 height: sideStage.height
388 orientation: Qt.Horizontal
389 }
391 DragHandle {
392 id: stagesDragHandle
394 anchors.top: parent.top
395 anchors.bottom: parent.bottom
396 anchors.right: parent.left
398 width: shell.edgeSize
399 direction: Direction.Leftwards
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 }
429 }
430 }
432 Connections {
433 target: LightDM.Greeter
435 onShowPrompt: {
436 if (LightDM.Users.count == 1) {
437 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
438 if (text == "PIN") {
439 lockscreen.alphaNumeric = false
440 } else {
441 lockscreen.alphaNumeric = true
442 }
443 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
444 lockscreen.show();
445 }
446 }
447 }
449 Greeter {
450 id: greeter
451 objectName: "greeter"
453 available: true
454 hides: [launcher, panel.indicators, hud]
455 shown: true
457 y: panel.panelHeight
458 width: parent.width
459 height: parent.height - panel.panelHeight
461 dragHandleWidth: shell.edgeSize
463 property var previousMainApp: null
464 property var previousSideApp: null
466 onShownChanged: {
467 if (shown) {
468 lockscreen.reset();
469 // If there is only one user, we start authenticating with that one here.
470 // If there are more users, the Greeter will handle that
471 if (LightDM.Users.count == 1) {
472 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
473 }
474 greeter.forceActiveFocus();
475 // FIXME: *FocusedApplication are not updated when unfocused, hence the need to check whether
476 // the stage was actually shown
477 if (mainStage.fullyShown) greeter.previousMainApp = applicationManager.mainStageFocusedApplication;
478 if (sideStage.fullyShown) greeter.previousSideApp = applicationManager.sideStageFocusedApplication;
479 applicationManager.unfocusCurrentApplication();
480 } else {
481 if (greeter.previousMainApp) {
482 applicationManager.focusApplication(greeter.previousMainApp);
483 greeter.previousMainApp = null;
484 }
485 if (greeter.previousSideApp) {
486 applicationManager.focusApplication(greeter.previousSideApp);
487 greeter.previousSideApp = null;
488 }
489 }
490 }
492 onUnlocked: greeter.hide()
493 onSelected: {
494 var bgPath = greeter.model.data(uid, LightDM.UserRoles.BackgroundPathRole)
495 shell.background = bgPath ? bgPath : default_background
496 }
498 onLeftTeaserPressedChanged: {
499 if (leftTeaserPressed) {
500 launcher.tease();
501 }
502 }
503 }
505 InputFilterArea {
506 anchors.fill: parent
507 blockInput: greeter.shown || lockscreen.shown
508 }
510 Item {
511 id: overlay
513 anchors.fill: parent
515 Panel {
516 id: panel
517 anchors.fill: parent //because this draws indicator menus
518 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
519 indicators {
520 hides: [launcher]
521 }
522 fullscreenMode: shell.fullscreenMode
523 searchVisible: !greeter.shown && !lockscreen.shown
525 InputFilterArea {
526 anchors.fill: parent
527 blockInput: panel.indicators.shown
528 }
529 }
531 Hud {
532 id: hud
534 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
535 height: parent.height
537 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown
538 shown: false
539 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
540 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
542 Connections {
543 target: shell.applicationManager
544 onMainStageFocusedApplicationChanged: hud.hide()
545 onSideStageFocusedApplicationChanged: hud.hide()
546 }
548 InputFilterArea {
549 anchors.fill: parent
550 blockInput: hud.shown
551 }
552 }
554 Revealer {
555 id: hudRevealer
557 enabled: hud.shown
558 width: hud.width
559 anchors.left: hud.left
560 height: parent.height
561 target: hud.revealerTarget
562 closedValue: height
563 openedValue: 0
564 direction: Qt.RightToLeft
565 orientation: Qt.Vertical
566 handleSize: hud.handleHeight
567 onCloseClicked: target.hide()
568 }
570 Bottombar {
571 theHud: hud
572 anchors.fill: parent
573 enabled: !panel.indicators.shown
574 applicationIsOnForeground: applicationManager.mainStageFocusedApplication
575 || applicationManager.sideStageFocusedApplication
576 }
578 InputFilterArea {
579 blockInput: launcher.shown
580 anchors {
581 top: parent.top
582 bottom: parent.bottom
583 left: parent.left
584 }
585 width: launcher.width
586 }
588 Launcher {
589 id: launcher
590 objectName: "launcher"
592 anchors.top: parent.top
593 anchors.bottom: parent.bottom
594 width: parent.width
595 dragAreaWidth: shell.edgeSize
596 available: !greeter.locked
597 onDashItemSelected: {
598 greeter.hide()
599 // Animate if moving between application and dash
600 if (!stages.shown) {
601 dash.setCurrentScope("home.scope", true, false)
602 } else {
603 dash.setCurrentScope("home.scope", false, false)
604 }
605 stages.hide();
606 }
607 onDash: {
608 if (stages.shown) {
609 dash.setCurrentScope("applications.scope", true /* animate */, true /* reset */)
610 stages.hide();
611 launcher.hide();
612 }
613 }
614 onLauncherApplicationSelected:{
615 greeter.hide()
616 shell.activateApplication(desktopFile)
617 }
618 onShownChanged: {
619 if (shown) {
620 panel.indicators.hide()
621 hud.hide()
622 }
623 }
624 }
626 Notifications {
627 id: notifications
629 model: NotificationBackend.Model
630 anchors {
631 top: parent.top
632 right: parent.right
633 bottom: parent.bottom
634 leftMargin: units.gu(1)
635 rightMargin: units.gu(1)
636 topMargin: panel.panelHeight + units.gu(1)
637 }
638 states: [
639 State {
640 name: "narrow"
641 when: overlay.width <= units.gu(60)
642 AnchorChanges { target: notifications; anchors.left: parent.left }
643 },
644 State {
645 name: "wide"
646 when: overlay.width > units.gu(60)
647 AnchorChanges { target: notifications; anchors.left: undefined }
648 PropertyChanges { target: notifications; width: units.gu(38) }
649 }
650 ]
651 }
652 }
654 focus: true
656 InputFilterArea {
657 anchors {
658 top: parent.top
659 bottom: parent.bottom
660 left: parent.left
661 }
662 width: shell.edgeSize
663 blockInput: true
664 }
666 InputFilterArea {
667 anchors {
668 top: parent.top
669 bottom: parent.bottom
670 right: parent.right
671 }
672 width: shell.edgeSize
673 blockInput: true
674 }
676 Binding {
677 target: i18n
678 property: "domain"
679 value: "unity8"
680 }
682 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
683 MouseArea {
684 anchors.bottom: parent.bottom
685 anchors.left: parent.left
686 anchors.right: parent.right
687 height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
689 enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
690 }