on bdb6c02 help sign in
unity8/qml/Shell.qml
697 lines 23 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 && greeterWrapper.showProgress !== 1.0
152 enabled: disappearingAnimationProgress === 0.0 && greeterWrapper.showProgress === 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 (stages.overlayMode) {
163 return 0;
164 } else {
165 return stages.showProgress
166 }
167 }
169 // FIXME: only necessary because stages.showProgress is not animated
170 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
171 }
172 }
173 }
175 EdgeDragArea {
176 id: stagesDragHandle
177 direction: Direction.Leftwards
179 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
180 width: shell.edgeSize
182 property real progress: stages.width
184 onTouchXChanged: {
185 if (status == DirectionalDragArea.Recognized) {
186 if (ApplicationManager.count == 0) {
187 progress = Math.max(stages.width - stagesDragHandle.width + touchX, stages.width * .3)
188 } else {
189 progress = stages.width - stagesDragHandle.width + touchX
190 }
191 }
192 }
194 onDraggingChanged: {
195 if (!dragging) {
196 if (ApplicationManager.count > 0 && progress < stages.width - units.gu(10)) {
197 stages.show(true)
198 }
199 stagesDragHandle.progress = stages.width;
200 }
201 }
202 }
204 Item {
205 id: stages
206 objectName: "stages"
207 width: parent.width
208 height: parent.height
210 x: {
211 if (shown) {
212 if (overlayMode || locked) {
213 return 0;
214 }
215 return launcher.progress
216 } else {
217 return stagesDragHandle.progress
218 }
219 }
221 Behavior on x { SmoothedAnimation { velocity: 600; duration: UbuntuAnimation.FastDuration } }
223 property bool shown: false
225 property real showProgress: overlayMode ? 0 : MathUtils.clamp(1 - x / shell.width, 0, 1)
227 property bool fullyShown: x == 0
228 property bool fullyHidden: x == width
230 property bool painting: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.painting : false
231 property bool fullscreen: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.fullscreen : false
232 property bool overlayMode: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayMode : false
233 property int overlayWidth: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayWidth : false
234 property bool locked: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.locked : false
236 function show(focusApp) {
237 shown = true;
238 panel.indicators.hide();
239 if (!ApplicationManager.focusedApplicationId && ApplicationManager.count > 0 && focusApp) {
240 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
241 }
242 }
244 function hide() {
245 shown = false;
246 if (ApplicationManager.focusedApplicationId) {
247 ApplicationManager.unfocusCurrentApplication();
248 }
249 }
251 Connections {
252 target: ApplicationManager
254 onFocusRequested: {
255 stages.show(true);
256 }
258 onFocusedApplicationIdChanged: {
259 if (ApplicationManager.focusedApplicationId.length > 0) {
260 stages.show(false);
261 } else {
262 if (!stages.overlayMode) {
263 stages.hide();
264 }
265 }
267 // If any app is focused when greeter is open, it's due to a user action
268 // like a snap decision (say, an incoming call).
269 // TODO: these should be protected to only unlock for certain applications / certain usecases
270 // potentially only in connection with a notification.
271 greeter.hide()
272 }
274 onApplicationAdded: {
275 stages.show(false);
276 }
278 onApplicationRemoved: {
279 if (ApplicationManager.focusedApplicationId.length == 0) {
280 stages.hide();
281 }
282 }
283 }
285 Loader {
286 id: applicationsDisplayLoader
287 anchors.fill: parent
289 source: shell.sideStageEnabled ? "Stages/StageWithSideStage.qml" : "Stages/PhoneStage.qml"
291 Binding {
292 target: applicationsDisplayLoader.item
293 property: "moving"
294 value: !stages.fullyShown
295 }
296 Binding {
297 target: applicationsDisplayLoader.item
298 property: "shown"
299 value: stages.shown
300 }
301 Binding {
302 target: applicationsDisplayLoader.item
303 property: "dragAreaWidth"
304 value: shell.edgeSize
305 }
306 }
307 }
309 Lockscreen {
310 id: lockscreen
311 objectName: "lockscreen"
313 readonly property int backgroundTopMargin: -panel.panelHeight
315 hides: [launcher, panel.indicators, hud]
316 shown: false
317 enabled: true
318 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
319 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
320 y: panel.panelHeight
321 x: required ? 0 : - width
322 width: parent.width
323 height: parent.height - panel.panelHeight
324 background: shell.background
325 pinLength: 4
327 onEntered: LightDM.Greeter.respond(passphrase);
328 onCancel: greeter.show()
330 Component.onCompleted: {
331 if (LightDM.Users.count == 1) {
332 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
333 }
334 }
335 }
337 Connections {
338 target: LightDM.Greeter
340 onShowPrompt: {
341 if (LightDM.Users.count == 1) {
342 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
343 if (text == "PIN") {
344 lockscreen.alphaNumeric = false
345 } else {
346 lockscreen.alphaNumeric = true
347 }
348 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
349 lockscreen.show();
350 }
351 }
353 onAuthenticationComplete: {
354 if (LightDM.Greeter.promptless) {
355 return;
356 }
357 if (LightDM.Greeter.authenticated) {
358 lockscreen.hide();
359 } else {
360 lockscreen.clear(true);
361 }
362 }
363 }
365 Rectangle {
366 anchors.fill: parent
367 color: "black"
368 opacity: greeterWrapper.showProgress * 0.8
369 }
371 Item {
372 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
373 id: greeterWrapper
374 x: launcher.progress
375 y: panel.panelHeight
376 width: parent.width
377 height: parent.height - panel.panelHeight
379 Behavior on x {SmoothedAnimation{velocity: 600}}
381 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
383 Greeter {
384 id: greeter
385 objectName: "greeter"
387 available: true
388 hides: [launcher, panel.indicators, hud]
389 shown: true
391 defaultBackground: shell.background
393 width: parent.width
394 height: parent.height
396 dragHandleWidth: shell.edgeSize
398 onShownChanged: {
399 if (shown) {
400 lockscreen.reset();
401 // If there is only one user, we start authenticating with that one here.
402 // If there are more users, the Greeter will handle that
403 if (LightDM.Users.count == 1) {
404 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
405 }
406 greeter.forceActiveFocus();
407 }
408 }
410 onUnlocked: greeter.hide()
411 onSelected: {
412 // Update launcher items for new user
413 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
414 AccountsService.user = user;
415 LauncherModel.setUser(user);
416 }
418 onTease: launcher.tease()
420 Binding {
421 target: ApplicationManager
422 property: "suspended"
423 value: greeter.shown && greeterWrapper.showProgress == 1
424 }
425 }
426 }
428 InputFilterArea {
429 anchors.fill: parent
430 blockInput: ApplicationManager.focusedApplicationId.length === 0 || greeter.shown || lockscreen.shown || launcher.shown
431 || panel.indicators.shown || hud.shown
432 }
434 Connections {
435 id: powerConnection
436 target: Powerd
438 onDisplayPowerStateChange: {
439 // We ignore any display-off signals when the proximity sensor
440 // is active. This usually indicates something like a phone call.
441 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
442 greeter.showNow();
443 }
445 // No reason to chew demo CPU when user isn't watching
446 if (status == Powerd.Off) {
447 edgeDemo.paused = true;
448 } else if (status == Powerd.On) {
449 edgeDemo.paused = false;
450 }
451 }
452 }
454 function showHome() {
455 var animate = !greeter.shown && !stages.shown
456 greeter.hide()
457 dash.setCurrentScope("clickscope", animate, false)
458 stages.hide()
459 }
461 function hideIndicatorMenu(delay) {
462 panel.hideIndicatorMenu(delay);
463 }
465 Item {
466 id: overlay
468 anchors.fill: parent
470 Panel {
471 id: panel
472 anchors.fill: parent //because this draws indicator menus
473 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
474 indicators {
475 hides: [launcher]
476 available: edgeDemo.panelEnabled
477 contentEnabled: edgeDemo.panelContentEnabled
478 }
479 property string focusedAppId: ApplicationManager.focusedApplicationId
480 property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
481 fullscreenMode: focusedApplication && stages.fullscreen && !greeter.shown && !lockscreen.shown
482 searchVisible: !greeter.shown && !lockscreen.shown && dash.shown && dash.searchable
484 InputFilterArea {
485 anchors {
486 top: parent.top
487 left: parent.left
488 right: parent.right
489 }
490 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
491 blockInput: true
492 }
493 }
495 Hud {
496 id: hud
498 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
499 height: parent.height
501 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown && edgeDemo.dashEnabled
502 shown: false
503 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
504 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
506 Connections {
507 target: ApplicationManager
508 onFocusedApplicationIdChanged: hud.hide()
509 }
510 }
512 Revealer {
513 id: hudRevealer
515 enabled: hud.shown
516 width: hud.width
517 anchors.left: hud.left
518 height: parent.height
519 target: hud.revealerTarget
520 closedValue: height
521 openedValue: 0
522 direction: Qt.RightToLeft
523 orientation: Qt.Vertical
524 handleSize: hud.handleHeight
525 onCloseClicked: target.hide()
526 }
528 Bottombar {
529 id: bottombar
530 theHud: hud
531 anchors.fill: parent
532 enabled: hud.available
533 applicationIsOnForeground: ApplicationManager.focusedApplicationId
534 }
536 InputFilterArea {
537 blockInput: launcher.shown
538 anchors {
539 top: parent.top
540 bottom: parent.bottom
541 left: parent.left
542 }
543 width: launcher.width
544 }
546 Launcher {
547 id: launcher
549 readonly property bool dashSwipe: progress > 0
551 anchors.top: parent.top
552 anchors.bottom: parent.bottom
553 width: parent.width
554 dragAreaWidth: shell.edgeSize
555 available: (!greeter.shown || greeter.narrowMode) && edgeDemo.launcherEnabled
557 onShowDashHome: {
558 if (edgeDemo.running)
559 return;
561 showHome()
562 }
563 onDash: {
564 if (stages.shown && !stages.overlayMode) {
565 if (!stages.locked) {
566 stages.hide();
567 launcher.hide();
568 }
569 }
570 if (greeter.shown) {
571 greeter.hideRight();
572 launcher.hide();
573 }
574 }
575 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
576 onLauncherApplicationSelected:{
577 if (edgeDemo.running)
578 return;
580 greeter.hide()
581 shell.activateApplication(appId)
582 }
583 onShownChanged: {
584 if (shown) {
585 panel.indicators.hide()
586 hud.hide()
587 bottombar.hide()
588 }
589 }
590 }
592 Notifications {
593 id: notifications
595 model: NotificationBackend.Model
596 margin: units.gu(1)
598 anchors {
599 top: parent.top
600 right: parent.right
601 bottom: parent.bottom
602 topMargin: panel.panelHeight
603 }
604 states: [
605 State {
606 name: "narrow"
607 when: overlay.width <= units.gu(60)
608 AnchorChanges { target: notifications; anchors.left: parent.left }
609 },
610 State {
611 name: "wide"
612 when: overlay.width > units.gu(60)
613 AnchorChanges { target: notifications; anchors.left: undefined }
614 PropertyChanges { target: notifications; width: units.gu(38) }
615 }
616 ]
618 InputFilterArea {
619 anchors { left: parent.left; right: parent.right }
620 height: parent.contentHeight
621 blockInput: height > 0
622 }
623 }
624 }
626 focus: true
627 onFocusChanged: if (!focus) forceActiveFocus();
629 InputFilterArea {
630 anchors {
631 top: parent.top
632 bottom: parent.bottom
633 left: parent.left
634 }
635 width: shell.edgeSize
636 blockInput: true
637 }
639 InputFilterArea {
640 anchors {
641 top: parent.top
642 bottom: parent.bottom
643 right: parent.right
644 }
645 width: shell.edgeSize
646 blockInput: true
647 }
649 Binding {
650 target: i18n
651 property: "domain"
652 value: "unity8"
653 }
655 OSKController {
656 anchors.topMargin: panel.panelHeight
657 anchors.fill: parent // as needs to know the geometry of the shell
658 }
660 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
661 MouseArea {
662 anchors.bottom: parent.bottom
663 anchors.left: parent.left
664 anchors.right: parent.right
665 height: ApplicationManager.keyboardVisible ? ApplicationManager.keyboardHeight : 0
667 enabled: ApplicationManager.keyboardVisible
668 }
670 Label {
671 anchors.centerIn: parent
672 visible: ApplicationManager.fake ? ApplicationManager.fake : false
673 text: "EARLY ALPHA\nNOT READY FOR USE"
674 color: "lightgrey"
675 opacity: 0.2
676 font.weight: Font.Black
677 horizontalAlignment: Text.AlignHCenter
678 verticalAlignment: Text.AlignVCenter
679 fontSizeMode: Text.Fit
680 rotation: -45
681 scale: Math.min(parent.width, parent.height) / width
682 }
684 EdgeDemo {
685 id: edgeDemo
686 greeter: greeter
687 launcher: launcher
688 dash: dash
689 indicators: panel.indicators
690 underlay: underlay
691 }
693 Connections {
694 target: SessionBroadcast
695 onShowHome: showHome()
696 }