on 8e91eb7 help sign in
unity8/Shell.qml
674 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 } else {
130 backgroundImage.sourceSize.height = 0
131 backgroundImage.sourceSize.width = Qt.binding(function() { return shell.width })
132 }
134 shell.background = testImage.source
135 } else if (testImage.status == Image.Error) {
136 shell.background = shell.default_background
137 }
139 // unload testImage source to save memory
140 testImage.source = ""
141 }
143 Image {
144 id: testImage
146 property url gsettingsImage: backgroundSettings.pictureUri
148 source: gsettingsImage
149 visible: false
151 onGsettingsImageChanged: source = gsettingsImage
152 onStatusChanged: updateImage()
153 }
155 VolumeControl {
156 id: volumeControl
157 }
159 Keys.onVolumeUpPressed: volumeControl.volumeUp()
160 Keys.onVolumeDownPressed: volumeControl.volumeDown()
162 Keys.onReleased: {
163 if (event.key == Qt.Key_PowerOff) {
164 greeter.show()
165 }
166 }
168 Item {
169 id: underlay
170 anchors.fill: parent
171 visible: !(panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth)
172 && (stages.fullyHidden
173 || (stages.fullyShown && mainStage.usingScreenshots)
174 || !stages.fullyShown && (mainStage.usingScreenshots || (sideStage.shown && sideStage.usingScreenshots)))
176 Image {
177 id: backgroundImage
178 source: shell.background
179 anchors.fill: parent
180 fillMode: Image.PreserveAspectCrop
181 }
183 Rectangle {
184 anchors.fill: parent
185 color: "black"
186 opacity: dash.disappearingAnimationProgress
187 }
189 Dash {
190 id: dash
192 available: !greeter.shown && !lockscreen.shown
193 hides: [stages, launcher, panel.indicators]
194 shown: disappearingAnimationProgress !== 1.0
195 enabled: disappearingAnimationProgress === 0.0
196 // FIXME: unfocus all applications when going back to the dash
197 onEnabledChanged: {
198 if (enabled) {
199 shell.applicationManager.unfocusCurrentApplication()
200 }
201 }
203 anchors {
204 fill: parent
205 topMargin: panel.panelHeight
206 }
208 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
209 opacity: 1.0 - disappearingAnimationProgress
210 property real disappearingAnimationProgress: {
211 if (greeter.shown) {
212 return greeter.showProgress;
213 } else {
214 return stagesOuterContainer.showProgress;
215 }
216 }
218 // FIXME: only necessary because stagesRevealer.animatedProgress and
219 // greeterRevealer.animatedProgress are not animated
220 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
221 }
222 }
225 Item {
226 id: stagesOuterContainer
228 width: parent.width
229 height: parent.height
230 x: launcher.progress
231 Behavior on x {SmoothedAnimation{velocity: 600}}
233 property real showProgress:
234 MathLocal.clamp(1 - (x + stages.x) / shell.width, 0, 1)
236 Showable {
237 id: stages
239 x: width
241 property bool fullyShown: shown && x == 0 && parent.x == 0
242 property bool fullyHidden: !shown && x == width
243 available: !greeter.shown
244 hides: [launcher, panel.indicators]
245 shown: false
246 opacity: 1.0
247 showAnimation: StandardAnimation { property: "x"; duration: 350; to: 0; easing.type: Easing.OutCubic }
248 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: width; easing.type: Easing.OutCubic }
250 width: parent.width
251 height: parent.height
253 // close the stages when no focused application remains
254 Connections {
255 target: shell.applicationManager
256 onMainStageFocusedApplicationChanged: stages.closeIfNoApplications()
257 onSideStageFocusedApplicationChanged: stages.closeIfNoApplications()
258 ignoreUnknownSignals: true
259 }
261 function closeIfNoApplications() {
262 if (!shell.applicationManager.mainStageFocusedApplication
263 && !shell.applicationManager.sideStageFocusedApplication
264 && shell.applicationManager.mainStageApplications.count == 0
265 && shell.applicationManager.sideStageApplications.count == 0) {
266 stages.hide();
267 }
268 }
270 // show the stages when an application gets the focus
271 Connections {
272 target: shell.applicationManager
273 onMainStageFocusedApplicationChanged: {
274 if (shell.applicationManager.mainStageFocusedApplication) {
275 mainStage.show();
276 stages.show();
277 }
278 }
279 onSideStageFocusedApplicationChanged: {
280 if (shell.applicationManager.sideStageFocusedApplication) {
281 sideStage.show();
282 stages.show();
283 }
284 }
285 ignoreUnknownSignals: true
286 }
289 Stage {
290 id: mainStage
292 anchors.fill: parent
293 fullyShown: stages.fullyShown
294 shouldUseScreenshots: !fullyShown
295 rightEdgeEnabled: !sideStage.enabled
297 applicationManager: shell.applicationManager
298 rightEdgeDraggingAreaWidth: shell.edgeSize
299 normalApplicationY: shell.panelHeight
301 shown: true
302 function show() {
303 stages.show();
304 }
305 function showWithoutAnimation() {
306 stages.showWithoutAnimation();
307 }
308 function hide() {
309 }
311 // FIXME: workaround the fact that focusing a main stage application
312 // raises its surface on top of all other surfaces including the ones
313 // that belong to side stage applications.
314 onFocusedApplicationChanged: {
315 if (focusedApplication && sideStage.focusedApplication && sideStage.fullyShown) {
316 shell.applicationManager.focusApplication(sideStage.focusedApplication);
317 }
318 }
319 }
321 SideStage {
322 id: sideStage
324 applicationManager: shell.applicationManager
325 rightEdgeDraggingAreaWidth: shell.edgeSize
326 normalApplicationY: shell.panelHeight
328 onShownChanged: {
329 if (!shown && mainStage.applications.count == 0) {
330 stages.hide();
331 }
332 }
333 // FIXME: when hiding the side stage, refocus the main stage
334 // application so that it goes in front of the side stage
335 // application and hides it
336 onFullyShownChanged: {
337 if (!fullyShown && stages.fullyShown && sideStage.focusedApplication != null) {
338 shell.applicationManager.focusApplication(mainStage.focusedApplication);
339 }
340 }
342 enabled: shell.width >= units.gu(60)
343 visible: enabled
344 fullyShown: stages.fullyShown && shown
345 && sideStage[sideStageRevealer.boundProperty] == sideStageRevealer.openedValue
346 shouldUseScreenshots: !fullyShown || mainStage.usingScreenshots || sideStageRevealer.pressed
348 available: !greeter.shown && !lockscreen.shown && enabled
349 hides: [launcher, panel.indicators]
350 shown: false
351 showAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.openedValue; easing.type: Easing.OutQuint }
352 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.closedValue; easing.type: Easing.OutQuint }
354 width: units.gu(40)
355 height: stages.height
356 handleExpanded: sideStageRevealer.pressed
357 }
359 Revealer {
360 id: sideStageRevealer
362 enabled: mainStage.applications.count > 0 && sideStage.applications.count > 0
363 && sideStage.available
364 direction: Qt.RightToLeft
365 openedValue: parent.width - sideStage.width
366 hintDisplacement: units.gu(3)
367 /* The size of the sidestage handle needs to be bigger than the
368 typical size used for edge detection otherwise it is really
369 hard to grab.
370 */
371 handleSize: sideStage.shown ? units.gu(4) : shell.edgeSize
372 closedValue: parent.width + sideStage.handleSizeCollapsed
373 target: sideStage
374 x: parent.width - width
375 width: sideStage.width + handleSize * 0.7
376 height: sideStage.height
377 orientation: Qt.Horizontal
378 }
380 DragHandle {
381 id: stagesRevealer
383 anchors.top: parent.top
384 anchors.bottom: parent.bottom
385 anchors.right: parent.left
387 width: shell.edgeSize
388 direction: Direction.Leftwards
389 enabled: mainStage.applications.count > 0 || sideStage.applications.count > 0
390 }
391 }
392 }
394 Lockscreen {
395 id: lockscreen
396 hides: [launcher, panel.indicators, hud]
397 shown: false
398 enabled: true
399 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
400 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
401 y: panel.panelHeight
402 x: required ? 0 : - width
403 width: parent.width
404 height: parent.height - panel.panelHeight
405 background: shell.background
407 onUnlocked: lockscreen.hide()
408 onCancel: greeter.show()
410 Component.onCompleted: {
411 if (LightDM.Users.count == 1) {
412 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
413 }
414 }
415 }
417 Connections {
418 target: LightDM.Greeter
420 onShowPrompt: {
421 if (LightDM.Users.count == 1) {
422 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
423 if (text == "PIN") {
424 lockscreen.alphaNumeric = false
425 } else {
426 lockscreen.alphaNumeric = true
427 }
428 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
429 lockscreen.show();
430 }
431 }
432 }
434 Greeter {
435 id: greeter
437 available: true
438 hides: [launcher, panel.indicators, hud]
439 shown: true
441 y: panel.panelHeight
442 width: parent.width
443 height: parent.height - panel.panelHeight
445 dragHandleWidth: shell.edgeSize
447 property var previousMainApp: null
448 property var previousSideApp: null
450 onShownChanged: {
451 if (shown) {
452 lockscreen.reset();
453 // If there is only one user, we start authenticating with that one here.
454 // If there are more users, the Greeter will handle that
455 if (LightDM.Users.count == 1) {
456 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
457 }
458 greeter.forceActiveFocus();
459 // FIXME: *FocusedApplication are not updated when unfocused, hence the need to check whether
460 // the stage was actually shown
461 if (mainStage.fullyShown) greeter.previousMainApp = applicationManager.mainStageFocusedApplication;
462 if (sideStage.fullyShown) greeter.previousSideApp = applicationManager.sideStageFocusedApplication;
463 applicationManager.unfocusCurrentApplication();
464 } else {
465 if (greeter.previousMainApp) {
466 applicationManager.focusApplication(greeter.previousMainApp);
467 greeter.previousMainApp = null;
468 }
469 if (greeter.previousSideApp) {
470 applicationManager.focusApplication(greeter.previousSideApp);
471 greeter.previousSideApp = null;
472 }
473 }
474 }
476 onUnlocked: greeter.hide()
477 onSelected: {
478 var bgPath = greeter.model.data(uid, LightDM.UserRoles.BackgroundPathRole)
479 shell.background = bgPath ? bgPath : default_background
480 }
482 onLeftTeaserPressedChanged: {
483 if (leftTeaserPressed) {
484 launcher.tease();
485 }
486 }
487 }
489 InputFilterArea {
490 anchors.fill: parent
491 blockInput: greeter.shown || lockscreen.shown
492 }
494 Item {
495 id: overlay
497 anchors.fill: parent
499 Panel {
500 id: panel
501 anchors.fill: parent //because this draws indicator menus
502 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
503 indicators {
504 hides: [launcher]
505 }
506 fullscreenMode: shell.fullscreenMode
507 searchVisible: !greeter.shown && !lockscreen.shown
509 InputFilterArea {
510 anchors.fill: parent
511 blockInput: panel.indicators.shown
512 }
513 }
515 Hud {
516 id: hud
518 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
519 height: parent.height
521 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown
522 shown: false
523 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
524 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
526 Connections {
527 target: shell.applicationManager
528 onMainStageFocusedApplicationChanged: hud.hide()
529 onSideStageFocusedApplicationChanged: hud.hide()
530 }
532 InputFilterArea {
533 anchors.fill: parent
534 blockInput: hud.shown
535 }
536 }
538 Revealer {
539 id: hudRevealer
541 enabled: hud.shown
542 width: hud.width
543 anchors.left: hud.left
544 height: parent.height
545 target: hud.revealerTarget
546 closedValue: height
547 openedValue: 0
548 direction: Qt.RightToLeft
549 orientation: Qt.Vertical
550 handleSize: hud.handleHeight
551 onCloseClicked: target.hide()
552 }
554 Bottombar {
555 theHud: hud
556 anchors.fill: parent
557 enabled: !panel.indicators.shown
558 applicationIsOnForeground: applicationManager.mainStageFocusedApplication
559 || applicationManager.sideStageFocusedApplication
560 }
562 InputFilterArea {
563 blockInput: launcher.shown
564 anchors {
565 top: parent.top
566 bottom: parent.bottom
567 left: parent.left
568 }
569 width: launcher.width
570 }
572 Launcher {
573 id: launcher
575 anchors.top: parent.top
576 anchors.bottom: parent.bottom
577 width: parent.width
578 dragAreaWidth: shell.edgeSize
579 available: !greeter.locked
580 onDashItemSelected: {
581 greeter.hide()
582 // Animate if moving between application and dash
583 if (!stages.shown) {
584 dash.setCurrentScope("home.scope", true, false)
585 } else {
586 dash.setCurrentScope("home.scope", false, false)
587 }
588 stages.hide();
589 }
590 onDash: {
591 if (stages.shown) {
592 dash.setCurrentScope("applications.scope", true, false)
593 stages.hide();
594 launcher.hide();
595 }
596 }
597 onLauncherApplicationSelected:{
598 greeter.hide()
599 shell.activateApplication(desktopFile)
600 }
601 onShownChanged: {
602 if (shown) {
603 panel.indicators.hide()
604 hud.hide()
605 }
606 }
607 }
609 Notifications {
610 id: notifications
612 model: NotificationBackend.Model
613 anchors {
614 top: parent.top
615 right: parent.right
616 bottom: parent.bottom
617 leftMargin: units.gu(1)
618 rightMargin: units.gu(1)
619 topMargin: panel.panelHeight + units.gu(1)
620 }
621 states: [
622 State {
623 name: "narrow"
624 when: overlay.width <= units.gu(60)
625 AnchorChanges { target: notifications; anchors.left: parent.left }
626 },
627 State {
628 name: "wide"
629 when: overlay.width > units.gu(60)
630 AnchorChanges { target: notifications; anchors.left: undefined }
631 PropertyChanges { target: notifications; width: units.gu(38) }
632 }
633 ]
634 }
635 }
637 focus: true
639 InputFilterArea {
640 anchors {
641 top: parent.top
642 bottom: parent.bottom
643 left: parent.left
644 }
645 width: shell.edgeSize
646 blockInput: true
647 }
649 InputFilterArea {
650 anchors {
651 top: parent.top
652 bottom: parent.bottom
653 right: parent.right
654 }
655 width: shell.edgeSize
656 blockInput: true
657 }
659 Binding {
660 target: i18n
661 property: "domain"
662 value: "unity8"
663 }
665 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
666 MouseArea {
667 anchors.bottom: parent.bottom
668 anchors.left: parent.left
669 anchors.right: parent.right
670 height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
672 enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
673 }