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