on dc9964e help sign in
unity8/Shell.qml
630 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 }
442 }
444 InputFilterArea {
445 anchors.fill: parent
446 blockInput: greeter.shown || lockscreen.shown
447 }
449 Item {
450 id: overlay
452 anchors.fill: parent
454 Panel {
455 id: panel
456 anchors.fill: parent //because this draws indicator menus
457 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
458 indicators {
459 hides: [launcher]
460 }
461 fullscreenMode: shell.fullscreenMode
462 searchVisible: !greeter.shown && !lockscreen.shown
464 InputFilterArea {
465 anchors.fill: parent
466 blockInput: panel.indicators.shown
467 }
468 }
470 Hud {
471 id: hud
473 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
474 height: parent.height
476 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown
477 shown: false
478 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
479 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
481 Connections {
482 target: shell.applicationManager
483 onMainStageFocusedApplicationChanged: hud.hide()
484 onSideStageFocusedApplicationChanged: hud.hide()
485 }
487 InputFilterArea {
488 anchors.fill: parent
489 blockInput: hud.shown
490 }
491 }
493 Revealer {
494 id: hudRevealer
496 enabled: hud.shown
497 width: hud.width
498 anchors.left: hud.left
499 height: parent.height
500 target: hud.revealerTarget
501 closedValue: height
502 openedValue: 0
503 direction: Qt.RightToLeft
504 orientation: Qt.Vertical
505 handleSize: hud.handleHeight
506 onCloseClicked: target.hide()
507 }
509 Bottombar {
510 theHud: hud
511 anchors.fill: parent
512 enabled: !panel.indicators.shown
513 applicationIsOnForeground: applicationManager.mainStageFocusedApplication
514 || applicationManager.sideStageFocusedApplication
515 }
517 InputFilterArea {
518 blockInput: launcher.shown
519 anchors {
520 top: parent.top
521 bottom: parent.bottom
522 left: parent.left
523 }
524 width: launcher.width
525 }
527 Launcher {
528 id: launcher
530 anchors.top: parent.top
531 anchors.bottom: parent.bottom
532 width: parent.width
533 dragAreaWidth: shell.edgeSize
534 available: !greeter.locked
535 teasing: available && greeter.leftTeaserPressed
536 onDashItemSelected: {
537 greeter.hide()
538 // Animate if moving between application and dash
539 if (!stages.shown) {
540 dash.setCurrentScope("home.scope", true, false)
541 } else {
542 dash.setCurrentScope("home.scope", false, false)
543 }
544 stages.hide();
545 }
546 onDash: {
547 if (stages.shown) {
548 dash.setCurrentScope("applications.scope", true, false)
549 stages.hide();
550 launcher.hide();
551 }
552 }
553 onLauncherApplicationSelected:{
554 greeter.hide()
555 shell.activateApplication(desktopFile)
556 }
557 onShownChanged: {
558 if (shown) {
559 panel.indicators.hide()
560 hud.hide()
561 }
562 }
563 }
565 Notifications {
566 id: notifications
568 model: NotificationBackend.Model
569 anchors {
570 top: parent.top
571 right: parent.right
572 bottom: parent.bottom
573 leftMargin: units.gu(1)
574 rightMargin: units.gu(1)
575 topMargin: panel.panelHeight + units.gu(1)
576 }
577 states: [
578 State {
579 name: "narrow"
580 when: overlay.width <= units.gu(60)
581 AnchorChanges { target: notifications; anchors.left: parent.left }
582 },
583 State {
584 name: "wide"
585 when: overlay.width > units.gu(60)
586 AnchorChanges { target: notifications; anchors.left: undefined }
587 PropertyChanges { target: notifications; width: units.gu(38) }
588 }
589 ]
590 }
591 }
593 focus: true
595 InputFilterArea {
596 anchors {
597 top: parent.top
598 bottom: parent.bottom
599 left: parent.left
600 }
601 width: shell.edgeSize
602 blockInput: true
603 }
605 InputFilterArea {
606 anchors {
607 top: parent.top
608 bottom: parent.bottom
609 right: parent.right
610 }
611 width: shell.edgeSize
612 blockInput: true
613 }
615 Binding {
616 target: i18n
617 property: "domain"
618 value: "unity8"
619 }
621 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
622 MouseArea {
623 anchors.bottom: parent.bottom
624 anchors.left: parent.left
625 anchors.right: parent.right
626 height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
628 enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
629 }