on 97f99d4 help sign in
unity8/qml/Shell.qml
612 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 if (!stages.shown) {
227 stages.show();
228 }
229 }
231 onEmptyChanged: {
232 if (ApplicationManager.empty) {
233 stages.hide();
234 }
235 }
236 }
238 Connections {
239 target: DBusUnitySessionService
241 function closeAllApps() {
242 while (true) {
243 var app = ApplicationManager.get(0);
244 if (app === null) {
245 break;
246 }
247 ApplicationManager.stopApplication(app.appId);
248 }
249 }
251 onLogoutRequested: {
252 // TODO: Display a dialog to ask the user to confirm.
253 DBusUnitySessionService.Logout();
254 }
256 onLogoutReady: {
257 closeAllApps();
258 Qt.quit();
259 }
260 }
262 Loader {
263 id: applicationsDisplayLoader
264 anchors.fill: parent
266 source: shell.sideStageEnabled ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
268 Binding {
269 target: applicationsDisplayLoader.item
270 property: "dragAreaWidth"
271 value: shell.edgeSize
272 }
273 Binding {
274 target: applicationsDisplayLoader.item
275 property: "maximizedAppTopMargin"
276 // Not just using panel.panelHeight as that changes depending on the focused app.
277 value: panel.indicators.panelHeight
278 }
279 Binding {
280 target: applicationsDisplayLoader.item
281 property: "interactive"
282 value: stages.roughlyFullyShown
283 }
284 }
285 }
287 InputMethod {
288 id: inputMethod
289 anchors.fill: parent
290 z: notifications.useModal || panel.indicators.shown ? overlay.z + 1 : overlay.z - 1
291 }
293 Connections {
294 target: SurfaceManager
295 onSurfaceCreated: {
296 if (surface.type == MirSurfaceItem.InputMethod) {
297 inputMethod.surface = surface;
298 }
299 }
301 onSurfaceDestroyed: {
302 if (inputMethod.surface == surface) {
303 inputMethod.surface = null;
304 surface.parent = null;
305 }
306 if (!surface.parent) {
307 // there's no one displaying it. delete it right away
308 surface.release();
309 }
310 }
311 }
313 Lockscreen {
314 id: lockscreen
315 objectName: "lockscreen"
317 readonly property int backgroundTopMargin: -panel.panelHeight
319 hides: [launcher, panel.indicators]
320 shown: false
321 enabled: true
322 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
323 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
324 y: panel.panelHeight
325 x: required ? 0 : - width
326 width: parent.width
327 height: parent.height - panel.panelHeight
328 //background: shell.background // FIXME: Commenting out as Qt was complaining about this line
329 minPinLength: 4
330 maxPinLength: 4
332 onEntered: LightDM.Greeter.respond(passphrase);
333 onCancel: greeter.show()
335 Component.onCompleted: {
336 if (LightDM.Users.count == 1) {
337 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
338 }
339 }
340 }
342 Connections {
343 target: LightDM.Greeter
345 onShowPrompt: {
346 if (LightDM.Users.count == 1) {
347 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
348 if (text == "PIN") {
349 lockscreen.alphaNumeric = false
350 } else {
351 lockscreen.alphaNumeric = true
352 }
353 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
354 lockscreen.show();
355 }
356 }
358 onAuthenticationComplete: {
359 if (LightDM.Greeter.promptless) {
360 return;
361 }
362 if (LightDM.Greeter.authenticated) {
363 lockscreen.hide();
364 } else {
365 lockscreen.clear(true);
366 }
367 }
368 }
370 Rectangle {
371 anchors.fill: parent
372 color: "black"
373 opacity: greeterWrapper.showProgress * 0.8
374 }
376 Item {
377 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
378 id: greeterWrapper
379 x: launcher.progress
380 y: panel.panelHeight
381 width: parent.width
382 height: parent.height - panel.panelHeight
384 Behavior on x {
385 enabled: !launcher.dashSwipe
386 StandardAnimation {}
387 }
389 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
391 Greeter {
392 id: greeter
393 objectName: "greeter"
395 available: true
396 hides: [launcher, panel.indicators]
397 shown: true
399 defaultBackground: shell.background
401 width: parent.width
402 height: parent.height
404 dragHandleWidth: shell.edgeSize
406 onShownChanged: {
407 if (shown) {
408 lockscreen.reset();
409 // If there is only one user, we start authenticating with that one here.
410 // If there are more users, the Greeter will handle that
411 if (LightDM.Users.count == 1) {
412 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
413 }
414 greeter.forceActiveFocus();
415 }
416 }
418 onUnlocked: greeter.hide()
419 onSelected: {
420 // Update launcher items for new user
421 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
422 AccountsService.user = user;
423 LauncherModel.setUser(user);
424 }
426 onTease: launcher.tease()
428 Binding {
429 target: ApplicationManager
430 property: "suspended"
431 value: greeter.shown && greeterWrapper.showProgress == 1
432 }
433 }
434 }
436 Connections {
437 id: powerConnection
438 target: Powerd
440 onDisplayPowerStateChange: {
441 // We ignore any display-off signals when the proximity sensor
442 // is active. This usually indicates something like a phone call.
443 if (status == Powerd.Off && reason != Powerd.Proximity) {
444 greeter.showNow();
445 }
447 // No reason to chew demo CPU when user isn't watching
448 if (status == Powerd.Off) {
449 edgeDemo.paused = true;
450 } else if (status == Powerd.On) {
451 edgeDemo.paused = false;
452 }
453 }
454 }
456 function showHome() {
457 var animate = !greeter.shown && !stages.shown
458 greeter.hide()
459 dash.setCurrentScope("clickscope", animate, false)
460 stages.hide()
461 }
463 function hideIndicatorMenu(delay) {
464 panel.hideIndicatorMenu(delay);
465 }
467 Item {
468 id: overlay
469 z: 10
471 anchors.fill: parent
473 Panel {
474 id: panel
475 anchors.fill: parent //because this draws indicator menus
476 indicators {
477 hides: [launcher]
478 available: edgeDemo.panelEnabled
479 contentEnabled: edgeDemo.panelContentEnabled
480 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
481 panelHeight: units.gu(3)
482 }
484 property bool topmostApplicationIsFullscreen:
485 ApplicationManager.topmostApplication &&
486 ApplicationManager.topmostApplication.fullscreen
488 fullscreenMode: stages.roughlyFullyShown && topmostApplicationIsFullscreen
489 && !greeter.shown && !lockscreen.shown
490 }
492 Launcher {
493 id: launcher
495 readonly property bool dashSwipe: progress > 0
497 anchors.top: parent.top
498 anchors.bottom: parent.bottom
499 width: parent.width
500 dragAreaWidth: shell.edgeSize
501 available: edgeDemo.launcherEnabled
503 onShowDashHome: {
504 if (edgeDemo.running)
505 return;
507 showHome()
508 }
509 onDash: {
510 if (!stages.locked) {
511 stages.hide();
512 launcher.fadeOut();
513 } else {
514 launcher.switchToNextState("visible");
515 }
517 if (greeter.shown) {
518 greeter.hideRight();
519 launcher.fadeOut();
520 }
521 }
522 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
523 onLauncherApplicationSelected: {
524 if (!edgeDemo.running)
525 shell.activateApplication(appId)
526 }
527 onShownChanged: {
528 if (shown) {
529 panel.indicators.hide()
530 }
531 }
532 }
534 Rectangle {
535 id: modalNotificationBackground
537 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
538 color: "#000000"
539 anchors.fill: parent
540 opacity: 0.5
542 MouseArea {
543 anchors.fill: parent
544 }
545 }
547 Notifications {
548 id: notifications
550 model: NotificationBackend.Model
551 margin: units.gu(1)
553 y: panel.panelHeight
554 width: parent.width
555 height: parent.height - panel.panelHeight
557 states: [
558 State {
559 name: "narrow"
560 when: overlay.width <= units.gu(60)
561 AnchorChanges { target: notifications; anchors.left: parent.left }
562 },
563 State {
564 name: "wide"
565 when: overlay.width > units.gu(60)
566 AnchorChanges { target: notifications; anchors.left: undefined }
567 PropertyChanges { target: notifications; width: units.gu(38) }
568 }
569 ]
570 }
571 }
573 focus: true
574 onFocusChanged: if (!focus) forceActiveFocus();
576 Binding {
577 target: i18n
578 property: "domain"
579 value: "unity8"
580 }
582 Label {
583 id: alphaDisclaimerLabel
584 anchors.centerIn: parent
585 visible: ApplicationManager.fake ? ApplicationManager.fake : false
586 z: overlay.z + 10
587 text: "EARLY ALPHA\nNOT READY FOR USE"
588 color: "lightgrey"
589 opacity: 0.2
590 font.weight: Font.Black
591 horizontalAlignment: Text.AlignHCenter
592 verticalAlignment: Text.AlignVCenter
593 fontSizeMode: Text.Fit
594 rotation: -45
595 scale: Math.min(parent.width, parent.height) / width
596 }
598 EdgeDemo {
599 id: edgeDemo
600 z: alphaDisclaimerLabel.z + 10
601 greeter: greeter
602 launcher: launcher
603 dash: dash
604 indicators: panel.indicators
605 underlay: underlay
606 }
608 Connections {
609 target: SessionBroadcast
610 onShowHome: showHome()
611 }