on d23453f 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 property real showAnimationProgress: MathLocal.clamp(1 + x / width, 0, 1)
370 onUnlocked: lockscreen.hide()
371 onCancel: greeter.show()
373 Component.onCompleted: {
374 if (LightDM.Users.count == 1) {
375 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
376 }
377 }
378 }
380 Connections {
381 target: LightDM.Greeter
383 onShowPrompt: {
384 if (LightDM.Users.count == 1) {
385 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
386 if (text == "PIN") {
387 lockscreen.alphaNumeric = false
388 } else {
389 lockscreen.alphaNumeric = true
390 }
391 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
392 lockscreen.show();
393 }
394 }
395 }
397 Greeter {
398 id: greeter
400 available: true
401 hides: [launcher, panel.indicators, hud]
402 shown: true
404 y: panel.panelHeight
405 width: parent.width
406 height: parent.height - panel.panelHeight
408 dragHandleWidth: shell.edgeSize
410 property var previousMainApp: null
411 property var previousSideApp: null
413 onShownChanged: {
414 if (shown) {
415 lockscreen.reset();
416 // If there is only one user, we start authenticating with that one here.
417 // If there are more users, the Greeter will handle that
418 if (LightDM.Users.count == 1) {
419 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
420 }
421 greeter.forceActiveFocus();
422 // FIXME: *FocusedApplication are not updated when unfocused, hence the need to check whether
423 // the stage was actually shown
424 if (mainStage.fullyShown) greeter.previousMainApp = applicationManager.mainStageFocusedApplication;
425 if (sideStage.fullyShown) greeter.previousSideApp = applicationManager.sideStageFocusedApplication;
426 applicationManager.unfocusCurrentApplication();
427 } else {
428 if (greeter.previousMainApp) {
429 applicationManager.focusApplication(greeter.previousMainApp);
430 greeter.previousMainApp = null;
431 }
432 if (greeter.previousSideApp) {
433 applicationManager.focusApplication(greeter.previousSideApp);
434 greeter.previousSideApp = null;
435 }
436 }
437 }
439 onUnlocked: greeter.hide()
440 onSelected: {
441 var bgPath = greeter.model.data(uid, LightDM.UserRoles.BackgroundPathRole)
442 shell.background = bgPath ? bgPath : default_background
443 }
445 }
447 InputFilterArea {
448 anchors.fill: parent
449 blockInput: greeter.shown || lockscreen.shown
450 }
452 Item {
453 id: overlay
455 anchors.fill: parent
457 Panel {
458 id: panel
459 anchors.fill: parent //because this draws indicator menus
460 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
461 indicators {
462 hides: [launcher]
463 }
464 fullscreenMode: shell.fullscreenMode
465 searchVisible: !greeter.shown && !lockscreen.shown
467 InputFilterArea {
468 anchors.fill: parent
469 blockInput: panel.indicators.shown
470 }
471 }
473 Hud {
474 id: hud
476 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
477 height: parent.height
479 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown
480 shown: false
481 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
482 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
484 Connections {
485 target: shell.applicationManager
486 onMainStageFocusedApplicationChanged: hud.hide()
487 onSideStageFocusedApplicationChanged: hud.hide()
488 }
490 InputFilterArea {
491 anchors.fill: parent
492 blockInput: hud.shown
493 }
494 }
496 Revealer {
497 id: hudRevealer
499 enabled: hud.shown
500 width: hud.width
501 anchors.left: hud.left
502 height: parent.height
503 target: hud.revealerTarget
504 closedValue: height
505 openedValue: 0
506 direction: Qt.RightToLeft
507 orientation: Qt.Vertical
508 handleSize: hud.handleHeight
509 onCloseClicked: target.hide()
510 }
512 Bottombar {
513 theHud: hud
514 anchors.fill: parent
515 enabled: !panel.indicators.shown
516 applicationIsOnForeground: applicationManager.mainStageFocusedApplication
517 || applicationManager.sideStageFocusedApplication
518 }
520 InputFilterArea {
521 blockInput: launcher.shown
522 anchors {
523 top: parent.top
524 bottom: parent.bottom
525 left: parent.left
526 }
527 width: launcher.width
528 }
530 Launcher {
531 id: launcher
533 anchors.top: parent.top
534 anchors.bottom: parent.bottom
535 width: parent.width
536 dragAreaWidth: shell.edgeSize
537 available: !greeter.locked
538 teasing: available && greeter.leftTeaserPressed
539 onDashItemSelected: {
540 greeter.hide()
541 // Animate if moving between application and dash
542 if (!stages.shown) {
543 dash.setCurrentScope("home.scope", true, false)
544 } else {
545 dash.setCurrentScope("home.scope", false, false)
546 }
547 stages.hide();
548 }
549 onDash: {
550 dash.setCurrentScope("applications.scope", true, false)
551 stages.hide();
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 }