on c1924f5 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 "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 Rectangle {
122 anchors.fill: parent
123 color: "black"
124 opacity: dash.disappearingAnimationProgress
125 }
127 Image {
128 anchors.fill: dash
129 source: shell.width > shell.height ? "Dash/graphics/paper_landscape.png" : "Dash/graphics/paper_portrait.png"
130 fillMode: Image.PreserveAspectCrop
131 horizontalAlignment: Image.AlignRight
132 verticalAlignment: Image.AlignTop
133 }
135 Dash {
136 id: dash
137 objectName: "dash"
139 available: !greeter.shown && !lockscreen.shown
140 hides: [stages, launcher, panel.indicators]
141 shown: disappearingAnimationProgress !== 1.0 && greeterWrapper.showProgress !== 1.0
142 enabled: disappearingAnimationProgress === 0.0 && greeterWrapper.showProgress === 0.0 && edgeDemo.dashEnabled
144 anchors {
145 fill: parent
146 topMargin: panel.panelHeight
147 }
149 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
150 opacity: 1.0 - disappearingAnimationProgress
151 property real disappearingAnimationProgress: {
152 if (stages.overlayMode) {
153 return 0;
154 } else {
155 return stages.showProgress
156 }
157 }
159 // FIXME: only necessary because stages.showProgress is not animated
160 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
161 }
162 }
163 }
165 EdgeDragArea {
166 id: stagesDragHandle
167 direction: Direction.Leftwards
169 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
170 width: shell.edgeSize
172 property real progress: stages.width
174 onTouchXChanged: {
175 if (status == DirectionalDragArea.Recognized) {
176 if (ApplicationManager.count == 0) {
177 progress = Math.max(stages.width - stagesDragHandle.width + touchX, stages.width * .3)
178 } else {
179 progress = stages.width - stagesDragHandle.width + touchX
180 }
181 }
182 }
184 onDraggingChanged: {
185 if (!dragging) {
186 if (ApplicationManager.count > 0 && progress < stages.width - units.gu(10)) {
187 stages.show(true)
188 }
189 stagesDragHandle.progress = stages.width;
190 }
191 }
192 }
194 Item {
195 id: stages
196 objectName: "stages"
197 width: parent.width
198 height: parent.height
200 x: {
201 if (shown) {
202 if (overlayMode || locked) {
203 return 0;
204 }
205 return launcher.progress
206 } else {
207 return stagesDragHandle.progress
208 }
209 }
211 Behavior on x { SmoothedAnimation { velocity: 600; duration: UbuntuAnimation.FastDuration } }
213 property bool shown: false
215 property real showProgress: overlayMode ? 0 : MathUtils.clamp(1 - x / shell.width, 0, 1)
217 property bool fullyShown: x == 0
218 property bool fullyHidden: x == width
220 property bool painting: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.painting : false
221 property bool fullscreen: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.fullscreen : false
222 property bool overlayMode: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayMode : false
223 property int overlayWidth: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayWidth : false
224 property bool locked: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.locked : false
226 function show(focusApp) {
227 shown = true;
228 panel.indicators.hide();
229 edgeDemo.stopDemo();
230 greeter.hide();
231 if (!ApplicationManager.focusedApplicationId && ApplicationManager.count > 0 && focusApp) {
232 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
233 }
234 }
236 function hide() {
237 shown = false;
238 if (ApplicationManager.focusedApplicationId) {
239 ApplicationManager.unfocusCurrentApplication();
240 }
241 }
243 Connections {
244 target: ApplicationManager
246 onFocusRequested: {
247 stages.show(true);
248 }
250 onFocusedApplicationIdChanged: {
251 if (ApplicationManager.focusedApplicationId.length > 0) {
252 stages.show(false);
253 } else {
254 if (!stages.overlayMode) {
255 stages.hide();
256 }
257 }
258 }
260 onApplicationAdded: {
261 stages.show(false);
262 }
264 onApplicationRemoved: {
265 if (ApplicationManager.focusedApplicationId.length == 0) {
266 stages.hide();
267 }
268 }
269 }
271 Connections {
272 target: DBusUnitySessionService
274 function closeAllApps() {
275 while (true) {
276 var app = ApplicationManager.get(0);
277 if (app === null) {
278 break;
279 }
280 ApplicationManager.stopApplication(app.appId);
281 }
282 }
284 onLogoutRequested: {
285 // TODO: Display a dialog to ask the user to confirm.
286 DBusUnitySessionService.Logout();
287 }
289 onLogoutReady: {
290 closeAllApps();
291 Qt.quit();
292 }
293 }
295 Loader {
296 id: applicationsDisplayLoader
297 anchors.fill: parent
299 source: shell.sideStageEnabled ? "Stages/StageWithSideStage.qml" : "Stages/PhoneStage.qml"
301 Binding {
302 target: applicationsDisplayLoader.item
303 property: "moving"
304 value: !stages.fullyShown
305 }
306 Binding {
307 target: applicationsDisplayLoader.item
308 property: "shown"
309 value: stages.shown
310 }
311 Binding {
312 target: applicationsDisplayLoader.item
313 property: "dragAreaWidth"
314 value: shell.edgeSize
315 }
316 }
317 }
319 Lockscreen {
320 id: lockscreen
321 objectName: "lockscreen"
323 readonly property int backgroundTopMargin: -panel.panelHeight
325 hides: [launcher, panel.indicators]
326 shown: false
327 enabled: true
328 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
329 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
330 y: panel.panelHeight
331 x: required ? 0 : - width
332 width: parent.width
333 height: parent.height - panel.panelHeight
334 background: shell.background
335 minPinLength: 4
336 maxPinLength: 4
338 onEntered: LightDM.Greeter.respond(passphrase);
339 onCancel: greeter.show()
341 Component.onCompleted: {
342 if (LightDM.Users.count == 1) {
343 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
344 }
345 }
346 }
348 Connections {
349 target: LightDM.Greeter
351 onShowPrompt: {
352 if (LightDM.Users.count == 1) {
353 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
354 if (text == "PIN") {
355 lockscreen.alphaNumeric = false
356 } else {
357 lockscreen.alphaNumeric = true
358 }
359 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
360 lockscreen.show();
361 }
362 }
364 onAuthenticationComplete: {
365 if (LightDM.Greeter.promptless) {
366 return;
367 }
368 if (LightDM.Greeter.authenticated) {
369 lockscreen.hide();
370 } else {
371 lockscreen.clear(true);
372 }
373 }
374 }
376 Rectangle {
377 anchors.fill: parent
378 color: "black"
379 opacity: greeterWrapper.showProgress * 0.8
380 }
382 Item {
383 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
384 id: greeterWrapper
385 x: launcher.progress
386 y: panel.panelHeight
387 width: parent.width
388 height: parent.height - panel.panelHeight
390 Behavior on x {
391 enabled: !launcher.dashSwipe
392 StandardAnimation {}
393 }
395 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
397 Greeter {
398 id: greeter
399 objectName: "greeter"
401 available: true
402 hides: [launcher, panel.indicators]
403 shown: true
405 defaultBackground: shell.background
407 width: parent.width
408 height: parent.height
410 dragHandleWidth: shell.edgeSize
412 onShownChanged: {
413 if (shown) {
414 lockscreen.reset();
415 // If there is only one user, we start authenticating with that one here.
416 // If there are more users, the Greeter will handle that
417 if (LightDM.Users.count == 1) {
418 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
419 }
420 greeter.forceActiveFocus();
421 }
422 }
424 onUnlocked: greeter.hide()
425 onSelected: {
426 // Update launcher items for new user
427 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
428 AccountsService.user = user;
429 LauncherModel.setUser(user);
430 }
432 onTease: launcher.tease()
434 Binding {
435 target: ApplicationManager
436 property: "suspended"
437 value: greeter.shown && greeterWrapper.showProgress == 1
438 }
439 }
440 }
442 InputFilterArea {
443 anchors.fill: parent
444 blockInput: ApplicationManager.focusedApplicationId.length === 0 || greeter.shown || lockscreen.shown || launcher.shown
445 || panel.indicators.shown
446 }
448 Connections {
449 id: powerConnection
450 target: Powerd
452 onDisplayPowerStateChange: {
453 // We ignore any display-off signals when the proximity sensor
454 // is active. This usually indicates something like a phone call.
455 if (status == Powerd.Off && reason != Powerd.Proximity) {
456 greeter.showNow();
457 }
459 // No reason to chew demo CPU when user isn't watching
460 if (status == Powerd.Off) {
461 edgeDemo.paused = true;
462 } else if (status == Powerd.On) {
463 edgeDemo.paused = false;
464 }
465 }
466 }
468 function showHome() {
469 var animate = !greeter.shown && !stages.shown
470 greeter.hide()
471 dash.setCurrentScope("clickscope", animate, false)
472 stages.hide()
473 }
475 function hideIndicatorMenu(delay) {
476 panel.hideIndicatorMenu(delay);
477 }
479 Item {
480 id: overlay
482 anchors.fill: parent
484 Panel {
485 id: panel
486 anchors.fill: parent //because this draws indicator menus
487 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
488 indicators {
489 hides: [launcher]
490 available: edgeDemo.panelEnabled
491 contentEnabled: edgeDemo.panelContentEnabled
492 }
493 property string focusedAppId: ApplicationManager.focusedApplicationId
494 property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
495 fullscreenMode: focusedApplication && stages.fullscreen && !greeter.shown && !lockscreen.shown
496 searchVisible: !greeter.shown && !lockscreen.shown && dash.shown && dash.searchable
498 InputFilterArea {
499 anchors {
500 top: parent.top
501 left: parent.left
502 right: parent.right
503 }
504 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
505 blockInput: true
506 }
507 }
509 InputFilterArea {
510 blockInput: launcher.shown
511 anchors {
512 top: parent.top
513 bottom: parent.bottom
514 left: parent.left
515 }
516 width: launcher.width
517 }
519 Launcher {
520 id: launcher
522 readonly property bool dashSwipe: progress > 0
524 anchors.top: parent.top
525 anchors.bottom: parent.bottom
526 width: parent.width
527 dragAreaWidth: shell.edgeSize
528 available: edgeDemo.launcherEnabled
530 onShowDashHome: {
531 if (edgeDemo.running)
532 return;
534 showHome()
535 }
536 onDash: {
537 if (stages.shown && !stages.overlayMode) {
538 if (!stages.locked) {
539 stages.hide();
540 launcher.hide();
541 }
542 }
543 if (greeter.shown) {
544 greeter.hideRight();
545 launcher.hide();
546 }
547 }
548 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
549 onLauncherApplicationSelected: {
550 if (!edgeDemo.running)
551 shell.activateApplication(appId)
552 }
553 onShownChanged: {
554 if (shown) {
555 panel.indicators.hide()
556 }
557 }
558 }
560 Rectangle {
561 id: modalNotificationBackground
563 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
564 color: "#000000"
565 anchors.fill: parent
566 opacity: 0.5
568 MouseArea {
569 anchors.fill: parent
570 }
572 InputFilterArea {
573 anchors.fill: parent
574 blockInput: modalNotificationBackground.visible
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 }