on 09ee339 help sign in
unity8/Shell.qml
679 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 Ubuntu.Application 0.1
19import Ubuntu.Components 0.1
20import Ubuntu.Gestures 0.1
21import LightDM 0.1 as LightDM
22import Powerd 0.1
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) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
41 height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : 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 VolumeControl {
119 id: volumeControl
120 }
122 Keys.onVolumeUpPressed: volumeControl.volumeUp()
123 Keys.onVolumeDownPressed: volumeControl.volumeDown()
125 Item {
126 id: underlay
127 objectName: "underlay"
128 anchors.fill: parent
130 // Whether the underlay is fully covered by opaque UI elements.
131 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
133 // Whether the user should see the topmost application surface (if there's one at all).
134 property bool applicationSurfaceShouldBeSeen:
135 (mainStage.applications && mainStage.applications.count > 0)
136 && (!stages.fullyHidden && !mainStage.usingScreenshots)
138 // NB! Application surfaces are stacked behing the shell one. So they can only be seen by the user
139 // through the translucent parts of the shell surface.
140 visible: !fullyCovered && !applicationSurfaceShouldBeSeen
142 Image {
143 id: backgroundImage
144 source: shell.background
145 sourceSize.width: parent.width
146 sourceSize.height: parent.height
147 anchors.fill: parent
148 }
150 Rectangle {
151 anchors.fill: parent
152 color: "black"
153 opacity: dash.disappearingAnimationProgress
154 }
156 Dash {
157 id: dash
158 objectName: "dash"
160 available: !greeter.shown && !lockscreen.shown
161 hides: [stages, launcher, panel.indicators]
162 shown: disappearingAnimationProgress !== 1.0
163 enabled: disappearingAnimationProgress === 0.0
164 // FIXME: unfocus all applications when going back to the dash
165 onEnabledChanged: {
166 if (enabled) {
167 shell.applicationManager.unfocusCurrentApplication()
168 }
169 }
171 anchors {
172 fill: parent
173 topMargin: panel.panelHeight
174 }
176 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
177 opacity: 1.0 - disappearingAnimationProgress
178 property real disappearingAnimationProgress: {
179 if (greeter.shown) {
180 return greeter.showProgress;
181 } else {
182 return stagesOuterContainer.showProgress;
183 }
184 }
186 // FIXME: only necessary because stagesOuterContainer.showProgress and
187 // greeterRevealer.animatedProgress are not animated
188 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
189 }
190 }
193 Item {
194 id: stagesOuterContainer
196 width: parent.width
197 height: parent.height
198 x: launcher.progress
199 Behavior on x {SmoothedAnimation{velocity: 600}}
201 property real showProgress:
202 MathLocal.clamp(1 - (x + stages.x) / shell.width, 0, 1)
204 Showable {
205 id: stages
206 objectName: "stages"
208 x: width
210 property bool fullyShown: shown && x == 0 && parent.x == 0
211 property bool fullyHidden: !shown && x == width
212 available: !greeter.shown
213 hides: [launcher, panel.indicators]
214 shown: false
215 opacity: 1.0
216 showAnimation: StandardAnimation { property: "x"; duration: 350; to: 0; easing.type: Easing.OutCubic }
217 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: width; easing.type: Easing.OutCubic }
219 width: parent.width
220 height: parent.height
222 // close the stages when no focused application remains
223 Connections {
224 target: shell.applicationManager
225 onMainStageFocusedApplicationChanged: stages.closeIfNoApplications()
226 onSideStageFocusedApplicationChanged: stages.closeIfNoApplications()
227 ignoreUnknownSignals: true
228 }
230 function closeIfNoApplications() {
231 if (!shell.applicationManager.mainStageFocusedApplication
232 && !shell.applicationManager.sideStageFocusedApplication
233 && shell.applicationManager.mainStageApplications.count == 0
234 && shell.applicationManager.sideStageApplications.count == 0) {
235 stages.hide();
236 }
237 }
239 // show the stages when an application gets the focus
240 Connections {
241 target: shell.applicationManager
242 onMainStageFocusedApplicationChanged: {
243 if (shell.applicationManager.mainStageFocusedApplication) {
244 mainStage.show();
245 stages.show();
246 }
247 }
248 onSideStageFocusedApplicationChanged: {
249 if (shell.applicationManager.sideStageFocusedApplication) {
250 sideStage.show();
251 stages.show();
252 }
253 }
254 ignoreUnknownSignals: true
255 }
258 Stage {
259 id: mainStage
261 anchors.fill: parent
262 fullyShown: stages.fullyShown
263 fullyHidden: stages.fullyHidden
264 shouldUseScreenshots: !fullyShown
265 rightEdgeEnabled: !sideStage.enabled
267 applicationManager: shell.applicationManager
268 rightEdgeDraggingAreaWidth: shell.edgeSize
269 normalApplicationY: shell.panelHeight
271 shown: true
272 function show() {
273 stages.show();
274 }
275 function hide() {
276 }
278 // FIXME: workaround the fact that focusing a main stage application
279 // raises its surface on top of all other surfaces including the ones
280 // that belong to side stage applications.
281 onFocusedApplicationChanged: {
282 if (focusedApplication && sideStage.focusedApplication && sideStage.fullyShown) {
283 shell.applicationManager.focusApplication(sideStage.focusedApplication);
284 }
285 }
286 }
288 SideStage {
289 id: sideStage
291 applicationManager: shell.applicationManager
292 rightEdgeDraggingAreaWidth: shell.edgeSize
293 normalApplicationY: shell.panelHeight
295 onShownChanged: {
296 if (!shown && mainStage.applications.count == 0) {
297 stages.hide();
298 }
299 }
300 // FIXME: when hiding the side stage, refocus the main stage
301 // application so that it goes in front of the side stage
302 // application and hides it
303 onFullyShownChanged: {
304 if (!fullyShown && stages.fullyShown && sideStage.focusedApplication != null) {
305 shell.applicationManager.focusApplication(mainStage.focusedApplication);
306 }
307 }
309 enabled: shell.width >= units.gu(60)
310 visible: enabled
311 fullyShown: stages.fullyShown && shown
312 && sideStage[sideStageRevealer.boundProperty] == sideStageRevealer.openedValue
313 shouldUseScreenshots: !fullyShown || mainStage.usingScreenshots || sideStageRevealer.pressed
315 available: !greeter.shown && !lockscreen.shown && enabled
316 hides: [launcher, panel.indicators]
317 shown: false
318 showAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.openedValue; easing.type: Easing.OutQuint }
319 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.closedValue; easing.type: Easing.OutQuint }
321 width: units.gu(40)
322 height: stages.height
323 handleExpanded: sideStageRevealer.pressed
324 }
326 Revealer {
327 id: sideStageRevealer
329 enabled: mainStage.applications.count > 0 && sideStage.applications.count > 0
330 && sideStage.available
331 direction: Qt.RightToLeft
332 openedValue: parent.width - sideStage.width
333 hintDisplacement: units.gu(3)
334 /* The size of the sidestage handle needs to be bigger than the
335 typical size used for edge detection otherwise it is really
336 hard to grab.
337 */
338 handleSize: sideStage.shown ? units.gu(4) : shell.edgeSize
339 closedValue: parent.width + sideStage.handleSizeCollapsed
340 target: sideStage
341 x: parent.width - width
342 width: sideStage.width + handleSize * 0.7
343 height: sideStage.height
344 orientation: Qt.Horizontal
345 }
347 DragHandle {
348 id: stagesDragHandle
350 anchors.top: parent.top
351 anchors.bottom: parent.bottom
352 anchors.right: parent.left
354 width: shell.edgeSize
355 direction: Direction.Leftwards
356 property bool haveApps: mainStage.applications.count > 0 || sideStage.applications.count > 0
358 maxTotalDragDistance: haveApps ? parent.width : parent.width * 0.7
359 // Make autocompletion impossible when !haveApps
360 edgeDragEvaluator.minDragDistance: haveApps ? maxTotalDragDistance * 0.1 : Number.MAX_VALUE
361 }
362 }
363 }
365 Lockscreen {
366 id: lockscreen
367 hides: [launcher, panel.indicators, hud]
368 shown: false
369 enabled: true
370 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
371 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
372 y: panel.panelHeight
373 x: required ? 0 : - width
374 width: parent.width
375 height: parent.height - panel.panelHeight
376 background: shell.background
378 onUnlocked: lockscreen.hide()
379 onCancel: greeter.show()
381 Component.onCompleted: {
382 if (LightDM.Users.count == 1) {
383 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
384 }
385 }
386 }
388 Connections {
389 target: LightDM.Greeter
391 onShowPrompt: {
392 if (LightDM.Users.count == 1) {
393 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
394 if (text == "PIN") {
395 lockscreen.alphaNumeric = false
396 } else {
397 lockscreen.alphaNumeric = true
398 }
399 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
400 lockscreen.show();
401 }
402 }
403 }
405 Greeter {
406 id: greeter
407 objectName: "greeter"
409 available: true
410 hides: [launcher, panel.indicators, hud]
411 shown: true
413 y: panel.panelHeight
414 width: parent.width
415 height: parent.height - panel.panelHeight
417 dragHandleWidth: shell.edgeSize
419 onShownChanged: {
420 if (shown) {
421 lockscreen.reset();
422 // If there is only one user, we start authenticating with that one here.
423 // If there are more users, the Greeter will handle that
424 if (LightDM.Users.count == 1) {
425 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
426 }
427 greeter.forceActiveFocus();
428 }
429 }
431 onUnlocked: greeter.hide()
432 onSelected: {
433 var bgPath = greeter.model.data(uid, LightDM.UserRoles.BackgroundPathRole)
434 shell.background = bgPath ? bgPath : default_background
435 }
437 onLeftTeaserPressedChanged: {
438 if (leftTeaserPressed) {
439 launcher.tease();
440 }
441 }
442 }
444 InputFilterArea {
445 anchors.fill: parent
446 blockInput: greeter.shown || lockscreen.shown
447 }
449 Connections {
450 id: powerConnection
451 target: Powerd
453 property var previousMainApp: null
454 property var previousSideApp: null
456 function setFocused(focused) {
457 if (!focused) {
458 // FIXME: *FocusedApplication are not updated when unfocused, hence the need to check whether
459 // the stage was actually shown
460 if (mainStage.fullyShown) powerConnection.previousMainApp = applicationManager.mainStageFocusedApplication;
461 if (sideStage.fullyShown) powerConnection.previousSideApp = applicationManager.sideStageFocusedApplication;
462 applicationManager.unfocusCurrentApplication();
463 } else {
464 if (powerConnection.previousMainApp) {
465 applicationManager.focusApplication(powerConnection.previousMainApp);
466 powerConnection.previousMainApp = null;
467 }
468 if (powerConnection.previousSideApp) {
469 applicationManager.focusApplication(powerConnection.previousSideApp);
470 powerConnection.previousSideApp = null;
471 }
472 }
473 }
475 onPowerStateChange: {
476 if (state == 0) { // suspend
477 powerConnection.setFocused(false);
478 greeter.show();
479 } else if (state == 1) { // active
480 powerConnection.setFocused(true);
481 }
482 }
483 }
485 Item {
486 id: overlay
488 anchors.fill: parent
490 Panel {
491 id: panel
492 anchors.fill: parent //because this draws indicator menus
493 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
494 indicators {
495 hides: [launcher]
496 }
497 fullscreenMode: shell.fullscreenMode
498 searchVisible: !greeter.shown && !lockscreen.shown
500 InputFilterArea {
501 anchors.fill: parent
502 blockInput: panel.indicators.shown
503 }
504 }
506 Hud {
507 id: hud
509 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
510 height: parent.height
512 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown
513 shown: false
514 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
515 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
517 Connections {
518 target: shell.applicationManager
519 onMainStageFocusedApplicationChanged: hud.hide()
520 onSideStageFocusedApplicationChanged: hud.hide()
521 }
523 InputFilterArea {
524 anchors.fill: parent
525 blockInput: hud.shown
526 }
527 }
529 Revealer {
530 id: hudRevealer
532 enabled: hud.shown
533 width: hud.width
534 anchors.left: hud.left
535 height: parent.height
536 target: hud.revealerTarget
537 closedValue: height
538 openedValue: 0
539 direction: Qt.RightToLeft
540 orientation: Qt.Vertical
541 handleSize: hud.handleHeight
542 onCloseClicked: target.hide()
543 }
545 Bottombar {
546 theHud: hud
547 anchors.fill: parent
548 enabled: !panel.indicators.shown
549 applicationIsOnForeground: applicationManager.mainStageFocusedApplication
550 || applicationManager.sideStageFocusedApplication
551 }
553 InputFilterArea {
554 blockInput: launcher.shown
555 anchors {
556 top: parent.top
557 bottom: parent.bottom
558 left: parent.left
559 }
560 width: launcher.width
561 }
563 Launcher {
564 id: launcher
566 anchors.top: parent.top
567 anchors.bottom: parent.bottom
568 width: parent.width
569 dragAreaWidth: shell.edgeSize
570 available: !greeter.shown || greeter.narrowMode
571 onDashItemSelected: {
572 greeter.hide()
573 // Animate if moving between application and dash
574 if (!stages.shown) {
575 dash.setCurrentScope("home.scope", true, false)
576 } else {
577 dash.setCurrentScope("home.scope", false, false)
578 }
579 stages.hide();
580 }
581 onDash: {
582 if (stages.shown) {
583 dash.setCurrentScope("applications.scope", true, false)
584 stages.hide();
585 launcher.hide();
586 }
587 }
588 onLauncherApplicationSelected:{
589 greeter.hide()
590 shell.activateApplication(desktopFile)
591 }
592 onShownChanged: {
593 if (shown) {
594 panel.indicators.hide()
595 hud.hide()
596 }
597 }
598 }
600 Notifications {
601 id: notifications
603 model: NotificationBackend.Model
604 anchors {
605 top: parent.top
606 right: parent.right
607 bottom: parent.bottom
608 leftMargin: units.gu(1)
609 rightMargin: units.gu(1)
610 topMargin: panel.panelHeight + units.gu(1)
611 }
612 states: [
613 State {
614 name: "narrow"
615 when: overlay.width <= units.gu(60)
616 AnchorChanges { target: notifications; anchors.left: parent.left }
617 },
618 State {
619 name: "wide"
620 when: overlay.width > units.gu(60)
621 AnchorChanges { target: notifications; anchors.left: undefined }
622 PropertyChanges { target: notifications; width: units.gu(38) }
623 }
624 ]
625 }
626 }
628 focus: true
630 InputFilterArea {
631 anchors {
632 top: parent.top
633 bottom: parent.bottom
634 left: parent.left
635 }
636 width: shell.edgeSize
637 blockInput: true
638 }
640 InputFilterArea {
641 anchors {
642 top: parent.top
643 bottom: parent.bottom
644 right: parent.right
645 }
646 width: shell.edgeSize
647 blockInput: true
648 }
650 Binding {
651 target: i18n
652 property: "domain"
653 value: "unity8"
654 }
656 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
657 MouseArea {
658 anchors.bottom: parent.bottom
659 anchors.left: parent.left
660 anchors.right: parent.right
661 height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
663 enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
664 }
666 Label {
667 anchors.centerIn: parent
668 visible: applicationManager.fake
669 text: "EARLY ALPHA\nNOT READY FOR USE"
670 color: "lightgrey"
671 opacity: 0.2
672 font.weight: Font.Black
673 horizontalAlignment: Text.AlignHCenter
674 verticalAlignment: Text.AlignVCenter
675 fontSizeMode: Text.Fit
676 rotation: -45
677 scale: Math.min(parent.width, parent.height) / width
678 }