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