on 0bfda68 help sign in
unity8/Shell.qml
678 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
408 available: true
409 hides: [launcher, panel.indicators, hud]
410 shown: true
412 y: panel.panelHeight
413 width: parent.width
414 height: parent.height - panel.panelHeight
416 dragHandleWidth: shell.edgeSize
418 onShownChanged: {
419 if (shown) {
420 lockscreen.reset();
421 // If there is only one user, we start authenticating with that one here.
422 // If there are more users, the Greeter will handle that
423 if (LightDM.Users.count == 1) {
424 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
425 }
426 greeter.forceActiveFocus();
427 }
428 }
430 onUnlocked: greeter.hide()
431 onSelected: {
432 var bgPath = greeter.model.data(uid, LightDM.UserRoles.BackgroundPathRole)
433 shell.background = bgPath ? bgPath : default_background
434 }
436 onLeftTeaserPressedChanged: {
437 if (leftTeaserPressed) {
438 launcher.tease();
439 }
440 }
441 }
443 InputFilterArea {
444 anchors.fill: parent
445 blockInput: greeter.shown || lockscreen.shown
446 }
448 Connections {
449 id: powerConnection
450 target: Powerd
452 property var previousMainApp: null
453 property var previousSideApp: null
455 function setFocused(focused) {
456 if (!focused) {
457 // FIXME: *FocusedApplication are not updated when unfocused, hence the need to check whether
458 // the stage was actually shown
459 if (mainStage.fullyShown) powerConnection.previousMainApp = applicationManager.mainStageFocusedApplication;
460 if (sideStage.fullyShown) powerConnection.previousSideApp = applicationManager.sideStageFocusedApplication;
461 applicationManager.unfocusCurrentApplication();
462 } else {
463 if (powerConnection.previousMainApp) {
464 applicationManager.focusApplication(powerConnection.previousMainApp);
465 powerConnection.previousMainApp = null;
466 }
467 if (powerConnection.previousSideApp) {
468 applicationManager.focusApplication(powerConnection.previousSideApp);
469 powerConnection.previousSideApp = null;
470 }
471 }
472 }
474 onPowerStateChange: {
475 if (state == 0) { // suspend
476 powerConnection.setFocused(false);
477 greeter.show();
478 } else if (state == 1) { // active
479 powerConnection.setFocused(true);
480 }
481 }
482 }
484 Item {
485 id: overlay
487 anchors.fill: parent
489 Panel {
490 id: panel
491 anchors.fill: parent //because this draws indicator menus
492 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
493 indicators {
494 hides: [launcher]
495 }
496 fullscreenMode: shell.fullscreenMode
497 searchVisible: !greeter.shown && !lockscreen.shown
499 InputFilterArea {
500 anchors.fill: parent
501 blockInput: panel.indicators.shown
502 }
503 }
505 Hud {
506 id: hud
508 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
509 height: parent.height
511 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown
512 shown: false
513 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
514 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
516 Connections {
517 target: shell.applicationManager
518 onMainStageFocusedApplicationChanged: hud.hide()
519 onSideStageFocusedApplicationChanged: hud.hide()
520 }
522 InputFilterArea {
523 anchors.fill: parent
524 blockInput: hud.shown
525 }
526 }
528 Revealer {
529 id: hudRevealer
531 enabled: hud.shown
532 width: hud.width
533 anchors.left: hud.left
534 height: parent.height
535 target: hud.revealerTarget
536 closedValue: height
537 openedValue: 0
538 direction: Qt.RightToLeft
539 orientation: Qt.Vertical
540 handleSize: hud.handleHeight
541 onCloseClicked: target.hide()
542 }
544 Bottombar {
545 theHud: hud
546 anchors.fill: parent
547 enabled: !panel.indicators.shown
548 applicationIsOnForeground: applicationManager.mainStageFocusedApplication
549 || applicationManager.sideStageFocusedApplication
550 }
552 InputFilterArea {
553 blockInput: launcher.shown
554 anchors {
555 top: parent.top
556 bottom: parent.bottom
557 left: parent.left
558 }
559 width: launcher.width
560 }
562 Launcher {
563 id: launcher
565 anchors.top: parent.top
566 anchors.bottom: parent.bottom
567 width: parent.width
568 dragAreaWidth: shell.edgeSize
569 available: !greeter.shown || greeter.narrowMode
570 onDashItemSelected: {
571 greeter.hide()
572 // Animate if moving between application and dash
573 if (!stages.shown) {
574 dash.setCurrentScope("home.scope", true, false)
575 } else {
576 dash.setCurrentScope("home.scope", false, false)
577 }
578 stages.hide();
579 }
580 onDash: {
581 if (stages.shown) {
582 dash.setCurrentScope("applications.scope", true, false)
583 stages.hide();
584 launcher.hide();
585 }
586 }
587 onLauncherApplicationSelected:{
588 greeter.hide()
589 shell.activateApplication(desktopFile)
590 }
591 onShownChanged: {
592 if (shown) {
593 panel.indicators.hide()
594 hud.hide()
595 }
596 }
597 }
599 Notifications {
600 id: notifications
602 model: NotificationBackend.Model
603 anchors {
604 top: parent.top
605 right: parent.right
606 bottom: parent.bottom
607 leftMargin: units.gu(1)
608 rightMargin: units.gu(1)
609 topMargin: panel.panelHeight + units.gu(1)
610 }
611 states: [
612 State {
613 name: "narrow"
614 when: overlay.width <= units.gu(60)
615 AnchorChanges { target: notifications; anchors.left: parent.left }
616 },
617 State {
618 name: "wide"
619 when: overlay.width > units.gu(60)
620 AnchorChanges { target: notifications; anchors.left: undefined }
621 PropertyChanges { target: notifications; width: units.gu(38) }
622 }
623 ]
624 }
625 }
627 focus: true
629 InputFilterArea {
630 anchors {
631 top: parent.top
632 bottom: parent.bottom
633 left: parent.left
634 }
635 width: shell.edgeSize
636 blockInput: true
637 }
639 InputFilterArea {
640 anchors {
641 top: parent.top
642 bottom: parent.bottom
643 right: parent.right
644 }
645 width: shell.edgeSize
646 blockInput: true
647 }
649 Binding {
650 target: i18n
651 property: "domain"
652 value: "unity8"
653 }
655 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
656 MouseArea {
657 anchors.bottom: parent.bottom
658 anchors.left: parent.left
659 anchors.right: parent.right
660 height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
662 enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
663 }
665 Label {
666 anchors.fill: parent
667 visible: applicationManager.fake
668 text: "EARLY ALPHA\nNOT READY FOR USE"
669 color: "lightgrey"
670 opacity: 0.2
671 font.weight: Font.Black
672 horizontalAlignment: Text.AlignHCenter
673 verticalAlignment: Text.AlignVCenter
674 fontSizeMode: Text.Fit
675 font.pixelSize: height/2
676 rotation: -45
677 }