on 4455e73 help sign in
unity8/qml/Shell.qml
608 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 value: panel.panelHeight
274 }
275 Binding {
276 target: applicationsDisplayLoader.item
277 property: "interactive"
278 value: stages.roughlyFullyShown
279 }
280 }
281 }
283 InputMethod {
284 id: inputMethod
285 anchors.fill: parent
286 z: notifications.useModal || panel.indicators.shown ? overlay.z + 1 : overlay.z - 1
287 }
289 Connections {
290 target: SurfaceManager
291 onSurfaceCreated: {
292 if (surface.type == MirSurfaceItem.InputMethod) {
293 inputMethod.surface = surface;
294 }
295 }
297 onSurfaceDestroyed: {
298 if (inputMethod.surface == surface) {
299 inputMethod.surface = null;
300 surface.parent = null;
301 }
302 if (!surface.parent) {
303 // there's no one displaying it. delete it right away
304 surface.release();
305 }
306 }
307 }
309 Lockscreen {
310 id: lockscreen
311 objectName: "lockscreen"
313 readonly property int backgroundTopMargin: -panel.panelHeight
315 hides: [launcher, panel.indicators]
316 shown: false
317 enabled: true
318 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
319 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
320 y: panel.panelHeight
321 x: required ? 0 : - width
322 width: parent.width
323 height: parent.height - panel.panelHeight
324 //background: shell.background // FIXME: Commenting out as Qt was complaining about this line
325 minPinLength: 4
326 maxPinLength: 4
328 onEntered: LightDM.Greeter.respond(passphrase);
329 onCancel: greeter.show()
331 Component.onCompleted: {
332 if (LightDM.Users.count == 1) {
333 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
334 }
335 }
336 }
338 Connections {
339 target: LightDM.Greeter
341 onShowPrompt: {
342 if (LightDM.Users.count == 1) {
343 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
344 if (text == "PIN") {
345 lockscreen.alphaNumeric = false
346 } else {
347 lockscreen.alphaNumeric = true
348 }
349 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
350 lockscreen.show();
351 }
352 }
354 onAuthenticationComplete: {
355 if (LightDM.Greeter.promptless) {
356 return;
357 }
358 if (LightDM.Greeter.authenticated) {
359 lockscreen.hide();
360 } else {
361 lockscreen.clear(true);
362 }
363 }
364 }
366 Rectangle {
367 anchors.fill: parent
368 color: "black"
369 opacity: greeterWrapper.showProgress * 0.8
370 }
372 Item {
373 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
374 id: greeterWrapper
375 x: launcher.progress
376 y: panel.panelHeight
377 width: parent.width
378 height: parent.height - panel.panelHeight
380 Behavior on x {
381 enabled: !launcher.dashSwipe
382 StandardAnimation {}
383 }
385 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
387 Greeter {
388 id: greeter
389 objectName: "greeter"
391 available: true
392 hides: [launcher, panel.indicators]
393 shown: true
395 defaultBackground: shell.background
397 width: parent.width
398 height: parent.height
400 dragHandleWidth: shell.edgeSize
402 onShownChanged: {
403 if (shown) {
404 lockscreen.reset();
405 // If there is only one user, we start authenticating with that one here.
406 // If there are more users, the Greeter will handle that
407 if (LightDM.Users.count == 1) {
408 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
409 }
410 greeter.forceActiveFocus();
411 }
412 }
414 onUnlocked: greeter.hide()
415 onSelected: {
416 // Update launcher items for new user
417 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
418 AccountsService.user = user;
419 LauncherModel.setUser(user);
420 }
422 onTease: launcher.tease()
424 Binding {
425 target: ApplicationManager
426 property: "suspended"
427 value: greeter.shown && greeterWrapper.showProgress == 1
428 }
429 }
430 }
432 Connections {
433 id: powerConnection
434 target: Powerd
436 onDisplayPowerStateChange: {
437 // We ignore any display-off signals when the proximity sensor
438 // is active. This usually indicates something like a phone call.
439 if (status == Powerd.Off && reason != Powerd.Proximity) {
440 greeter.showNow();
441 }
443 // No reason to chew demo CPU when user isn't watching
444 if (status == Powerd.Off) {
445 edgeDemo.paused = true;
446 } else if (status == Powerd.On) {
447 edgeDemo.paused = false;
448 }
449 }
450 }
452 function showHome() {
453 var animate = !greeter.shown && !stages.shown
454 greeter.hide()
455 dash.setCurrentScope("clickscope", animate, false)
456 stages.hide()
457 }
459 function hideIndicatorMenu(delay) {
460 panel.hideIndicatorMenu(delay);
461 }
463 Item {
464 id: overlay
465 z: 10
467 anchors.fill: parent
469 Panel {
470 id: panel
471 anchors.fill: parent //because this draws indicator menus
472 indicators {
473 hides: [launcher]
474 available: edgeDemo.panelEnabled
475 contentEnabled: edgeDemo.panelContentEnabled
476 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
477 panelHeight: units.gu(3)
478 }
480 property bool topmostApplicationIsFullscreen:
481 ApplicationManager.topmostApplication &&
482 ApplicationManager.topmostApplication.fullscreen
484 fullscreenMode: stages.roughlyFullyShown && topmostApplicationIsFullscreen
485 && !greeter.shown && !lockscreen.shown
486 }
488 Launcher {
489 id: launcher
491 readonly property bool dashSwipe: progress > 0
493 anchors.top: parent.top
494 anchors.bottom: parent.bottom
495 width: parent.width
496 dragAreaWidth: shell.edgeSize
497 available: edgeDemo.launcherEnabled
499 onShowDashHome: {
500 if (edgeDemo.running)
501 return;
503 showHome()
504 }
505 onDash: {
506 if (!stages.locked) {
507 stages.hide();
508 launcher.fadeOut();
509 } else {
510 launcher.switchToNextState("visible");
511 }
513 if (greeter.shown) {
514 greeter.hideRight();
515 launcher.fadeOut();
516 }
517 }
518 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
519 onLauncherApplicationSelected: {
520 if (!edgeDemo.running)
521 shell.activateApplication(appId)
522 }
523 onShownChanged: {
524 if (shown) {
525 panel.indicators.hide()
526 }
527 }
528 }
530 Rectangle {
531 id: modalNotificationBackground
533 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
534 color: "#000000"
535 anchors.fill: parent
536 opacity: 0.5
538 MouseArea {
539 anchors.fill: parent
540 }
541 }
543 Notifications {
544 id: notifications
546 model: NotificationBackend.Model
547 margin: units.gu(1)
549 y: panel.panelHeight
550 width: parent.width
551 height: parent.height - panel.panelHeight
553 states: [
554 State {
555 name: "narrow"
556 when: overlay.width <= units.gu(60)
557 AnchorChanges { target: notifications; anchors.left: parent.left }
558 },
559 State {
560 name: "wide"
561 when: overlay.width > units.gu(60)
562 AnchorChanges { target: notifications; anchors.left: undefined }
563 PropertyChanges { target: notifications; width: units.gu(38) }
564 }
565 ]
566 }
567 }
569 focus: true
570 onFocusChanged: if (!focus) forceActiveFocus();
572 Binding {
573 target: i18n
574 property: "domain"
575 value: "unity8"
576 }
578 Label {
579 id: alphaDisclaimerLabel
580 anchors.centerIn: parent
581 visible: ApplicationManager.fake ? ApplicationManager.fake : false
582 z: overlay.z + 10
583 text: "EARLY ALPHA\nNOT READY FOR USE"
584 color: "lightgrey"
585 opacity: 0.2
586 font.weight: Font.Black
587 horizontalAlignment: Text.AlignHCenter
588 verticalAlignment: Text.AlignVCenter
589 fontSizeMode: Text.Fit
590 rotation: -45
591 scale: Math.min(parent.width, parent.height) / width
592 }
594 EdgeDemo {
595 id: edgeDemo
596 z: alphaDisclaimerLabel.z + 10
597 greeter: greeter
598 launcher: launcher
599 dash: dash
600 indicators: panel.indicators
601 underlay: underlay
602 }
604 Connections {
605 target: SessionBroadcast
606 onShowHome: showHome()
607 }