on 4451c08 help sign in
unity8/qml/Shell.qml
685 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 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 }
331 anchors {
332 top: parent.top
333 right: parent.right
334 bottom: parent.bottom
335 topMargin: panel.panelHeight
336 }
337 background: shell.background
338 minPinLength: 4
339 maxPinLength: 4
341 onEntered: LightDM.Greeter.respond(passphrase);
342 onCancel: greeter.show()
344 Component.onCompleted: {
345 if (LightDM.Users.count == 1) {
346 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
347 }
348 }
349 }
351 Connections {
352 target: LightDM.Greeter
354 onShowPrompt: {
355 if (LightDM.Users.count == 1) {
356 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
357 if (text == "PIN") {
358 lockscreen.alphaNumeric = false
359 } else {
360 lockscreen.alphaNumeric = true
361 }
362 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
363 lockscreen.show();
364 }
365 }
367 onAuthenticationComplete: {
368 if (LightDM.Greeter.promptless) {
369 return;
370 }
371 if (LightDM.Greeter.authenticated) {
372 lockscreen.hide();
373 } else {
374 lockscreen.clear(true);
375 }
376 }
377 }
379 Rectangle {
380 anchors.fill: parent
381 color: "black"
382 opacity: greeterWrapper.showProgress * 0.8
383 }
385 Item {
386 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
387 id: greeterWrapper
388 x: launcher.progress
389 y: panel.panelHeight
390 width: parent.width
391 height: parent.height - panel.panelHeight
393 Behavior on x {
394 enabled: !launcher.dashSwipe
395 StandardAnimation {}
396 }
398 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
400 Greeter {
401 id: greeter
402 objectName: "greeter"
404 available: true
405 hides: [launcher, panel.indicators]
406 shown: true
408 defaultBackground: shell.background
410 width: parent.width
411 height: parent.height
413 dragHandleWidth: shell.edgeSize
415 onShownChanged: {
416 if (shown) {
417 lockscreen.reset();
418 // If there is only one user, we start authenticating with that one here.
419 // If there are more users, the Greeter will handle that
420 if (LightDM.Users.count == 1) {
421 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
422 }
423 greeter.forceActiveFocus();
424 }
425 }
427 onUnlocked: greeter.hide()
428 onSelected: {
429 // Update launcher items for new user
430 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
431 AccountsService.user = user;
432 LauncherModel.setUser(user);
433 }
435 onTease: launcher.tease()
437 Binding {
438 target: ApplicationManager
439 property: "suspended"
440 value: greeter.shown && greeterWrapper.showProgress == 1
441 }
442 }
443 }
445 InputFilterArea {
446 anchors.fill: parent
447 blockInput: ApplicationManager.focusedApplicationId.length === 0 || greeter.shown || lockscreen.shown || launcher.shown
448 || panel.indicators.shown
449 }
451 Connections {
452 id: powerConnection
453 target: Powerd
455 onDisplayPowerStateChange: {
456 // We ignore any display-off signals when the proximity sensor
457 // is active. This usually indicates something like a phone call.
458 if (status == Powerd.Off && reason != Powerd.Proximity) {
459 greeter.showNow();
460 }
462 // No reason to chew demo CPU when user isn't watching
463 if (status == Powerd.Off) {
464 edgeDemo.paused = true;
465 } else if (status == Powerd.On) {
466 edgeDemo.paused = false;
467 }
468 }
469 }
471 function showHome() {
472 var animate = !greeter.shown && !stages.shown
473 greeter.hide()
474 dash.setCurrentScope("clickscope", animate, false)
475 stages.hide()
476 }
478 function hideIndicatorMenu(delay) {
479 panel.hideIndicatorMenu(delay);
480 }
482 Item {
483 id: overlay
485 anchors.fill: parent
487 Panel {
488 id: panel
489 anchors.fill: parent //because this draws indicator menus
490 indicators {
491 hides: [launcher]
492 available: edgeDemo.panelEnabled
493 contentEnabled: edgeDemo.panelContentEnabled
494 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
495 panelHeight: units.gu(3)
496 }
497 property string focusedAppId: ApplicationManager.focusedApplicationId
498 property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
499 fullscreenMode: focusedApplication && stages.fullscreen && !greeter.shown && !lockscreen.shown
500 searchVisible: !greeter.shown && !lockscreen.shown && dash.shown && dash.searchable
502 InputFilterArea {
503 anchors {
504 top: parent.top
505 left: parent.left
506 right: parent.right
507 }
508 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
509 blockInput: true
510 }
511 }
513 InputFilterArea {
514 blockInput: launcher.shown
515 anchors {
516 top: parent.top
517 bottom: parent.bottom
518 left: parent.left
519 }
520 width: launcher.width
521 }
523 Launcher {
524 id: launcher
526 readonly property bool dashSwipe: progress > 0
528 anchors.top: parent.top
529 anchors.bottom: parent.bottom
530 width: parent.width
531 dragAreaWidth: shell.edgeSize
532 available: edgeDemo.launcherEnabled
534 onShowDashHome: {
535 if (edgeDemo.running)
536 return;
538 showHome()
539 }
540 onDash: {
541 if (stages.shown && !stages.overlayMode) {
542 if (!stages.locked) {
543 stages.hide();
544 launcher.hide();
545 }
546 }
547 if (greeter.shown) {
548 greeter.hideRight();
549 launcher.hide();
550 }
551 }
552 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
553 onLauncherApplicationSelected: {
554 if (!edgeDemo.running)
555 shell.activateApplication(appId)
556 }
557 onShownChanged: {
558 if (shown) {
559 panel.indicators.hide()
560 }
561 }
562 }
564 Rectangle {
565 id: modalNotificationBackground
567 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
568 color: "#000000"
569 anchors.fill: parent
570 opacity: 0.5
572 MouseArea {
573 anchors.fill: parent
574 }
576 InputFilterArea {
577 anchors.fill: parent
578 blockInput: modalNotificationBackground.visible
579 }
580 }
582 Notifications {
583 id: notifications
585 model: NotificationBackend.Model
586 margin: units.gu(1)
588 y: panel.panelHeight
589 width: parent.width
590 height: parent.height - panel.panelHeight
592 states: [
593 State {
594 name: "narrow"
595 when: overlay.width <= units.gu(60)
596 AnchorChanges { target: notifications; anchors.left: parent.left }
597 },
598 State {
599 name: "wide"
600 when: overlay.width > units.gu(60)
601 AnchorChanges { target: notifications; anchors.left: undefined }
602 PropertyChanges { target: notifications; width: units.gu(38) }
603 }
604 ]
606 InputFilterArea {
607 anchors { left: parent.left; right: parent.right }
608 height: parent.contentHeight
609 blockInput: height > 0
610 }
611 }
612 }
614 focus: true
615 onFocusChanged: if (!focus) forceActiveFocus();
617 InputFilterArea {
618 anchors {
619 top: parent.top
620 bottom: parent.bottom
621 left: parent.left
622 }
623 width: shell.edgeSize
624 blockInput: true
625 }
627 InputFilterArea {
628 anchors {
629 top: parent.top
630 bottom: parent.bottom
631 right: parent.right
632 }
633 width: shell.edgeSize
634 blockInput: true
635 }
637 Binding {
638 target: i18n
639 property: "domain"
640 value: "unity8"
641 }
643 OSKController {
644 anchors.topMargin: panel.panelHeight
645 anchors.fill: parent // as needs to know the geometry of the shell
646 }
648 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
649 MouseArea {
650 anchors.bottom: parent.bottom
651 anchors.left: parent.left
652 anchors.right: parent.right
653 height: ApplicationManager.keyboardVisible ? ApplicationManager.keyboardHeight : 0
655 enabled: ApplicationManager.keyboardVisible
656 }
658 Label {
659 anchors.centerIn: parent
660 visible: ApplicationManager.fake ? ApplicationManager.fake : false
661 text: "EARLY ALPHA\nNOT READY FOR USE"
662 color: "lightgrey"
663 opacity: 0.2
664 font.weight: Font.Black
665 horizontalAlignment: Text.AlignHCenter
666 verticalAlignment: Text.AlignVCenter
667 fontSizeMode: Text.Fit
668 rotation: -45
669 scale: Math.min(parent.width, parent.height) / width
670 }
672 EdgeDemo {
673 id: edgeDemo
674 greeter: greeter
675 launcher: launcher
676 dash: dash
677 indicators: panel.indicators
678 underlay: underlay
679 }
681 Connections {
682 target: SessionBroadcast
683 onShowHome: showHome()
684 }