on f1e24fd help sign in
unity8/qml/Shell.qml
649 lines 21 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 print("Shell.qml: activating app", appId);
57 ApplicationManager.activateApplication(appId);
58 stages.show();
59 } else {
60 print("starting app", appId);
62 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
63 ApplicationManager.startApplication(appId, execFlags);
64 stages.show();
65 }
66 }
68 Binding {
69 target: LauncherModel
70 property: "applicationManager"
71 value: ApplicationManager
72 }
74 Component.onCompleted: {
75 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
76 }
78 GSettings {
79 id: backgroundSettings
80 schema.id: "org.gnome.desktop.background"
81 }
82 property url gSettingsPicture: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : shell.defaultBackground
83 onGSettingsPictureChanged: {
84 shell.background = gSettingsPicture
85 }
87 // This is a dummy image that is needed to determine if the picture url
88 // in backgroundSettings points to a valid picture file.
89 // We can't do this with the real background image because setting a
90 // new source in onStatusChanged triggers a binding loop detection
91 // inside Image, which causes it not to render even though a valid source
92 // would be set. We don't mind about this image staying black and just
93 // use it for verification to populate the source for the real
94 // background image.
95 Image {
96 source: shell.background
97 height: 0
98 width: 0
99 sourceSize.height: 0
100 sourceSize.width: 0
101 onStatusChanged: {
102 if (status == Image.Error && source != shell.defaultBackground) {
103 shell.background = defaultBackground
104 }
105 }
106 }
108 VolumeControl {
109 id: volumeControl
110 }
112 Keys.onVolumeUpPressed: volumeControl.volumeUp()
113 Keys.onVolumeDownPressed: volumeControl.volumeDown()
115 Item {
116 id: underlay
117 objectName: "underlay"
118 anchors.fill: parent
120 // Whether the underlay is fully covered by opaque UI elements.
121 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
123 readonly property bool applicationRunning: ApplicationManager.focusedApplicationId.length > 0
125 // Whether the user should see the topmost application surface (if there's one at all).
126 readonly property bool applicationSurfaceShouldBeSeen: stages.shown && !stages.painting
128 // NB! Application surfaces are stacked behind the shell one. So they can only be seen by the user
129 // through the translucent parts of the shell surface.
130 visible: !fullyCovered && !applicationSurfaceShouldBeSeen
132 CrossFadeImage {
133 id: backgroundImage
134 objectName: "backgroundImage"
136 anchors.fill: parent
137 source: shell.background
138 fillMode: Image.PreserveAspectCrop
139 }
141 Rectangle {
142 anchors.fill: parent
143 color: "black"
144 opacity: dash.disappearingAnimationProgress
145 }
147 Dash {
148 id: dash
149 objectName: "dash"
151 available: !greeter.shown && !lockscreen.shown
152 hides: [stages, launcher, panel.indicators]
153 shown: disappearingAnimationProgress !== 1.0
154 enabled: disappearingAnimationProgress === 0.0 && edgeDemo.dashEnabled
156 anchors {
157 fill: parent
158 topMargin: panel.panelHeight
159 }
161 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
162 opacity: 1.0 - disappearingAnimationProgress
163 property real disappearingAnimationProgress: greeter.shown ? greeter.showProgress : stages.showProgress
165 // FIXME: only necessary because stages.showProgress and
166 // greeterRevealer.animatedProgress are not animated
167 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
168 }
169 }
171 EdgeDragArea {
172 id: stagesDragHandle
173 direction: Direction.LeftWards
175 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
176 width: shell.edgeSize
178 property real progress: stages.width
180 onTouchXChanged: {
181 if (status == DirectionalDragArea.Recognized) {
182 if (ApplicationManager.count == 0) {
183 progress = Math.max(stages.width - stagesDragHandle.width + touchX, stages.width * .3)
184 } else {
185 progress = stages.width - stagesDragHandle.width + touchX
186 }
187 }
188 }
190 onDraggingChanged: {
191 if (!dragging) {
192 if (ApplicationManager.count > 0 && progress < stages.width - units.gu(10)) {
193 stages.show()
194 }
195 stagesDragHandle.progress = stages.width;
196 }
197 }
198 }
200 Item {
201 id: stages
202 objectName: "stages"
203 width: parent.width
204 height: parent.height
206 x: shown ? (locked ? 0 : launcher.progress) : stagesDragHandle.progress
208 Behavior on x { SmoothedAnimation { velocity: 600; duration: UbuntuAnimation.FastDuration } }
210 property bool shown: false
212 property real showProgress: MathUtils.clamp(1 - x / shell.width, 0, 1)
214 property bool fullyShown: x == 0
215 property bool fullyHidden: x == width
217 property bool painting: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.painting : false
218 property bool fullscreen: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.fullscreen : false
219 property bool locked: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.locked : false
221 function show() {
222 shown = true;
223 panel.indicators.hide();
224 if (!ApplicationManager.focusedApplicationId && ApplicationManager.count > 0) {
225 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
226 }
227 }
229 function hide() {
230 if (locked) {
231 return;
232 }
234 shown = false;
235 if (ApplicationManager.focusedApplicationId) {
236 ApplicationManager.unfocusCurrentApplication();
237 }
238 }
240 Connections {
241 target: ApplicationManager
242 onFocusedApplicationIdChanged: {
243 if (ApplicationManager.focusedApplicationId.length > 0) {
244 stages.show();
245 } else {
246 stages.hide();
247 }
248 }
250 onApplicationAdded: {
251 stages.show();
252 }
253 }
255 Loader {
256 id: applicationsDisplayLoader
257 anchors.fill: parent
259 source: shell.sideStageEnabled ? "Stages/StageWithSideStage.qml" : "Stages/PhoneStage.qml"
261 Binding {
262 target: applicationsDisplayLoader.item
263 property: "moving"
264 value: !stages.fullyShown
265 }
266 Binding {
267 target: applicationsDisplayLoader.item
268 property: "shown"
269 value: stages.shown
270 }
271 Binding {
272 target: applicationsDisplayLoader.item
273 property: "dragAreaWidth"
274 value: shell.edgeSize
275 }
276 }
277 }
279 Lockscreen {
280 id: lockscreen
281 objectName: "lockscreen"
283 readonly property int backgroundTopMargin: -panel.panelHeight
285 hides: [launcher, panel.indicators, hud]
286 shown: false
287 enabled: true
288 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
289 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
290 y: panel.panelHeight
291 x: required ? 0 : - width
292 width: parent.width
293 height: parent.height - panel.panelHeight
294 background: shell.background
295 pinLength: 4
297 onEntered: LightDM.Greeter.respond(passphrase);
298 onCancel: greeter.show()
300 Component.onCompleted: {
301 if (LightDM.Users.count == 1) {
302 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
303 greeter.selected(0)
304 }
305 }
306 }
308 Connections {
309 target: LightDM.Greeter
311 onShowPrompt: {
312 if (LightDM.Users.count == 1) {
313 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
314 if (text == "PIN") {
315 lockscreen.alphaNumeric = false
316 } else {
317 lockscreen.alphaNumeric = true
318 }
319 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
320 lockscreen.show();
321 }
322 }
324 onAuthenticationComplete: {
325 if (LightDM.Greeter.promptless) {
326 return;
327 }
328 if (LightDM.Greeter.authenticated) {
329 lockscreen.hide();
330 } else {
331 lockscreen.clear(true);
332 }
333 }
334 }
336 Greeter {
337 id: greeter
338 objectName: "greeter"
340 available: true
341 hides: [launcher, panel.indicators, hud]
342 shown: true
344 defaultBackground: shell.background
346 y: panel.panelHeight
347 width: parent.width
348 height: parent.height - panel.panelHeight
350 dragHandleWidth: shell.edgeSize
352 onShownChanged: {
353 if (shown) {
354 lockscreen.reset();
355 // If there is only one user, we start authenticating with that one here.
356 // If there are more users, the Greeter will handle that
357 if (LightDM.Users.count == 1) {
358 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
359 greeter.selected(0);
360 }
361 greeter.forceActiveFocus();
362 }
363 }
365 onUnlocked: greeter.hide()
366 onSelected: {
367 // Update launcher items for new user
368 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
369 AccountsService.user = user;
370 LauncherModel.setUser(user);
371 }
373 onLeftTeaserPressedChanged: {
374 if (leftTeaserPressed) {
375 launcher.tease();
376 }
377 }
379 Binding {
380 target: ApplicationManager
381 property: "suspended"
382 value: greeter.shown
383 }
384 }
386 InputFilterArea {
387 anchors.fill: parent
388 blockInput: ApplicationManager.focusedApplicationId.length === 0 || greeter.shown || lockscreen.shown || launcher.shown
389 || panel.indicators.shown || hud.shown
390 }
392 Connections {
393 id: powerConnection
394 target: Powerd
396 onDisplayPowerStateChange: {
397 // We ignore any display-off signals when the proximity sensor
398 // is active. This usually indicates something like a phone call.
399 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
400 greeter.showNow();
401 }
403 // No reason to chew demo CPU when user isn't watching
404 if (status == Powerd.Off) {
405 edgeDemo.paused = true;
406 } else if (status == Powerd.On) {
407 edgeDemo.paused = false;
408 }
409 }
410 }
412 function showHome() {
413 var animate = !greeter.shown && !stages.shown
414 greeter.hide()
415 dash.setCurrentScope("home.scope", animate, false)
416 stages.hide()
417 }
419 function hideIndicatorMenu(delay) {
420 panel.hideIndicatorMenu(delay);
421 }
423 Item {
424 id: overlay
426 anchors.fill: parent
428 Panel {
429 id: panel
430 anchors.fill: parent //because this draws indicator menus
431 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
432 indicators {
433 hides: [launcher]
434 available: edgeDemo.panelEnabled
435 contentEnabled: edgeDemo.panelContentEnabled
436 }
437 property string focusedAppId: ApplicationManager.focusedApplicationId
438 property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
439 fullscreenMode: focusedApplication && stages.fullscreen && !greeter.shown && !lockscreen.shown
440 searchVisible: !greeter.shown && !lockscreen.shown && dash.shown
442 InputFilterArea {
443 anchors {
444 top: parent.top
445 left: parent.left
446 right: parent.right
447 }
448 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
449 blockInput: true
450 }
451 }
453 Hud {
454 id: hud
456 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
457 height: parent.height
459 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown && edgeDemo.dashEnabled
460 shown: false
461 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
462 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
464 Connections {
465 target: ApplicationManager
466 onFocusedApplicationIdChanged: hud.hide()
467 }
468 }
470 Revealer {
471 id: hudRevealer
473 enabled: hud.shown
474 width: hud.width
475 anchors.left: hud.left
476 height: parent.height
477 target: hud.revealerTarget
478 closedValue: height
479 openedValue: 0
480 direction: Qt.RightToLeft
481 orientation: Qt.Vertical
482 handleSize: hud.handleHeight
483 onCloseClicked: target.hide()
484 }
486 Bottombar {
487 id: bottombar
488 theHud: hud
489 anchors.fill: parent
490 enabled: hud.available
491 applicationIsOnForeground: ApplicationManager.focusedApplicationId
492 }
494 InputFilterArea {
495 blockInput: launcher.shown
496 anchors {
497 top: parent.top
498 bottom: parent.bottom
499 left: parent.left
500 }
501 width: launcher.width
502 }
504 Launcher {
505 id: launcher
507 readonly property bool dashSwipe: progress > 0
509 anchors.top: parent.top
510 anchors.bottom: parent.bottom
511 width: parent.width
512 dragAreaWidth: shell.edgeSize
513 available: (!greeter.shown || greeter.narrowMode) && edgeDemo.launcherEnabled
515 onShowDashHome: {
516 if (edgeDemo.running)
517 return;
519 showHome()
520 }
521 onDash: {
522 if (stages.shown) {
523 stages.hide();
524 launcher.hide();
525 }
526 }
527 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("applications.scope", false, true)
528 onLauncherApplicationSelected:{
529 if (edgeDemo.running)
530 return;
532 greeter.hide()
533 shell.activateApplication(appId)
534 }
535 onShownChanged: {
536 if (shown) {
537 panel.indicators.hide()
538 hud.hide()
539 bottombar.hide()
540 }
541 }
542 }
544 Notifications {
545 id: notifications
547 model: NotificationBackend.Model
548 margin: units.gu(1)
550 anchors {
551 top: parent.top
552 right: parent.right
553 bottom: parent.bottom
554 topMargin: panel.panelHeight
555 }
556 states: [
557 State {
558 name: "narrow"
559 when: overlay.width <= units.gu(60)
560 AnchorChanges { target: notifications; anchors.left: parent.left }
561 },
562 State {
563 name: "wide"
564 when: overlay.width > units.gu(60)
565 AnchorChanges { target: notifications; anchors.left: undefined }
566 PropertyChanges { target: notifications; width: units.gu(38) }
567 }
568 ]
570 InputFilterArea {
571 anchors { left: parent.left; right: parent.right }
572 height: parent.contentHeight
573 blockInput: height > 0
574 }
575 }
576 }
578 focus: true
579 onFocusChanged: if (!focus) forceActiveFocus();
581 InputFilterArea {
582 anchors {
583 top: parent.top
584 bottom: parent.bottom
585 left: parent.left
586 }
587 width: shell.edgeSize
588 blockInput: true
589 }
591 InputFilterArea {
592 anchors {
593 top: parent.top
594 bottom: parent.bottom
595 right: parent.right
596 }
597 width: shell.edgeSize
598 blockInput: true
599 }
601 Binding {
602 target: i18n
603 property: "domain"
604 value: "unity8"
605 }
607 OSKController {
608 anchors.topMargin: panel.panelHeight
609 anchors.fill: parent // as needs to know the geometry of the shell
610 }
612 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
613 MouseArea {
614 anchors.bottom: parent.bottom
615 anchors.left: parent.left
616 anchors.right: parent.right
617 height: ApplicationManager.keyboardVisible ? ApplicationManager.keyboardHeight : 0
619 enabled: ApplicationManager.keyboardVisible
620 }
622 Label {
623 anchors.centerIn: parent
624 visible: ApplicationManager.fake ? ApplicationManager.fake : false
625 text: "EARLY ALPHA\nNOT READY FOR USE"
626 color: "lightgrey"
627 opacity: 0.2
628 font.weight: Font.Black
629 horizontalAlignment: Text.AlignHCenter
630 verticalAlignment: Text.AlignVCenter
631 fontSizeMode: Text.Fit
632 rotation: -45
633 scale: Math.min(parent.width, parent.height) / width
634 }
636 EdgeDemo {
637 id: edgeDemo
638 greeter: greeter
639 launcher: launcher
640 dash: dash
641 indicators: panel.indicators
642 underlay: underlay
643 }
645 Connections {
646 target: SessionBroadcast
647 onShowHome: showHome()
648 }