on 44cf7c3 help sign in
unity8/qml/Shell.qml
679 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 AccountsService 0.1
19import GSettings 1.0
20import Unity.Application 0.1
21import Ubuntu.Components 0.1
22import Ubuntu.Gestures 0.1
23import Unity.Launcher 0.1
24import LightDM 0.1 as LightDM
25import Powerd 0.1
26import SessionBroadcast 0.1
27import "Dash"
28import "Greeter"
29import "Launcher"
30import "Panel"
31import "Hud"
32import "Components"
33import "Bottombar"
34import "Notifications"
35import Unity.Notifications 1.0 as NotificationBackend
37FocusScope {
38 id: shell
40 // this is only here to select the width / height of the window if not running fullscreen
41 property bool tablet: false
42 width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
43 height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
45 property real edgeSize: units.gu(2)
46 property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
47 property url background
48 readonly property real panelHeight: panel.panelHeight
50 property bool dashShown: dash.shown
52 property bool sideStageEnabled: shell.width >= units.gu(60)
54 function activateApplication(appId) {
55 if (ApplicationManager.findApplication(appId)) {
56 ApplicationManager.activateApplication(appId);
57 stages.show();
58 } else {
59 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
60 ApplicationManager.startApplication(appId, execFlags);
61 stages.show();
62 }
63 }
65 Binding {
66 target: LauncherModel
67 property: "applicationManager"
68 value: ApplicationManager
69 }
71 Component.onCompleted: {
72 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
73 }
75 GSettings {
76 id: backgroundSettings
77 schema.id: "org.gnome.desktop.background"
78 }
79 property url gSettingsPicture: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : shell.defaultBackground
80 onGSettingsPictureChanged: {
81 shell.background = gSettingsPicture
82 }
84 // This is a dummy image that is needed to determine if the picture url
85 // in backgroundSettings points to a valid picture file.
86 // We can't do this with the real background image because setting a
87 // new source in onStatusChanged triggers a binding loop detection
88 // inside Image, which causes it not to render even though a valid source
89 // would be set. We don't mind about this image staying black and just
90 // use it for verification to populate the source for the real
91 // background image.
92 Image {
93 source: shell.background
94 height: 0
95 width: 0
96 sourceSize.height: 0
97 sourceSize.width: 0
98 onStatusChanged: {
99 if (status == Image.Error && source != shell.defaultBackground) {
100 shell.background = defaultBackground
101 }
102 }
103 }
105 VolumeControl {
106 id: volumeControl
107 }
109 Keys.onVolumeUpPressed: volumeControl.volumeUp()
110 Keys.onVolumeDownPressed: volumeControl.volumeDown()
112 Item {
113 id: underlayClipper
114 anchors.fill: parent
115 anchors.rightMargin: stages.overlayWidth
116 clip: stages.overlayMode && !stages.painting
118 Item {
119 id: underlay
120 objectName: "underlay"
121 anchors.fill: parent
122 anchors.rightMargin: -parent.anchors.rightMargin
124 // Whether the underlay is fully covered by opaque UI elements.
125 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
127 readonly property bool applicationRunning: ApplicationManager.focusedApplicationId.length > 0
129 // Whether the user should see the topmost application surface (if there's one at all).
130 readonly property bool applicationSurfaceShouldBeSeen: stages.shown && !stages.painting && !stages.overlayMode
132 // NB! Application surfaces are stacked behind the shell one. So they can only be seen by the user
133 // through the translucent parts of the shell surface.
134 visible: !fullyCovered && !applicationSurfaceShouldBeSeen
136 CrossFadeImage {
137 id: backgroundImage
138 objectName: "backgroundImage"
140 anchors.fill: parent
141 source: shell.background
142 fillMode: Image.PreserveAspectCrop
143 }
145 Rectangle {
146 anchors.fill: parent
147 color: "black"
148 opacity: dash.disappearingAnimationProgress
149 }
151 Dash {
152 id: dash
153 objectName: "dash"
155 available: !greeter.shown && !lockscreen.shown
156 hides: [stages, launcher, panel.indicators]
157 shown: disappearingAnimationProgress !== 1.0
158 enabled: disappearingAnimationProgress === 0.0 && edgeDemo.dashEnabled
160 anchors {
161 fill: parent
162 topMargin: panel.panelHeight
163 }
165 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
166 opacity: 1.0 - disappearingAnimationProgress
167 property real disappearingAnimationProgress: {
168 if (greeter.shown) {
169 return greeter.showProgress;
170 } else {
171 if (stages.overlayMode) {
172 return 0;
173 }
174 return stages.showProgress
175 }
176 }
178 // FIXME: only necessary because stages.showProgress and
179 // greeterRevealer.animatedProgress are not animated
180 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
181 }
182 }
183 }
185 EdgeDragArea {
186 id: stagesDragHandle
187 direction: Direction.Leftwards
189 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
190 width: shell.edgeSize
192 property real progress: stages.width
194 onTouchXChanged: {
195 if (status == DirectionalDragArea.Recognized) {
196 if (ApplicationManager.count == 0) {
197 progress = Math.max(stages.width - stagesDragHandle.width + touchX, stages.width * .3)
198 } else {
199 progress = stages.width - stagesDragHandle.width + touchX
200 }
201 }
202 }
204 onDraggingChanged: {
205 if (!dragging) {
206 if (ApplicationManager.count > 0 && progress < stages.width - units.gu(10)) {
207 stages.show()
208 }
209 stagesDragHandle.progress = stages.width;
210 }
211 }
212 }
214 Item {
215 id: stages
216 objectName: "stages"
217 width: parent.width
218 height: parent.height
220 x: {
221 if (shown) {
222 if (overlayMode || locked) {
223 return 0;
224 }
225 return launcher.progress
226 } else {
227 return stagesDragHandle.progress
228 }
229 }
231 Behavior on x { SmoothedAnimation { velocity: 600; duration: UbuntuAnimation.FastDuration } }
233 property bool shown: false
235 property real showProgress: overlayMode ? 0 : MathUtils.clamp(1 - x / shell.width, 0, 1)
237 property bool fullyShown: x == 0
238 property bool fullyHidden: x == width
240 property bool painting: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.painting : false
241 property bool fullscreen: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.fullscreen : false
242 property bool overlayMode: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayMode : false
243 property int overlayWidth: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayWidth : false
244 property bool locked: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.locked : false
246 function show() {
247 shown = true;
248 panel.indicators.hide();
249 if (!ApplicationManager.focusedApplicationId && ApplicationManager.count > 0) {
250 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
251 }
252 }
254 function hide() {
255 if (locked) {
256 return;
257 }
259 shown = false;
260 if (ApplicationManager.focusedApplicationId) {
261 ApplicationManager.unfocusCurrentApplication();
262 }
263 }
265 Connections {
266 target: ApplicationManager
268 onFocusRequested: {
269 stages.show();
270 }
272 onFocusedApplicationIdChanged: {
273 if (ApplicationManager.focusedApplicationId.length > 0) {
274 stages.show();
275 } else {
276 if (!stages.overlayMode) {
277 stages.hide();
278 }
279 }
280 }
282 onApplicationAdded: {
283 stages.show();
284 }
285 }
287 Loader {
288 id: applicationsDisplayLoader
289 anchors.fill: parent
291 source: shell.sideStageEnabled ? "Stages/StageWithSideStage.qml" : "Stages/PhoneStage.qml"
293 Binding {
294 target: applicationsDisplayLoader.item
295 property: "moving"
296 value: !stages.fullyShown
297 }
298 Binding {
299 target: applicationsDisplayLoader.item
300 property: "shown"
301 value: stages.shown
302 }
303 Binding {
304 target: applicationsDisplayLoader.item
305 property: "dragAreaWidth"
306 value: shell.edgeSize
307 }
308 }
309 }
311 Lockscreen {
312 id: lockscreen
313 objectName: "lockscreen"
315 readonly property int backgroundTopMargin: -panel.panelHeight
317 hides: [launcher, panel.indicators, hud]
318 shown: false
319 enabled: true
320 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
321 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
322 y: panel.panelHeight
323 x: required ? 0 : - width
324 width: parent.width
325 height: parent.height - panel.panelHeight
326 background: shell.background
327 pinLength: 4
329 onEntered: LightDM.Greeter.respond(passphrase);
330 onCancel: greeter.show()
332 Component.onCompleted: {
333 if (LightDM.Users.count == 1) {
334 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
335 }
336 }
337 }
339 Connections {
340 target: LightDM.Greeter
342 onShowPrompt: {
343 if (LightDM.Users.count == 1) {
344 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
345 if (text == "PIN") {
346 lockscreen.alphaNumeric = false
347 } else {
348 lockscreen.alphaNumeric = true
349 }
350 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
351 lockscreen.show();
352 }
353 }
355 onAuthenticationComplete: {
356 if (LightDM.Greeter.promptless) {
357 return;
358 }
359 if (LightDM.Greeter.authenticated) {
360 lockscreen.hide();
361 } else {
362 lockscreen.clear(true);
363 }
364 }
365 }
367 Greeter {
368 id: greeter
369 objectName: "greeter"
371 available: true
372 hides: [launcher, panel.indicators, hud]
373 shown: true
375 defaultBackground: shell.background
377 y: panel.panelHeight
378 width: parent.width
379 height: parent.height - panel.panelHeight
381 dragHandleWidth: shell.edgeSize
383 onShownChanged: {
384 if (shown) {
385 lockscreen.reset();
386 // If there is only one user, we start authenticating with that one here.
387 // If there are more users, the Greeter will handle that
388 if (LightDM.Users.count == 1) {
389 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
390 }
391 greeter.forceActiveFocus();
392 }
393 }
395 onUnlocked: greeter.hide()
396 onSelected: {
397 // Update launcher items for new user
398 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
399 AccountsService.user = user;
400 LauncherModel.setUser(user);
401 }
403 onLeftTeaserPressedChanged: {
404 if (leftTeaserPressed) {
405 launcher.tease();
406 }
407 }
409 Binding {
410 target: ApplicationManager
411 property: "suspended"
412 value: greeter.shown
413 }
414 }
416 InputFilterArea {
417 anchors.fill: parent
418 blockInput: ApplicationManager.focusedApplicationId.length === 0 || greeter.shown || lockscreen.shown || launcher.shown
419 || panel.indicators.shown || hud.shown
420 }
422 Connections {
423 id: powerConnection
424 target: Powerd
426 onDisplayPowerStateChange: {
427 // We ignore any display-off signals when the proximity sensor
428 // is active. This usually indicates something like a phone call.
429 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
430 greeter.showNow();
431 }
433 // No reason to chew demo CPU when user isn't watching
434 if (status == Powerd.Off) {
435 edgeDemo.paused = true;
436 } else if (status == Powerd.On) {
437 edgeDemo.paused = false;
438 }
439 }
440 }
442 function showHome() {
443 var animate = !greeter.shown && !stages.shown
444 greeter.hide()
445 dash.setCurrentScope("home.scope", animate, false)
446 stages.hide()
447 }
449 function hideIndicatorMenu(delay) {
450 panel.hideIndicatorMenu(delay);
451 }
453 Item {
454 id: overlay
456 anchors.fill: parent
458 Panel {
459 id: panel
460 anchors.fill: parent //because this draws indicator menus
461 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
462 indicators {
463 hides: [launcher]
464 available: edgeDemo.panelEnabled
465 contentEnabled: edgeDemo.panelContentEnabled
466 }
467 property string focusedAppId: ApplicationManager.focusedApplicationId
468 property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
469 fullscreenMode: focusedApplication && stages.fullscreen && !greeter.shown && !lockscreen.shown
470 searchVisible: !greeter.shown && !lockscreen.shown && dash.shown
472 InputFilterArea {
473 anchors {
474 top: parent.top
475 left: parent.left
476 right: parent.right
477 }
478 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
479 blockInput: true
480 }
481 }
483 Hud {
484 id: hud
486 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
487 height: parent.height
489 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown && edgeDemo.dashEnabled
490 shown: false
491 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
492 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
494 Connections {
495 target: ApplicationManager
496 onFocusedApplicationIdChanged: hud.hide()
497 }
498 }
500 Revealer {
501 id: hudRevealer
503 enabled: hud.shown
504 width: hud.width
505 anchors.left: hud.left
506 height: parent.height
507 target: hud.revealerTarget
508 closedValue: height
509 openedValue: 0
510 direction: Qt.RightToLeft
511 orientation: Qt.Vertical
512 handleSize: hud.handleHeight
513 onCloseClicked: target.hide()
514 }
516 Bottombar {
517 id: bottombar
518 theHud: hud
519 anchors.fill: parent
520 enabled: hud.available
521 applicationIsOnForeground: ApplicationManager.focusedApplicationId
522 }
524 InputFilterArea {
525 blockInput: launcher.shown
526 anchors {
527 top: parent.top
528 bottom: parent.bottom
529 left: parent.left
530 }
531 width: launcher.width
532 }
534 Launcher {
535 id: launcher
537 readonly property bool dashSwipe: progress > 0
539 anchors.top: parent.top
540 anchors.bottom: parent.bottom
541 width: parent.width
542 dragAreaWidth: shell.edgeSize
543 available: (!greeter.shown || greeter.narrowMode) && edgeDemo.launcherEnabled
545 onShowDashHome: {
546 if (edgeDemo.running)
547 return;
549 showHome()
550 }
551 onDash: {
552 if (stages.shown && !stages.overlayMode) {
553 stages.hide();
554 launcher.hide();
555 }
556 }
557 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("applications.scope", false, true)
558 onLauncherApplicationSelected:{
559 if (edgeDemo.running)
560 return;
562 greeter.hide()
563 shell.activateApplication(appId)
564 }
565 onShownChanged: {
566 if (shown) {
567 panel.indicators.hide()
568 hud.hide()
569 bottombar.hide()
570 }
571 }
572 }
574 Notifications {
575 id: notifications
577 model: NotificationBackend.Model
578 margin: units.gu(1)
580 anchors {
581 top: parent.top
582 right: parent.right
583 bottom: parent.bottom
584 topMargin: panel.panelHeight
585 }
586 states: [
587 State {
588 name: "narrow"
589 when: overlay.width <= units.gu(60)
590 AnchorChanges { target: notifications; anchors.left: parent.left }
591 },
592 State {
593 name: "wide"
594 when: overlay.width > units.gu(60)
595 AnchorChanges { target: notifications; anchors.left: undefined }
596 PropertyChanges { target: notifications; width: units.gu(38) }
597 }
598 ]
600 InputFilterArea {
601 anchors { left: parent.left; right: parent.right }
602 height: parent.contentHeight
603 blockInput: height > 0
604 }
605 }
606 }
608 focus: true
609 onFocusChanged: if (!focus) forceActiveFocus();
611 InputFilterArea {
612 anchors {
613 top: parent.top
614 bottom: parent.bottom
615 left: parent.left
616 }
617 width: shell.edgeSize
618 blockInput: true
619 }
621 InputFilterArea {
622 anchors {
623 top: parent.top
624 bottom: parent.bottom
625 right: parent.right
626 }
627 width: shell.edgeSize
628 blockInput: true
629 }
631 Binding {
632 target: i18n
633 property: "domain"
634 value: "unity8"
635 }
637 OSKController {
638 anchors.topMargin: panel.panelHeight
639 anchors.fill: parent // as needs to know the geometry of the shell
640 }
642 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
643 MouseArea {
644 anchors.bottom: parent.bottom
645 anchors.left: parent.left
646 anchors.right: parent.right
647 height: ApplicationManager.keyboardVisible ? ApplicationManager.keyboardHeight : 0
649 enabled: ApplicationManager.keyboardVisible
650 }
652 Label {
653 anchors.centerIn: parent
654 visible: ApplicationManager.fake ? ApplicationManager.fake : false
655 text: "EARLY ALPHA\nNOT READY FOR USE"
656 color: "lightgrey"
657 opacity: 0.2
658 font.weight: Font.Black
659 horizontalAlignment: Text.AlignHCenter
660 verticalAlignment: Text.AlignVCenter
661 fontSizeMode: Text.Fit
662 rotation: -45
663 scale: Math.min(parent.width, parent.height) / width
664 }
666 EdgeDemo {
667 id: edgeDemo
668 greeter: greeter
669 launcher: launcher
670 dash: dash
671 indicators: panel.indicators
672 underlay: underlay
673 }
675 Connections {
676 target: SessionBroadcast
677 onShowHome: showHome()
678 }