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