on 8d54a42 help sign in
unity8/qml/Shell.qml
610 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 } else {
295 stages.show();
296 }
297 }
299 onSurfaceDestroyed: {
300 if (inputMethod.surface == surface) {
301 inputMethod.surface = null;
302 surface.parent = null;
303 }
304 if (!surface.parent) {
305 // there's no one displaying it. delete it right away
306 surface.release();
307 }
308 }
309 }
311 Lockscreen {
312 id: lockscreen
313 objectName: "lockscreen"
315 readonly property int backgroundTopMargin: -panel.panelHeight
317 hides: [launcher, panel.indicators]
318 shown: false
319 enabled: true
320 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
321 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
322 y: panel.panelHeight
323 x: required ? 0 : - width
324 width: parent.width
325 height: parent.height - panel.panelHeight
326 //background: shell.background // FIXME: Commenting out as Qt was complaining about this line
327 minPinLength: 4
328 maxPinLength: 4
330 onEntered: LightDM.Greeter.respond(passphrase);
331 onCancel: greeter.show()
333 Component.onCompleted: {
334 if (LightDM.Users.count == 1) {
335 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
336 }
337 }
338 }
340 Connections {
341 target: LightDM.Greeter
343 onShowPrompt: {
344 if (LightDM.Users.count == 1) {
345 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
346 if (text == "PIN") {
347 lockscreen.alphaNumeric = false
348 } else {
349 lockscreen.alphaNumeric = true
350 }
351 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
352 lockscreen.show();
353 }
354 }
356 onAuthenticationComplete: {
357 if (LightDM.Greeter.promptless) {
358 return;
359 }
360 if (LightDM.Greeter.authenticated) {
361 lockscreen.hide();
362 } else {
363 lockscreen.clear(true);
364 }
365 }
366 }
368 Rectangle {
369 anchors.fill: parent
370 color: "black"
371 opacity: greeterWrapper.showProgress * 0.8
372 }
374 Item {
375 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
376 id: greeterWrapper
377 x: launcher.progress
378 y: panel.panelHeight
379 width: parent.width
380 height: parent.height - panel.panelHeight
382 Behavior on x {
383 enabled: !launcher.dashSwipe
384 StandardAnimation {}
385 }
387 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
389 Greeter {
390 id: greeter
391 objectName: "greeter"
393 available: true
394 hides: [launcher, panel.indicators]
395 shown: true
397 defaultBackground: shell.background
399 width: parent.width
400 height: parent.height
402 dragHandleWidth: shell.edgeSize
404 onShownChanged: {
405 if (shown) {
406 lockscreen.reset();
407 // If there is only one user, we start authenticating with that one here.
408 // If there are more users, the Greeter will handle that
409 if (LightDM.Users.count == 1) {
410 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
411 }
412 greeter.forceActiveFocus();
413 }
414 }
416 onUnlocked: greeter.hide()
417 onSelected: {
418 // Update launcher items for new user
419 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
420 AccountsService.user = user;
421 LauncherModel.setUser(user);
422 }
424 onTease: launcher.tease()
426 Binding {
427 target: ApplicationManager
428 property: "suspended"
429 value: greeter.shown && greeterWrapper.showProgress == 1
430 }
431 }
432 }
434 Connections {
435 id: powerConnection
436 target: Powerd
438 onDisplayPowerStateChange: {
439 // We ignore any display-off signals when the proximity sensor
440 // is active. This usually indicates something like a phone call.
441 if (status == Powerd.Off && reason != Powerd.Proximity) {
442 greeter.showNow();
443 }
445 // No reason to chew demo CPU when user isn't watching
446 if (status == Powerd.Off) {
447 edgeDemo.paused = true;
448 } else if (status == Powerd.On) {
449 edgeDemo.paused = false;
450 }
451 }
452 }
454 function showHome() {
455 var animate = !greeter.shown && !stages.shown
456 greeter.hide()
457 dash.setCurrentScope("clickscope", animate, false)
458 stages.hide()
459 }
461 function hideIndicatorMenu(delay) {
462 panel.hideIndicatorMenu(delay);
463 }
465 Item {
466 id: overlay
467 z: 10
469 anchors.fill: parent
471 Panel {
472 id: panel
473 anchors.fill: parent //because this draws indicator menus
474 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
475 indicators {
476 hides: [launcher]
477 available: edgeDemo.panelEnabled
478 contentEnabled: edgeDemo.panelContentEnabled
479 }
481 property bool topmostApplicationIsFullscreen:
482 ApplicationManager.topmostApplication &&
483 ApplicationManager.topmostApplication.fullscreen
485 fullscreenMode: stages.roughlyFullyShown && topmostApplicationIsFullscreen
486 && !greeter.shown && !lockscreen.shown
488 searchVisible: !greeter.shown && !lockscreen.shown && dash.shown && dash.searchable
489 }
491 Launcher {
492 id: launcher
494 readonly property bool dashSwipe: progress > 0
496 anchors.top: parent.top
497 anchors.bottom: parent.bottom
498 width: parent.width
499 dragAreaWidth: shell.edgeSize
500 available: edgeDemo.launcherEnabled
502 onShowDashHome: {
503 if (edgeDemo.running)
504 return;
506 showHome()
507 }
508 onDash: {
509 if (!stages.locked) {
510 stages.hide();
511 launcher.hide();
512 }
513 if (greeter.shown) {
514 greeter.hideRight();
515 launcher.hide();
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 anchors {
550 top: parent.top
551 right: parent.right
552 bottom: parent.bottom
553 topMargin: panel.panelHeight
554 }
555 states: [
556 State {
557 name: "narrow"
558 when: overlay.width <= units.gu(60)
559 AnchorChanges { target: notifications; anchors.left: parent.left }
560 },
561 State {
562 name: "wide"
563 when: overlay.width > units.gu(60)
564 AnchorChanges { target: notifications; anchors.left: undefined }
565 PropertyChanges { target: notifications; width: units.gu(38) }
566 }
567 ]
568 }
569 }
571 focus: true
572 onFocusChanged: if (!focus) forceActiveFocus();
574 Binding {
575 target: i18n
576 property: "domain"
577 value: "unity8"
578 }
580 Label {
581 id: alphaDisclaimerLabel
582 anchors.centerIn: parent
583 visible: ApplicationManager.fake ? ApplicationManager.fake : false
584 z: overlay.z + 10
585 text: "EARLY ALPHA\nNOT READY FOR USE"
586 color: "lightgrey"
587 opacity: 0.2
588 font.weight: Font.Black
589 horizontalAlignment: Text.AlignHCenter
590 verticalAlignment: Text.AlignVCenter
591 fontSizeMode: Text.Fit
592 rotation: -45
593 scale: Math.min(parent.width, parent.height) / width
594 }
596 EdgeDemo {
597 id: edgeDemo
598 z: alphaDisclaimerLabel.z + 10
599 greeter: greeter
600 launcher: launcher
601 dash: dash
602 indicators: panel.indicators
603 underlay: underlay
604 }
606 Connections {
607 target: SessionBroadcast
608 onShowHome: showHome()
609 }