on 2492425 help sign in
unity8/qml/Shell.qml
683 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(100)
53 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
55 function activateApplication(appId) {
56 if (ApplicationManager.findApplication(appId)) {
57 ApplicationManager.requestFocusApplication(appId);
58 stages.show(true);
59 if (stages.locked && ApplicationManager.focusedApplicationId == appId) {
60 applicationsDisplayLoader.item.select(appId);
61 }
62 } else {
63 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
64 ApplicationManager.startApplication(appId, execFlags);
65 stages.show(false);
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 VolumeControl {
89 id: volumeControl
90 }
92 Keys.onVolumeUpPressed: volumeControl.volumeUp()
93 Keys.onVolumeDownPressed: volumeControl.volumeDown()
95 Item {
96 id: underlayClipper
97 anchors.fill: parent
98 anchors.rightMargin: stages.overlayWidth
99 clip: stages.overlayMode && !stages.painting
101 InputFilterArea {
102 anchors.fill: parent
103 blockInput: parent.clip
104 }
106 Item {
107 id: underlay
108 objectName: "underlay"
109 anchors.fill: parent
110 anchors.rightMargin: -parent.anchors.rightMargin
112 // Whether the underlay is fully covered by opaque UI elements.
113 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
115 // Whether the user should see the topmost application surface (if there's one at all).
116 readonly property bool applicationSurfaceShouldBeSeen: stages.shown && !stages.painting && !stages.overlayMode
118 // NB! Application surfaces are stacked behind the shell one. So they can only be seen by the user
119 // through the translucent parts of the shell surface.
120 visible: !fullyCovered && !applicationSurfaceShouldBeSeen
122 CrossFadeImage {
123 id: backgroundImage
124 objectName: "backgroundImage"
126 anchors.fill: parent
127 source: shell.background
128 fillMode: Image.PreserveAspectCrop
129 }
131 Rectangle {
132 anchors.fill: parent
133 color: "black"
134 opacity: dash.disappearingAnimationProgress
135 }
137 Image {
138 anchors.fill: dash
139 source: shell.width > shell.height ? "Dash/graphics/paper_landscape.png" : "Dash/graphics/paper_portrait.png"
140 fillMode: Image.PreserveAspectCrop
141 horizontalAlignment: Image.AlignRight
142 verticalAlignment: Image.AlignTop
143 }
145 Dash {
146 id: dash
147 objectName: "dash"
149 available: !greeter.shown && !lockscreen.shown
150 hides: [stages, launcher, panel.indicators]
151 shown: disappearingAnimationProgress !== 1.0
152 enabled: disappearingAnimationProgress === 0.0 && edgeDemo.dashEnabled
154 anchors {
155 fill: parent
156 topMargin: panel.panelHeight
157 }
159 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
160 opacity: 1.0 - disappearingAnimationProgress
161 property real disappearingAnimationProgress: {
162 if (greeter.shown) {
163 return greeter.showProgress;
164 } else {
165 if (stages.overlayMode) {
166 return 0;
167 }
168 return stages.showProgress
169 }
170 }
172 // FIXME: only necessary because stages.showProgress and
173 // greeterRevealer.animatedProgress are not animated
174 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
175 }
176 }
177 }
179 EdgeDragArea {
180 id: stagesDragHandle
181 direction: Direction.Leftwards
183 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
184 width: shell.edgeSize
186 property real progress: stages.width
188 onTouchXChanged: {
189 if (status == DirectionalDragArea.Recognized) {
190 if (ApplicationManager.count == 0) {
191 progress = Math.max(stages.width - stagesDragHandle.width + touchX, stages.width * .3)
192 } else {
193 progress = stages.width - stagesDragHandle.width + touchX
194 }
195 }
196 }
198 onDraggingChanged: {
199 if (!dragging) {
200 if (ApplicationManager.count > 0 && progress < stages.width - units.gu(10)) {
201 stages.show(true)
202 }
203 stagesDragHandle.progress = stages.width;
204 }
205 }
206 }
208 Item {
209 id: stages
210 objectName: "stages"
211 width: parent.width
212 height: parent.height
214 x: {
215 if (shown) {
216 if (overlayMode || locked) {
217 return 0;
218 }
219 return launcher.progress
220 } else {
221 return stagesDragHandle.progress
222 }
223 }
225 Behavior on x { SmoothedAnimation { velocity: 600; duration: UbuntuAnimation.FastDuration } }
227 property bool shown: false
229 property real showProgress: overlayMode ? 0 : MathUtils.clamp(1 - x / shell.width, 0, 1)
231 property bool fullyShown: x == 0
232 property bool fullyHidden: x == width
234 property bool painting: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.painting : false
235 property bool fullscreen: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.fullscreen : false
236 property bool overlayMode: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayMode : false
237 property int overlayWidth: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayWidth : false
238 property bool locked: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.locked : false
240 function show(focusApp) {
241 shown = true;
242 panel.indicators.hide();
243 if (!ApplicationManager.focusedApplicationId && ApplicationManager.count > 0 && focusApp) {
244 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
245 }
246 }
248 function hide() {
249 shown = false;
250 if (ApplicationManager.focusedApplicationId) {
251 ApplicationManager.unfocusCurrentApplication();
252 }
253 }
255 Connections {
256 target: ApplicationManager
258 onFocusRequested: {
259 stages.show(true);
260 }
262 onFocusedApplicationIdChanged: {
263 if (ApplicationManager.focusedApplicationId.length > 0) {
264 stages.show(false);
265 } else {
266 if (!stages.overlayMode) {
267 stages.hide();
268 }
269 }
271 // If any app is focused when greeter is open, it's due to a user action
272 // like a snap decision (say, an incoming call).
273 // TODO: these should be protected to only unlock for certain applications / certain usecases
274 // potentially only in connection with a notification.
275 greeter.hide()
276 }
278 onApplicationAdded: {
279 stages.show(false);
280 }
282 onApplicationRemoved: {
283 if (ApplicationManager.focusedApplicationId.length == 0) {
284 stages.hide();
285 }
286 }
287 }
289 Loader {
290 id: applicationsDisplayLoader
291 anchors.fill: parent
293 source: shell.sideStageEnabled ? "Stages/StageWithSideStage.qml" : "Stages/PhoneStage.qml"
295 Binding {
296 target: applicationsDisplayLoader.item
297 property: "moving"
298 value: !stages.fullyShown
299 }
300 Binding {
301 target: applicationsDisplayLoader.item
302 property: "shown"
303 value: stages.shown
304 }
305 Binding {
306 target: applicationsDisplayLoader.item
307 property: "dragAreaWidth"
308 value: shell.edgeSize
309 }
310 }
311 }
313 Lockscreen {
314 id: lockscreen
315 objectName: "lockscreen"
317 readonly property int backgroundTopMargin: -panel.panelHeight
319 hides: [launcher, panel.indicators, hud]
320 shown: false
321 enabled: true
322 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
323 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
324 y: panel.panelHeight
325 x: required ? 0 : - width
326 width: parent.width
327 height: parent.height - panel.panelHeight
328 background: shell.background
329 pinLength: 4
331 onEntered: LightDM.Greeter.respond(passphrase);
332 onCancel: greeter.show()
334 Component.onCompleted: {
335 if (LightDM.Users.count == 1) {
336 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
337 }
338 }
339 }
341 Connections {
342 target: LightDM.Greeter
344 onShowPrompt: {
345 if (LightDM.Users.count == 1) {
346 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
347 if (text == "PIN") {
348 lockscreen.alphaNumeric = false
349 } else {
350 lockscreen.alphaNumeric = true
351 }
352 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
353 lockscreen.show();
354 }
355 }
357 onAuthenticationComplete: {
358 if (LightDM.Greeter.promptless) {
359 return;
360 }
361 if (LightDM.Greeter.authenticated) {
362 lockscreen.hide();
363 } else {
364 lockscreen.clear(true);
365 }
366 }
367 }
369 Greeter {
370 id: greeter
371 objectName: "greeter"
373 available: true
374 hides: [launcher, panel.indicators, hud]
375 shown: true
377 defaultBackground: shell.background
379 y: panel.panelHeight
380 width: parent.width
381 height: parent.height - panel.panelHeight
383 dragHandleWidth: shell.edgeSize
385 onShownChanged: {
386 if (shown) {
387 lockscreen.reset();
388 // If there is only one user, we start authenticating with that one here.
389 // If there are more users, the Greeter will handle that
390 if (LightDM.Users.count == 1) {
391 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
392 }
393 greeter.forceActiveFocus();
394 }
395 }
397 onUnlocked: greeter.hide()
398 onSelected: {
399 // Update launcher items for new user
400 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
401 AccountsService.user = user;
402 LauncherModel.setUser(user);
403 }
405 onLeftTeaserPressedChanged: {
406 if (leftTeaserPressed) {
407 launcher.tease();
408 }
409 }
411 Binding {
412 target: ApplicationManager
413 property: "suspended"
414 value: greeter.shown && greeter.showProgress == 1
415 }
416 }
418 InputFilterArea {
419 anchors.fill: parent
420 blockInput: ApplicationManager.focusedApplicationId.length === 0 || greeter.shown || lockscreen.shown || launcher.shown
421 || panel.indicators.shown || hud.shown
422 }
424 Connections {
425 id: powerConnection
426 target: Powerd
428 onDisplayPowerStateChange: {
429 // We ignore any display-off signals when the proximity sensor
430 // is active. This usually indicates something like a phone call.
431 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
432 greeter.showNow();
433 }
435 // No reason to chew demo CPU when user isn't watching
436 if (status == Powerd.Off) {
437 edgeDemo.paused = true;
438 } else if (status == Powerd.On) {
439 edgeDemo.paused = false;
440 }
441 }
442 }
444 function showHome() {
445 var animate = !greeter.shown && !stages.shown
446 greeter.hide()
447 dash.setCurrentScope("clickscope", animate, false)
448 stages.hide()
449 }
451 function hideIndicatorMenu(delay) {
452 panel.hideIndicatorMenu(delay);
453 }
455 Item {
456 id: overlay
458 anchors.fill: parent
460 Panel {
461 id: panel
462 anchors.fill: parent //because this draws indicator menus
463 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
464 indicators {
465 hides: [launcher]
466 available: edgeDemo.panelEnabled
467 contentEnabled: edgeDemo.panelContentEnabled
468 }
469 property string focusedAppId: ApplicationManager.focusedApplicationId
470 property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
471 fullscreenMode: focusedApplication && stages.fullscreen && !greeter.shown && !lockscreen.shown
472 searchVisible: !greeter.shown && !lockscreen.shown && dash.shown && dash.searchable
474 InputFilterArea {
475 anchors {
476 top: parent.top
477 left: parent.left
478 right: parent.right
479 }
480 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
481 blockInput: true
482 }
483 }
485 Hud {
486 id: hud
488 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
489 height: parent.height
491 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown && edgeDemo.dashEnabled
492 shown: false
493 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
494 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
496 Connections {
497 target: ApplicationManager
498 onFocusedApplicationIdChanged: hud.hide()
499 }
500 }
502 Revealer {
503 id: hudRevealer
505 enabled: hud.shown
506 width: hud.width
507 anchors.left: hud.left
508 height: parent.height
509 target: hud.revealerTarget
510 closedValue: height
511 openedValue: 0
512 direction: Qt.RightToLeft
513 orientation: Qt.Vertical
514 handleSize: hud.handleHeight
515 onCloseClicked: target.hide()
516 }
518 Bottombar {
519 id: bottombar
520 theHud: hud
521 anchors.fill: parent
522 enabled: hud.available
523 applicationIsOnForeground: ApplicationManager.focusedApplicationId
524 }
526 InputFilterArea {
527 blockInput: launcher.shown
528 anchors {
529 top: parent.top
530 bottom: parent.bottom
531 left: parent.left
532 }
533 width: launcher.width
534 }
536 Launcher {
537 id: launcher
539 readonly property bool dashSwipe: progress > 0
541 anchors.top: parent.top
542 anchors.bottom: parent.bottom
543 width: parent.width
544 dragAreaWidth: shell.edgeSize
545 available: (!greeter.shown || greeter.narrowMode) && edgeDemo.launcherEnabled
547 onShowDashHome: {
548 if (edgeDemo.running)
549 return;
551 showHome()
552 }
553 onDash: {
554 if (stages.shown && !stages.overlayMode) {
555 if (!stages.locked) {
556 stages.hide();
557 launcher.hide();
558 }
559 }
560 }
561 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
562 onLauncherApplicationSelected:{
563 if (edgeDemo.running)
564 return;
566 greeter.hide()
567 shell.activateApplication(appId)
568 }
569 onShownChanged: {
570 if (shown) {
571 panel.indicators.hide()
572 hud.hide()
573 bottombar.hide()
574 }
575 }
576 }
578 Notifications {
579 id: notifications
581 model: NotificationBackend.Model
582 margin: units.gu(1)
584 anchors {
585 top: parent.top
586 right: parent.right
587 bottom: parent.bottom
588 topMargin: panel.panelHeight
589 }
590 states: [
591 State {
592 name: "narrow"
593 when: overlay.width <= units.gu(60)
594 AnchorChanges { target: notifications; anchors.left: parent.left }
595 },
596 State {
597 name: "wide"
598 when: overlay.width > units.gu(60)
599 AnchorChanges { target: notifications; anchors.left: undefined }
600 PropertyChanges { target: notifications; width: units.gu(38) }
601 }
602 ]
604 InputFilterArea {
605 anchors { left: parent.left; right: parent.right }
606 height: parent.contentHeight
607 blockInput: height > 0
608 }
609 }
610 }
612 focus: true
613 onFocusChanged: if (!focus) forceActiveFocus();
615 InputFilterArea {
616 anchors {
617 top: parent.top
618 bottom: parent.bottom
619 left: parent.left
620 }
621 width: shell.edgeSize
622 blockInput: true
623 }
625 InputFilterArea {
626 anchors {
627 top: parent.top
628 bottom: parent.bottom
629 right: parent.right
630 }
631 width: shell.edgeSize
632 blockInput: true
633 }
635 Binding {
636 target: i18n
637 property: "domain"
638 value: "unity8"
639 }
641 OSKController {
642 anchors.topMargin: panel.panelHeight
643 anchors.fill: parent // as needs to know the geometry of the shell
644 }
646 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
647 MouseArea {
648 anchors.bottom: parent.bottom
649 anchors.left: parent.left
650 anchors.right: parent.right
651 height: ApplicationManager.keyboardVisible ? ApplicationManager.keyboardHeight : 0
653 enabled: ApplicationManager.keyboardVisible
654 }
656 Label {
657 anchors.centerIn: parent
658 visible: ApplicationManager.fake ? ApplicationManager.fake : false
659 text: "EARLY ALPHA\nNOT READY FOR USE"
660 color: "lightgrey"
661 opacity: 0.2
662 font.weight: Font.Black
663 horizontalAlignment: Text.AlignHCenter
664 verticalAlignment: Text.AlignVCenter
665 fontSizeMode: Text.Fit
666 rotation: -45
667 scale: Math.min(parent.width, parent.height) / width
668 }
670 EdgeDemo {
671 id: edgeDemo
672 greeter: greeter
673 launcher: launcher
674 dash: dash
675 indicators: panel.indicators
676 underlay: underlay
677 }
679 Connections {
680 target: SessionBroadcast
681 onShowHome: showHome()
682 }