on ccedc8a help sign in
unity8/Shell.qml
660 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 defaultBackground: shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg"
45 property url background: backgroundSettings.pictureUri
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 VolumeControl {
124 id: volumeControl
125 }
127 Keys.onVolumeUpPressed: volumeControl.volumeUp()
128 Keys.onVolumeDownPressed: volumeControl.volumeDown()
130 Keys.onReleased: {
131 if (event.key == Qt.Key_PowerOff) {
132 greeter.show()
133 }
134 }
136 Item {
137 id: underlay
138 objectName: "underlay"
139 anchors.fill: parent
141 // Whether the underlay is fully covered by opaque UI elements.
142 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
144 // Whether the user should see the topmost application surface (if there's one at all).
145 property bool applicationSurfaceShouldBeSeen:
146 (mainStage.applications && mainStage.applications.count > 0)
147 && (!stages.fullyHidden && !mainStage.usingScreenshots)
149 // NB! Application surfaces are stacked behing the shell one. So they can only be seen by the user
150 // through the translucent parts of the shell surface.
151 visible: !fullyCovered && !applicationSurfaceShouldBeSeen
153 CrossFadeImage {
154 id: backgroundImage
155 objectName: "backgroundImage"
156 source: shell.background
157 anchors.fill: parent
158 onStatusChanged: {
159 if (status == Image.Error) {
160 backgroundSettings.pictureUri = shell.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
420 objectName: "greeter"
422 available: true
423 hides: [launcher, panel.indicators, hud]
424 shown: true
426 y: panel.panelHeight
427 width: parent.width
428 height: parent.height - panel.panelHeight
430 dragHandleWidth: shell.edgeSize
432 property var previousMainApp: null
433 property var previousSideApp: null
435 onShownChanged: {
436 if (shown) {
437 lockscreen.reset();
438 // If there is only one user, we start authenticating with that one here.
439 // If there are more users, the Greeter will handle that
440 if (LightDM.Users.count == 1) {
441 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
442 }
443 greeter.forceActiveFocus();
444 // FIXME: *FocusedApplication are not updated when unfocused, hence the need to check whether
445 // the stage was actually shown
446 if (mainStage.fullyShown) greeter.previousMainApp = applicationManager.mainStageFocusedApplication;
447 if (sideStage.fullyShown) greeter.previousSideApp = applicationManager.sideStageFocusedApplication;
448 applicationManager.unfocusCurrentApplication();
449 } else {
450 if (greeter.previousMainApp) {
451 applicationManager.focusApplication(greeter.previousMainApp);
452 greeter.previousMainApp = null;
453 }
454 if (greeter.previousSideApp) {
455 applicationManager.focusApplication(greeter.previousSideApp);
456 greeter.previousSideApp = null;
457 }
458 }
459 }
461 onUnlocked: greeter.hide()
462 onSelected: {
463 var bgPath = greeter.model.data(uid, LightDM.UserRoles.BackgroundPathRole)
464 shell.background = bgPath ? bgPath : defaultBackground
465 }
467 onLeftTeaserPressedChanged: {
468 if (leftTeaserPressed) {
469 launcher.tease();
470 }
471 }
472 }
474 InputFilterArea {
475 anchors.fill: parent
476 blockInput: greeter.shown || lockscreen.shown
477 }
479 Item {
480 id: overlay
482 anchors.fill: parent
484 Panel {
485 id: panel
486 anchors.fill: parent //because this draws indicator menus
487 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
488 indicators {
489 hides: [launcher]
490 }
491 fullscreenMode: shell.fullscreenMode
492 searchVisible: !greeter.shown && !lockscreen.shown
494 InputFilterArea {
495 anchors.fill: parent
496 blockInput: panel.indicators.shown
497 }
498 }
500 Hud {
501 id: hud
503 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
504 height: parent.height
506 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown
507 shown: false
508 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
509 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
511 Connections {
512 target: shell.applicationManager
513 onMainStageFocusedApplicationChanged: hud.hide()
514 onSideStageFocusedApplicationChanged: hud.hide()
515 }
517 InputFilterArea {
518 anchors.fill: parent
519 blockInput: hud.shown
520 }
521 }
523 Revealer {
524 id: hudRevealer
526 enabled: hud.shown
527 width: hud.width
528 anchors.left: hud.left
529 height: parent.height
530 target: hud.revealerTarget
531 closedValue: height
532 openedValue: 0
533 direction: Qt.RightToLeft
534 orientation: Qt.Vertical
535 handleSize: hud.handleHeight
536 onCloseClicked: target.hide()
537 }
539 Bottombar {
540 theHud: hud
541 anchors.fill: parent
542 enabled: !panel.indicators.shown
543 applicationIsOnForeground: applicationManager.mainStageFocusedApplication
544 || applicationManager.sideStageFocusedApplication
545 }
547 InputFilterArea {
548 blockInput: launcher.shown
549 anchors {
550 top: parent.top
551 bottom: parent.bottom
552 left: parent.left
553 }
554 width: launcher.width
555 }
557 Launcher {
558 id: launcher
559 objectName: "launcher"
561 anchors.top: parent.top
562 anchors.bottom: parent.bottom
563 width: parent.width
564 dragAreaWidth: shell.edgeSize
565 available: !greeter.locked
566 onDashItemSelected: {
567 greeter.hide()
568 // Animate if moving between application and dash
569 if (!stages.shown) {
570 dash.setCurrentScope("home.scope", true, false)
571 } else {
572 dash.setCurrentScope("home.scope", false, false)
573 }
574 stages.hide();
575 }
576 onDash: {
577 if (stages.shown) {
578 dash.setCurrentScope("applications.scope", true /* animate */, true /* reset */)
579 stages.hide();
580 launcher.hide();
581 }
582 }
583 onLauncherApplicationSelected:{
584 greeter.hide()
585 shell.activateApplication(desktopFile)
586 }
587 onShownChanged: {
588 if (shown) {
589 panel.indicators.hide()
590 hud.hide()
591 }
592 }
593 }
595 Notifications {
596 id: notifications
598 model: NotificationBackend.Model
599 anchors {
600 top: parent.top
601 right: parent.right
602 bottom: parent.bottom
603 leftMargin: units.gu(1)
604 rightMargin: units.gu(1)
605 topMargin: panel.panelHeight + units.gu(1)
606 }
607 states: [
608 State {
609 name: "narrow"
610 when: overlay.width <= units.gu(60)
611 AnchorChanges { target: notifications; anchors.left: parent.left }
612 },
613 State {
614 name: "wide"
615 when: overlay.width > units.gu(60)
616 AnchorChanges { target: notifications; anchors.left: undefined }
617 PropertyChanges { target: notifications; width: units.gu(38) }
618 }
619 ]
620 }
621 }
623 focus: true
625 InputFilterArea {
626 anchors {
627 top: parent.top
628 bottom: parent.bottom
629 left: parent.left
630 }
631 width: shell.edgeSize
632 blockInput: true
633 }
635 InputFilterArea {
636 anchors {
637 top: parent.top
638 bottom: parent.bottom
639 right: parent.right
640 }
641 width: shell.edgeSize
642 blockInput: true
643 }
645 Binding {
646 target: i18n
647 property: "domain"
648 value: "unity8"
649 }
651 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
652 MouseArea {
653 anchors.bottom: parent.bottom
654 anchors.left: parent.left
655 anchors.right: parent.right
656 height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
658 enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
659 }