on c348b71 help sign in
unity8/Shell.qml
704 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 != undefined && 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
436 objectName: "greeter"
438 available: true
439 hides: [launcher, panel.indicators, hud]
440 shown: true
442 y: panel.panelHeight
443 width: parent.width
444 height: parent.height - panel.panelHeight
446 dragHandleWidth: shell.edgeSize
448 onShownChanged: {
449 if (shown) {
450 lockscreen.reset();
451 // If there is only one user, we start authenticating with that one here.
452 // If there are more users, the Greeter will handle that
453 if (LightDM.Users.count == 1) {
454 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
455 }
456 greeter.forceActiveFocus();
457 }
458 }
460 onUnlocked: greeter.hide()
462 onLeftTeaserPressedChanged: {
463 if (leftTeaserPressed) {
464 launcher.tease();
465 }
466 }
467 }
469 InputFilterArea {
470 anchors.fill: parent
471 blockInput: greeter.shown || lockscreen.shown
472 }
474 Connections {
475 id: powerConnection
476 target: Powerd
478 property var previousMainApp: null
479 property var previousSideApp: null
481 function setFocused(focused) {
482 if (!focused) {
483 // FIXME: *FocusedApplication are not updated when unfocused, hence the need to check whether
484 // the stage was actually shown
485 if (mainStage.fullyShown) powerConnection.previousMainApp = applicationManager.mainStageFocusedApplication;
486 if (sideStage.fullyShown) powerConnection.previousSideApp = applicationManager.sideStageFocusedApplication;
487 applicationManager.unfocusCurrentApplication();
488 } else {
489 if (powerConnection.previousMainApp) {
490 applicationManager.focusApplication(powerConnection.previousMainApp);
491 powerConnection.previousMainApp = null;
492 }
493 if (powerConnection.previousSideApp) {
494 applicationManager.focusApplication(powerConnection.previousSideApp);
495 powerConnection.previousSideApp = null;
496 }
497 }
498 }
500 onPowerStateChange: {
501 if (state == 0) { // suspend
502 powerConnection.setFocused(false);
503 greeter.show();
504 } else if (state == 1) { // active
505 powerConnection.setFocused(true);
506 }
507 }
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
591 anchors.top: parent.top
592 anchors.bottom: parent.bottom
593 width: parent.width
594 dragAreaWidth: shell.edgeSize
595 available: !greeter.shown || greeter.narrowMode
596 onDashItemSelected: {
597 greeter.hide()
598 // Animate if moving between application and dash
599 if (!stages.shown) {
600 dash.setCurrentScope("home.scope", true, false)
601 } else {
602 dash.setCurrentScope("home.scope", false, false)
603 }
604 stages.hide();
605 }
606 onDash: {
607 if (stages.shown) {
608 dash.setCurrentScope("applications.scope", true, false)
609 stages.hide();
610 launcher.hide();
611 }
612 }
613 onLauncherApplicationSelected:{
614 greeter.hide()
615 shell.activateApplication(desktopFile)
616 }
617 onShownChanged: {
618 if (shown) {
619 panel.indicators.hide()
620 hud.hide()
621 }
622 }
623 }
625 Notifications {
626 id: notifications
628 model: NotificationBackend.Model
629 anchors {
630 top: parent.top
631 right: parent.right
632 bottom: parent.bottom
633 leftMargin: units.gu(1)
634 rightMargin: units.gu(1)
635 topMargin: panel.panelHeight + units.gu(1)
636 }
637 states: [
638 State {
639 name: "narrow"
640 when: overlay.width <= units.gu(60)
641 AnchorChanges { target: notifications; anchors.left: parent.left }
642 },
643 State {
644 name: "wide"
645 when: overlay.width > units.gu(60)
646 AnchorChanges { target: notifications; anchors.left: undefined }
647 PropertyChanges { target: notifications; width: units.gu(38) }
648 }
649 ]
650 }
651 }
653 focus: true
655 InputFilterArea {
656 anchors {
657 top: parent.top
658 bottom: parent.bottom
659 left: parent.left
660 }
661 width: shell.edgeSize
662 blockInput: true
663 }
665 InputFilterArea {
666 anchors {
667 top: parent.top
668 bottom: parent.bottom
669 right: parent.right
670 }
671 width: shell.edgeSize
672 blockInput: true
673 }
675 Binding {
676 target: i18n
677 property: "domain"
678 value: "unity8"
679 }
681 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
682 MouseArea {
683 anchors.bottom: parent.bottom
684 anchors.left: parent.left
685 anchors.right: parent.right
686 height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
688 enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
689 }
691 Label {
692 anchors.centerIn: parent
693 visible: applicationManager.fake
694 text: "EARLY ALPHA\nNOT READY FOR USE"
695 color: "lightgrey"
696 opacity: 0.2
697 font.weight: Font.Black
698 horizontalAlignment: Text.AlignHCenter
699 verticalAlignment: Text.AlignVCenter
700 fontSizeMode: Text.Fit
701 rotation: -45
702 scale: Math.min(parent.width, parent.height) / width
703 }