on a095a88 help sign in
unity8/qml/Shell.qml
676 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 "SideStage"
35import "Notifications"
36import Unity.Notifications 1.0 as NotificationBackend
38FocusScope {
39 id: shell
41 // this is only here to select the width / height of the window if not running fullscreen
42 property bool tablet: true
43 width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
44 height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
46 property real edgeSize: units.gu(2)
47 property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
48 property url background
49 readonly property real panelHeight: panel.panelHeight
51 property bool dashShown: dash.shown
53 property bool sideStageEnabled: shell.width >= units.gu(60)
55 function activateApplication(appId) {
56 if (ApplicationManager.findApplication(appId)) {
57 print("Shell.qml: activating app", appId);
58 ApplicationManager.activateApplication(appId);
59 stages.show();
60 } else {
61 print("starting app", appId);
63 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
64 ApplicationManager.startApplication(appId, execFlags);
65 stages.show();
66 }
67 }
69 Binding {
70 target: LauncherModel
71 property: "applicationManager"
72 value: ApplicationManager
73 }
75 Component.onCompleted: {
76 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
77 }
79 GSettings {
80 id: backgroundSettings
81 schema.id: "org.gnome.desktop.background"
82 }
83 property url gSettingsPicture: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : shell.defaultBackground
84 onGSettingsPictureChanged: {
85 shell.background = gSettingsPicture
86 }
88 // This is a dummy image that is needed to determine if the picture url
89 // in backgroundSettings points to a valid picture file.
90 // We can't do this with the real background image because setting a
91 // new source in onStatusChanged triggers a binding loop detection
92 // inside Image, which causes it not to render even though a valid source
93 // would be set. We don't mind about this image staying black and just
94 // use it for verification to populate the source for the real
95 // background image.
96 Image {
97 source: shell.background
98 height: 0
99 width: 0
100 sourceSize.height: 0
101 sourceSize.width: 0
102 onStatusChanged: {
103 if (status == Image.Error && source != shell.defaultBackground) {
104 shell.background = defaultBackground
105 }
106 }
107 }
109 VolumeControl {
110 id: volumeControl
111 }
113 Keys.onVolumeUpPressed: volumeControl.volumeUp()
114 Keys.onVolumeDownPressed: volumeControl.volumeDown()
116 Item {
117 id: underlayClipper
118 anchors.fill: parent
119 anchors.rightMargin: stages.overlayWidth
120 clip: stages.overlayMode && !stages.painting
122 Item {
123 id: underlay
124 objectName: "underlay"
125 anchors.fill: parent
126 anchors.rightMargin: -parent.anchors.rightMargin
128 // Whether the underlay is fully covered by opaque UI elements.
129 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
131 readonly property bool applicationRunning: ApplicationManager.focusedApplicationId.length > 0
133 // Whether the user should see the topmost application surface (if there's one at all).
134 readonly property bool applicationSurfaceShouldBeSeen: stages.shown && !stages.painting && !stages.overlayMode
136 // NB! Application surfaces are stacked behind the shell one. So they can only be seen by the user
137 // through the translucent parts of the shell surface.
138 visible: !fullyCovered && !applicationSurfaceShouldBeSeen
140 CrossFadeImage {
141 id: backgroundImage
142 objectName: "backgroundImage"
144 anchors.fill: parent
145 source: shell.background
146 fillMode: Image.PreserveAspectCrop
147 }
149 Rectangle {
150 anchors.fill: parent
151 color: "black"
152 opacity: dash.disappearingAnimationProgress
153 }
155 Dash {
156 id: dash
157 objectName: "dash"
159 available: !greeter.shown && !lockscreen.shown
160 hides: [stages, launcher, panel.indicators]
161 shown: disappearingAnimationProgress !== 1.0
162 enabled: disappearingAnimationProgress === 0.0 && edgeDemo.dashEnabled
164 anchors {
165 fill: parent
166 topMargin: panel.panelHeight
167 }
169 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
170 opacity: 1.0 - disappearingAnimationProgress
171 property real disappearingAnimationProgress: {
172 if (greeter.shown) {
173 return greeter.showProgress;
174 } else {
175 if (stages.overlayMode) {
176 return 0;
177 }
178 return stages.showProgress
179 }
180 }
182 // FIXME: only necessary because stages.showProgress and
183 // greeterRevealer.animatedProgress are not animated
184 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
185 }
186 }
187 }
189 EdgeDragArea {
190 id: stagesDragHandle
191 direction: Direction.LeftWards
193 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
194 width: shell.edgeSize
196 property real progress: stages.width
198 onTouchXChanged: {
199 if (status == DirectionalDragArea.Recognized) {
200 if (ApplicationManager.count == 0) {
201 progress = Math.max(stages.width - stagesDragHandle.width + touchX, stages.width * .3)
202 } else {
203 progress = stages.width - stagesDragHandle.width + touchX
204 }
205 }
206 }
208 onDraggingChanged: {
209 if (!dragging) {
210 if (ApplicationManager.count > 0 && progress < stages.width - units.gu(10)) {
211 stages.show()
212 }
213 stagesDragHandle.progress = stages.width;
214 }
215 }
216 }
218 Item {
219 id: stages
220 objectName: "stages"
221 width: parent.width
222 height: parent.height
224 x: {
225 if (shown) {
226 if (overlayMode) {
227 return 0;
228 }
229 return launcher.progress
230 } else {
231 return stagesDragHandle.progress
232 }
233 }
235 Behavior on x { SmoothedAnimation { velocity: 600; duration: UbuntuAnimation.SlowDuration } }
237 property bool shown: false
239 property real showProgress: overlayMode ? 0 : MathUtils.clamp(1 - x / shell.width, 0, 1)
241 property bool fullyShown: x == 0
242 property bool fullyHidden: x == width
244 property bool painting: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.painting : false
245 property bool fullscreen: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.fullscreen : false
246 property bool overlayMode: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayMode : false
247 property int overlayWidth: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayWidth : 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 shown = false;
259 if (ApplicationManager.focusedApplicationId) {
260 ApplicationManager.unfocusCurrentApplication();
261 }
262 }
264 Connections {
265 target: ApplicationManager
266 onFocusedApplicationIdChanged: {
267 if (ApplicationManager.focusedApplicationId.length > 0) {
268 stages.show();
269 } else {
270 print("app unfocused", stages.overlayMode)
271 if (!stages.overlayMode) {
272 stages.hide();
273 }
274 }
275 }
277 onApplicationAdded: {
278 stages.show();
279 }
280 }
282 Loader {
283 id: applicationsDisplayLoader
284 anchors.fill: parent
286 source: shell.sideStageEnabled ? "Stages/StageWithSideStage.qml" : "Stages/PhoneStage.qml"
288 Binding {
289 target: applicationsDisplayLoader.item
290 property: "moving"
291 value: !stages.fullyShown
292 }
293 Binding {
294 target: applicationsDisplayLoader.item
295 property: "shown"
296 value: stages.shown
297 }
298 Binding {
299 target: applicationsDisplayLoader.item
300 property: "dragAreaWidth"
301 value: shell.edgeSize
302 }
303 }
304 }
306 Lockscreen {
307 id: lockscreen
308 objectName: "lockscreen"
310 readonly property int backgroundTopMargin: -panel.panelHeight
312 hides: [launcher, panel.indicators, hud]
313 shown: false
314 enabled: true
315 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
316 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
317 y: panel.panelHeight
318 x: required ? 0 : - width
319 width: parent.width
320 height: parent.height - panel.panelHeight
321 background: shell.background
322 pinLength: 4
324 onEntered: LightDM.Greeter.respond(passphrase);
325 onCancel: greeter.show()
327 Component.onCompleted: {
328 if (LightDM.Users.count == 1) {
329 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
330 greeter.selected(0)
331 }
332 }
333 }
335 Connections {
336 target: LightDM.Greeter
338 onShowPrompt: {
339 if (LightDM.Users.count == 1) {
340 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
341 if (text == "PIN") {
342 lockscreen.alphaNumeric = false
343 } else {
344 lockscreen.alphaNumeric = true
345 }
346 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
347 lockscreen.show();
348 }
349 }
351 onAuthenticationComplete: {
352 if (LightDM.Greeter.promptless) {
353 return;
354 }
355 if (LightDM.Greeter.authenticated) {
356 lockscreen.hide();
357 } else {
358 lockscreen.clear(true);
359 }
360 }
361 }
363 Greeter {
364 id: greeter
365 objectName: "greeter"
367 available: true
368 hides: [launcher, panel.indicators, hud]
369 shown: true
371 defaultBackground: shell.background
373 y: panel.panelHeight
374 width: parent.width
375 height: parent.height - panel.panelHeight
377 dragHandleWidth: shell.edgeSize
379 onShownChanged: {
380 if (shown) {
381 lockscreen.reset();
382 // If there is only one user, we start authenticating with that one here.
383 // If there are more users, the Greeter will handle that
384 if (LightDM.Users.count == 1) {
385 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
386 greeter.selected(0);
387 }
388 greeter.forceActiveFocus();
389 }
390 }
392 onUnlocked: greeter.hide()
393 onSelected: {
394 // Update launcher items for new user
395 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
396 AccountsService.user = user;
397 LauncherModel.setUser(user);
398 }
400 onLeftTeaserPressedChanged: {
401 if (leftTeaserPressed) {
402 launcher.tease();
403 }
404 }
406 Binding {
407 target: ApplicationManager
408 property: "suspended"
409 value: greeter.shown
410 }
411 }
413 InputFilterArea {
414 anchors.fill: parent
415 blockInput: ApplicationManager.focusedApplicationId.length === 0 || greeter.shown || lockscreen.shown || launcher.shown
416 || panel.indicators.shown || hud.shown
417 }
419 Connections {
420 id: powerConnection
421 target: Powerd
423 onDisplayPowerStateChange: {
424 // We ignore any display-off signals when the proximity sensor
425 // is active. This usually indicates something like a phone call.
426 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
427 greeter.showNow();
428 }
430 // No reason to chew demo CPU when user isn't watching
431 if (status == Powerd.Off) {
432 edgeDemo.paused = true;
433 } else if (status == Powerd.On) {
434 edgeDemo.paused = false;
435 }
436 }
437 }
439 function showHome() {
440 var animate = !greeter.shown && !stages.shown
441 greeter.hide()
442 dash.setCurrentScope("home.scope", animate, false)
443 stages.hide()
444 }
446 function hideIndicatorMenu(delay) {
447 panel.hideIndicatorMenu(delay);
448 }
450 Item {
451 id: overlay
453 anchors.fill: parent
455 Panel {
456 id: panel
457 anchors.fill: parent //because this draws indicator menus
458 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
459 indicators {
460 hides: [launcher]
461 available: edgeDemo.panelEnabled
462 contentEnabled: edgeDemo.panelContentEnabled
463 }
464 property string focusedAppId: ApplicationManager.focusedApplicationId
465 property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
466 fullscreenMode: focusedApplication && stages.fullscreen && !greeter.shown && !lockscreen.shown
467 searchVisible: !greeter.shown && !lockscreen.shown && dash.shown
469 InputFilterArea {
470 anchors {
471 top: parent.top
472 left: parent.left
473 right: parent.right
474 }
475 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
476 blockInput: true
477 }
478 }
480 Hud {
481 id: hud
483 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
484 height: parent.height
486 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown && edgeDemo.dashEnabled
487 shown: false
488 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
489 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
491 Connections {
492 target: ApplicationManager
493 onFocusedApplicationIdChanged: hud.hide()
494 }
495 }
497 Revealer {
498 id: hudRevealer
500 enabled: hud.shown
501 width: hud.width
502 anchors.left: hud.left
503 height: parent.height
504 target: hud.revealerTarget
505 closedValue: height
506 openedValue: 0
507 direction: Qt.RightToLeft
508 orientation: Qt.Vertical
509 handleSize: hud.handleHeight
510 onCloseClicked: target.hide()
511 }
513 Bottombar {
514 id: bottombar
515 theHud: hud
516 anchors.fill: parent
517 enabled: hud.available
518 applicationIsOnForeground: ApplicationManager.focusedApplicationId
519 }
521 InputFilterArea {
522 blockInput: launcher.shown
523 anchors {
524 top: parent.top
525 bottom: parent.bottom
526 left: parent.left
527 }
528 width: launcher.width
529 }
531 Launcher {
532 id: launcher
534 readonly property bool dashSwipe: progress > 0
536 anchors.top: parent.top
537 anchors.bottom: parent.bottom
538 width: parent.width
539 dragAreaWidth: shell.edgeSize
540 available: (!greeter.shown || greeter.narrowMode) && edgeDemo.launcherEnabled
542 onShowDashHome: {
543 if (edgeDemo.running)
544 return;
546 showHome()
547 }
548 onDash: {
549 if (stages.shown && !stages.overlayMode) {
550 stages.hide();
551 launcher.hide();
552 }
553 }
554 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("applications.scope", false, true)
555 onLauncherApplicationSelected:{
556 if (edgeDemo.running)
557 return;
559 greeter.hide()
560 shell.activateApplication(appId)
561 }
562 onShownChanged: {
563 if (shown) {
564 panel.indicators.hide()
565 hud.hide()
566 bottombar.hide()
567 }
568 }
569 }
571 Notifications {
572 id: notifications
574 model: NotificationBackend.Model
575 margin: units.gu(1)
577 anchors {
578 top: parent.top
579 right: parent.right
580 bottom: parent.bottom
581 topMargin: panel.panelHeight
582 }
583 states: [
584 State {
585 name: "narrow"
586 when: overlay.width <= units.gu(60)
587 AnchorChanges { target: notifications; anchors.left: parent.left }
588 },
589 State {
590 name: "wide"
591 when: overlay.width > units.gu(60)
592 AnchorChanges { target: notifications; anchors.left: undefined }
593 PropertyChanges { target: notifications; width: units.gu(38) }
594 }
595 ]
597 InputFilterArea {
598 anchors { left: parent.left; right: parent.right }
599 height: parent.contentHeight
600 blockInput: height > 0
601 }
602 }
603 }
605 focus: true
606 onFocusChanged: if (!focus) forceActiveFocus();
608 InputFilterArea {
609 anchors {
610 top: parent.top
611 bottom: parent.bottom
612 left: parent.left
613 }
614 width: shell.edgeSize
615 blockInput: true
616 }
618 InputFilterArea {
619 anchors {
620 top: parent.top
621 bottom: parent.bottom
622 right: parent.right
623 }
624 width: shell.edgeSize
625 blockInput: true
626 }
628 Binding {
629 target: i18n
630 property: "domain"
631 value: "unity8"
632 }
634 OSKController {
635 anchors.topMargin: panel.panelHeight
636 anchors.fill: parent // as needs to know the geometry of the shell
637 }
639 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
640 MouseArea {
641 anchors.bottom: parent.bottom
642 anchors.left: parent.left
643 anchors.right: parent.right
644 height: ApplicationManager.keyboardVisible ? ApplicationManager.keyboardHeight : 0
646 enabled: ApplicationManager.keyboardVisible
647 }
649 Label {
650 anchors.centerIn: parent
651 visible: ApplicationManager.fake ? ApplicationManager.fake : false
652 text: "EARLY ALPHA\nNOT READY FOR USE"
653 color: "lightgrey"
654 opacity: 0.2
655 font.weight: Font.Black
656 horizontalAlignment: Text.AlignHCenter
657 verticalAlignment: Text.AlignVCenter
658 fontSizeMode: Text.Fit
659 rotation: -45
660 scale: Math.min(parent.width, parent.height) / width
661 }
663 EdgeDemo {
664 id: edgeDemo
665 greeter: greeter
666 launcher: launcher
667 dash: dash
668 indicators: panel.indicators
669 underlay: underlay
670 }
672 Connections {
673 target: SessionBroadcast
674 onShowHome: showHome()
675 }