on 770ffe6 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 "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.indicators.width
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 Rectangle {
122 anchors.fill: parent
123 color: "black"
124 opacity: dash.disappearingAnimationProgress
125 }
127 Dash {
128 id: dash
129 objectName: "dash"
131 available: !greeter.shown && !lockscreen.shown
132 hides: [stages, launcher, panel.indicators]
133 shown: disappearingAnimationProgress !== 1.0 && greeterWrapper.showProgress !== 1.0
134 enabled: disappearingAnimationProgress === 0.0 && greeterWrapper.showProgress === 0.0 && edgeDemo.dashEnabled
136 overviewHandleHeight: shell.edgeSize
138 anchors {
139 fill: parent
140 topMargin: panel.panelHeight
141 }
143 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
144 opacity: 1.0 - disappearingAnimationProgress
145 property real disappearingAnimationProgress: {
146 if (stages.overlayMode) {
147 return 0;
148 } else {
149 return stages.showProgress
150 }
151 }
153 // FIXME: only necessary because stages.showProgress is not animated
154 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
155 }
156 }
157 }
159 EdgeDragArea {
160 id: stagesDragHandle
161 direction: Direction.Leftwards
163 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
164 width: shell.edgeSize
166 property real progress: stages.width
168 onTouchXChanged: {
169 if (status == DirectionalDragArea.Recognized) {
170 if (ApplicationManager.count == 0) {
171 progress = Math.max(stages.width - stagesDragHandle.width + touchX, stages.width * .3)
172 } else {
173 progress = stages.width - stagesDragHandle.width + touchX
174 }
175 }
176 }
178 onDraggingChanged: {
179 if (!dragging) {
180 if (ApplicationManager.count > 0 && progress < stages.width - units.gu(10)) {
181 stages.show(true)
182 }
183 stagesDragHandle.progress = stages.width;
184 }
185 }
186 }
188 Item {
189 id: stages
190 objectName: "stages"
191 width: parent.width
192 height: parent.height
194 x: {
195 if (shown) {
196 if (overlayMode || locked) {
197 return 0;
198 }
199 return launcher.progress
200 } else {
201 return stagesDragHandle.progress
202 }
203 }
205 Behavior on x { SmoothedAnimation { velocity: 600; duration: UbuntuAnimation.FastDuration } }
207 property bool shown: false
209 property real showProgress: overlayMode ? 0 : MathUtils.clamp(1 - x / shell.width, 0, 1)
211 property bool fullyShown: x == 0
212 property bool fullyHidden: x == width
214 property bool painting: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.painting : false
215 property bool fullscreen: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.fullscreen : false
216 property bool overlayMode: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayMode : false
217 property int overlayWidth: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayWidth : false
218 property bool locked: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.locked : false
220 function show(focusApp) {
221 shown = true;
222 panel.indicators.hide();
223 edgeDemo.stopDemo();
224 greeter.hide();
225 if (!ApplicationManager.focusedApplicationId && ApplicationManager.count > 0 && focusApp) {
226 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
227 }
228 }
230 function hide() {
231 shown = false;
232 if (ApplicationManager.focusedApplicationId) {
233 ApplicationManager.unfocusCurrentApplication();
234 }
235 }
237 Connections {
238 target: ApplicationManager
240 onFocusRequested: {
241 stages.show(true);
242 }
244 onFocusedApplicationIdChanged: {
245 if (ApplicationManager.focusedApplicationId.length > 0) {
246 stages.show(false);
247 } else {
248 if (!stages.overlayMode) {
249 stages.hide();
250 }
251 }
252 }
254 onApplicationAdded: {
255 stages.show(false);
256 }
258 onApplicationRemoved: {
259 if (ApplicationManager.focusedApplicationId.length == 0) {
260 stages.hide();
261 }
262 }
263 }
265 Connections {
266 target: DBusUnitySessionService
268 function closeAllApps() {
269 while (true) {
270 var app = ApplicationManager.get(0);
271 if (app === null) {
272 break;
273 }
274 ApplicationManager.stopApplication(app.appId);
275 }
276 }
278 onLogoutRequested: {
279 // TODO: Display a dialog to ask the user to confirm.
280 DBusUnitySessionService.Logout();
281 }
283 onLogoutReady: {
284 closeAllApps();
285 Qt.quit();
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]
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 minPinLength: 4
330 maxPinLength: 4
332 onEntered: LightDM.Greeter.respond(passphrase);
333 onCancel: greeter.show()
335 Component.onCompleted: {
336 if (LightDM.Users.count == 1) {
337 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
338 }
339 }
340 }
342 Connections {
343 target: LightDM.Greeter
345 onShowPrompt: {
346 if (LightDM.Users.count == 1) {
347 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
348 if (text == "PIN") {
349 lockscreen.alphaNumeric = false
350 } else {
351 lockscreen.alphaNumeric = true
352 }
353 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
354 lockscreen.show();
355 }
356 }
358 onAuthenticationComplete: {
359 if (LightDM.Greeter.promptless) {
360 return;
361 }
362 if (LightDM.Greeter.authenticated) {
363 lockscreen.hide();
364 } else {
365 lockscreen.clear(true);
366 }
367 }
368 }
370 Rectangle {
371 anchors.fill: parent
372 color: "black"
373 opacity: greeterWrapper.showProgress * 0.8
374 }
376 Item {
377 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
378 id: greeterWrapper
379 x: launcher.progress
380 y: panel.panelHeight
381 width: parent.width
382 height: parent.height - panel.panelHeight
384 Behavior on x {
385 enabled: !launcher.dashSwipe
386 StandardAnimation {}
387 }
389 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
391 Greeter {
392 id: greeter
393 objectName: "greeter"
395 available: true
396 hides: [launcher, panel.indicators]
397 shown: true
399 defaultBackground: shell.background
401 width: parent.width
402 height: parent.height
404 dragHandleWidth: shell.edgeSize
406 onShownChanged: {
407 if (shown) {
408 lockscreen.reset();
409 // If there is only one user, we start authenticating with that one here.
410 // If there are more users, the Greeter will handle that
411 if (LightDM.Users.count == 1) {
412 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
413 }
414 greeter.forceActiveFocus();
415 }
416 }
418 onUnlocked: greeter.hide()
419 onSelected: {
420 // Update launcher items for new user
421 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
422 AccountsService.user = user;
423 LauncherModel.setUser(user);
424 }
426 onTease: launcher.tease()
428 Binding {
429 target: ApplicationManager
430 property: "suspended"
431 value: greeter.shown && greeterWrapper.showProgress == 1
432 }
433 }
434 }
436 InputFilterArea {
437 anchors.fill: parent
438 blockInput: ApplicationManager.focusedApplicationId.length === 0 || greeter.shown || lockscreen.shown || launcher.shown
439 || panel.indicators.shown
440 }
442 Connections {
443 id: powerConnection
444 target: Powerd
446 onDisplayPowerStateChange: {
447 // We ignore any display-off signals when the proximity sensor
448 // is active. This usually indicates something like a phone call.
449 if (status == Powerd.Off && reason != Powerd.Proximity) {
450 greeter.showNow();
451 }
453 // No reason to chew demo CPU when user isn't watching
454 if (status == Powerd.Off) {
455 edgeDemo.paused = true;
456 } else if (status == Powerd.On) {
457 edgeDemo.paused = false;
458 }
459 }
460 }
462 function showHome() {
463 var animate = !greeter.shown && !stages.shown
464 greeter.hide()
465 dash.setCurrentScope("clickscope", animate, false)
466 stages.hide()
467 }
469 function hideIndicatorMenu(delay) {
470 panel.hideIndicatorMenu(delay);
471 }
473 Item {
474 id: overlay
476 anchors.fill: parent
478 Panel {
479 id: panel
480 anchors.fill: parent //because this draws indicator menus
481 indicators {
482 hides: [launcher]
483 available: edgeDemo.panelEnabled
484 contentEnabled: edgeDemo.panelContentEnabled
485 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
486 panelHeight: units.gu(3)
487 }
488 property string focusedAppId: ApplicationManager.focusedApplicationId
489 property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
490 fullscreenMode: focusedApplication && stages.fullscreen && !greeter.shown && !lockscreen.shown
492 InputFilterArea {
493 anchors {
494 top: parent.top
495 left: parent.left
496 right: parent.right
497 }
498 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
499 blockInput: true
500 }
501 }
503 InputFilterArea {
504 blockInput: launcher.shown
505 anchors {
506 top: parent.top
507 bottom: parent.bottom
508 left: parent.left
509 }
510 width: launcher.width
511 }
513 Launcher {
514 id: launcher
516 readonly property bool dashSwipe: progress > 0
518 anchors.top: parent.top
519 anchors.bottom: parent.bottom
520 width: parent.width
521 dragAreaWidth: shell.edgeSize
522 available: edgeDemo.launcherEnabled
524 onShowDashHome: {
525 if (edgeDemo.running)
526 return;
528 showHome()
529 }
530 onDash: {
531 if (stages.shown && !stages.overlayMode && !stages.locked) {
532 stages.hide();
533 launcher.fadeOut();
534 } else {
535 launcher.switchToNextState("visible");
536 }
538 if (greeter.shown) {
539 greeter.hideRight();
540 launcher.fadeOut();
541 }
542 }
543 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
544 onLauncherApplicationSelected: {
545 if (!edgeDemo.running)
546 shell.activateApplication(appId)
547 }
548 onShownChanged: {
549 if (shown) {
550 panel.indicators.hide()
551 }
552 }
553 }
555 Rectangle {
556 id: modalNotificationBackground
558 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
559 color: "#000000"
560 anchors.fill: parent
561 opacity: 0.5
563 MouseArea {
564 anchors.fill: parent
565 }
567 InputFilterArea {
568 anchors.fill: parent
569 blockInput: modalNotificationBackground.visible
570 }
571 }
573 Notifications {
574 id: notifications
576 model: NotificationBackend.Model
577 margin: units.gu(1)
579 y: panel.panelHeight
580 width: parent.width
581 height: parent.height - panel.panelHeight
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 }