on 70db4ab help sign in
unity8/Shell.qml
648 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 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) : ApplicationArguments.hasGeometry() ? ApplicationArguments.width() : units.gu(40)
40 height: tablet ? units.gu(100) : ApplicationArguments.hasGeometry() ? ApplicationArguments.height() : 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 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
67 applicationManager.sideStageEnabled = Qt.binding(function() { return sideStage.enabled })
69 // FIXME: if application focused before shell starts, shell draws on top of it only.
70 // We should detect already running applications on shell start and bring them to the front.
71 applicationManager.unfocusCurrentApplication();
72 }
74 readonly property bool fullscreenMode: {
75 if (greeter.shown || lockscreen.shown) {
76 return false;
77 } else if (mainStage.usingScreenshots) { // Window Manager animating so want to re-evaluate fullscreen mode
78 return mainStage.switchingFromFullscreenToFullscreen;
79 } else if (applicationManager.mainStageFocusedApplication) {
80 return applicationManager.mainStageFocusedApplication.fullscreen;
81 } else {
82 return false;
83 }
84 }
86 Connections {
87 target: applicationManager
88 ignoreUnknownSignals: true
89 onFocusRequested: {
90 // TODO: this should be protected to only unlock for certain applications / certain usecases
91 // potentially only in connection with a notification
92 greeter.hide();
93 shell.activateApplication(desktopFile);
94 }
95 }
97 function activateApplication(desktopFile, argument) {
98 if (applicationManager) {
99 // For newly started applications, as it takes them time to draw their first frame
100 // we add a delay before we hide the animation screenshots to compensate.
101 var addDelay = !applicationManager.getApplicationFromDesktopFile(desktopFile);
103 var application;
104 application = applicationManager.activateApplication(desktopFile, argument);
105 if (application == null) {
106 return;
107 }
108 if (application.stage == ApplicationInfo.MainStage || !sideStage.enabled) {
109 mainStage.activateApplication(desktopFile, addDelay);
110 } else {
111 sideStage.activateApplication(desktopFile, addDelay);
112 }
113 stages.show();
114 }
115 }
117 VolumeControl {
118 id: volumeControl
119 }
121 Keys.onVolumeUpPressed: volumeControl.volumeUp()
122 Keys.onVolumeDownPressed: volumeControl.volumeDown()
124 Keys.onReleased: {
125 if (event.key == Qt.Key_PowerOff) {
126 greeter.show()
127 }
128 }
130 Item {
131 id: underlay
132 anchors.fill: parent
134 // Whether the underlay is fully covered by opaque UI elements.
135 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
137 // Whether the user should see the topmost application surface (if there's one at all).
138 property bool applicationSurfaceShouldBeSeen:
139 (mainStage.applications && mainStage.applications.count > 0)
140 && (!stages.fullyHidden && !mainStage.usingScreenshots)
142 // NB! Application surfaces are stacked behing the shell one. So they can only be seen by the user
143 // through the translucent parts of the shell surface.
144 visible: !fullyCovered && !applicationSurfaceShouldBeSeen
146 Image {
147 id: backgroundImage
148 source: shell.background
149 sourceSize.width: parent.width
150 sourceSize.height: parent.height
151 anchors.fill: parent
152 }
154 Rectangle {
155 anchors.fill: parent
156 color: "black"
157 opacity: dash.disappearingAnimationProgress
158 }
160 Dash {
161 id: dash
162 objectName: "dash"
164 available: !greeter.shown && !lockscreen.shown
165 hides: [stages, launcher, panel.indicators]
166 shown: disappearingAnimationProgress !== 1.0
167 enabled: disappearingAnimationProgress === 0.0
168 // FIXME: unfocus all applications when going back to the dash
169 onEnabledChanged: {
170 if (enabled) {
171 shell.applicationManager.unfocusCurrentApplication()
172 }
173 }
175 anchors {
176 fill: parent
177 topMargin: panel.panelHeight
178 }
180 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
181 opacity: 1.0 - disappearingAnimationProgress
182 property real disappearingAnimationProgress: {
183 if (greeter.shown) {
184 return greeter.showProgress;
185 } else {
186 return stagesOuterContainer.showProgress;
187 }
188 }
190 // FIXME: only necessary because stagesOuterContainer.showProgress and
191 // greeterRevealer.animatedProgress are not animated
192 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
193 }
194 }
197 Item {
198 id: stagesOuterContainer
200 width: parent.width
201 height: parent.height
202 x: launcher.progress
203 Behavior on x {SmoothedAnimation{velocity: 600}}
205 property real showProgress:
206 MathLocal.clamp(1 - (x + stages.x) / shell.width, 0, 1)
208 Showable {
209 id: stages
211 x: width
213 property bool fullyShown: shown && x == 0 && parent.x == 0
214 property bool fullyHidden: !shown && x == width
215 available: !greeter.shown
216 hides: [launcher, panel.indicators]
217 shown: false
218 opacity: 1.0
219 showAnimation: StandardAnimation { property: "x"; duration: 350; to: 0; easing.type: Easing.OutCubic }
220 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: width; easing.type: Easing.OutCubic }
222 width: parent.width
223 height: parent.height
225 // close the stages when no focused application remains
226 Connections {
227 target: shell.applicationManager
228 onMainStageFocusedApplicationChanged: stages.closeIfNoApplications()
229 onSideStageFocusedApplicationChanged: stages.closeIfNoApplications()
230 ignoreUnknownSignals: true
231 }
233 function closeIfNoApplications() {
234 if (!shell.applicationManager.mainStageFocusedApplication
235 && !shell.applicationManager.sideStageFocusedApplication
236 && shell.applicationManager.mainStageApplications.count == 0
237 && shell.applicationManager.sideStageApplications.count == 0) {
238 stages.hide();
239 }
240 }
242 // show the stages when an application gets the focus
243 Connections {
244 target: shell.applicationManager
245 onMainStageFocusedApplicationChanged: {
246 if (shell.applicationManager.mainStageFocusedApplication) {
247 mainStage.show();
248 stages.show();
249 }
250 }
251 onSideStageFocusedApplicationChanged: {
252 if (shell.applicationManager.sideStageFocusedApplication) {
253 sideStage.show();
254 stages.show();
255 }
256 }
257 ignoreUnknownSignals: true
258 }
261 Stage {
262 id: mainStage
264 anchors.fill: parent
265 fullyShown: stages.fullyShown
266 fullyHidden: stages.fullyHidden
267 shouldUseScreenshots: !fullyShown
268 rightEdgeEnabled: !sideStage.enabled
270 applicationManager: shell.applicationManager
271 rightEdgeDraggingAreaWidth: shell.edgeSize
272 normalApplicationY: shell.panelHeight
274 shown: true
275 function show() {
276 stages.show();
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: stagesDragHandle
353 anchors.top: parent.top
354 anchors.bottom: parent.bottom
355 anchors.right: parent.left
357 width: shell.edgeSize
358 direction: Direction.Leftwards
359 property bool haveApps: mainStage.applications.count > 0 || sideStage.applications.count > 0
361 maxTotalDragDistance: haveApps ? parent.width : parent.width * 0.7
362 // Make autocompletion impossible when !haveApps
363 edgeDragEvaluator.minDragDistance: haveApps ? maxTotalDragDistance * 0.1 : Number.MAX_VALUE
364 }
365 }
366 }
368 Lockscreen {
369 id: lockscreen
370 hides: [launcher, panel.indicators, hud]
371 shown: false
372 enabled: true
373 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
374 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
375 y: panel.panelHeight
376 x: required ? 0 : - width
377 width: parent.width
378 height: parent.height - panel.panelHeight
379 background: shell.background
381 onUnlocked: lockscreen.hide()
382 onCancel: greeter.show()
384 Component.onCompleted: {
385 if (LightDM.Users.count == 1) {
386 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
387 }
388 }
389 }
391 Connections {
392 target: LightDM.Greeter
394 onShowPrompt: {
395 if (LightDM.Users.count == 1) {
396 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
397 if (text == "PIN") {
398 lockscreen.alphaNumeric = false
399 } else {
400 lockscreen.alphaNumeric = true
401 }
402 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
403 lockscreen.show();
404 }
405 }
406 }
408 Greeter {
409 id: greeter
411 available: true
412 hides: [launcher, panel.indicators, hud]
413 shown: true
415 y: panel.panelHeight
416 width: parent.width
417 height: parent.height - panel.panelHeight
419 dragHandleWidth: shell.edgeSize
421 property var previousMainApp: null
422 property var previousSideApp: null
424 onShownChanged: {
425 if (shown) {
426 lockscreen.reset();
427 // If there is only one user, we start authenticating with that one here.
428 // If there are more users, the Greeter will handle that
429 if (LightDM.Users.count == 1) {
430 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
431 }
432 greeter.forceActiveFocus();
433 // FIXME: *FocusedApplication are not updated when unfocused, hence the need to check whether
434 // the stage was actually shown
435 if (mainStage.fullyShown) greeter.previousMainApp = applicationManager.mainStageFocusedApplication;
436 if (sideStage.fullyShown) greeter.previousSideApp = applicationManager.sideStageFocusedApplication;
437 applicationManager.unfocusCurrentApplication();
438 } else {
439 if (greeter.previousMainApp) {
440 applicationManager.focusApplication(greeter.previousMainApp);
441 greeter.previousMainApp = null;
442 }
443 if (greeter.previousSideApp) {
444 applicationManager.focusApplication(greeter.previousSideApp);
445 greeter.previousSideApp = null;
446 }
447 }
448 }
450 onUnlocked: greeter.hide()
451 onSelected: {
452 var bgPath = greeter.model.data(uid, LightDM.UserRoles.BackgroundPathRole)
453 shell.background = bgPath ? bgPath : default_background
454 }
456 onLeftTeaserPressedChanged: {
457 if (leftTeaserPressed) {
458 launcher.tease();
459 }
460 }
461 }
463 InputFilterArea {
464 anchors.fill: parent
465 blockInput: greeter.shown || lockscreen.shown
466 }
468 Item {
469 id: overlay
471 anchors.fill: parent
473 Panel {
474 id: panel
475 anchors.fill: parent //because this draws indicator menus
476 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
477 indicators {
478 hides: [launcher]
479 }
480 fullscreenMode: shell.fullscreenMode
481 searchVisible: !greeter.shown && !lockscreen.shown
483 InputFilterArea {
484 anchors.fill: parent
485 blockInput: panel.indicators.shown
486 }
487 }
489 Hud {
490 id: hud
492 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
493 height: parent.height
495 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown
496 shown: false
497 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
498 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
500 Connections {
501 target: shell.applicationManager
502 onMainStageFocusedApplicationChanged: hud.hide()
503 onSideStageFocusedApplicationChanged: hud.hide()
504 }
506 InputFilterArea {
507 anchors.fill: parent
508 blockInput: hud.shown
509 }
510 }
512 Revealer {
513 id: hudRevealer
515 enabled: hud.shown
516 width: hud.width
517 anchors.left: hud.left
518 height: parent.height
519 target: hud.revealerTarget
520 closedValue: height
521 openedValue: 0
522 direction: Qt.RightToLeft
523 orientation: Qt.Vertical
524 handleSize: hud.handleHeight
525 onCloseClicked: target.hide()
526 }
528 Bottombar {
529 theHud: hud
530 anchors.fill: parent
531 enabled: !panel.indicators.shown
532 applicationIsOnForeground: applicationManager.mainStageFocusedApplication
533 || applicationManager.sideStageFocusedApplication
534 }
536 InputFilterArea {
537 blockInput: launcher.shown
538 anchors {
539 top: parent.top
540 bottom: parent.bottom
541 left: parent.left
542 }
543 width: launcher.width
544 }
546 Launcher {
547 id: launcher
549 anchors.top: parent.top
550 anchors.bottom: parent.bottom
551 width: parent.width
552 dragAreaWidth: shell.edgeSize
553 available: !greeter.locked
554 onDashItemSelected: {
555 greeter.hide()
556 // Animate if moving between application and dash
557 if (!stages.shown) {
558 dash.setCurrentScope("home.scope", true, false)
559 } else {
560 dash.setCurrentScope("home.scope", false, false)
561 }
562 stages.hide();
563 }
564 onDash: {
565 if (stages.shown) {
566 dash.setCurrentScope("applications.scope", true, false)
567 stages.hide();
568 launcher.hide();
569 }
570 }
571 onLauncherApplicationSelected:{
572 greeter.hide()
573 shell.activateApplication(desktopFile)
574 }
575 onShownChanged: {
576 if (shown) {
577 panel.indicators.hide()
578 hud.hide()
579 }
580 }
581 }
583 Notifications {
584 id: notifications
586 model: NotificationBackend.Model
587 anchors {
588 top: parent.top
589 right: parent.right
590 bottom: parent.bottom
591 leftMargin: units.gu(1)
592 rightMargin: units.gu(1)
593 topMargin: panel.panelHeight + units.gu(1)
594 }
595 states: [
596 State {
597 name: "narrow"
598 when: overlay.width <= units.gu(60)
599 AnchorChanges { target: notifications; anchors.left: parent.left }
600 },
601 State {
602 name: "wide"
603 when: overlay.width > units.gu(60)
604 AnchorChanges { target: notifications; anchors.left: undefined }
605 PropertyChanges { target: notifications; width: units.gu(38) }
606 }
607 ]
608 }
609 }
611 focus: true
613 InputFilterArea {
614 anchors {
615 top: parent.top
616 bottom: parent.bottom
617 left: parent.left
618 }
619 width: shell.edgeSize
620 blockInput: true
621 }
623 InputFilterArea {
624 anchors {
625 top: parent.top
626 bottom: parent.bottom
627 right: parent.right
628 }
629 width: shell.edgeSize
630 blockInput: true
631 }
633 Binding {
634 target: i18n
635 property: "domain"
636 value: "unity8"
637 }
639 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
640 MouseArea {
641 anchors.bottom: parent.bottom
642 anchors.left: parent.left
643 anchors.right: parent.right
644 height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
646 enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
647 }