on dd84c49 help sign in
unity8/Shell.qml
652 lines 23 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 "Dash"
23import "Greeter"
24import "Launcher"
25import "Panel"
26import "Hud"
27import "Components"
28import "Components/Math.js" as MathLocal
29import "Bottombar"
30import "SideStage"
31import "Notifications"
32import Unity.Notifications 1.0 as NotificationBackend
34FocusScope {
35 id: shell
37 // this is only here to select the width / height of the window if not running fullscreen
38 property bool tablet: false
39 width: tablet ? units.gu(160) : units.gu(40)
40 height: tablet ? units.gu(100) : units.gu(71)
42 property real edgeSize: units.gu(2)
43 property url default_background: shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg"
44 property url background: default_background
45 readonly property real panelHeight: panel.panelHeight
47 property bool dashShown: dash.shown
48 property bool stageScreenshotsReady: {
49 if (sideStage.shown) {
50 if (mainStage.applications.count > 0) {
51 return mainStage.usingScreenshots && sideStage.usingScreenshots;
52 } else {
53 return sideStage.usingScreenshots;
54 }
55 } else {
56 return mainStage.usingScreenshots;
57 }
58 }
60 property ListModel searchHistory: SearchHistoryModel {}
62 property var applicationManager: ApplicationManagerWrapper {}
64 Component.onCompleted: {
65 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
67 applicationManager.sideStageEnabled = Qt.binding(function() { return sideStage.enabled })
69 // FIXME: if application focused before shell starts, shell draws on top of it only.
70 // We should detect already running applications on shell start and bring them to the front.
71 applicationManager.unfocusCurrentApplication();
72 }
74 readonly property bool fullscreenMode: {
75 if (greeter.shown || lockscreen.shown) {
76 return false;
77 } else if (mainStage.usingScreenshots) { // Window Manager animating so want to re-evaluate fullscreen mode
78 return mainStage.switchingFromFullscreenToFullscreen;
79 } else if (applicationManager.mainStageFocusedApplication) {
80 return applicationManager.mainStageFocusedApplication.fullscreen;
81 } else {
82 return false;
83 }
84 }
86 Connections {
87 target: applicationManager
88 ignoreUnknownSignals: true
89 onFocusRequested: {
90 // TODO: this should be protected to only unlock for certain applications / certain usecases
91 // potentially only in connection with a notification
92 greeter.hide();
93 shell.activateApplication(desktopFile);
94 }
95 }
97 function activateApplication(desktopFile, argument) {
98 if (applicationManager) {
99 // For newly started applications, as it takes them time to draw their first frame
100 // we add a delay before we hide the animation screenshots to compensate.
101 var addDelay = !applicationManager.getApplicationFromDesktopFile(desktopFile);
103 var application;
104 application = applicationManager.activateApplication(desktopFile, argument);
105 if (application == null) {
106 return;
107 }
108 if (application.stage == ApplicationInfo.MainStage || !sideStage.enabled) {
109 mainStage.activateApplication(desktopFile, addDelay);
110 } else {
111 sideStage.activateApplication(desktopFile, addDelay);
112 }
113 stages.show();
114 }
115 }
117 VolumeControl {
118 id: volumeControl
119 }
121 Keys.onVolumeUpPressed: volumeControl.volumeUp()
122 Keys.onVolumeDownPressed: volumeControl.volumeDown()
124 Keys.onReleased: {
125 if (event.key == Qt.Key_PowerOff) {
126 greeter.show()
127 }
128 }
130 Item {
131 id: underlay
132 objectName: "underlay"
133 anchors.fill: parent
135 // Whether the underlay is fully covered by opaque UI elements.
136 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
138 // Whether the user should see the topmost application surface (if there's one at all).
139 property bool applicationSurfaceShouldBeSeen:
140 (mainStage.applications && mainStage.applications.count > 0)
141 && (!stages.fullyHidden && !mainStage.usingScreenshots)
143 // NB! Application surfaces are stacked behing the shell one. So they can only be seen by the user
144 // through the translucent parts of the shell surface.
145 visible: !fullyCovered && !applicationSurfaceShouldBeSeen
147 Image {
148 id: backgroundImage
149 source: shell.background
150 sourceSize.width: parent.width
151 sourceSize.height: parent.height
152 anchors.fill: parent
153 }
155 Rectangle {
156 anchors.fill: parent
157 color: "black"
158 opacity: dash.disappearingAnimationProgress
159 }
161 Dash {
162 id: dash
163 objectName: "dash"
165 available: !greeter.shown && !lockscreen.shown
166 hides: [stages, launcher, panel.indicators]
167 shown: disappearingAnimationProgress !== 1.0
168 enabled: disappearingAnimationProgress === 0.0
169 // FIXME: unfocus all applications when going back to the dash
170 onEnabledChanged: {
171 if (enabled) {
172 shell.applicationManager.unfocusCurrentApplication()
173 }
174 }
176 anchors {
177 fill: parent
178 topMargin: panel.panelHeight
179 }
181 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
182 opacity: 1.0 - disappearingAnimationProgress
183 property real disappearingAnimationProgress: {
184 if (greeter.shown) {
185 return greeter.showProgress;
186 } else {
187 return stagesOuterContainer.showProgress;
188 }
189 }
191 // FIXME: only necessary because stagesOuterContainer.showProgress and
192 // greeterRevealer.animatedProgress are not animated
193 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
194 }
195 }
198 Item {
199 id: stagesOuterContainer
201 width: parent.width
202 height: parent.height
203 x: launcher.progress
204 Behavior on x {SmoothedAnimation{velocity: 600}}
206 property real showProgress:
207 MathLocal.clamp(1 - (x + stages.x) / shell.width, 0, 1)
209 Showable {
210 id: stages
211 objectName: "stages"
213 x: width
215 property bool fullyShown: shown && x == 0 && parent.x == 0
216 property bool fullyHidden: !shown && x == width
217 available: !greeter.shown
218 hides: [launcher, panel.indicators]
219 shown: false
220 opacity: 1.0
221 showAnimation: StandardAnimation { property: "x"; duration: 350; to: 0; easing.type: Easing.OutCubic }
222 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: width; easing.type: Easing.OutCubic }
224 width: parent.width
225 height: parent.height
227 // close the stages when no focused application remains
228 Connections {
229 target: shell.applicationManager
230 onMainStageFocusedApplicationChanged: stages.closeIfNoApplications()
231 onSideStageFocusedApplicationChanged: stages.closeIfNoApplications()
232 ignoreUnknownSignals: true
233 }
235 function closeIfNoApplications() {
236 if (!shell.applicationManager.mainStageFocusedApplication
237 && !shell.applicationManager.sideStageFocusedApplication
238 && shell.applicationManager.mainStageApplications.count == 0
239 && shell.applicationManager.sideStageApplications.count == 0) {
240 stages.hide();
241 }
242 }
244 // show the stages when an application gets the focus
245 Connections {
246 target: shell.applicationManager
247 onMainStageFocusedApplicationChanged: {
248 if (shell.applicationManager.mainStageFocusedApplication) {
249 mainStage.show();
250 stages.show();
251 }
252 }
253 onSideStageFocusedApplicationChanged: {
254 if (shell.applicationManager.sideStageFocusedApplication) {
255 sideStage.show();
256 stages.show();
257 }
258 }
259 ignoreUnknownSignals: true
260 }
263 Stage {
264 id: mainStage
266 anchors.fill: parent
267 fullyShown: stages.fullyShown
268 fullyHidden: stages.fullyHidden
269 shouldUseScreenshots: !fullyShown
270 rightEdgeEnabled: !sideStage.enabled
272 applicationManager: shell.applicationManager
273 rightEdgeDraggingAreaWidth: shell.edgeSize
274 normalApplicationY: shell.panelHeight
276 shown: true
277 function show() {
278 stages.show();
279 }
280 function hide() {
281 }
283 // FIXME: workaround the fact that focusing a main stage application
284 // raises its surface on top of all other surfaces including the ones
285 // that belong to side stage applications.
286 onFocusedApplicationChanged: {
287 if (focusedApplication && sideStage.focusedApplication && sideStage.fullyShown) {
288 shell.applicationManager.focusApplication(sideStage.focusedApplication);
289 }
290 }
291 }
293 SideStage {
294 id: sideStage
296 applicationManager: shell.applicationManager
297 rightEdgeDraggingAreaWidth: shell.edgeSize
298 normalApplicationY: shell.panelHeight
300 onShownChanged: {
301 if (!shown && mainStage.applications.count == 0) {
302 stages.hide();
303 }
304 }
305 // FIXME: when hiding the side stage, refocus the main stage
306 // application so that it goes in front of the side stage
307 // application and hides it
308 onFullyShownChanged: {
309 if (!fullyShown && stages.fullyShown && sideStage.focusedApplication != null) {
310 shell.applicationManager.focusApplication(mainStage.focusedApplication);
311 }
312 }
314 enabled: shell.width >= units.gu(60)
315 visible: enabled
316 fullyShown: stages.fullyShown && shown
317 && sideStage[sideStageRevealer.boundProperty] == sideStageRevealer.openedValue
318 shouldUseScreenshots: !fullyShown || mainStage.usingScreenshots || sideStageRevealer.pressed
320 available: !greeter.shown && !lockscreen.shown && enabled
321 hides: [launcher, panel.indicators]
322 shown: false
323 showAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.openedValue; easing.type: Easing.OutQuint }
324 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.closedValue; easing.type: Easing.OutQuint }
326 width: units.gu(40)
327 height: stages.height
328 handleExpanded: sideStageRevealer.pressed
329 }
331 Revealer {
332 id: sideStageRevealer
334 enabled: mainStage.applications.count > 0 && sideStage.applications.count > 0
335 && sideStage.available
336 direction: Qt.RightToLeft
337 openedValue: parent.width - sideStage.width
338 hintDisplacement: units.gu(3)
339 /* The size of the sidestage handle needs to be bigger than the
340 typical size used for edge detection otherwise it is really
341 hard to grab.
342 */
343 handleSize: sideStage.shown ? units.gu(4) : shell.edgeSize
344 closedValue: parent.width + sideStage.handleSizeCollapsed
345 target: sideStage
346 x: parent.width - width
347 width: sideStage.width + handleSize * 0.7
348 height: sideStage.height
349 orientation: Qt.Horizontal
350 }
352 DragHandle {
353 id: stagesDragHandle
355 anchors.top: parent.top
356 anchors.bottom: parent.bottom
357 anchors.right: parent.left
359 width: shell.edgeSize
360 direction: Direction.Leftwards
361 property bool haveApps: mainStage.applications.count > 0 || sideStage.applications.count > 0
363 maxTotalDragDistance: haveApps ? parent.width : parent.width * 0.7
364 // Make autocompletion impossible when !haveApps
365 edgeDragEvaluator.minDragDistance: haveApps ? maxTotalDragDistance * 0.1 : Number.MAX_VALUE
366 }
367 }
368 }
370 Lockscreen {
371 id: lockscreen
372 hides: [launcher, panel.indicators, hud]
373 shown: false
374 enabled: true
375 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
376 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
377 y: panel.panelHeight
378 x: required ? 0 : - width
379 width: parent.width
380 height: parent.height - panel.panelHeight
381 background: shell.background
383 onUnlocked: lockscreen.hide()
384 onCancel: greeter.show()
386 Component.onCompleted: {
387 if (LightDM.Users.count == 1) {
388 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
389 }
390 }
391 }
393 Connections {
394 target: LightDM.Greeter
396 onShowPrompt: {
397 if (LightDM.Users.count == 1) {
398 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
399 if (text == "PIN") {
400 lockscreen.alphaNumeric = false
401 } else {
402 lockscreen.alphaNumeric = true
403 }
404 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
405 lockscreen.show();
406 }
407 }
408 }
410 Greeter {
411 id: greeter
412 objectName: "greeter"
414 available: true
415 hides: [launcher, panel.indicators, hud]
416 shown: true
418 y: panel.panelHeight
419 width: parent.width
420 height: parent.height - panel.panelHeight
422 dragHandleWidth: shell.edgeSize
424 property var previousMainApp: null
425 property var previousSideApp: null
427 onShownChanged: {
428 if (shown) {
429 lockscreen.reset();
430 // If there is only one user, we start authenticating with that one here.
431 // If there are more users, the Greeter will handle that
432 if (LightDM.Users.count == 1) {
433 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
434 }
435 greeter.forceActiveFocus();
436 // FIXME: *FocusedApplication are not updated when unfocused, hence the need to check whether
437 // the stage was actually shown
438 if (mainStage.fullyShown) greeter.previousMainApp = applicationManager.mainStageFocusedApplication;
439 if (sideStage.fullyShown) greeter.previousSideApp = applicationManager.sideStageFocusedApplication;
440 applicationManager.unfocusCurrentApplication();
441 } else {
442 if (greeter.previousMainApp) {
443 applicationManager.focusApplication(greeter.previousMainApp);
444 greeter.previousMainApp = null;
445 }
446 if (greeter.previousSideApp) {
447 applicationManager.focusApplication(greeter.previousSideApp);
448 greeter.previousSideApp = null;
449 }
450 }
451 }
453 onUnlocked: greeter.hide()
454 onSelected: {
455 var bgPath = greeter.model.data(uid, LightDM.UserRoles.BackgroundPathRole)
456 shell.background = bgPath ? bgPath : default_background
457 }
459 onLeftTeaserPressedChanged: {
460 if (leftTeaserPressed) {
461 launcher.tease();
462 }
463 }
464 }
466 InputFilterArea {
467 anchors.fill: parent
468 blockInput: greeter.shown || lockscreen.shown
469 }
471 Item {
472 id: overlay
474 anchors.fill: parent
476 Panel {
477 id: panel
478 anchors.fill: parent //because this draws indicator menus
479 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
480 indicators {
481 hides: [launcher]
482 }
483 fullscreenMode: shell.fullscreenMode
484 searchVisible: !greeter.shown && !lockscreen.shown
486 InputFilterArea {
487 anchors.fill: parent
488 blockInput: panel.indicators.shown
489 }
490 }
492 Hud {
493 id: hud
495 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
496 height: parent.height
498 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown
499 shown: false
500 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
501 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
503 Connections {
504 target: shell.applicationManager
505 onMainStageFocusedApplicationChanged: hud.hide()
506 onSideStageFocusedApplicationChanged: hud.hide()
507 }
509 InputFilterArea {
510 anchors.fill: parent
511 blockInput: hud.shown
512 }
513 }
515 Revealer {
516 id: hudRevealer
518 enabled: hud.shown
519 width: hud.width
520 anchors.left: hud.left
521 height: parent.height
522 target: hud.revealerTarget
523 closedValue: height
524 openedValue: 0
525 direction: Qt.RightToLeft
526 orientation: Qt.Vertical
527 handleSize: hud.handleHeight
528 onCloseClicked: target.hide()
529 }
531 Bottombar {
532 theHud: hud
533 anchors.fill: parent
534 enabled: !panel.indicators.shown
535 applicationIsOnForeground: applicationManager.mainStageFocusedApplication
536 || applicationManager.sideStageFocusedApplication
537 }
539 InputFilterArea {
540 blockInput: launcher.shown
541 anchors {
542 top: parent.top
543 bottom: parent.bottom
544 left: parent.left
545 }
546 width: launcher.width
547 }
549 Launcher {
550 id: launcher
551 objectName: "launcher"
553 anchors.top: parent.top
554 anchors.bottom: parent.bottom
555 width: parent.width
556 dragAreaWidth: shell.edgeSize
557 available: !greeter.locked
558 onDashItemSelected: {
559 greeter.hide()
560 // Animate if moving between application and dash
561 if (!stages.shown) {
562 dash.setCurrentScope("home.scope", true, false)
563 } else {
564 dash.setCurrentScope("home.scope", false, false)
565 }
566 stages.hide();
567 }
568 onDash: {
569 if (stages.shown) {
570 dash.setCurrentScope("applications.scope", true /* animate */, true /* reset */)
571 stages.hide();
572 launcher.hide();
573 }
574 }
575 onLauncherApplicationSelected:{
576 greeter.hide()
577 shell.activateApplication(desktopFile)
578 }
579 onShownChanged: {
580 if (shown) {
581 panel.indicators.hide()
582 hud.hide()
583 }
584 }
585 }
587 Notifications {
588 id: notifications
590 model: NotificationBackend.Model
591 anchors {
592 top: parent.top
593 right: parent.right
594 bottom: parent.bottom
595 leftMargin: units.gu(1)
596 rightMargin: units.gu(1)
597 topMargin: panel.panelHeight + units.gu(1)
598 }
599 states: [
600 State {
601 name: "narrow"
602 when: overlay.width <= units.gu(60)
603 AnchorChanges { target: notifications; anchors.left: parent.left }
604 },
605 State {
606 name: "wide"
607 when: overlay.width > units.gu(60)
608 AnchorChanges { target: notifications; anchors.left: undefined }
609 PropertyChanges { target: notifications; width: units.gu(38) }
610 }
611 ]
612 }
613 }
615 focus: true
617 InputFilterArea {
618 anchors {
619 top: parent.top
620 bottom: parent.bottom
621 left: parent.left
622 }
623 width: shell.edgeSize
624 blockInput: true
625 }
627 InputFilterArea {
628 anchors {
629 top: parent.top
630 bottom: parent.bottom
631 right: parent.right
632 }
633 width: shell.edgeSize
634 blockInput: true
635 }
637 Binding {
638 target: i18n
639 property: "domain"
640 value: "unity8"
641 }
643 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
644 MouseArea {
645 anchors.bottom: parent.bottom
646 anchors.left: parent.left
647 anchors.right: parent.right
648 height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
650 enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
651 }