on c88ea0a help sign in
unity8/qml/Shell.qml
708 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 edgeDemo.stopDemo();
240 greeter.hide();
241 if (!ApplicationManager.focusedApplicationId && ApplicationManager.count > 0 && focusApp) {
242 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
243 }
244 }
246 function hide() {
247 shown = false;
248 if (ApplicationManager.focusedApplicationId) {
249 ApplicationManager.unfocusCurrentApplication();
250 }
251 }
253 Connections {
254 target: ApplicationManager
256 onFocusRequested: {
257 stages.show(true);
258 }
260 onFocusedApplicationIdChanged: {
261 if (ApplicationManager.focusedApplicationId.length > 0) {
262 stages.show(false);
263 } else {
264 if (!stages.overlayMode) {
265 stages.hide();
266 }
267 }
268 }
270 onApplicationAdded: {
271 stages.show(false);
272 }
274 onApplicationRemoved: {
275 if (ApplicationManager.focusedApplicationId.length == 0) {
276 stages.hide();
277 }
278 }
279 }
281 Loader {
282 id: applicationsDisplayLoader
283 anchors.fill: parent
285 source: shell.sideStageEnabled ? "Stages/StageWithSideStage.qml" : "Stages/PhoneStage.qml"
287 Binding {
288 target: applicationsDisplayLoader.item
289 property: "moving"
290 value: !stages.fullyShown
291 }
292 Binding {
293 target: applicationsDisplayLoader.item
294 property: "shown"
295 value: stages.shown
296 }
297 Binding {
298 target: applicationsDisplayLoader.item
299 property: "dragAreaWidth"
300 value: shell.edgeSize
301 }
302 }
303 }
305 Lockscreen {
306 id: lockscreen
307 objectName: "lockscreen"
309 readonly property int backgroundTopMargin: -panel.panelHeight
311 hides: [launcher, panel.indicators, hud]
312 shown: false
313 enabled: true
314 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
315 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
316 y: panel.panelHeight
317 x: required ? 0 : - width
318 width: parent.width
319 height: parent.height - panel.panelHeight
320 background: shell.background
321 pinLength: 4
323 onEntered: LightDM.Greeter.respond(passphrase);
324 onCancel: greeter.show()
326 Component.onCompleted: {
327 if (LightDM.Users.count == 1) {
328 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
329 }
330 }
331 }
333 Connections {
334 target: LightDM.Greeter
336 onShowPrompt: {
337 if (LightDM.Users.count == 1) {
338 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
339 if (text == "PIN") {
340 lockscreen.alphaNumeric = false
341 } else {
342 lockscreen.alphaNumeric = true
343 }
344 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
345 lockscreen.show();
346 }
347 }
349 onAuthenticationComplete: {
350 if (LightDM.Greeter.promptless) {
351 return;
352 }
353 if (LightDM.Greeter.authenticated) {
354 lockscreen.hide();
355 } else {
356 lockscreen.clear(true);
357 }
358 }
359 }
361 Rectangle {
362 anchors.fill: parent
363 color: "black"
364 opacity: greeterWrapper.showProgress * 0.8
365 }
367 Item {
368 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
369 id: greeterWrapper
370 x: launcher.progress
371 y: panel.panelHeight
372 width: parent.width
373 height: parent.height - panel.panelHeight
375 Behavior on x {SmoothedAnimation{velocity: 600}}
377 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
379 Greeter {
380 id: greeter
381 objectName: "greeter"
383 available: true
384 hides: [launcher, panel.indicators, hud]
385 shown: true
387 defaultBackground: shell.background
389 width: parent.width
390 height: parent.height
392 dragHandleWidth: shell.edgeSize
394 onShownChanged: {
395 if (shown) {
396 lockscreen.reset();
397 // If there is only one user, we start authenticating with that one here.
398 // If there are more users, the Greeter will handle that
399 if (LightDM.Users.count == 1) {
400 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
401 }
402 greeter.forceActiveFocus();
403 }
404 }
406 onUnlocked: greeter.hide()
407 onSelected: {
408 // Update launcher items for new user
409 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
410 AccountsService.user = user;
411 LauncherModel.setUser(user);
412 }
414 onTease: launcher.tease()
416 Binding {
417 target: ApplicationManager
418 property: "suspended"
419 value: greeter.shown && greeterWrapper.showProgress == 1
420 }
421 }
422 }
424 InputFilterArea {
425 anchors.fill: parent
426 blockInput: ApplicationManager.focusedApplicationId.length === 0 || greeter.shown || lockscreen.shown || launcher.shown
427 || panel.indicators.shown || hud.shown
428 }
430 Connections {
431 id: powerConnection
432 target: Powerd
434 onDisplayPowerStateChange: {
435 // We ignore any display-off signals when the proximity sensor
436 // is active. This usually indicates something like a phone call.
437 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
438 greeter.showNow();
439 }
441 // No reason to chew demo CPU when user isn't watching
442 if (status == Powerd.Off) {
443 edgeDemo.paused = true;
444 } else if (status == Powerd.On) {
445 edgeDemo.paused = false;
446 }
447 }
448 }
450 function showHome() {
451 var animate = !greeter.shown && !stages.shown
452 greeter.hide()
453 dash.setCurrentScope("clickscope", animate, false)
454 stages.hide()
455 }
457 function hideIndicatorMenu(delay) {
458 panel.hideIndicatorMenu(delay);
459 }
461 Item {
462 id: overlay
464 anchors.fill: parent
466 Panel {
467 id: panel
468 anchors.fill: parent //because this draws indicator menus
469 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
470 indicators {
471 hides: [launcher]
472 available: edgeDemo.panelEnabled
473 contentEnabled: edgeDemo.panelContentEnabled
474 }
475 property string focusedAppId: ApplicationManager.focusedApplicationId
476 property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
477 fullscreenMode: focusedApplication && stages.fullscreen && !greeter.shown && !lockscreen.shown
478 searchVisible: !greeter.shown && !lockscreen.shown && dash.shown && dash.searchable
480 InputFilterArea {
481 anchors {
482 top: parent.top
483 left: parent.left
484 right: parent.right
485 }
486 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
487 blockInput: true
488 }
489 }
491 Hud {
492 id: hud
494 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
495 height: parent.height
497 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown && edgeDemo.dashEnabled
498 shown: false
499 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
500 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
502 Connections {
503 target: ApplicationManager
504 onFocusedApplicationIdChanged: hud.hide()
505 }
506 }
508 Revealer {
509 id: hudRevealer
511 enabled: hud.shown
512 width: hud.width
513 anchors.left: hud.left
514 height: parent.height
515 target: hud.revealerTarget
516 closedValue: height
517 openedValue: 0
518 direction: Qt.RightToLeft
519 orientation: Qt.Vertical
520 handleSize: hud.handleHeight
521 onCloseClicked: target.hide()
522 }
524 Bottombar {
525 id: bottombar
526 theHud: hud
527 anchors.fill: parent
528 enabled: hud.available
529 applicationIsOnForeground: ApplicationManager.focusedApplicationId
530 }
532 InputFilterArea {
533 blockInput: launcher.shown
534 anchors {
535 top: parent.top
536 bottom: parent.bottom
537 left: parent.left
538 }
539 width: launcher.width
540 }
542 Launcher {
543 id: launcher
545 readonly property bool dashSwipe: progress > 0
547 anchors.top: parent.top
548 anchors.bottom: parent.bottom
549 width: parent.width
550 dragAreaWidth: shell.edgeSize
551 available: edgeDemo.launcherEnabled
553 onShowDashHome: {
554 if (edgeDemo.running)
555 return;
557 showHome()
558 }
559 onDash: {
560 if (stages.shown && !stages.overlayMode) {
561 if (!stages.locked) {
562 stages.hide();
563 launcher.hide();
564 }
565 }
566 if (greeter.shown) {
567 greeter.hideRight();
568 launcher.hide();
569 }
570 }
571 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
572 onLauncherApplicationSelected: {
573 if (!edgeDemo.running)
574 shell.activateApplication(appId)
575 }
576 onShownChanged: {
577 if (shown) {
578 panel.indicators.hide()
579 hud.hide()
580 bottombar.hide()
581 }
582 }
583 }
585 Rectangle {
586 id: modalNotificationBackground
588 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
589 color: "#000000"
590 anchors.fill: parent
591 opacity: 0.5
593 MouseArea {
594 anchors.fill: parent
595 }
597 InputFilterArea {
598 anchors.fill: parent
599 blockInput: modalNotificationBackground.visible
600 }
601 }
603 Notifications {
604 id: notifications
606 model: NotificationBackend.Model
607 margin: units.gu(1)
609 anchors {
610 top: parent.top
611 right: parent.right
612 bottom: parent.bottom
613 topMargin: panel.panelHeight
614 }
615 states: [
616 State {
617 name: "narrow"
618 when: overlay.width <= units.gu(60)
619 AnchorChanges { target: notifications; anchors.left: parent.left }
620 },
621 State {
622 name: "wide"
623 when: overlay.width > units.gu(60)
624 AnchorChanges { target: notifications; anchors.left: undefined }
625 PropertyChanges { target: notifications; width: units.gu(38) }
626 }
627 ]
629 InputFilterArea {
630 anchors { left: parent.left; right: parent.right }
631 height: parent.contentHeight
632 blockInput: height > 0
633 }
634 }
635 }
637 focus: true
638 onFocusChanged: if (!focus) forceActiveFocus();
640 InputFilterArea {
641 anchors {
642 top: parent.top
643 bottom: parent.bottom
644 left: parent.left
645 }
646 width: shell.edgeSize
647 blockInput: true
648 }
650 InputFilterArea {
651 anchors {
652 top: parent.top
653 bottom: parent.bottom
654 right: parent.right
655 }
656 width: shell.edgeSize
657 blockInput: true
658 }
660 Binding {
661 target: i18n
662 property: "domain"
663 value: "unity8"
664 }
666 OSKController {
667 anchors.topMargin: panel.panelHeight
668 anchors.fill: parent // as needs to know the geometry of the shell
669 }
671 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
672 MouseArea {
673 anchors.bottom: parent.bottom
674 anchors.left: parent.left
675 anchors.right: parent.right
676 height: ApplicationManager.keyboardVisible ? ApplicationManager.keyboardHeight : 0
678 enabled: ApplicationManager.keyboardVisible
679 }
681 Label {
682 anchors.centerIn: parent
683 visible: ApplicationManager.fake ? ApplicationManager.fake : false
684 text: "EARLY ALPHA\nNOT READY FOR USE"
685 color: "lightgrey"
686 opacity: 0.2
687 font.weight: Font.Black
688 horizontalAlignment: Text.AlignHCenter
689 verticalAlignment: Text.AlignVCenter
690 fontSizeMode: Text.Fit
691 rotation: -45
692 scale: Math.min(parent.width, parent.height) / width
693 }
695 EdgeDemo {
696 id: edgeDemo
697 greeter: greeter
698 launcher: launcher
699 dash: dash
700 indicators: panel.indicators
701 underlay: underlay
702 }
704 Connections {
705 target: SessionBroadcast
706 onShowHome: showHome()
707 }