on 097c240 help sign in
unity8/qml/Shell.qml
570 lines 18 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 Mir.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
36FocusScope {
37 id: shell
39 property int supportedScreenOrientations: {
40 if (stages.shown && ApplicationManager.topmostApplication) {
41 return ApplicationManager.topmostApplication.supportedOrientations;
42 } else {
43 return Qt.PortraitOrientation
44 | Qt.InvertedPortraitOrientation
45 | Qt.LandscapeOrientation
46 | Qt.InvertedLandscapeOrientation;
47 }
48 }
50 property real edgeSize: units.gu(2)
51 property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
52 property url background
53 readonly property real panelHeight: panel.panelHeight
55 property bool dashShown: dash.shown
57 property bool sideStageEnabled: shell.width >= units.gu(100)
59 function activateApplication(appId) {
60 if (ApplicationManager.findApplication(appId)) {
61 ApplicationManager.requestFocusApplication(appId);
62 } else {
63 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
64 ApplicationManager.startApplication(appId, execFlags);
65 stages.show();
66 }
67 }
69 Binding {
70 target: LauncherModel
71 property: "applicationManager"
72 value: ApplicationManager
73 }
75 Component.onCompleted: {
76 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
77 }
79 GSettings {
80 id: backgroundSettings
81 schema.id: "org.gnome.desktop.background"
82 }
83 property url gSettingsPicture: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : shell.defaultBackground
84 onGSettingsPictureChanged: {
85 shell.background = gSettingsPicture
86 }
88 VolumeControl {
89 id: volumeControl
90 }
92 Keys.onVolumeUpPressed: volumeControl.volumeUp()
93 Keys.onVolumeDownPressed: volumeControl.volumeDown()
95 Item {
96 id: underlay
97 objectName: "underlay"
98 anchors.fill: parent
100 // Whether the underlay is fully covered by opaque UI elements.
101 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
103 // NB! Application surfaces are stacked behind the shell one. So they can only be seen by the user
104 // through the translucent parts of the shell surface.
105 visible: !fullyCovered
107 Rectangle {
108 anchors.fill: parent
109 color: "black"
110 opacity: dash.disappearingAnimationProgress
111 }
113 Image {
114 anchors.fill: dash
115 source: shell.width > shell.height ? "Dash/graphics/paper_landscape.png" : "Dash/graphics/paper_portrait.png"
116 fillMode: Image.PreserveAspectCrop
117 horizontalAlignment: Image.AlignRight
118 verticalAlignment: Image.AlignTop
119 }
121 Dash {
122 id: dash
123 objectName: "dash"
125 available: !greeter.shown && !lockscreen.shown
126 hides: [stages, launcher, panel.indicators]
127 shown: disappearingAnimationProgress !== 1.0 && greeterWrapper.showProgress !== 1.0
128 enabled: disappearingAnimationProgress === 0.0 && greeterWrapper.showProgress === 0.0 && edgeDemo.dashEnabled
130 anchors {
131 fill: parent
132 topMargin: panel.panelHeight
133 }
135 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
136 opacity: 1.0 - disappearingAnimationProgress
137 property real disappearingAnimationProgress: stages.showProgress
139 // FIXME: only necessary because stages.showProgress is not animated
140 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
141 }
142 }
144 EdgeDragArea {
145 id: stagesDragArea
146 direction: Direction.Leftwards
148 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
149 width: shell.edgeSize
151 property real progress: stages.width
153 onTouchXChanged: {
154 if (status == DirectionalDragArea.Recognized) {
155 if (ApplicationManager.empty) {
156 progress = Math.max(stages.width - stagesDragArea.width + touchX, stages.width * .3);
157 } else {
158 progress = stages.width - stagesDragArea.width + touchX;
159 }
160 }
161 }
163 onDraggingChanged: {
164 if (!dragging) {
165 if (!ApplicationManager.empty && progress < stages.width - units.gu(10)) {
166 stages.show();
167 }
168 stagesDragArea.progress = Qt.binding(function () { return stages.width; });
169 }
170 }
171 }
173 Rectangle {
174 id: stages
175 objectName: "stages"
176 width: parent.width
177 height: parent.height
178 color: "khaki"
180 visible: !fullyHidden
182 x: shown ? launcher.progress : stagesDragArea.progress
183 //Behavior on x { SmoothedAnimation { velocity: 600; duration: UbuntuAnimation.FastDuration } }
185 property bool shown: false
186 onShownChanged: {
187 if (shown) {
188 if (ApplicationManager.topmostApplication) {
189 ApplicationManager.focusApplication(ApplicationManager.topmostApplication.appId);
190 }
191 } else {
192 if (ApplicationManager.focusedApplicationId) {
193 ApplicationManager.updateScreenshot(ApplicationManager.focusedApplicationId);
194 ApplicationManager.unfocusCurrentApplication();
195 }
196 }
197 }
199 // Avoid a silent "divide by zero -> NaN" situation during init as shell.width will be
200 // zero. That breaks the property binding and the function won't be reevaluated once
201 // shell.width is set, with the NaN result staying there for good.
202 property real showProgress: shell.width ? MathUtils.clamp(1 - x / shell.width, 0, 1) : 0
204 property bool fullyShown: x == 0
205 property bool fullyHidden: x == width
207 // It might technically not be fullyShown but visually it just looks so.
208 property bool roughlyFullyShown: x >= 0 && x <= units.gu(1)
210 function show() {
211 shown = true;
212 }
214 function hide() {
215 shown = false;
216 }
218 Connections {
219 target: ApplicationManager
220 onFocusRequested: { stages.show(); }
221 }
223 Loader {
224 id: applicationsDisplayLoader
225 anchors.fill: parent
227 source: shell.sideStageEnabled ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
229 Binding {
230 target: applicationsDisplayLoader.item
231 property: "dragAreaWidth"
232 value: shell.edgeSize
233 }
234 Binding {
235 target: applicationsDisplayLoader.item
236 property: "maximizedAppTopMargin"
237 value: panel.panelHeight
238 }
239 }
240 }
242 InputMethod {
243 id: inputMethod
244 anchors.fill: parent
245 }
247 Connections {
248 target: SurfaceManager
249 onSurfaceCreated: {
250 if (surface.type == MirSurfaceItem.InputMethod) {
251 inputMethod.surface = surface;
252 } else {
253 stages.show();
254 }
255 }
257 onSurfaceDestroyed: {
258 if (inputMethod.surface == surface) {
259 inputMethod.surface = null;
260 surface.parent = null;
261 }
262 if (!surface.parent) {
263 // there's no one displaying it. delete it right away
264 surface.release();
265 }
266 }
268 onEmptyChanged: {
269 if (SurfaceManager.empty) {
270 stages.hide();
271 }
272 }
273 }
275 Lockscreen {
276 id: lockscreen
277 objectName: "lockscreen"
279 readonly property int backgroundTopMargin: -panel.panelHeight
281 hides: [launcher, panel.indicators]
282 shown: false
283 enabled: true
284 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
285 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
286 y: panel.panelHeight
287 x: required ? 0 : - width
288 width: parent.width
289 height: parent.height - panel.panelHeight
290 background: shell.background
291 minPinLength: 4
292 maxPinLength: 4
294 onEntered: LightDM.Greeter.respond(passphrase);
295 onCancel: greeter.show()
297 Component.onCompleted: {
298 if (LightDM.Users.count == 1) {
299 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
300 }
301 }
302 }
304 Connections {
305 target: LightDM.Greeter
307 onShowPrompt: {
308 if (LightDM.Users.count == 1) {
309 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
310 if (text == "PIN") {
311 lockscreen.alphaNumeric = false
312 } else {
313 lockscreen.alphaNumeric = true
314 }
315 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
316 lockscreen.show();
317 }
318 }
320 onAuthenticationComplete: {
321 if (LightDM.Greeter.promptless) {
322 return;
323 }
324 if (LightDM.Greeter.authenticated) {
325 lockscreen.hide();
326 } else {
327 lockscreen.clear(true);
328 }
329 }
330 }
332 Rectangle {
333 anchors.fill: parent
334 color: "black"
335 opacity: greeterWrapper.showProgress * 0.8
336 }
338 Item {
339 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
340 id: greeterWrapper
341 x: launcher.progress
342 y: panel.panelHeight
343 width: parent.width
344 height: parent.height - panel.panelHeight
346 Behavior on x {
347 enabled: !launcher.dashSwipe
348 StandardAnimation {}
349 }
351 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
353 Greeter {
354 id: greeter
355 objectName: "greeter"
357 available: true
358 hides: [launcher, panel.indicators]
359 shown: true
361 defaultBackground: shell.background
363 width: parent.width
364 height: parent.height
366 dragHandleWidth: shell.edgeSize
368 onShownChanged: {
369 if (shown) {
370 lockscreen.reset();
371 // If there is only one user, we start authenticating with that one here.
372 // If there are more users, the Greeter will handle that
373 if (LightDM.Users.count == 1) {
374 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
375 }
376 greeter.forceActiveFocus();
377 }
378 }
380 onUnlocked: greeter.hide()
381 onSelected: {
382 // Update launcher items for new user
383 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
384 AccountsService.user = user;
385 LauncherModel.setUser(user);
386 }
388 onTease: launcher.tease()
390 Binding {
391 target: ApplicationManager
392 property: "suspended"
393 value: greeter.shown && greeterWrapper.showProgress == 1
394 }
395 }
396 }
398 Connections {
399 id: powerConnection
400 target: Powerd
402 onDisplayPowerStateChange: {
403 // We ignore any display-off signals when the proximity sensor
404 // is active. This usually indicates something like a phone call.
405 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
406 greeter.showNow();
407 }
409 // No reason to chew demo CPU when user isn't watching
410 if (status == Powerd.Off) {
411 edgeDemo.paused = true;
412 } else if (status == Powerd.On) {
413 edgeDemo.paused = false;
414 }
415 }
416 }
418 function showHome() {
419 var animate = !greeter.shown && !stages.shown
420 greeter.hide()
421 dash.setCurrentScope("clickscope", animate, false)
422 stages.hide()
423 }
425 function hideIndicatorMenu(delay) {
426 panel.hideIndicatorMenu(delay);
427 }
429 Item {
430 id: overlay
432 anchors.fill: parent
434 Panel {
435 id: panel
436 anchors.fill: parent //because this draws indicator menus
437 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
438 indicators {
439 hides: [launcher]
440 available: edgeDemo.panelEnabled
441 contentEnabled: edgeDemo.panelContentEnabled
442 }
444 property bool topmostApplicationIsFullscreen:
445 ApplicationManager.topmostApplication &&
446 ApplicationManager.topmostApplication.fullscreen
448 fullscreenMode: stages.roughlyFullyShown && topmostApplicationIsFullscreen
449 && !greeter.shown && !lockscreen.shown
451 searchVisible: !greeter.shown && !lockscreen.shown && dash.shown && dash.searchable
452 }
454 Launcher {
455 id: launcher
457 readonly property bool dashSwipe: progress > 0
459 anchors.top: parent.top
460 anchors.bottom: parent.bottom
461 width: parent.width
462 dragAreaWidth: shell.edgeSize
463 available: edgeDemo.launcherEnabled
465 onShowDashHome: {
466 if (edgeDemo.running)
467 return;
469 showHome()
470 }
471 onDash: {
472 if (!stages.locked) {
473 stages.hide();
474 launcher.hide();
475 }
476 if (greeter.shown) {
477 greeter.hideRight();
478 launcher.hide();
479 }
480 }
481 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
482 onLauncherApplicationSelected: {
483 if (!edgeDemo.running)
484 shell.activateApplication(appId)
485 }
486 onShownChanged: {
487 if (shown) {
488 panel.indicators.hide()
489 }
490 }
491 }
493 Rectangle {
494 id: modalNotificationBackground
496 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
497 color: "#000000"
498 anchors.fill: parent
499 opacity: 0.5
501 MouseArea {
502 anchors.fill: parent
503 }
504 }
506 Notifications {
507 id: notifications
509 model: NotificationBackend.Model
510 margin: units.gu(1)
512 anchors {
513 top: parent.top
514 right: parent.right
515 bottom: parent.bottom
516 topMargin: panel.panelHeight
517 }
518 states: [
519 State {
520 name: "narrow"
521 when: overlay.width <= units.gu(60)
522 AnchorChanges { target: notifications; anchors.left: parent.left }
523 },
524 State {
525 name: "wide"
526 when: overlay.width > units.gu(60)
527 AnchorChanges { target: notifications; anchors.left: undefined }
528 PropertyChanges { target: notifications; width: units.gu(38) }
529 }
530 ]
531 }
532 }
534 focus: true
535 onFocusChanged: if (!focus) forceActiveFocus();
537 Binding {
538 target: i18n
539 property: "domain"
540 value: "unity8"
541 }
543 Label {
544 anchors.centerIn: parent
545 visible: ApplicationManager.fake ? ApplicationManager.fake : false
546 text: "EARLY ALPHA\nNOT READY FOR USE"
547 color: "lightgrey"
548 opacity: 0.2
549 font.weight: Font.Black
550 horizontalAlignment: Text.AlignHCenter
551 verticalAlignment: Text.AlignVCenter
552 fontSizeMode: Text.Fit
553 rotation: -45
554 scale: Math.min(parent.width, parent.height) / width
555 }
557 EdgeDemo {
558 id: edgeDemo
559 greeter: greeter
560 launcher: launcher
561 dash: dash
562 indicators: panel.indicators
563 underlay: underlay
564 }
566 Connections {
567 target: SessionBroadcast
568 onShowHome: showHome()
569 }