on b1e8d4e help sign in
unity8/Shell.qml
650 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) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
40 height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : 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
413 available: true
414 hides: [launcher, panel.indicators, hud]
415 shown: true
417 y: panel.panelHeight
418 width: parent.width
419 height: parent.height - panel.panelHeight
421 dragHandleWidth: shell.edgeSize
423 property var previousMainApp: null
424 property var previousSideApp: null
426 onShownChanged: {
427 if (shown) {
428 lockscreen.reset();
429 // If there is only one user, we start authenticating with that one here.
430 // If there are more users, the Greeter will handle that
431 if (LightDM.Users.count == 1) {
432 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
433 }
434 greeter.forceActiveFocus();
435 // FIXME: *FocusedApplication are not updated when unfocused, hence the need to check whether
436 // the stage was actually shown
437 if (mainStage.fullyShown) greeter.previousMainApp = applicationManager.mainStageFocusedApplication;
438 if (sideStage.fullyShown) greeter.previousSideApp = applicationManager.sideStageFocusedApplication;
439 applicationManager.unfocusCurrentApplication();
440 } else {
441 if (greeter.previousMainApp) {
442 applicationManager.focusApplication(greeter.previousMainApp);
443 greeter.previousMainApp = null;
444 }
445 if (greeter.previousSideApp) {
446 applicationManager.focusApplication(greeter.previousSideApp);
447 greeter.previousSideApp = null;
448 }
449 }
450 }
452 onUnlocked: greeter.hide()
453 onSelected: {
454 var bgPath = greeter.model.data(uid, LightDM.UserRoles.BackgroundPathRole)
455 shell.background = bgPath ? bgPath : default_background
456 }
458 onLeftTeaserPressedChanged: {
459 if (leftTeaserPressed) {
460 launcher.tease();
461 }
462 }
463 }
465 InputFilterArea {
466 anchors.fill: parent
467 blockInput: greeter.shown || lockscreen.shown
468 }
470 Item {
471 id: overlay
473 anchors.fill: parent
475 Panel {
476 id: panel
477 anchors.fill: parent //because this draws indicator menus
478 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
479 indicators {
480 hides: [launcher]
481 }
482 fullscreenMode: shell.fullscreenMode
483 searchVisible: !greeter.shown && !lockscreen.shown
485 InputFilterArea {
486 anchors.fill: parent
487 blockInput: panel.indicators.shown
488 }
489 }
491 Hud {
492 id: hud
494 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
495 height: parent.height
497 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown
498 shown: false
499 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
500 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
502 Connections {
503 target: shell.applicationManager
504 onMainStageFocusedApplicationChanged: hud.hide()
505 onSideStageFocusedApplicationChanged: hud.hide()
506 }
508 InputFilterArea {
509 anchors.fill: parent
510 blockInput: hud.shown
511 }
512 }
514 Revealer {
515 id: hudRevealer
517 enabled: hud.shown
518 width: hud.width
519 anchors.left: hud.left
520 height: parent.height
521 target: hud.revealerTarget
522 closedValue: height
523 openedValue: 0
524 direction: Qt.RightToLeft
525 orientation: Qt.Vertical
526 handleSize: hud.handleHeight
527 onCloseClicked: target.hide()
528 }
530 Bottombar {
531 theHud: hud
532 anchors.fill: parent
533 enabled: !panel.indicators.shown
534 applicationIsOnForeground: applicationManager.mainStageFocusedApplication
535 || applicationManager.sideStageFocusedApplication
536 }
538 InputFilterArea {
539 blockInput: launcher.shown
540 anchors {
541 top: parent.top
542 bottom: parent.bottom
543 left: parent.left
544 }
545 width: launcher.width
546 }
548 Launcher {
549 id: launcher
551 anchors.top: parent.top
552 anchors.bottom: parent.bottom
553 width: parent.width
554 dragAreaWidth: shell.edgeSize
555 available: !greeter.locked
556 onDashItemSelected: {
557 greeter.hide()
558 // Animate if moving between application and dash
559 if (!stages.shown) {
560 dash.setCurrentScope("home.scope", true, false)
561 } else {
562 dash.setCurrentScope("home.scope", false, false)
563 }
564 stages.hide();
565 }
566 onDash: {
567 if (stages.shown) {
568 dash.setCurrentScope("applications.scope", true, false)
569 stages.hide();
570 launcher.hide();
571 }
572 }
573 onLauncherApplicationSelected:{
574 greeter.hide()
575 shell.activateApplication(desktopFile)
576 }
577 onShownChanged: {
578 if (shown) {
579 panel.indicators.hide()
580 hud.hide()
581 }
582 }
583 }
585 Notifications {
586 id: notifications
588 model: NotificationBackend.Model
589 anchors {
590 top: parent.top
591 right: parent.right
592 bottom: parent.bottom
593 leftMargin: units.gu(1)
594 rightMargin: units.gu(1)
595 topMargin: panel.panelHeight + units.gu(1)
596 }
597 states: [
598 State {
599 name: "narrow"
600 when: overlay.width <= units.gu(60)
601 AnchorChanges { target: notifications; anchors.left: parent.left }
602 },
603 State {
604 name: "wide"
605 when: overlay.width > units.gu(60)
606 AnchorChanges { target: notifications; anchors.left: undefined }
607 PropertyChanges { target: notifications; width: units.gu(38) }
608 }
609 ]
610 }
611 }
613 focus: true
615 InputFilterArea {
616 anchors {
617 top: parent.top
618 bottom: parent.bottom
619 left: parent.left
620 }
621 width: shell.edgeSize
622 blockInput: true
623 }
625 InputFilterArea {
626 anchors {
627 top: parent.top
628 bottom: parent.bottom
629 right: parent.right
630 }
631 width: shell.edgeSize
632 blockInput: true
633 }
635 Binding {
636 target: i18n
637 property: "domain"
638 value: "unity8"
639 }
641 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
642 MouseArea {
643 anchors.bottom: parent.bottom
644 anchors.left: parent.left
645 anchors.right: parent.right
646 height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
648 enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
649 }