on 08b5d88 help sign in
unity8/qml/Shell.qml
688 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 "Components"
32import "Notifications"
33import Unity.Notifications 1.0 as NotificationBackend
34import Unity.Session 0.1
36FocusScope {
37 id: shell
39 // this is only here to select the width / height of the window if not running fullscreen
40 property bool tablet: false
41 width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
42 height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
44 property real edgeSize: units.gu(2)
45 property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
46 property url background
47 readonly property real panelHeight: panel.panelHeight
49 property bool dashShown: dash.shown
51 property bool sideStageEnabled: shell.width >= units.gu(100)
52 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
54 function activateApplication(appId) {
55 if (ApplicationManager.findApplication(appId)) {
56 ApplicationManager.requestFocusApplication(appId);
57 stages.show(true);
58 if (stages.locked && ApplicationManager.focusedApplicationId == appId) {
59 applicationsDisplayLoader.item.select(appId);
60 }
61 } else {
62 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
63 ApplicationManager.startApplication(appId, execFlags);
64 stages.show(false);
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 VolumeControl {
88 id: volumeControl
89 }
91 Keys.onVolumeUpPressed: volumeControl.volumeUp()
92 Keys.onVolumeDownPressed: volumeControl.volumeDown()
94 Item {
95 id: underlayClipper
96 anchors.fill: parent
97 anchors.rightMargin: stages.overlayWidth
98 clip: stages.overlayMode && !stages.painting
100 InputFilterArea {
101 anchors.fill: parent
102 blockInput: parent.clip
103 }
105 Item {
106 id: underlay
107 objectName: "underlay"
108 anchors.fill: parent
109 anchors.rightMargin: -parent.anchors.rightMargin
111 // Whether the underlay is fully covered by opaque UI elements.
112 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
114 // Whether the user should see the topmost application surface (if there's one at all).
115 readonly property bool applicationSurfaceShouldBeSeen: stages.shown && !stages.painting && !stages.overlayMode
117 // NB! Application surfaces are stacked behind the shell one. So they can only be seen by the user
118 // through the translucent parts of the shell surface.
119 visible: !fullyCovered && !applicationSurfaceShouldBeSeen
121 CrossFadeImage {
122 id: backgroundImage
123 objectName: "backgroundImage"
125 anchors.fill: parent
126 source: shell.background
127 fillMode: Image.PreserveAspectCrop
128 }
130 Rectangle {
131 anchors.fill: parent
132 color: "black"
133 opacity: dash.disappearingAnimationProgress
134 }
136 Image {
137 anchors.fill: dash
138 source: shell.width > shell.height ? "Dash/graphics/paper_landscape.png" : "Dash/graphics/paper_portrait.png"
139 fillMode: Image.PreserveAspectCrop
140 horizontalAlignment: Image.AlignRight
141 verticalAlignment: Image.AlignTop
142 }
144 Dash {
145 id: dash
146 objectName: "dash"
148 available: !greeter.shown && !lockscreen.shown
149 hides: [stages, launcher, panel.indicators]
150 shown: disappearingAnimationProgress !== 1.0 && greeterWrapper.showProgress !== 1.0
151 enabled: disappearingAnimationProgress === 0.0 && greeterWrapper.showProgress === 0.0 && edgeDemo.dashEnabled
153 anchors {
154 fill: parent
155 topMargin: panel.panelHeight
156 }
158 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
159 opacity: 1.0 - disappearingAnimationProgress
160 property real disappearingAnimationProgress: {
161 if (stages.overlayMode) {
162 return 0;
163 } else {
164 return stages.showProgress
165 }
166 }
168 // FIXME: only necessary because stages.showProgress is not animated
169 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
170 }
171 }
172 }
174 EdgeDragArea {
175 id: stagesDragHandle
176 direction: Direction.Leftwards
178 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
179 width: shell.edgeSize
181 property real progress: stages.width
183 onTouchXChanged: {
184 if (status == DirectionalDragArea.Recognized) {
185 if (ApplicationManager.count == 0) {
186 progress = Math.max(stages.width - stagesDragHandle.width + touchX, stages.width * .3)
187 } else {
188 progress = stages.width - stagesDragHandle.width + touchX
189 }
190 }
191 }
193 onDraggingChanged: {
194 if (!dragging) {
195 if (ApplicationManager.count > 0 && progress < stages.width - units.gu(10)) {
196 stages.show(true)
197 }
198 stagesDragHandle.progress = stages.width;
199 }
200 }
201 }
203 Item {
204 id: stages
205 objectName: "stages"
206 width: parent.width
207 height: parent.height
209 x: {
210 if (shown) {
211 if (overlayMode || locked) {
212 return 0;
213 }
214 return launcher.progress
215 } else {
216 return stagesDragHandle.progress
217 }
218 }
220 Behavior on x { SmoothedAnimation { velocity: 600; duration: UbuntuAnimation.FastDuration } }
222 property bool shown: false
224 property real showProgress: overlayMode ? 0 : MathUtils.clamp(1 - x / shell.width, 0, 1)
226 property bool fullyShown: x == 0
227 property bool fullyHidden: x == width
229 property bool painting: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.painting : false
230 property bool fullscreen: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.fullscreen : false
231 property bool overlayMode: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayMode : false
232 property int overlayWidth: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayWidth : false
233 property bool locked: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.locked : false
235 function show(focusApp) {
236 shown = true;
237 panel.indicators.hide();
238 edgeDemo.stopDemo();
239 greeter.hide();
240 if (!ApplicationManager.focusedApplicationId && ApplicationManager.count > 0 && focusApp) {
241 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
242 }
243 }
245 function hide() {
246 shown = false;
247 if (ApplicationManager.focusedApplicationId) {
248 ApplicationManager.unfocusCurrentApplication();
249 }
250 }
252 Connections {
253 target: ApplicationManager
255 onFocusRequested: {
256 stages.show(true);
257 }
259 onFocusedApplicationIdChanged: {
260 if (ApplicationManager.focusedApplicationId.length > 0) {
261 stages.show(false);
262 } else {
263 if (!stages.overlayMode) {
264 stages.hide();
265 }
266 }
267 }
269 onApplicationAdded: {
270 stages.show(false);
271 }
273 onApplicationRemoved: {
274 if (ApplicationManager.focusedApplicationId.length == 0) {
275 stages.hide();
276 }
277 }
278 }
280 Connections {
281 target: DBusUnitySessionService
283 function closeAllApps() {
284 while (true) {
285 var app = ApplicationManager.get(0);
286 if (app === null) {
287 break;
288 }
289 ApplicationManager.stopApplication(app.appId);
290 }
291 }
293 onLogoutRequested: {
294 // TODO: Display a dialog to ask the user to confirm.
295 DBusUnitySessionService.Logout();
296 }
298 onLogoutReady: {
299 closeAllApps();
300 Qt.quit();
301 }
302 }
304 Loader {
305 id: applicationsDisplayLoader
306 anchors.fill: parent
308 source: shell.sideStageEnabled ? "Stages/StageWithSideStage.qml" : "Stages/PhoneStage.qml"
310 Binding {
311 target: applicationsDisplayLoader.item
312 property: "moving"
313 value: !stages.fullyShown
314 }
315 Binding {
316 target: applicationsDisplayLoader.item
317 property: "shown"
318 value: stages.shown
319 }
320 Binding {
321 target: applicationsDisplayLoader.item
322 property: "dragAreaWidth"
323 value: shell.edgeSize
324 }
325 }
326 }
328 Lockscreen {
329 id: lockscreen
330 objectName: "lockscreen"
332 readonly property int backgroundTopMargin: -panel.panelHeight
334 hides: [launcher, panel.indicators]
335 shown: false
336 enabled: true
337 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
338 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
339 y: panel.panelHeight
340 x: required ? 0 : - width
341 width: parent.width
342 height: parent.height - panel.panelHeight
343 background: shell.background
344 pinLength: 4
346 onEntered: LightDM.Greeter.respond(passphrase);
347 onCancel: greeter.show()
349 Component.onCompleted: {
350 if (LightDM.Users.count == 1) {
351 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
352 }
353 }
354 }
356 Connections {
357 target: LightDM.Greeter
359 onShowPrompt: {
360 if (LightDM.Users.count == 1) {
361 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
362 if (text == "PIN") {
363 lockscreen.alphaNumeric = false
364 } else {
365 lockscreen.alphaNumeric = true
366 }
367 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
368 lockscreen.show();
369 }
370 }
372 onAuthenticationComplete: {
373 if (LightDM.Greeter.promptless) {
374 return;
375 }
376 if (LightDM.Greeter.authenticated) {
377 lockscreen.hide();
378 } else {
379 lockscreen.clear(true);
380 }
381 }
382 }
384 Rectangle {
385 anchors.fill: parent
386 color: "black"
387 opacity: greeterWrapper.showProgress * 0.8
388 }
390 Item {
391 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
392 id: greeterWrapper
393 x: launcher.progress
394 y: panel.panelHeight
395 width: parent.width
396 height: parent.height - panel.panelHeight
398 Behavior on x {SmoothedAnimation{velocity: 600}}
400 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
402 Greeter {
403 id: greeter
404 objectName: "greeter"
406 available: true
407 hides: [launcher, panel.indicators]
408 shown: true
410 defaultBackground: shell.background
412 width: parent.width
413 height: parent.height
415 dragHandleWidth: shell.edgeSize
417 onShownChanged: {
418 if (shown) {
419 lockscreen.reset();
420 // If there is only one user, we start authenticating with that one here.
421 // If there are more users, the Greeter will handle that
422 if (LightDM.Users.count == 1) {
423 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
424 }
425 greeter.forceActiveFocus();
426 }
427 }
429 onUnlocked: greeter.hide()
430 onSelected: {
431 // Update launcher items for new user
432 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
433 AccountsService.user = user;
434 LauncherModel.setUser(user);
435 }
437 onTease: launcher.tease()
439 Binding {
440 target: ApplicationManager
441 property: "suspended"
442 value: greeter.shown && greeterWrapper.showProgress == 1
443 }
444 }
445 }
447 InputFilterArea {
448 anchors.fill: parent
449 blockInput: ApplicationManager.focusedApplicationId.length === 0 || greeter.shown || lockscreen.shown || launcher.shown
450 || panel.indicators.shown
451 }
453 Connections {
454 id: powerConnection
455 target: Powerd
457 onDisplayPowerStateChange: {
458 // We ignore any display-off signals when the proximity sensor
459 // is active. This usually indicates something like a phone call.
460 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
461 greeter.showNow();
462 }
464 // No reason to chew demo CPU when user isn't watching
465 if (status == Powerd.Off) {
466 edgeDemo.paused = true;
467 } else if (status == Powerd.On) {
468 edgeDemo.paused = false;
469 }
470 }
471 }
473 function showHome() {
474 var animate = !greeter.shown && !stages.shown
475 greeter.hide()
476 dash.setCurrentScope("clickscope", animate, false)
477 stages.hide()
478 }
480 function hideIndicatorMenu(delay) {
481 panel.hideIndicatorMenu(delay);
482 }
484 Item {
485 id: overlay
487 anchors.fill: parent
489 Panel {
490 id: panel
491 anchors.fill: parent //because this draws indicator menus
492 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
493 indicators {
494 hides: [launcher]
495 available: edgeDemo.panelEnabled
496 contentEnabled: edgeDemo.panelContentEnabled
497 }
498 property string focusedAppId: ApplicationManager.focusedApplicationId
499 property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
500 fullscreenMode: focusedApplication && stages.fullscreen && !greeter.shown && !lockscreen.shown
501 searchVisible: !greeter.shown && !lockscreen.shown && dash.shown && dash.searchable
503 InputFilterArea {
504 anchors {
505 top: parent.top
506 left: parent.left
507 right: parent.right
508 }
509 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
510 blockInput: true
511 }
512 }
514 InputFilterArea {
515 blockInput: launcher.shown
516 anchors {
517 top: parent.top
518 bottom: parent.bottom
519 left: parent.left
520 }
521 width: launcher.width
522 }
524 Launcher {
525 id: launcher
527 readonly property bool dashSwipe: progress > 0
529 anchors.top: parent.top
530 anchors.bottom: parent.bottom
531 width: parent.width
532 dragAreaWidth: shell.edgeSize
533 available: edgeDemo.launcherEnabled
535 onShowDashHome: {
536 if (edgeDemo.running)
537 return;
539 showHome()
540 }
541 onDash: {
542 if (stages.shown && !stages.overlayMode) {
543 if (!stages.locked) {
544 stages.hide();
545 launcher.hide();
546 }
547 }
548 if (greeter.shown) {
549 greeter.hideRight();
550 launcher.hide();
551 }
552 }
553 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
554 onLauncherApplicationSelected: {
555 if (!edgeDemo.running)
556 shell.activateApplication(appId)
557 }
558 onShownChanged: {
559 if (shown) {
560 panel.indicators.hide()
561 }
562 }
563 }
565 Rectangle {
566 id: modalNotificationBackground
568 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
569 color: "#000000"
570 anchors.fill: parent
571 opacity: 0.5
573 MouseArea {
574 anchors.fill: parent
575 }
577 InputFilterArea {
578 anchors.fill: parent
579 blockInput: modalNotificationBackground.visible
580 }
581 }
583 Notifications {
584 id: notifications
586 model: NotificationBackend.Model
587 margin: units.gu(1)
589 anchors {
590 top: parent.top
591 right: parent.right
592 bottom: parent.bottom
593 topMargin: panel.panelHeight
594 }
595 states: [
596 State {
597 name: "narrow"
598 when: overlay.width <= units.gu(60)
599 AnchorChanges { target: notifications; anchors.left: parent.left }
600 },
601 State {
602 name: "wide"
603 when: overlay.width > units.gu(60)
604 AnchorChanges { target: notifications; anchors.left: undefined }
605 PropertyChanges { target: notifications; width: units.gu(38) }
606 }
607 ]
609 InputFilterArea {
610 anchors { left: parent.left; right: parent.right }
611 height: parent.contentHeight
612 blockInput: height > 0
613 }
614 }
615 }
617 focus: true
618 onFocusChanged: if (!focus) forceActiveFocus();
620 InputFilterArea {
621 anchors {
622 top: parent.top
623 bottom: parent.bottom
624 left: parent.left
625 }
626 width: shell.edgeSize
627 blockInput: true
628 }
630 InputFilterArea {
631 anchors {
632 top: parent.top
633 bottom: parent.bottom
634 right: parent.right
635 }
636 width: shell.edgeSize
637 blockInput: true
638 }
640 Binding {
641 target: i18n
642 property: "domain"
643 value: "unity8"
644 }
646 OSKController {
647 anchors.topMargin: panel.panelHeight
648 anchors.fill: parent // as needs to know the geometry of the shell
649 }
651 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
652 MouseArea {
653 anchors.bottom: parent.bottom
654 anchors.left: parent.left
655 anchors.right: parent.right
656 height: ApplicationManager.keyboardVisible ? ApplicationManager.keyboardHeight : 0
658 enabled: ApplicationManager.keyboardVisible
659 }
661 Label {
662 anchors.centerIn: parent
663 visible: ApplicationManager.fake ? ApplicationManager.fake : false
664 text: "EARLY ALPHA\nNOT READY FOR USE"
665 color: "lightgrey"
666 opacity: 0.2
667 font.weight: Font.Black
668 horizontalAlignment: Text.AlignHCenter
669 verticalAlignment: Text.AlignVCenter
670 fontSizeMode: Text.Fit
671 rotation: -45
672 scale: Math.min(parent.width, parent.height) / width
673 }
675 EdgeDemo {
676 id: edgeDemo
677 greeter: greeter
678 launcher: launcher
679 dash: dash
680 indicators: panel.indicators
681 underlay: underlay
682 }
684 Connections {
685 target: SessionBroadcast
686 onShowHome: showHome()
687 }