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