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