on fe57a2a help sign in
unity8/qml/Shell.qml
677 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 }
257 shown = false;
258 if (ApplicationManager.focusedApplicationId) {
259 ApplicationManager.unfocusCurrentApplication();
260 }
261 }
263 Connections {
264 target: ApplicationManager
266 onFocusRequested: {
267 stages.show();
268 }
270 onFocusedApplicationIdChanged: {
271 if (ApplicationManager.focusedApplicationId.length > 0) {
272 stages.show();
273 } else {
274 if (!stages.overlayMode) {
275 stages.hide();
276 }
277 }
278 }
280 onApplicationAdded: {
281 stages.show();
282 }
283 }
285 Loader {
286 id: applicationsDisplayLoader
287 anchors.fill: parent
289 source: shell.sideStageEnabled ? "Stages/StageWithSideStage.qml" : "Stages/PhoneStage.qml"
291 Binding {
292 target: applicationsDisplayLoader.item
293 property: "moving"
294 value: !stages.fullyShown
295 }
296 Binding {
297 target: applicationsDisplayLoader.item
298 property: "shown"
299 value: stages.shown
300 }
301 Binding {
302 target: applicationsDisplayLoader.item
303 property: "dragAreaWidth"
304 value: shell.edgeSize
305 }
306 }
307 }
309 Lockscreen {
310 id: lockscreen
311 objectName: "lockscreen"
313 readonly property int backgroundTopMargin: -panel.panelHeight
315 hides: [launcher, panel.indicators, hud]
316 shown: false
317 enabled: true
318 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
319 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
320 y: panel.panelHeight
321 x: required ? 0 : - width
322 width: parent.width
323 height: parent.height - panel.panelHeight
324 background: shell.background
325 pinLength: 4
327 onEntered: LightDM.Greeter.respond(passphrase);
328 onCancel: greeter.show()
330 Component.onCompleted: {
331 if (LightDM.Users.count == 1) {
332 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
333 }
334 }
335 }
337 Connections {
338 target: LightDM.Greeter
340 onShowPrompt: {
341 if (LightDM.Users.count == 1) {
342 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
343 if (text == "PIN") {
344 lockscreen.alphaNumeric = false
345 } else {
346 lockscreen.alphaNumeric = true
347 }
348 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
349 lockscreen.show();
350 }
351 }
353 onAuthenticationComplete: {
354 if (LightDM.Greeter.promptless) {
355 return;
356 }
357 if (LightDM.Greeter.authenticated) {
358 lockscreen.hide();
359 } else {
360 lockscreen.clear(true);
361 }
362 }
363 }
365 Greeter {
366 id: greeter
367 objectName: "greeter"
369 available: true
370 hides: [launcher, panel.indicators, hud]
371 shown: true
373 defaultBackground: shell.background
375 y: panel.panelHeight
376 width: parent.width
377 height: parent.height - panel.panelHeight
379 dragHandleWidth: shell.edgeSize
381 onShownChanged: {
382 if (shown) {
383 lockscreen.reset();
384 // If there is only one user, we start authenticating with that one here.
385 // If there are more users, the Greeter will handle that
386 if (LightDM.Users.count == 1) {
387 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
388 }
389 greeter.forceActiveFocus();
390 }
391 }
393 onUnlocked: greeter.hide()
394 onSelected: {
395 // Update launcher items for new user
396 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
397 AccountsService.user = user;
398 LauncherModel.setUser(user);
399 }
401 onLeftTeaserPressedChanged: {
402 if (leftTeaserPressed) {
403 launcher.tease();
404 }
405 }
407 Binding {
408 target: ApplicationManager
409 property: "suspended"
410 value: greeter.shown
411 }
412 }
414 InputFilterArea {
415 anchors.fill: parent
416 blockInput: ApplicationManager.focusedApplicationId.length === 0 || greeter.shown || lockscreen.shown || launcher.shown
417 || panel.indicators.shown || hud.shown
418 }
420 Connections {
421 id: powerConnection
422 target: Powerd
424 onDisplayPowerStateChange: {
425 // We ignore any display-off signals when the proximity sensor
426 // is active. This usually indicates something like a phone call.
427 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
428 greeter.showNow();
429 }
431 // No reason to chew demo CPU when user isn't watching
432 if (status == Powerd.Off) {
433 edgeDemo.paused = true;
434 } else if (status == Powerd.On) {
435 edgeDemo.paused = false;
436 }
437 }
438 }
440 function showHome() {
441 var animate = !greeter.shown && !stages.shown
442 greeter.hide()
443 dash.setCurrentScope("home.scope", animate, false)
444 stages.hide()
445 }
447 function hideIndicatorMenu(delay) {
448 panel.hideIndicatorMenu(delay);
449 }
451 Item {
452 id: overlay
454 anchors.fill: parent
456 Panel {
457 id: panel
458 anchors.fill: parent //because this draws indicator menus
459 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
460 indicators {
461 hides: [launcher]
462 available: edgeDemo.panelEnabled
463 contentEnabled: edgeDemo.panelContentEnabled
464 }
465 property string focusedAppId: ApplicationManager.focusedApplicationId
466 property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
467 fullscreenMode: focusedApplication && stages.fullscreen && !greeter.shown && !lockscreen.shown
468 searchVisible: !greeter.shown && !lockscreen.shown && dash.shown
470 InputFilterArea {
471 anchors {
472 top: parent.top
473 left: parent.left
474 right: parent.right
475 }
476 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
477 blockInput: true
478 }
479 }
481 Hud {
482 id: hud
484 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
485 height: parent.height
487 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown && edgeDemo.dashEnabled
488 shown: false
489 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
490 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
492 Connections {
493 target: ApplicationManager
494 onFocusedApplicationIdChanged: hud.hide()
495 }
496 }
498 Revealer {
499 id: hudRevealer
501 enabled: hud.shown
502 width: hud.width
503 anchors.left: hud.left
504 height: parent.height
505 target: hud.revealerTarget
506 closedValue: height
507 openedValue: 0
508 direction: Qt.RightToLeft
509 orientation: Qt.Vertical
510 handleSize: hud.handleHeight
511 onCloseClicked: target.hide()
512 }
514 Bottombar {
515 id: bottombar
516 theHud: hud
517 anchors.fill: parent
518 enabled: hud.available
519 applicationIsOnForeground: ApplicationManager.focusedApplicationId
520 }
522 InputFilterArea {
523 blockInput: launcher.shown
524 anchors {
525 top: parent.top
526 bottom: parent.bottom
527 left: parent.left
528 }
529 width: launcher.width
530 }
532 Launcher {
533 id: launcher
535 readonly property bool dashSwipe: progress > 0
537 anchors.top: parent.top
538 anchors.bottom: parent.bottom
539 width: parent.width
540 dragAreaWidth: shell.edgeSize
541 available: (!greeter.shown || greeter.narrowMode) && edgeDemo.launcherEnabled
543 onShowDashHome: {
544 if (edgeDemo.running)
545 return;
547 showHome()
548 }
549 onDash: {
550 if (stages.shown && !stages.overlayMode) {
551 stages.hide();
552 launcher.hide();
553 }
554 }
555 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("applications.scope", false, true)
556 onLauncherApplicationSelected:{
557 if (edgeDemo.running)
558 return;
560 greeter.hide()
561 shell.activateApplication(appId)
562 }
563 onShownChanged: {
564 if (shown) {
565 panel.indicators.hide()
566 hud.hide()
567 bottombar.hide()
568 }
569 }
570 }
572 Notifications {
573 id: notifications
575 model: NotificationBackend.Model
576 margin: units.gu(1)
578 anchors {
579 top: parent.top
580 right: parent.right
581 bottom: parent.bottom
582 topMargin: panel.panelHeight
583 }
584 states: [
585 State {
586 name: "narrow"
587 when: overlay.width <= units.gu(60)
588 AnchorChanges { target: notifications; anchors.left: parent.left }
589 },
590 State {
591 name: "wide"
592 when: overlay.width > units.gu(60)
593 AnchorChanges { target: notifications; anchors.left: undefined }
594 PropertyChanges { target: notifications; width: units.gu(38) }
595 }
596 ]
598 InputFilterArea {
599 anchors { left: parent.left; right: parent.right }
600 height: parent.contentHeight
601 blockInput: height > 0
602 }
603 }
604 }
606 focus: true
607 onFocusChanged: if (!focus) forceActiveFocus();
609 InputFilterArea {
610 anchors {
611 top: parent.top
612 bottom: parent.bottom
613 left: parent.left
614 }
615 width: shell.edgeSize
616 blockInput: true
617 }
619 InputFilterArea {
620 anchors {
621 top: parent.top
622 bottom: parent.bottom
623 right: parent.right
624 }
625 width: shell.edgeSize
626 blockInput: true
627 }
629 Binding {
630 target: i18n
631 property: "domain"
632 value: "unity8"
633 }
635 OSKController {
636 anchors.topMargin: panel.panelHeight
637 anchors.fill: parent // as needs to know the geometry of the shell
638 }
640 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
641 MouseArea {
642 anchors.bottom: parent.bottom
643 anchors.left: parent.left
644 anchors.right: parent.right
645 height: ApplicationManager.keyboardVisible ? ApplicationManager.keyboardHeight : 0
647 enabled: ApplicationManager.keyboardVisible
648 }
650 Label {
651 anchors.centerIn: parent
652 visible: ApplicationManager.fake ? ApplicationManager.fake : false
653 text: "EARLY ALPHA\nNOT READY FOR USE"
654 color: "lightgrey"
655 opacity: 0.2
656 font.weight: Font.Black
657 horizontalAlignment: Text.AlignHCenter
658 verticalAlignment: Text.AlignVCenter
659 fontSizeMode: Text.Fit
660 rotation: -45
661 scale: Math.min(parent.width, parent.height) / width
662 }
664 EdgeDemo {
665 id: edgeDemo
666 greeter: greeter
667 launcher: launcher
668 dash: dash
669 indicators: panel.indicators
670 underlay: underlay
671 }
673 Connections {
674 target: SessionBroadcast
675 onShowHome: showHome()
676 }