on e55f122 help sign in
unity8/qml/Shell.qml
609 lines 19 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 "Stages"
34import Unity.Notifications 1.0 as NotificationBackend
35import Unity.Session 0.1
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 && dash.available && underlay.visible
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 } else {
59 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
60 ApplicationManager.startApplication(appId, execFlags);
61 stages.show();
62 }
63 }
65 Binding {
66 target: LauncherModel
67 property: "applicationManager"
68 value: ApplicationManager
69 }
71 Component.onCompleted: {
72 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
73 }
75 GSettings {
76 id: backgroundSettings
77 schema.id: "org.gnome.desktop.background"
78 }
79 property url gSettingsPicture: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : shell.defaultBackground
80 onGSettingsPictureChanged: {
81 shell.background = gSettingsPicture
82 }
84 VolumeControl {
85 id: volumeControl
86 }
88 Keys.onVolumeUpPressed: volumeControl.volumeUp()
89 Keys.onVolumeDownPressed: volumeControl.volumeDown()
91 Item {
92 id: underlay
93 objectName: "underlay"
94 anchors.fill: parent
96 // Whether the underlay is fully covered by opaque UI elements.
97 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
99 // NB! Application surfaces are stacked behind the shell one. So they can only be seen by the user
100 // through the translucent parts of the shell surface.
101 visible: !fullyCovered
103 Rectangle {
104 anchors.fill: parent
105 color: "black"
106 opacity: dash.disappearingAnimationProgress
107 }
109 Image {
110 anchors.fill: dash
111 source: shell.width > shell.height ? "Dash/graphics/paper_landscape.png" : "Dash/graphics/paper_portrait.png"
112 fillMode: Image.PreserveAspectCrop
113 horizontalAlignment: Image.AlignRight
114 verticalAlignment: Image.AlignTop
115 }
117 Dash {
118 id: dash
119 objectName: "dash"
121 available: !greeter.shown && !lockscreen.shown
122 hides: [stages, launcher, panel.indicators]
123 shown: disappearingAnimationProgress !== 1.0 && greeterWrapper.showProgress !== 1.0
124 enabled: disappearingAnimationProgress === 0.0 && greeterWrapper.showProgress === 0.0 && edgeDemo.dashEnabled
126 anchors {
127 fill: parent
128 topMargin: panel.panelHeight
129 }
131 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
132 opacity: 1.0 - disappearingAnimationProgress
133 property real disappearingAnimationProgress: stages.showProgress
135 // FIXME: only necessary because stages.showProgress is not animated
136 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
137 }
138 }
140 EdgeDragArea {
141 id: stagesDragArea
142 direction: Direction.Leftwards
144 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
145 width: shell.edgeSize
147 property real progress: stages.width
149 onTouchXChanged: {
150 if (status == DirectionalDragArea.Recognized) {
151 if (ApplicationManager.empty) {
152 progress = Math.max(stages.width - stagesDragArea.width + touchX, stages.width * .3);
153 } else {
154 progress = stages.width - stagesDragArea.width + touchX;
155 }
156 }
157 }
159 onDraggingChanged: {
160 if (!dragging) {
161 if (!ApplicationManager.empty && progress < stages.width - units.gu(10)) {
162 stages.show();
163 }
164 stagesDragArea.progress = Qt.binding(function () { return stages.width; });
165 }
166 }
167 }
169 Item {
170 id: stages
171 objectName: "stages"
172 width: parent.width
173 height: parent.height
175 visible: !fullyHidden && !ApplicationManager.empty
177 x: shown ? launcher.progress : stagesDragArea.progress
178 Behavior on x { SmoothedAnimation { velocity: 600; duration: UbuntuAnimation.FastDuration } }
180 property bool shown: false
181 onShownChanged: {
182 if (shown) {
183 if (ApplicationManager.topmostApplication) {
184 ApplicationManager.focusApplication(ApplicationManager.topmostApplication.appId);
185 }
186 } else {
187 if (ApplicationManager.focusedApplicationId) {
188 ApplicationManager.updateScreenshot(ApplicationManager.focusedApplicationId);
189 ApplicationManager.unfocusCurrentApplication();
190 }
191 }
192 }
194 // Avoid a silent "divide by zero -> NaN" situation during init as shell.width will be
195 // zero. That breaks the property binding and the function won't be reevaluated once
196 // shell.width is set, with the NaN result staying there for good.
197 property real showProgress: shell.width ? MathUtils.clamp(1 - x / shell.width, 0, 1) : 0
199 property bool fullyShown: x == 0
200 property bool fullyHidden: x == width
202 // It might technically not be fullyShown but visually it just looks so.
203 property bool roughlyFullyShown: x >= 0 && x <= units.gu(1)
205 function show() {
206 shown = true;
207 }
209 function hide() {
210 shown = false;
211 }
213 Connections {
214 target: ApplicationManager
215 onFocusRequested: {
216 if (greeter.shown) {
217 greeter.hide();
218 }
219 stages.show();
220 }
222 onApplicationAdded: {
223 if (greeter.shown) {
224 greeter.hide();
225 }
226 }
228 onEmptyChanged: {
229 if (ApplicationManager.empty) {
230 stages.hide();
231 }
232 }
233 }
235 Connections {
236 target: DBusUnitySessionService
238 function closeAllApps() {
239 while (true) {
240 var app = ApplicationManager.get(0);
241 if (app === null) {
242 break;
243 }
244 ApplicationManager.stopApplication(app.appId);
245 }
246 }
248 onLogoutRequested: {
249 // TODO: Display a dialog to ask the user to confirm.
250 DBusUnitySessionService.Logout();
251 }
253 onLogoutReady: {
254 closeAllApps();
255 Qt.quit();
256 }
257 }
259 Loader {
260 id: applicationsDisplayLoader
261 anchors.fill: parent
263 source: shell.sideStageEnabled ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
265 Binding {
266 target: applicationsDisplayLoader.item
267 property: "dragAreaWidth"
268 value: shell.edgeSize
269 }
270 Binding {
271 target: applicationsDisplayLoader.item
272 property: "maximizedAppTopMargin"
273 // Not just using panel.panelHeight as that changes depending on the focused app.
274 value: panel.indicators.panelHeight
275 }
276 Binding {
277 target: applicationsDisplayLoader.item
278 property: "interactive"
279 value: stages.roughlyFullyShown
280 }
281 }
282 }
284 InputMethod {
285 id: inputMethod
286 anchors.fill: parent
287 z: notifications.useModal || panel.indicators.shown ? overlay.z + 1 : overlay.z - 1
288 }
290 Connections {
291 target: SurfaceManager
292 onSurfaceCreated: {
293 if (surface.type == MirSurfaceItem.InputMethod) {
294 inputMethod.surface = surface;
295 }
296 }
298 onSurfaceDestroyed: {
299 if (inputMethod.surface == surface) {
300 inputMethod.surface = null;
301 surface.parent = null;
302 }
303 if (!surface.parent) {
304 // there's no one displaying it. delete it right away
305 surface.release();
306 }
307 }
308 }
310 Lockscreen {
311 id: lockscreen
312 objectName: "lockscreen"
314 readonly property int backgroundTopMargin: -panel.panelHeight
316 hides: [launcher, panel.indicators]
317 shown: false
318 enabled: true
319 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
320 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
321 y: panel.panelHeight
322 x: required ? 0 : - width
323 width: parent.width
324 height: parent.height - panel.panelHeight
325 //background: shell.background // FIXME: Commenting out as Qt was complaining about this line
326 minPinLength: 4
327 maxPinLength: 4
329 onEntered: LightDM.Greeter.respond(passphrase);
330 onCancel: greeter.show()
332 Component.onCompleted: {
333 if (LightDM.Users.count == 1) {
334 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
335 }
336 }
337 }
339 Connections {
340 target: LightDM.Greeter
342 onShowPrompt: {
343 if (LightDM.Users.count == 1) {
344 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
345 if (text == "PIN") {
346 lockscreen.alphaNumeric = false
347 } else {
348 lockscreen.alphaNumeric = true
349 }
350 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
351 lockscreen.show();
352 }
353 }
355 onAuthenticationComplete: {
356 if (LightDM.Greeter.promptless) {
357 return;
358 }
359 if (LightDM.Greeter.authenticated) {
360 lockscreen.hide();
361 } else {
362 lockscreen.clear(true);
363 }
364 }
365 }
367 Rectangle {
368 anchors.fill: parent
369 color: "black"
370 opacity: greeterWrapper.showProgress * 0.8
371 }
373 Item {
374 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
375 id: greeterWrapper
376 x: launcher.progress
377 y: panel.panelHeight
378 width: parent.width
379 height: parent.height - panel.panelHeight
381 Behavior on x {
382 enabled: !launcher.dashSwipe
383 StandardAnimation {}
384 }
386 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
388 Greeter {
389 id: greeter
390 objectName: "greeter"
392 available: true
393 hides: [launcher, panel.indicators]
394 shown: true
396 defaultBackground: shell.background
398 width: parent.width
399 height: parent.height
401 dragHandleWidth: shell.edgeSize
403 onShownChanged: {
404 if (shown) {
405 lockscreen.reset();
406 // If there is only one user, we start authenticating with that one here.
407 // If there are more users, the Greeter will handle that
408 if (LightDM.Users.count == 1) {
409 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
410 }
411 greeter.forceActiveFocus();
412 }
413 }
415 onUnlocked: greeter.hide()
416 onSelected: {
417 // Update launcher items for new user
418 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
419 AccountsService.user = user;
420 LauncherModel.setUser(user);
421 }
423 onTease: launcher.tease()
425 Binding {
426 target: ApplicationManager
427 property: "suspended"
428 value: greeter.shown && greeterWrapper.showProgress == 1
429 }
430 }
431 }
433 Connections {
434 id: powerConnection
435 target: Powerd
437 onDisplayPowerStateChange: {
438 // We ignore any display-off signals when the proximity sensor
439 // is active. This usually indicates something like a phone call.
440 if (status == Powerd.Off && reason != Powerd.Proximity) {
441 greeter.showNow();
442 }
444 // No reason to chew demo CPU when user isn't watching
445 if (status == Powerd.Off) {
446 edgeDemo.paused = true;
447 } else if (status == Powerd.On) {
448 edgeDemo.paused = false;
449 }
450 }
451 }
453 function showHome() {
454 var animate = !greeter.shown && !stages.shown
455 greeter.hide()
456 dash.setCurrentScope("clickscope", animate, false)
457 stages.hide()
458 }
460 function hideIndicatorMenu(delay) {
461 panel.hideIndicatorMenu(delay);
462 }
464 Item {
465 id: overlay
466 z: 10
468 anchors.fill: parent
470 Panel {
471 id: panel
472 anchors.fill: parent //because this draws indicator menus
473 indicators {
474 hides: [launcher]
475 available: edgeDemo.panelEnabled
476 contentEnabled: edgeDemo.panelContentEnabled
477 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
478 panelHeight: units.gu(3)
479 }
481 property bool topmostApplicationIsFullscreen:
482 ApplicationManager.topmostApplication &&
483 ApplicationManager.topmostApplication.fullscreen
485 fullscreenMode: stages.roughlyFullyShown && topmostApplicationIsFullscreen
486 && !greeter.shown && !lockscreen.shown
487 }
489 Launcher {
490 id: launcher
492 readonly property bool dashSwipe: progress > 0
494 anchors.top: parent.top
495 anchors.bottom: parent.bottom
496 width: parent.width
497 dragAreaWidth: shell.edgeSize
498 available: edgeDemo.launcherEnabled
500 onShowDashHome: {
501 if (edgeDemo.running)
502 return;
504 showHome()
505 }
506 onDash: {
507 if (!stages.locked) {
508 stages.hide();
509 launcher.fadeOut();
510 } else {
511 launcher.switchToNextState("visible");
512 }
514 if (greeter.shown) {
515 greeter.hideRight();
516 launcher.fadeOut();
517 }
518 }
519 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
520 onLauncherApplicationSelected: {
521 if (!edgeDemo.running)
522 shell.activateApplication(appId)
523 }
524 onShownChanged: {
525 if (shown) {
526 panel.indicators.hide()
527 }
528 }
529 }
531 Rectangle {
532 id: modalNotificationBackground
534 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
535 color: "#000000"
536 anchors.fill: parent
537 opacity: 0.5
539 MouseArea {
540 anchors.fill: parent
541 }
542 }
544 Notifications {
545 id: notifications
547 model: NotificationBackend.Model
548 margin: units.gu(1)
550 y: panel.panelHeight
551 width: parent.width
552 height: parent.height - panel.panelHeight
554 states: [
555 State {
556 name: "narrow"
557 when: overlay.width <= units.gu(60)
558 AnchorChanges { target: notifications; anchors.left: parent.left }
559 },
560 State {
561 name: "wide"
562 when: overlay.width > units.gu(60)
563 AnchorChanges { target: notifications; anchors.left: undefined }
564 PropertyChanges { target: notifications; width: units.gu(38) }
565 }
566 ]
567 }
568 }
570 focus: true
571 onFocusChanged: if (!focus) forceActiveFocus();
573 Binding {
574 target: i18n
575 property: "domain"
576 value: "unity8"
577 }
579 Label {
580 id: alphaDisclaimerLabel
581 anchors.centerIn: parent
582 visible: ApplicationManager.fake ? ApplicationManager.fake : false
583 z: overlay.z + 10
584 text: "EARLY ALPHA\nNOT READY FOR USE"
585 color: "lightgrey"
586 opacity: 0.2
587 font.weight: Font.Black
588 horizontalAlignment: Text.AlignHCenter
589 verticalAlignment: Text.AlignVCenter
590 fontSizeMode: Text.Fit
591 rotation: -45
592 scale: Math.min(parent.width, parent.height) / width
593 }
595 EdgeDemo {
596 id: edgeDemo
597 z: alphaDisclaimerLabel.z + 10
598 greeter: greeter
599 launcher: launcher
600 dash: dash
601 indicators: panel.indicators
602 underlay: underlay
603 }
605 Connections {
606 target: SessionBroadcast
607 onShowHome: showHome()
608 }