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