on c9f4dde help sign in
unity8/qml/Shell.qml
575 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(); }
222 onEmptyChanged: {
223 if (ApplicationManager.empty) {
224 stages.hide();
225 }
226 }
227 }
229 Loader {
230 id: applicationsDisplayLoader
231 anchors.fill: parent
233 source: shell.sideStageEnabled ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
235 Binding {
236 target: applicationsDisplayLoader.item
237 property: "dragAreaWidth"
238 value: shell.edgeSize
239 }
240 Binding {
241 target: applicationsDisplayLoader.item
242 property: "maximizedAppTopMargin"
243 value: panel.panelHeight
244 }
245 }
246 }
248 InputMethod {
249 id: inputMethod
250 anchors.fill: parent
251 z: notifications.useModal ? overlay.z + 1 : overlay.z - 1
252 }
254 Connections {
255 target: SurfaceManager
256 onSurfaceCreated: {
257 if (surface.type == MirSurfaceItem.InputMethod) {
258 inputMethod.surface = surface;
259 } else {
260 stages.show();
261 }
262 }
264 onSurfaceDestroyed: {
265 if (inputMethod.surface == surface) {
266 inputMethod.surface = null;
267 surface.parent = null;
268 }
269 if (!surface.parent) {
270 // there's no one displaying it. delete it right away
271 surface.release();
272 }
273 }
274 }
276 Lockscreen {
277 id: lockscreen
278 objectName: "lockscreen"
280 readonly property int backgroundTopMargin: -panel.panelHeight
282 hides: [launcher, panel.indicators]
283 shown: false
284 enabled: true
285 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
286 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
287 y: panel.panelHeight
288 x: required ? 0 : - width
289 width: parent.width
290 height: parent.height - panel.panelHeight
291 background: shell.background
292 minPinLength: 4
293 maxPinLength: 4
295 onEntered: LightDM.Greeter.respond(passphrase);
296 onCancel: greeter.show()
298 Component.onCompleted: {
299 if (LightDM.Users.count == 1) {
300 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
301 }
302 }
303 }
305 Connections {
306 target: LightDM.Greeter
308 onShowPrompt: {
309 if (LightDM.Users.count == 1) {
310 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
311 if (text == "PIN") {
312 lockscreen.alphaNumeric = false
313 } else {
314 lockscreen.alphaNumeric = true
315 }
316 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
317 lockscreen.show();
318 }
319 }
321 onAuthenticationComplete: {
322 if (LightDM.Greeter.promptless) {
323 return;
324 }
325 if (LightDM.Greeter.authenticated) {
326 lockscreen.hide();
327 } else {
328 lockscreen.clear(true);
329 }
330 }
331 }
333 Rectangle {
334 anchors.fill: parent
335 color: "black"
336 opacity: greeterWrapper.showProgress * 0.8
337 }
339 Item {
340 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
341 id: greeterWrapper
342 x: launcher.progress
343 y: panel.panelHeight
344 width: parent.width
345 height: parent.height - panel.panelHeight
347 Behavior on x {
348 enabled: !launcher.dashSwipe
349 StandardAnimation {}
350 }
352 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
354 Greeter {
355 id: greeter
356 objectName: "greeter"
358 available: true
359 hides: [launcher, panel.indicators]
360 shown: true
362 defaultBackground: shell.background
364 width: parent.width
365 height: parent.height
367 dragHandleWidth: shell.edgeSize
369 onShownChanged: {
370 if (shown) {
371 lockscreen.reset();
372 // If there is only one user, we start authenticating with that one here.
373 // If there are more users, the Greeter will handle that
374 if (LightDM.Users.count == 1) {
375 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
376 }
377 greeter.forceActiveFocus();
378 }
379 }
381 onUnlocked: greeter.hide()
382 onSelected: {
383 // Update launcher items for new user
384 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
385 AccountsService.user = user;
386 LauncherModel.setUser(user);
387 }
389 onTease: launcher.tease()
391 Binding {
392 target: ApplicationManager
393 property: "suspended"
394 value: greeter.shown && greeterWrapper.showProgress == 1
395 }
396 }
397 }
399 Connections {
400 id: powerConnection
401 target: Powerd
403 onDisplayPowerStateChange: {
404 // We ignore any display-off signals when the proximity sensor
405 // is active. This usually indicates something like a phone call.
406 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
407 greeter.showNow();
408 }
410 // No reason to chew demo CPU when user isn't watching
411 if (status == Powerd.Off) {
412 edgeDemo.paused = true;
413 } else if (status == Powerd.On) {
414 edgeDemo.paused = false;
415 }
416 }
417 }
419 function showHome() {
420 var animate = !greeter.shown && !stages.shown
421 greeter.hide()
422 dash.setCurrentScope("clickscope", animate, false)
423 stages.hide()
424 }
426 function hideIndicatorMenu(delay) {
427 panel.hideIndicatorMenu(delay);
428 }
430 Item {
431 id: overlay
432 z: 10
434 anchors.fill: parent
436 Panel {
437 id: panel
438 anchors.fill: parent //because this draws indicator menus
439 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
440 indicators {
441 hides: [launcher]
442 available: edgeDemo.panelEnabled
443 contentEnabled: edgeDemo.panelContentEnabled
444 }
446 property bool topmostApplicationIsFullscreen:
447 ApplicationManager.topmostApplication &&
448 ApplicationManager.topmostApplication.fullscreen
450 fullscreenMode: stages.roughlyFullyShown && topmostApplicationIsFullscreen
451 && !greeter.shown && !lockscreen.shown
453 searchVisible: !greeter.shown && !lockscreen.shown && dash.shown && dash.searchable
454 }
456 Launcher {
457 id: launcher
459 readonly property bool dashSwipe: progress > 0
461 anchors.top: parent.top
462 anchors.bottom: parent.bottom
463 width: parent.width
464 dragAreaWidth: shell.edgeSize
465 available: edgeDemo.launcherEnabled
467 onShowDashHome: {
468 if (edgeDemo.running)
469 return;
471 showHome()
472 }
473 onDash: {
474 if (!stages.locked) {
475 stages.hide();
476 launcher.hide();
477 }
478 if (greeter.shown) {
479 greeter.hideRight();
480 launcher.hide();
481 }
482 }
483 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
484 onLauncherApplicationSelected: {
485 if (!edgeDemo.running)
486 shell.activateApplication(appId)
487 }
488 onShownChanged: {
489 if (shown) {
490 panel.indicators.hide()
491 }
492 }
493 }
495 Rectangle {
496 id: modalNotificationBackground
498 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
499 color: "#000000"
500 anchors.fill: parent
501 opacity: 0.5
503 MouseArea {
504 anchors.fill: parent
505 }
506 }
508 Notifications {
509 id: notifications
511 model: NotificationBackend.Model
512 margin: units.gu(1)
514 anchors {
515 top: parent.top
516 right: parent.right
517 bottom: parent.bottom
518 topMargin: panel.panelHeight
519 }
520 states: [
521 State {
522 name: "narrow"
523 when: overlay.width <= units.gu(60)
524 AnchorChanges { target: notifications; anchors.left: parent.left }
525 },
526 State {
527 name: "wide"
528 when: overlay.width > units.gu(60)
529 AnchorChanges { target: notifications; anchors.left: undefined }
530 PropertyChanges { target: notifications; width: units.gu(38) }
531 }
532 ]
533 }
534 }
536 focus: true
537 onFocusChanged: if (!focus) forceActiveFocus();
539 Binding {
540 target: i18n
541 property: "domain"
542 value: "unity8"
543 }
545 Label {
546 id: alphaDisclaimerLabel
547 anchors.centerIn: parent
548 visible: ApplicationManager.fake ? ApplicationManager.fake : false
549 z: overlay.z + 10
550 text: "EARLY ALPHA\nNOT READY FOR USE"
551 color: "lightgrey"
552 opacity: 0.2
553 font.weight: Font.Black
554 horizontalAlignment: Text.AlignHCenter
555 verticalAlignment: Text.AlignVCenter
556 fontSizeMode: Text.Fit
557 rotation: -45
558 scale: Math.min(parent.width, parent.height) / width
559 }
561 EdgeDemo {
562 id: edgeDemo
563 z: alphaDisclaimerLabel.z + 10
564 greeter: greeter
565 launcher: launcher
566 dash: dash
567 indicators: panel.indicators
568 underlay: underlay
569 }
571 Connections {
572 target: SessionBroadcast
573 onShowHome: showHome()
574 }