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