on e39795f help sign in
unity8/Shell.qml
664 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 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.status == Image.Ready) {
123 if (shell.width / shell.height <= testImage.sourceSize.width / testImage.sourceSize.height) {
124 backgroundImage.sourceSize = undefined
125 backgroundImage.sourceSize.height = shell.height
126 }
127 else {
128 backgroundImage.sourceSize = undefined
129 backgroundImage.sourceSize.width = shell.width
130 }
132 shell.background = testImage.source
133 } else if (testImage.status == Image.Error) {
134 shell.background = shell.default_background
135 }
136 }
138 Image {
139 id: testImage
140 source: backgroundSettings.pictureUri
141 visible: false
142 onStatusChanged: updateImage()
143 }
145 VolumeControl {
146 id: volumeControl
147 }
149 Keys.onVolumeUpPressed: volumeControl.volumeUp()
150 Keys.onVolumeDownPressed: volumeControl.volumeDown()
152 Keys.onReleased: {
153 if (event.key == Qt.Key_PowerOff) {
154 greeter.show()
155 }
156 }
158 Item {
159 id: underlay
160 anchors.fill: parent
161 visible: !(panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth)
162 && (stages.fullyHidden
163 || (stages.fullyShown && mainStage.usingScreenshots)
164 || !stages.fullyShown && (mainStage.usingScreenshots || (sideStage.shown && sideStage.usingScreenshots)))
166 Image {
167 id: backgroundImage
168 source: shell.background
169 anchors.fill: parent
170 fillMode: Image.PreserveAspectCrop
171 }
173 Rectangle {
174 anchors.fill: parent
175 color: "black"
176 opacity: dash.disappearingAnimationProgress
177 }
179 Dash {
180 id: dash
182 available: !greeter.shown && !lockscreen.shown
183 hides: [stages, launcher, panel.indicators]
184 shown: disappearingAnimationProgress !== 1.0
185 enabled: disappearingAnimationProgress === 0.0
186 // FIXME: unfocus all applications when going back to the dash
187 onEnabledChanged: {
188 if (enabled) {
189 shell.applicationManager.unfocusCurrentApplication()
190 }
191 }
193 anchors {
194 fill: parent
195 topMargin: panel.panelHeight
196 }
198 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
199 opacity: 1.0 - disappearingAnimationProgress
200 property real disappearingAnimationProgress: {
201 if (greeter.shown) {
202 return greeter.showProgress;
203 } else {
204 return stagesOuterContainer.showProgress;
205 }
206 }
208 // FIXME: only necessary because stagesRevealer.animatedProgress and
209 // greeterRevealer.animatedProgress are not animated
210 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
211 }
212 }
215 Item {
216 id: stagesOuterContainer
218 width: parent.width
219 height: parent.height
220 x: launcher.progress
221 Behavior on x {SmoothedAnimation{velocity: 600}}
223 property real showProgress:
224 MathLocal.clamp(1 - (x + stages.x) / shell.width, 0, 1)
226 Showable {
227 id: stages
229 x: width
231 property bool fullyShown: shown && x == 0 && parent.x == 0
232 property bool fullyHidden: !shown && x == width
233 available: !greeter.shown
234 hides: [launcher, panel.indicators]
235 shown: false
236 opacity: 1.0
237 showAnimation: StandardAnimation { property: "x"; duration: 350; to: 0; easing.type: Easing.OutCubic }
238 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: width; easing.type: Easing.OutCubic }
240 width: parent.width
241 height: parent.height
243 // close the stages when no focused application remains
244 Connections {
245 target: shell.applicationManager
246 onMainStageFocusedApplicationChanged: stages.closeIfNoApplications()
247 onSideStageFocusedApplicationChanged: stages.closeIfNoApplications()
248 ignoreUnknownSignals: true
249 }
251 function closeIfNoApplications() {
252 if (!shell.applicationManager.mainStageFocusedApplication
253 && !shell.applicationManager.sideStageFocusedApplication
254 && shell.applicationManager.mainStageApplications.count == 0
255 && shell.applicationManager.sideStageApplications.count == 0) {
256 stages.hide();
257 }
258 }
260 // show the stages when an application gets the focus
261 Connections {
262 target: shell.applicationManager
263 onMainStageFocusedApplicationChanged: {
264 if (shell.applicationManager.mainStageFocusedApplication) {
265 mainStage.show();
266 stages.show();
267 }
268 }
269 onSideStageFocusedApplicationChanged: {
270 if (shell.applicationManager.sideStageFocusedApplication) {
271 sideStage.show();
272 stages.show();
273 }
274 }
275 ignoreUnknownSignals: true
276 }
279 Stage {
280 id: mainStage
282 anchors.fill: parent
283 fullyShown: stages.fullyShown
284 shouldUseScreenshots: !fullyShown
285 rightEdgeEnabled: !sideStage.enabled
287 applicationManager: shell.applicationManager
288 rightEdgeDraggingAreaWidth: shell.edgeSize
289 normalApplicationY: shell.panelHeight
291 shown: true
292 function show() {
293 stages.show();
294 }
295 function showWithoutAnimation() {
296 stages.showWithoutAnimation();
297 }
298 function hide() {
299 }
301 // FIXME: workaround the fact that focusing a main stage application
302 // raises its surface on top of all other surfaces including the ones
303 // that belong to side stage applications.
304 onFocusedApplicationChanged: {
305 if (focusedApplication && sideStage.focusedApplication && sideStage.fullyShown) {
306 shell.applicationManager.focusApplication(sideStage.focusedApplication);
307 }
308 }
309 }
311 SideStage {
312 id: sideStage
314 applicationManager: shell.applicationManager
315 rightEdgeDraggingAreaWidth: shell.edgeSize
316 normalApplicationY: shell.panelHeight
318 onShownChanged: {
319 if (!shown && mainStage.applications.count == 0) {
320 stages.hide();
321 }
322 }
323 // FIXME: when hiding the side stage, refocus the main stage
324 // application so that it goes in front of the side stage
325 // application and hides it
326 onFullyShownChanged: {
327 if (!fullyShown && stages.fullyShown && sideStage.focusedApplication != null) {
328 shell.applicationManager.focusApplication(mainStage.focusedApplication);
329 }
330 }
332 enabled: shell.width >= units.gu(60)
333 visible: enabled
334 fullyShown: stages.fullyShown && shown
335 && sideStage[sideStageRevealer.boundProperty] == sideStageRevealer.openedValue
336 shouldUseScreenshots: !fullyShown || mainStage.usingScreenshots || sideStageRevealer.pressed
338 available: !greeter.shown && !lockscreen.shown && enabled
339 hides: [launcher, panel.indicators]
340 shown: false
341 showAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.openedValue; easing.type: Easing.OutQuint }
342 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.closedValue; easing.type: Easing.OutQuint }
344 width: units.gu(40)
345 height: stages.height
346 handleExpanded: sideStageRevealer.pressed
347 }
349 Revealer {
350 id: sideStageRevealer
352 enabled: mainStage.applications.count > 0 && sideStage.applications.count > 0
353 && sideStage.available
354 direction: Qt.RightToLeft
355 openedValue: parent.width - sideStage.width
356 hintDisplacement: units.gu(3)
357 /* The size of the sidestage handle needs to be bigger than the
358 typical size used for edge detection otherwise it is really
359 hard to grab.
360 */
361 handleSize: sideStage.shown ? units.gu(4) : shell.edgeSize
362 closedValue: parent.width + sideStage.handleSizeCollapsed
363 target: sideStage
364 x: parent.width - width
365 width: sideStage.width + handleSize * 0.7
366 height: sideStage.height
367 orientation: Qt.Horizontal
368 }
370 DragHandle {
371 id: stagesRevealer
373 anchors.top: parent.top
374 anchors.bottom: parent.bottom
375 anchors.right: parent.left
377 width: shell.edgeSize
378 direction: Direction.Leftwards
379 enabled: mainStage.applications.count > 0 || sideStage.applications.count > 0
380 }
381 }
382 }
384 Lockscreen {
385 id: lockscreen
386 hides: [launcher, panel.indicators, hud]
387 shown: false
388 enabled: true
389 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
390 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
391 y: panel.panelHeight
392 x: required ? 0 : - width
393 width: parent.width
394 height: parent.height - panel.panelHeight
395 background: shell.background
397 onUnlocked: lockscreen.hide()
398 onCancel: greeter.show()
400 Component.onCompleted: {
401 if (LightDM.Users.count == 1) {
402 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
403 }
404 }
405 }
407 Connections {
408 target: LightDM.Greeter
410 onShowPrompt: {
411 if (LightDM.Users.count == 1) {
412 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
413 if (text == "PIN") {
414 lockscreen.alphaNumeric = false
415 } else {
416 lockscreen.alphaNumeric = true
417 }
418 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
419 lockscreen.show();
420 }
421 }
422 }
424 Greeter {
425 id: greeter
427 available: true
428 hides: [launcher, panel.indicators, hud]
429 shown: true
431 y: panel.panelHeight
432 width: parent.width
433 height: parent.height - panel.panelHeight
435 dragHandleWidth: shell.edgeSize
437 property var previousMainApp: null
438 property var previousSideApp: null
440 onShownChanged: {
441 if (shown) {
442 lockscreen.reset();
443 // If there is only one user, we start authenticating with that one here.
444 // If there are more users, the Greeter will handle that
445 if (LightDM.Users.count == 1) {
446 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
447 }
448 greeter.forceActiveFocus();
449 // FIXME: *FocusedApplication are not updated when unfocused, hence the need to check whether
450 // the stage was actually shown
451 if (mainStage.fullyShown) greeter.previousMainApp = applicationManager.mainStageFocusedApplication;
452 if (sideStage.fullyShown) greeter.previousSideApp = applicationManager.sideStageFocusedApplication;
453 applicationManager.unfocusCurrentApplication();
454 } else {
455 if (greeter.previousMainApp) {
456 applicationManager.focusApplication(greeter.previousMainApp);
457 greeter.previousMainApp = null;
458 }
459 if (greeter.previousSideApp) {
460 applicationManager.focusApplication(greeter.previousSideApp);
461 greeter.previousSideApp = null;
462 }
463 }
464 }
466 onUnlocked: greeter.hide()
467 onSelected: {
468 var bgPath = greeter.model.data(uid, LightDM.UserRoles.BackgroundPathRole)
469 shell.background = bgPath ? bgPath : default_background
470 }
472 onLeftTeaserPressedChanged: {
473 if (leftTeaserPressed) {
474 launcher.tease();
475 }
476 }
477 }
479 InputFilterArea {
480 anchors.fill: parent
481 blockInput: greeter.shown || lockscreen.shown
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.locked
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 }