on 97b41f2 help sign in
unity8/qml/Shell.qml
613 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 property int supportedScreenOrientations: {
41 // check device name and fix orientation if necessary
42 var deviceOrientations;
43 if (applicationArguments.device() === "flo") {
44 deviceOrientations = Qt.InvertedLandscapeOrientation;
45 } else if (applicationArguments.device() === "manta") {
46 deviceOrientations = Qt.LandscapeOrientation;
47 } else if (applicationArguments.device() === "mako") {
48 deviceOrientations = Qt.PortraitOrientation
49 | Qt.InvertedPortraitOrientation
50 | Qt.LandscapeOrientation
51 | Qt.InvertedLandscapeOrientation;
52 }
54 if (stages.shown && ApplicationManager.topmostApplication) {
55 return deviceOrientations & ApplicationManager.topmostApplication.supportedOrientations;
56 } else {
57 return deviceOrientations;
58 }
59 }
60 property bool dashShown: dash.shown && dash.available && underlay.visible
62 property real edgeSize: units.gu(2)
63 property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
64 property url background
65 readonly property real panelHeight: panel.panelHeight
67 property bool sideStageEnabled: shell.width >= units.gu(100)
69 function activateApplication(appId) {
70 if (ApplicationManager.findApplication(appId)) {
71 ApplicationManager.requestFocusApplication(appId);
72 } else {
73 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
74 ApplicationManager.startApplication(appId, execFlags);
75 stages.show();
76 }
77 }
79 Binding {
80 target: LauncherModel
81 property: "applicationManager"
82 value: ApplicationManager
83 }
85 Component.onCompleted: {
86 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
87 }
89 GSettings {
90 id: backgroundSettings
91 schema.id: "org.gnome.desktop.background"
92 }
93 property url gSettingsPicture: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : shell.defaultBackground
94 onGSettingsPictureChanged: {
95 shell.background = gSettingsPicture
96 }
98 VolumeControl {
99 id: volumeControl
100 }
102 Keys.onVolumeUpPressed: volumeControl.volumeUp()
103 Keys.onVolumeDownPressed: volumeControl.volumeDown()
105 Item {
106 id: underlay
107 objectName: "underlay"
108 anchors.fill: parent
110 // Whether the underlay is fully covered by opaque UI elements.
111 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
113 // NB! Application surfaces are stacked behind the shell one. So they can only be seen by the user
114 // through the translucent parts of the shell surface.
115 visible: !fullyCovered
117 Rectangle {
118 anchors.fill: parent
119 color: "black"
120 opacity: dash.disappearingAnimationProgress
121 }
123 Image {
124 anchors.fill: dash
125 source: shell.width > shell.height ? "Dash/graphics/paper_landscape.png" : "Dash/graphics/paper_portrait.png"
126 fillMode: Image.PreserveAspectCrop
127 horizontalAlignment: Image.AlignRight
128 verticalAlignment: Image.AlignTop
129 }
131 Dash {
132 id: dash
133 objectName: "dash"
135 available: !greeter.shown && !lockscreen.shown
136 hides: [stages, launcher, panel.indicators]
137 shown: disappearingAnimationProgress !== 1.0 && greeterWrapper.showProgress !== 1.0
138 enabled: disappearingAnimationProgress === 0.0 && greeterWrapper.showProgress === 0.0 && edgeDemo.dashEnabled
140 anchors {
141 fill: parent
142 topMargin: panel.panelHeight
143 }
145 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
146 opacity: 1.0 - disappearingAnimationProgress
147 property real disappearingAnimationProgress: stages.showProgress
149 // FIXME: only necessary because stages.showProgress is not animated
150 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
151 }
152 }
154 EdgeDragArea {
155 id: stagesDragArea
156 direction: Direction.Leftwards
158 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
159 width: shell.edgeSize
161 property real progress: stages.width
163 onTouchXChanged: {
164 if (status == DirectionalDragArea.Recognized) {
165 if (ApplicationManager.empty) {
166 progress = Math.max(stages.width - stagesDragArea.width + touchX, stages.width * .3);
167 } else {
168 progress = stages.width - stagesDragArea.width + touchX;
169 }
170 }
171 }
173 onDraggingChanged: {
174 if (!dragging) {
175 if (!ApplicationManager.empty && progress < stages.width - units.gu(10)) {
176 stages.show();
177 }
178 stagesDragArea.progress = Qt.binding(function () { return stages.width; });
179 }
180 }
181 }
183 Item {
184 id: stages
185 objectName: "stages"
186 width: parent.width
187 height: parent.height
189 visible: !fullyHidden && !ApplicationManager.empty
191 x: shown ? launcher.progress : stagesDragArea.progress
192 Behavior on x { SmoothedAnimation { velocity: 600; duration: UbuntuAnimation.FastDuration } }
194 property bool shown: false
195 onShownChanged: {
196 if (shown) {
197 if (ApplicationManager.topmostApplication) {
198 ApplicationManager.focusApplication(ApplicationManager.topmostApplication.appId);
199 }
200 } else {
201 if (ApplicationManager.focusedApplicationId) {
202 ApplicationManager.updateScreenshot(ApplicationManager.focusedApplicationId);
203 ApplicationManager.unfocusCurrentApplication();
204 }
205 }
206 }
208 // Avoid a silent "divide by zero -> NaN" situation during init as shell.width will be
209 // zero. That breaks the property binding and the function won't be reevaluated once
210 // shell.width is set, with the NaN result staying there for good.
211 property real showProgress: shell.width ? MathUtils.clamp(1 - x / shell.width, 0, 1) : 0
213 property bool fullyShown: x == 0
214 property bool fullyHidden: x == width
216 // It might technically not be fullyShown but visually it just looks so.
217 property bool roughlyFullyShown: x >= 0 && x <= units.gu(1)
219 function show() {
220 shown = true;
221 }
223 function hide() {
224 shown = false;
225 }
227 Connections {
228 target: ApplicationManager
229 onFocusRequested: { stages.show(); }
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 value: panel.panelHeight
277 }
278 Binding {
279 target: applicationsDisplayLoader.item
280 property: "interactive"
281 value: stages.roughlyFullyShown
282 }
283 }
284 }
286 InputMethod {
287 id: inputMethod
288 anchors.fill: parent
289 z: notifications.useModal ? overlay.z + 1 : overlay.z - 1
290 }
292 Connections {
293 target: SurfaceManager
294 onSurfaceCreated: {
295 if (surface.type == MirSurfaceItem.InputMethod) {
296 inputMethod.surface = surface;
297 } else {
298 stages.show();
299 }
300 }
302 onSurfaceDestroyed: {
303 if (inputMethod.surface == surface) {
304 inputMethod.surface = null;
305 surface.parent = null;
306 }
307 if (!surface.parent) {
308 // there's no one displaying it. delete it right away
309 surface.release();
310 }
311 }
312 }
314 Lockscreen {
315 id: lockscreen
316 objectName: "lockscreen"
318 readonly property int backgroundTopMargin: -panel.panelHeight
320 hides: [launcher, panel.indicators]
321 shown: false
322 enabled: true
323 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
324 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
325 y: panel.panelHeight
326 x: required ? 0 : - width
327 width: parent.width
328 height: parent.height - panel.panelHeight
329 //background: shell.background // FIXME: Commenting out as Qt was complaining about this line
330 minPinLength: 4
331 maxPinLength: 4
333 onEntered: LightDM.Greeter.respond(passphrase);
334 onCancel: greeter.show()
336 Component.onCompleted: {
337 if (LightDM.Users.count == 1) {
338 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
339 }
340 }
341 }
343 Connections {
344 target: LightDM.Greeter
346 onShowPrompt: {
347 if (LightDM.Users.count == 1) {
348 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
349 if (text == "PIN") {
350 lockscreen.alphaNumeric = false
351 } else {
352 lockscreen.alphaNumeric = true
353 }
354 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
355 lockscreen.show();
356 }
357 }
359 onAuthenticationComplete: {
360 if (LightDM.Greeter.promptless) {
361 return;
362 }
363 if (LightDM.Greeter.authenticated) {
364 lockscreen.hide();
365 } else {
366 lockscreen.clear(true);
367 }
368 }
369 }
371 Rectangle {
372 anchors.fill: parent
373 color: "black"
374 opacity: greeterWrapper.showProgress * 0.8
375 }
377 Item {
378 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
379 id: greeterWrapper
380 x: launcher.progress
381 y: panel.panelHeight
382 width: parent.width
383 height: parent.height - panel.panelHeight
385 Behavior on x {
386 enabled: !launcher.dashSwipe
387 StandardAnimation {}
388 }
390 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
392 Greeter {
393 id: greeter
394 objectName: "greeter"
396 available: true
397 hides: [launcher, panel.indicators]
398 shown: true
400 defaultBackground: shell.background
402 width: parent.width
403 height: parent.height
405 dragHandleWidth: shell.edgeSize
407 onShownChanged: {
408 if (shown) {
409 lockscreen.reset();
410 // If there is only one user, we start authenticating with that one here.
411 // If there are more users, the Greeter will handle that
412 if (LightDM.Users.count == 1) {
413 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
414 }
415 greeter.forceActiveFocus();
416 }
417 }
419 onUnlocked: greeter.hide()
420 onSelected: {
421 // Update launcher items for new user
422 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
423 AccountsService.user = user;
424 LauncherModel.setUser(user);
425 }
427 onTease: launcher.tease()
429 Binding {
430 target: ApplicationManager
431 property: "suspended"
432 value: greeter.shown && greeterWrapper.showProgress == 1
433 }
434 }
435 }
437 Connections {
438 id: powerConnection
439 target: Powerd
441 onDisplayPowerStateChange: {
442 // We ignore any display-off signals when the proximity sensor
443 // is active. This usually indicates something like a phone call.
444 if (status == Powerd.Off && reason != Powerd.Proximity) {
445 greeter.showNow();
446 }
448 // No reason to chew demo CPU when user isn't watching
449 if (status == Powerd.Off) {
450 edgeDemo.paused = true;
451 } else if (status == Powerd.On) {
452 edgeDemo.paused = false;
453 }
454 }
455 }
457 function showHome() {
458 var animate = !greeter.shown && !stages.shown
459 greeter.hide()
460 dash.setCurrentScope("clickscope", animate, false)
461 stages.hide()
462 }
464 function hideIndicatorMenu(delay) {
465 panel.hideIndicatorMenu(delay);
466 }
468 Item {
469 id: overlay
470 z: 10
472 anchors.fill: parent
474 Panel {
475 id: panel
476 anchors.fill: parent //because this draws indicator menus
477 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
478 indicators {
479 hides: [launcher]
480 available: edgeDemo.panelEnabled
481 contentEnabled: edgeDemo.panelContentEnabled
482 }
484 property bool topmostApplicationIsFullscreen:
485 ApplicationManager.topmostApplication &&
486 ApplicationManager.topmostApplication.fullscreen
488 fullscreenMode: stages.roughlyFullyShown && topmostApplicationIsFullscreen
489 && !greeter.shown && !lockscreen.shown
491 searchVisible: !greeter.shown && !lockscreen.shown && dash.shown && dash.searchable
492 }
494 Launcher {
495 id: launcher
497 readonly property bool dashSwipe: progress > 0
499 anchors.top: parent.top
500 anchors.bottom: parent.bottom
501 width: parent.width
502 dragAreaWidth: shell.edgeSize
503 available: edgeDemo.launcherEnabled
505 onShowDashHome: {
506 if (edgeDemo.running)
507 return;
509 showHome()
510 }
511 onDash: {
512 if (!stages.locked) {
513 stages.hide();
514 launcher.hide();
515 }
516 if (greeter.shown) {
517 greeter.hideRight();
518 launcher.hide();
519 }
520 }
521 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
522 onLauncherApplicationSelected: {
523 if (!edgeDemo.running)
524 shell.activateApplication(appId)
525 }
526 onShownChanged: {
527 if (shown) {
528 panel.indicators.hide()
529 }
530 }
531 }
533 Rectangle {
534 id: modalNotificationBackground
536 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
537 color: "#000000"
538 anchors.fill: parent
539 opacity: 0.5
541 MouseArea {
542 anchors.fill: parent
543 }
544 }
546 Notifications {
547 id: notifications
549 model: NotificationBackend.Model
550 margin: units.gu(1)
552 anchors {
553 top: parent.top
554 right: parent.right
555 bottom: parent.bottom
556 topMargin: panel.panelHeight
557 }
558 states: [
559 State {
560 name: "narrow"
561 when: overlay.width <= units.gu(60)
562 AnchorChanges { target: notifications; anchors.left: parent.left }
563 },
564 State {
565 name: "wide"
566 when: overlay.width > units.gu(60)
567 AnchorChanges { target: notifications; anchors.left: undefined }
568 PropertyChanges { target: notifications; width: units.gu(38) }
569 }
570 ]
571 }
572 }
574 focus: true
575 onFocusChanged: if (!focus) forceActiveFocus();
577 Binding {
578 target: i18n
579 property: "domain"
580 value: "unity8"
581 }
583 Label {
584 id: alphaDisclaimerLabel
585 anchors.centerIn: parent
586 visible: ApplicationManager.fake ? ApplicationManager.fake : false
587 z: overlay.z + 10
588 text: "EARLY ALPHA\nNOT READY FOR USE"
589 color: "lightgrey"
590 opacity: 0.2
591 font.weight: Font.Black
592 horizontalAlignment: Text.AlignHCenter
593 verticalAlignment: Text.AlignVCenter
594 fontSizeMode: Text.Fit
595 rotation: -45
596 scale: Math.min(parent.width, parent.height) / width
597 }
599 EdgeDemo {
600 id: edgeDemo
601 z: alphaDisclaimerLabel.z + 10
602 greeter: greeter
603 launcher: launcher
604 dash: dash
605 indicators: panel.indicators
606 underlay: underlay
607 }
609 Connections {
610 target: SessionBroadcast
611 onShowHome: showHome()
612 }