on 4934631 help sign in
unity8/Shell.qml
675 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 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 "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) : units.gu(40)
41 height: tablet ? units.gu(100) : 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 applicationManager.sideStageEnabled = Qt.binding(function() { return sideStage.enabled })
68 // FIXME: if application focused before shell starts, shell draws on top of it only.
69 // We should detect already running applications on shell start and bring them to the front.
70 applicationManager.unfocusCurrentApplication();
71 }
73 readonly property bool fullscreenMode: {
74 if (greeter.shown || lockscreen.shown) {
75 return false;
76 } else if (mainStage.usingScreenshots) { // Window Manager animating so want to re-evaluate fullscreen mode
77 return mainStage.switchingFromFullscreenToFullscreen;
78 } else if (applicationManager.mainStageFocusedApplication) {
79 return applicationManager.mainStageFocusedApplication.fullscreen;
80 } else {
81 return false;
82 }
83 }
85 Connections {
86 target: applicationManager
87 ignoreUnknownSignals: true
88 onFocusRequested: {
89 // TODO: this should be protected to only unlock for certain applications / certain usecases
90 // potentially only in connection with a notification
91 greeter.hide();
92 shell.activateApplication(desktopFile);
93 }
94 }
96 function activateApplication(desktopFile, argument) {
97 if (applicationManager) {
98 // For newly started applications, as it takes them time to draw their first frame
99 // we add a delay before we hide the animation screenshots to compensate.
100 var addDelay = !applicationManager.getApplicationFromDesktopFile(desktopFile);
102 var application;
103 application = applicationManager.activateApplication(desktopFile, argument);
104 if (application == null) {
105 return;
106 }
107 if (application.stage == ApplicationInfo.MainStage || !sideStage.enabled) {
108 mainStage.activateApplication(desktopFile, addDelay);
109 } else {
110 sideStage.activateApplication(desktopFile, addDelay);
111 }
112 stages.show();
113 }
114 }
116 GSettings {
117 id: backgroundSettings
118 schema: "org.gnome.desktop.background"
119 }
121 function updateImage() {
122 if (testImage.source == "")
123 return
125 if (testImage.status == Image.Ready) {
126 if (shell.width / shell.height <= testImage.sourceSize.width / testImage.sourceSize.height) {
127 backgroundImage.sourceSize.width = 0
128 backgroundImage.sourceSize.height = Qt.binding(function() { return shell.height })
129 }
130 else {
131 backgroundImage.sourceSize.height = 0
132 backgroundImage.sourceSize.width = Qt.binding(function() { return shell.width })
133 }
135 shell.background = testImage.source
136 } else if (testImage.status == Image.Error) {
137 shell.background = shell.default_background
138 }
140 // unload testImage source to save memory
141 testImage.source = ""
142 }
144 Image {
145 id: testImage
147 property url gsettingsImage: backgroundSettings.pictureUri
149 source: gsettingsImage
150 visible: false
152 onGsettingsImageChanged: source = gsettingsImage
153 onStatusChanged: updateImage()
154 }
156 VolumeControl {
157 id: volumeControl
158 }
160 Keys.onVolumeUpPressed: volumeControl.volumeUp()
161 Keys.onVolumeDownPressed: volumeControl.volumeDown()
163 Keys.onReleased: {
164 if (event.key == Qt.Key_PowerOff) {
165 greeter.show()
166 }
167 }
169 Item {
170 id: underlay
171 anchors.fill: parent
172 visible: !(panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth)
173 && (stages.fullyHidden
174 || (stages.fullyShown && mainStage.usingScreenshots)
175 || !stages.fullyShown && (mainStage.usingScreenshots || (sideStage.shown && sideStage.usingScreenshots)))
177 Image {
178 id: backgroundImage
179 source: shell.background
180 anchors.fill: parent
181 fillMode: Image.PreserveAspectCrop
182 }
184 Rectangle {
185 anchors.fill: parent
186 color: "black"
187 opacity: dash.disappearingAnimationProgress
188 }
190 Dash {
191 id: dash
193 available: !greeter.shown && !lockscreen.shown
194 hides: [stages, launcher, panel.indicators]
195 shown: disappearingAnimationProgress !== 1.0
196 enabled: disappearingAnimationProgress === 0.0
197 // FIXME: unfocus all applications when going back to the dash
198 onEnabledChanged: {
199 if (enabled) {
200 shell.applicationManager.unfocusCurrentApplication()
201 }
202 }
204 anchors {
205 fill: parent
206 topMargin: panel.panelHeight
207 }
209 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
210 opacity: 1.0 - disappearingAnimationProgress
211 property real disappearingAnimationProgress: {
212 if (greeter.shown) {
213 return greeter.showProgress;
214 } else {
215 return stagesOuterContainer.showProgress;
216 }
217 }
219 // FIXME: only necessary because stagesRevealer.animatedProgress and
220 // greeterRevealer.animatedProgress are not animated
221 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
222 }
223 }
226 Item {
227 id: stagesOuterContainer
229 width: parent.width
230 height: parent.height
231 x: launcher.progress
232 Behavior on x {SmoothedAnimation{velocity: 600}}
234 property real showProgress:
235 MathLocal.clamp(1 - (x + stages.x) / shell.width, 0, 1)
237 Showable {
238 id: stages
240 x: width
242 property bool fullyShown: shown && x == 0 && parent.x == 0
243 property bool fullyHidden: !shown && x == width
244 available: !greeter.shown
245 hides: [launcher, panel.indicators]
246 shown: false
247 opacity: 1.0
248 showAnimation: StandardAnimation { property: "x"; duration: 350; to: 0; easing.type: Easing.OutCubic }
249 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: width; easing.type: Easing.OutCubic }
251 width: parent.width
252 height: parent.height
254 // close the stages when no focused application remains
255 Connections {
256 target: shell.applicationManager
257 onMainStageFocusedApplicationChanged: stages.closeIfNoApplications()
258 onSideStageFocusedApplicationChanged: stages.closeIfNoApplications()
259 ignoreUnknownSignals: true
260 }
262 function closeIfNoApplications() {
263 if (!shell.applicationManager.mainStageFocusedApplication
264 && !shell.applicationManager.sideStageFocusedApplication
265 && shell.applicationManager.mainStageApplications.count == 0
266 && shell.applicationManager.sideStageApplications.count == 0) {
267 stages.hide();
268 }
269 }
271 // show the stages when an application gets the focus
272 Connections {
273 target: shell.applicationManager
274 onMainStageFocusedApplicationChanged: {
275 if (shell.applicationManager.mainStageFocusedApplication) {
276 mainStage.show();
277 stages.show();
278 }
279 }
280 onSideStageFocusedApplicationChanged: {
281 if (shell.applicationManager.sideStageFocusedApplication) {
282 sideStage.show();
283 stages.show();
284 }
285 }
286 ignoreUnknownSignals: true
287 }
290 Stage {
291 id: mainStage
293 anchors.fill: parent
294 fullyShown: stages.fullyShown
295 shouldUseScreenshots: !fullyShown
296 rightEdgeEnabled: !sideStage.enabled
298 applicationManager: shell.applicationManager
299 rightEdgeDraggingAreaWidth: shell.edgeSize
300 normalApplicationY: shell.panelHeight
302 shown: true
303 function show() {
304 stages.show();
305 }
306 function showWithoutAnimation() {
307 stages.showWithoutAnimation();
308 }
309 function hide() {
310 }
312 // FIXME: workaround the fact that focusing a main stage application
313 // raises its surface on top of all other surfaces including the ones
314 // that belong to side stage applications.
315 onFocusedApplicationChanged: {
316 if (focusedApplication && sideStage.focusedApplication && sideStage.fullyShown) {
317 shell.applicationManager.focusApplication(sideStage.focusedApplication);
318 }
319 }
320 }
322 SideStage {
323 id: sideStage
325 applicationManager: shell.applicationManager
326 rightEdgeDraggingAreaWidth: shell.edgeSize
327 normalApplicationY: shell.panelHeight
329 onShownChanged: {
330 if (!shown && mainStage.applications.count == 0) {
331 stages.hide();
332 }
333 }
334 // FIXME: when hiding the side stage, refocus the main stage
335 // application so that it goes in front of the side stage
336 // application and hides it
337 onFullyShownChanged: {
338 if (!fullyShown && stages.fullyShown && sideStage.focusedApplication != null) {
339 shell.applicationManager.focusApplication(mainStage.focusedApplication);
340 }
341 }
343 enabled: shell.width >= units.gu(60)
344 visible: enabled
345 fullyShown: stages.fullyShown && shown
346 && sideStage[sideStageRevealer.boundProperty] == sideStageRevealer.openedValue
347 shouldUseScreenshots: !fullyShown || mainStage.usingScreenshots || sideStageRevealer.pressed
349 available: !greeter.shown && !lockscreen.shown && enabled
350 hides: [launcher, panel.indicators]
351 shown: false
352 showAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.openedValue; easing.type: Easing.OutQuint }
353 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.closedValue; easing.type: Easing.OutQuint }
355 width: units.gu(40)
356 height: stages.height
357 handleExpanded: sideStageRevealer.pressed
358 }
360 Revealer {
361 id: sideStageRevealer
363 enabled: mainStage.applications.count > 0 && sideStage.applications.count > 0
364 && sideStage.available
365 direction: Qt.RightToLeft
366 openedValue: parent.width - sideStage.width
367 hintDisplacement: units.gu(3)
368 /* The size of the sidestage handle needs to be bigger than the
369 typical size used for edge detection otherwise it is really
370 hard to grab.
371 */
372 handleSize: sideStage.shown ? units.gu(4) : shell.edgeSize
373 closedValue: parent.width + sideStage.handleSizeCollapsed
374 target: sideStage
375 x: parent.width - width
376 width: sideStage.width + handleSize * 0.7
377 height: sideStage.height
378 orientation: Qt.Horizontal
379 }
381 DragHandle {
382 id: stagesRevealer
384 anchors.top: parent.top
385 anchors.bottom: parent.bottom
386 anchors.right: parent.left
388 width: shell.edgeSize
389 direction: Direction.Leftwards
390 enabled: mainStage.applications.count > 0 || sideStage.applications.count > 0
391 }
392 }
393 }
395 Lockscreen {
396 id: lockscreen
397 hides: [launcher, panel.indicators, hud]
398 shown: false
399 enabled: true
400 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
401 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
402 y: panel.panelHeight
403 x: required ? 0 : - width
404 width: parent.width
405 height: parent.height - panel.panelHeight
406 background: shell.background
408 onUnlocked: lockscreen.hide()
409 onCancel: greeter.show()
411 Component.onCompleted: {
412 if (LightDM.Users.count == 1) {
413 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
414 }
415 }
416 }
418 Connections {
419 target: LightDM.Greeter
421 onShowPrompt: {
422 if (LightDM.Users.count == 1) {
423 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
424 if (text == "PIN") {
425 lockscreen.alphaNumeric = false
426 } else {
427 lockscreen.alphaNumeric = true
428 }
429 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
430 lockscreen.show();
431 }
432 }
433 }
435 Greeter {
436 id: 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 property var previousMainApp: null
449 property var previousSideApp: null
451 onShownChanged: {
452 if (shown) {
453 lockscreen.reset();
454 // If there is only one user, we start authenticating with that one here.
455 // If there are more users, the Greeter will handle that
456 if (LightDM.Users.count == 1) {
457 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
458 }
459 greeter.forceActiveFocus();
460 // FIXME: *FocusedApplication are not updated when unfocused, hence the need to check whether
461 // the stage was actually shown
462 if (mainStage.fullyShown) greeter.previousMainApp = applicationManager.mainStageFocusedApplication;
463 if (sideStage.fullyShown) greeter.previousSideApp = applicationManager.sideStageFocusedApplication;
464 applicationManager.unfocusCurrentApplication();
465 } else {
466 if (greeter.previousMainApp) {
467 applicationManager.focusApplication(greeter.previousMainApp);
468 greeter.previousMainApp = null;
469 }
470 if (greeter.previousSideApp) {
471 applicationManager.focusApplication(greeter.previousSideApp);
472 greeter.previousSideApp = null;
473 }
474 }
475 }
477 onUnlocked: greeter.hide()
478 onSelected: {
479 var bgPath = greeter.model.data(uid, LightDM.UserRoles.BackgroundPathRole)
480 shell.background = bgPath ? bgPath : default_background
481 }
483 onLeftTeaserPressedChanged: {
484 if (leftTeaserPressed) {
485 launcher.tease();
486 }
487 }
488 }
490 InputFilterArea {
491 anchors.fill: parent
492 blockInput: greeter.shown || lockscreen.shown
493 }
495 Item {
496 id: overlay
498 anchors.fill: parent
500 Panel {
501 id: panel
502 anchors.fill: parent //because this draws indicator menus
503 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
504 indicators {
505 hides: [launcher]
506 }
507 fullscreenMode: shell.fullscreenMode
508 searchVisible: !greeter.shown && !lockscreen.shown
510 InputFilterArea {
511 anchors.fill: parent
512 blockInput: panel.indicators.shown
513 }
514 }
516 Hud {
517 id: hud
519 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
520 height: parent.height
522 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown
523 shown: false
524 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
525 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
527 Connections {
528 target: shell.applicationManager
529 onMainStageFocusedApplicationChanged: hud.hide()
530 onSideStageFocusedApplicationChanged: hud.hide()
531 }
533 InputFilterArea {
534 anchors.fill: parent
535 blockInput: hud.shown
536 }
537 }
539 Revealer {
540 id: hudRevealer
542 enabled: hud.shown
543 width: hud.width
544 anchors.left: hud.left
545 height: parent.height
546 target: hud.revealerTarget
547 closedValue: height
548 openedValue: 0
549 direction: Qt.RightToLeft
550 orientation: Qt.Vertical
551 handleSize: hud.handleHeight
552 onCloseClicked: target.hide()
553 }
555 Bottombar {
556 theHud: hud
557 anchors.fill: parent
558 enabled: !panel.indicators.shown
559 applicationIsOnForeground: applicationManager.mainStageFocusedApplication
560 || applicationManager.sideStageFocusedApplication
561 }
563 InputFilterArea {
564 blockInput: launcher.shown
565 anchors {
566 top: parent.top
567 bottom: parent.bottom
568 left: parent.left
569 }
570 width: launcher.width
571 }
573 Launcher {
574 id: launcher
576 anchors.top: parent.top
577 anchors.bottom: parent.bottom
578 width: parent.width
579 dragAreaWidth: shell.edgeSize
580 available: !greeter.locked
581 onDashItemSelected: {
582 greeter.hide()
583 // Animate if moving between application and dash
584 if (!stages.shown) {
585 dash.setCurrentScope("home.scope", true, false)
586 } else {
587 dash.setCurrentScope("home.scope", false, false)
588 }
589 stages.hide();
590 }
591 onDash: {
592 if (stages.shown) {
593 dash.setCurrentScope("applications.scope", true, false)
594 stages.hide();
595 launcher.hide();
596 }
597 }
598 onLauncherApplicationSelected:{
599 greeter.hide()
600 shell.activateApplication(desktopFile)
601 }
602 onShownChanged: {
603 if (shown) {
604 panel.indicators.hide()
605 hud.hide()
606 }
607 }
608 }
610 Notifications {
611 id: notifications
613 model: NotificationBackend.Model
614 anchors {
615 top: parent.top
616 right: parent.right
617 bottom: parent.bottom
618 leftMargin: units.gu(1)
619 rightMargin: units.gu(1)
620 topMargin: panel.panelHeight + units.gu(1)
621 }
622 states: [
623 State {
624 name: "narrow"
625 when: overlay.width <= units.gu(60)
626 AnchorChanges { target: notifications; anchors.left: parent.left }
627 },
628 State {
629 name: "wide"
630 when: overlay.width > units.gu(60)
631 AnchorChanges { target: notifications; anchors.left: undefined }
632 PropertyChanges { target: notifications; width: units.gu(38) }
633 }
634 ]
635 }
636 }
638 focus: true
640 InputFilterArea {
641 anchors {
642 top: parent.top
643 bottom: parent.bottom
644 left: parent.left
645 }
646 width: shell.edgeSize
647 blockInput: true
648 }
650 InputFilterArea {
651 anchors {
652 top: parent.top
653 bottom: parent.bottom
654 right: parent.right
655 }
656 width: shell.edgeSize
657 blockInput: true
658 }
660 Binding {
661 target: i18n
662 property: "domain"
663 value: "unity8"
664 }
666 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
667 MouseArea {
668 anchors.bottom: parent.bottom
669 anchors.left: parent.left
670 anchors.right: parent.right
671 height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
673 enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
674 }