on d2b4ff8 help sign in
unity8/qml/Shell.qml
680 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.requestFocusApplication(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 // Whether the user should see the topmost application surface (if there's one at all).
128 readonly property bool applicationSurfaceShouldBeSeen: stages.shown && !stages.painting && !stages.overlayMode
130 // NB! Application surfaces are stacked behind the shell one. So they can only be seen by the user
131 // through the translucent parts of the shell surface.
132 visible: !fullyCovered && !applicationSurfaceShouldBeSeen
134 CrossFadeImage {
135 id: backgroundImage
136 objectName: "backgroundImage"
138 anchors.fill: parent
139 source: shell.background
140 fillMode: Image.PreserveAspectCrop
141 }
143 Rectangle {
144 anchors.fill: parent
145 color: "black"
146 opacity: dash.disappearingAnimationProgress
147 }
149 Dash {
150 id: dash
151 objectName: "dash"
153 available: !greeter.shown && !lockscreen.shown
154 hides: [stages, launcher, panel.indicators]
155 shown: disappearingAnimationProgress !== 1.0
156 enabled: disappearingAnimationProgress === 0.0 && edgeDemo.dashEnabled
158 anchors {
159 fill: parent
160 topMargin: panel.panelHeight
161 }
163 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
164 opacity: 1.0 - disappearingAnimationProgress
165 property real disappearingAnimationProgress: {
166 if (greeter.shown) {
167 return greeter.showProgress;
168 } else {
169 if (stages.overlayMode) {
170 return 0;
171 }
172 return stages.showProgress
173 }
174 }
176 // FIXME: only necessary because stages.showProgress and
177 // greeterRevealer.animatedProgress are not animated
178 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
179 }
180 }
181 }
183 EdgeDragArea {
184 id: stagesDragHandle
185 direction: Direction.Leftwards
187 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
188 width: shell.edgeSize
190 property real progress: stages.width
192 onTouchXChanged: {
193 if (status == DirectionalDragArea.Recognized) {
194 if (ApplicationManager.count == 0) {
195 progress = Math.max(stages.width - stagesDragHandle.width + touchX, stages.width * .3)
196 } else {
197 progress = stages.width - stagesDragHandle.width + touchX
198 }
199 }
200 }
202 onDraggingChanged: {
203 if (!dragging) {
204 if (ApplicationManager.count > 0 && progress < stages.width - units.gu(10)) {
205 stages.show()
206 }
207 stagesDragHandle.progress = stages.width;
208 }
209 }
210 }
212 Item {
213 id: stages
214 objectName: "stages"
215 width: parent.width
216 height: parent.height
218 x: {
219 if (shown) {
220 if (overlayMode || locked) {
221 return 0;
222 }
223 return launcher.progress
224 } else {
225 return stagesDragHandle.progress
226 }
227 }
229 Behavior on x { SmoothedAnimation { velocity: 600; duration: UbuntuAnimation.FastDuration } }
231 property bool shown: false
233 property real showProgress: overlayMode ? 0 : MathUtils.clamp(1 - x / shell.width, 0, 1)
235 property bool fullyShown: x == 0
236 property bool fullyHidden: x == width
238 property bool painting: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.painting : false
239 property bool fullscreen: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.fullscreen : false
240 property bool overlayMode: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayMode : false
241 property int overlayWidth: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayWidth : false
242 property bool locked: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.locked : false
244 function show() {
245 shown = true;
246 panel.indicators.hide();
247 if (!ApplicationManager.focusedApplicationId && ApplicationManager.count > 0) {
248 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
249 }
250 }
252 function hide() {
253 if (locked) {
254 return;
255 }
256 forceHide();
257 }
259 function forceHide() {
260 shown = false;
261 if (ApplicationManager.focusedApplicationId) {
262 ApplicationManager.unfocusCurrentApplication();
263 }
264 }
266 Connections {
267 target: ApplicationManager
269 onFocusRequested: {
270 stages.show();
271 }
273 onFocusedApplicationIdChanged: {
274 if (ApplicationManager.focusedApplicationId.length > 0) {
275 stages.show();
276 } else {
277 if (!stages.overlayMode) {
278 stages.hide();
279 }
280 }
281 }
283 onApplicationAdded: {
284 stages.show();
285 }
286 }
288 Loader {
289 id: applicationsDisplayLoader
290 anchors.fill: parent
292 source: shell.sideStageEnabled ? "Stages/StageWithSideStage.qml" : "Stages/PhoneStage.qml"
294 Binding {
295 target: applicationsDisplayLoader.item
296 property: "moving"
297 value: !stages.fullyShown
298 }
299 Binding {
300 target: applicationsDisplayLoader.item
301 property: "shown"
302 value: stages.shown
303 }
304 Binding {
305 target: applicationsDisplayLoader.item
306 property: "dragAreaWidth"
307 value: shell.edgeSize
308 }
309 }
310 }
312 Lockscreen {
313 id: lockscreen
314 objectName: "lockscreen"
316 readonly property int backgroundTopMargin: -panel.panelHeight
318 hides: [launcher, panel.indicators, hud]
319 shown: false
320 enabled: true
321 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
322 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
323 y: panel.panelHeight
324 x: required ? 0 : - width
325 width: parent.width
326 height: parent.height - panel.panelHeight
327 background: shell.background
328 pinLength: 4
330 onEntered: LightDM.Greeter.respond(passphrase);
331 onCancel: greeter.show()
333 Component.onCompleted: {
334 if (LightDM.Users.count == 1) {
335 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
336 }
337 }
338 }
340 Connections {
341 target: LightDM.Greeter
343 onShowPrompt: {
344 if (LightDM.Users.count == 1) {
345 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
346 if (text == "PIN") {
347 lockscreen.alphaNumeric = false
348 } else {
349 lockscreen.alphaNumeric = true
350 }
351 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
352 lockscreen.show();
353 }
354 }
356 onAuthenticationComplete: {
357 if (LightDM.Greeter.promptless) {
358 return;
359 }
360 if (LightDM.Greeter.authenticated) {
361 lockscreen.hide();
362 } else {
363 lockscreen.clear(true);
364 }
365 }
366 }
368 Greeter {
369 id: greeter
370 objectName: "greeter"
372 available: true
373 hides: [launcher, panel.indicators, hud]
374 shown: true
376 defaultBackground: shell.background
378 y: panel.panelHeight
379 width: parent.width
380 height: parent.height - panel.panelHeight
382 dragHandleWidth: shell.edgeSize
384 onShownChanged: {
385 if (shown) {
386 lockscreen.reset();
387 // If there is only one user, we start authenticating with that one here.
388 // If there are more users, the Greeter will handle that
389 if (LightDM.Users.count == 1) {
390 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
391 }
392 greeter.forceActiveFocus();
393 }
394 }
396 onUnlocked: greeter.hide()
397 onSelected: {
398 // Update launcher items for new user
399 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
400 AccountsService.user = user;
401 LauncherModel.setUser(user);
402 }
404 onLeftTeaserPressedChanged: {
405 if (leftTeaserPressed) {
406 launcher.tease();
407 }
408 }
410 Binding {
411 target: ApplicationManager
412 property: "suspended"
413 value: greeter.shown && greeter.showProgress == 1
414 }
415 }
417 InputFilterArea {
418 anchors.fill: parent
419 blockInput: ApplicationManager.focusedApplicationId.length === 0 || greeter.shown || lockscreen.shown || launcher.shown
420 || panel.indicators.shown || hud.shown
421 }
423 Connections {
424 id: powerConnection
425 target: Powerd
427 onDisplayPowerStateChange: {
428 // We ignore any display-off signals when the proximity sensor
429 // is active. This usually indicates something like a phone call.
430 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
431 greeter.showNow();
432 }
434 // No reason to chew demo CPU when user isn't watching
435 if (status == Powerd.Off) {
436 edgeDemo.paused = true;
437 } else if (status == Powerd.On) {
438 edgeDemo.paused = false;
439 }
440 }
441 }
443 function showHome() {
444 var animate = !greeter.shown && !stages.shown
445 greeter.hide()
446 dash.setCurrentScope("home.scope", animate, false)
447 stages.forceHide()
448 }
450 function hideIndicatorMenu(delay) {
451 panel.hideIndicatorMenu(delay);
452 }
454 Item {
455 id: overlay
457 anchors.fill: parent
459 Panel {
460 id: panel
461 anchors.fill: parent //because this draws indicator menus
462 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
463 indicators {
464 hides: [launcher]
465 available: edgeDemo.panelEnabled
466 contentEnabled: edgeDemo.panelContentEnabled
467 }
468 property string focusedAppId: ApplicationManager.focusedApplicationId
469 property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
470 fullscreenMode: focusedApplication && stages.fullscreen && !greeter.shown && !lockscreen.shown
471 searchVisible: !greeter.shown && !lockscreen.shown && dash.shown
473 InputFilterArea {
474 anchors {
475 top: parent.top
476 left: parent.left
477 right: parent.right
478 }
479 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
480 blockInput: true
481 }
482 }
484 Hud {
485 id: hud
487 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
488 height: parent.height
490 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown && edgeDemo.dashEnabled
491 shown: false
492 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
493 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
495 Connections {
496 target: ApplicationManager
497 onFocusedApplicationIdChanged: hud.hide()
498 }
499 }
501 Revealer {
502 id: hudRevealer
504 enabled: hud.shown
505 width: hud.width
506 anchors.left: hud.left
507 height: parent.height
508 target: hud.revealerTarget
509 closedValue: height
510 openedValue: 0
511 direction: Qt.RightToLeft
512 orientation: Qt.Vertical
513 handleSize: hud.handleHeight
514 onCloseClicked: target.hide()
515 }
517 Bottombar {
518 id: bottombar
519 theHud: hud
520 anchors.fill: parent
521 enabled: hud.available
522 applicationIsOnForeground: ApplicationManager.focusedApplicationId
523 }
525 InputFilterArea {
526 blockInput: launcher.shown
527 anchors {
528 top: parent.top
529 bottom: parent.bottom
530 left: parent.left
531 }
532 width: launcher.width
533 }
535 Launcher {
536 id: launcher
538 readonly property bool dashSwipe: progress > 0
540 anchors.top: parent.top
541 anchors.bottom: parent.bottom
542 width: parent.width
543 dragAreaWidth: shell.edgeSize
544 available: (!greeter.shown || greeter.narrowMode) && edgeDemo.launcherEnabled
546 onShowDashHome: {
547 if (edgeDemo.running)
548 return;
550 showHome()
551 }
552 onDash: {
553 if (stages.shown && !stages.overlayMode) {
554 stages.hide();
555 launcher.hide();
556 }
557 }
558 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("applications.scope", false, true)
559 onLauncherApplicationSelected:{
560 if (edgeDemo.running)
561 return;
563 greeter.hide()
564 shell.activateApplication(appId)
565 }
566 onShownChanged: {
567 if (shown) {
568 panel.indicators.hide()
569 hud.hide()
570 bottombar.hide()
571 }
572 }
573 }
575 Notifications {
576 id: notifications
578 model: NotificationBackend.Model
579 margin: units.gu(1)
581 anchors {
582 top: parent.top
583 right: parent.right
584 bottom: parent.bottom
585 topMargin: panel.panelHeight
586 }
587 states: [
588 State {
589 name: "narrow"
590 when: overlay.width <= units.gu(60)
591 AnchorChanges { target: notifications; anchors.left: parent.left }
592 },
593 State {
594 name: "wide"
595 when: overlay.width > units.gu(60)
596 AnchorChanges { target: notifications; anchors.left: undefined }
597 PropertyChanges { target: notifications; width: units.gu(38) }
598 }
599 ]
601 InputFilterArea {
602 anchors { left: parent.left; right: parent.right }
603 height: parent.contentHeight
604 blockInput: height > 0
605 }
606 }
607 }
609 focus: true
610 onFocusChanged: if (!focus) forceActiveFocus();
612 InputFilterArea {
613 anchors {
614 top: parent.top
615 bottom: parent.bottom
616 left: parent.left
617 }
618 width: shell.edgeSize
619 blockInput: true
620 }
622 InputFilterArea {
623 anchors {
624 top: parent.top
625 bottom: parent.bottom
626 right: parent.right
627 }
628 width: shell.edgeSize
629 blockInput: true
630 }
632 Binding {
633 target: i18n
634 property: "domain"
635 value: "unity8"
636 }
638 OSKController {
639 anchors.topMargin: panel.panelHeight
640 anchors.fill: parent // as needs to know the geometry of the shell
641 }
643 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
644 MouseArea {
645 anchors.bottom: parent.bottom
646 anchors.left: parent.left
647 anchors.right: parent.right
648 height: ApplicationManager.keyboardVisible ? ApplicationManager.keyboardHeight : 0
650 enabled: ApplicationManager.keyboardVisible
651 }
653 Label {
654 anchors.centerIn: parent
655 visible: ApplicationManager.fake ? ApplicationManager.fake : false
656 text: "EARLY ALPHA\nNOT READY FOR USE"
657 color: "lightgrey"
658 opacity: 0.2
659 font.weight: Font.Black
660 horizontalAlignment: Text.AlignHCenter
661 verticalAlignment: Text.AlignVCenter
662 fontSizeMode: Text.Fit
663 rotation: -45
664 scale: Math.min(parent.width, parent.height) / width
665 }
667 EdgeDemo {
668 id: edgeDemo
669 greeter: greeter
670 launcher: launcher
671 dash: dash
672 indicators: panel.indicators
673 underlay: underlay
674 }
676 Connections {
677 target: SessionBroadcast
678 onShowHome: showHome()
679 }