on b69bcf1 help sign in
unity8/qml/Shell.qml
657 lines 21 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 Unity.Notifications 1.0 as NotificationBackend
35FocusScope {
36 id: shell
38 // this is only here to select the width / height of the window if not running fullscreen
39 property bool tablet: false
40 width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
41 height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
43 property real edgeSize: units.gu(2)
44 property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
45 property url background
46 readonly property real panelHeight: panel.panelHeight
48 property bool dashShown: dash.shown
50 property bool sideStageEnabled: shell.width >= units.gu(100)
51 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
53 function activateApplication(appId) {
54 if (ApplicationManager.findApplication(appId)) {
55 ApplicationManager.requestFocusApplication(appId);
56 stages.show(true);
57 if (stages.locked && ApplicationManager.focusedApplicationId == appId) {
58 applicationsDisplayLoader.item.select(appId);
59 }
60 } else {
61 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
62 ApplicationManager.startApplication(appId, execFlags);
63 stages.show(false);
64 }
65 }
67 Binding {
68 target: LauncherModel
69 property: "applicationManager"
70 value: ApplicationManager
71 }
73 Component.onCompleted: {
74 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
75 }
77 GSettings {
78 id: backgroundSettings
79 schema.id: "org.gnome.desktop.background"
80 }
81 property url gSettingsPicture: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : shell.defaultBackground
82 onGSettingsPictureChanged: {
83 shell.background = gSettingsPicture
84 }
86 VolumeControl {
87 id: volumeControl
88 }
90 Keys.onVolumeUpPressed: volumeControl.volumeUp()
91 Keys.onVolumeDownPressed: volumeControl.volumeDown()
93 Item {
94 id: underlayClipper
95 anchors.fill: parent
96 anchors.rightMargin: stages.overlayWidth
97 clip: stages.overlayMode && !stages.painting
99 InputFilterArea {
100 anchors.fill: parent
101 blockInput: parent.clip
102 }
104 Item {
105 id: underlay
106 objectName: "underlay"
107 anchors.fill: parent
108 anchors.rightMargin: -parent.anchors.rightMargin
110 // Whether the underlay is fully covered by opaque UI elements.
111 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
113 // Whether the user should see the topmost application surface (if there's one at all).
114 readonly property bool applicationSurfaceShouldBeSeen: stages.shown && !stages.painting && !stages.overlayMode
116 // NB! Application surfaces are stacked behind the shell one. So they can only be seen by the user
117 // through the translucent parts of the shell surface.
118 visible: !fullyCovered && !applicationSurfaceShouldBeSeen
120 Rectangle {
121 anchors.fill: parent
122 color: "black"
123 opacity: dash.disappearingAnimationProgress
124 }
126 Image {
127 anchors.fill: dash
128 source: shell.width > shell.height ? "Dash/graphics/paper_landscape.png" : "Dash/graphics/paper_portrait.png"
129 fillMode: Image.PreserveAspectCrop
130 horizontalAlignment: Image.AlignRight
131 verticalAlignment: Image.AlignTop
132 }
134 Dash {
135 id: dash
136 objectName: "dash"
138 available: !greeter.shown && !lockscreen.shown
139 hides: [stages, launcher, panel.indicators]
140 shown: disappearingAnimationProgress !== 1.0 && greeterWrapper.showProgress !== 1.0
141 enabled: disappearingAnimationProgress === 0.0 && greeterWrapper.showProgress === 0.0 && edgeDemo.dashEnabled
143 anchors {
144 fill: parent
145 topMargin: panel.panelHeight
146 }
148 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
149 opacity: 1.0 - disappearingAnimationProgress
150 property real disappearingAnimationProgress: {
151 if (stages.overlayMode) {
152 return 0;
153 } else {
154 return stages.showProgress
155 }
156 }
158 // FIXME: only necessary because stages.showProgress is not animated
159 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
160 }
161 }
162 }
164 EdgeDragArea {
165 id: stagesDragHandle
166 direction: Direction.Leftwards
168 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
169 width: shell.edgeSize
171 property real progress: stages.width
173 onTouchXChanged: {
174 if (status == DirectionalDragArea.Recognized) {
175 if (ApplicationManager.count == 0) {
176 progress = Math.max(stages.width - stagesDragHandle.width + touchX, stages.width * .3)
177 } else {
178 progress = stages.width - stagesDragHandle.width + touchX
179 }
180 }
181 }
183 onDraggingChanged: {
184 if (!dragging) {
185 if (ApplicationManager.count > 0 && progress < stages.width - units.gu(10)) {
186 stages.show(true)
187 }
188 stagesDragHandle.progress = stages.width;
189 }
190 }
191 }
193 Item {
194 id: stages
195 objectName: "stages"
196 width: parent.width
197 height: parent.height
199 x: {
200 if (shown) {
201 if (overlayMode || locked) {
202 return 0;
203 }
204 return launcher.progress
205 } else {
206 return stagesDragHandle.progress
207 }
208 }
210 Behavior on x { SmoothedAnimation { velocity: 600; duration: UbuntuAnimation.FastDuration } }
212 property bool shown: false
214 property real showProgress: overlayMode ? 0 : MathUtils.clamp(1 - x / shell.width, 0, 1)
216 property bool fullyShown: x == 0
217 property bool fullyHidden: x == width
219 property bool painting: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.painting : false
220 property bool fullscreen: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.fullscreen : false
221 property bool overlayMode: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayMode : false
222 property int overlayWidth: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayWidth : false
223 property bool locked: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.locked : false
225 function show(focusApp) {
226 shown = true;
227 panel.indicators.hide();
228 edgeDemo.stopDemo();
229 greeter.hide();
230 if (!ApplicationManager.focusedApplicationId && ApplicationManager.count > 0 && focusApp) {
231 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
232 }
233 }
235 function hide() {
236 shown = false;
237 if (ApplicationManager.focusedApplicationId) {
238 ApplicationManager.unfocusCurrentApplication();
239 }
240 }
242 Connections {
243 target: ApplicationManager
245 onFocusRequested: {
246 stages.show(true);
247 }
249 onFocusedApplicationIdChanged: {
250 if (ApplicationManager.focusedApplicationId.length > 0) {
251 stages.show(false);
252 } else {
253 if (!stages.overlayMode) {
254 stages.hide();
255 }
256 }
257 }
259 onApplicationAdded: {
260 stages.show(false);
261 }
263 onApplicationRemoved: {
264 if (ApplicationManager.focusedApplicationId.length == 0) {
265 stages.hide();
266 }
267 }
268 }
270 Loader {
271 id: applicationsDisplayLoader
272 anchors.fill: parent
274 source: shell.sideStageEnabled ? "Stages/StageWithSideStage.qml" : "Stages/PhoneStage.qml"
276 Binding {
277 target: applicationsDisplayLoader.item
278 property: "moving"
279 value: !stages.fullyShown
280 }
281 Binding {
282 target: applicationsDisplayLoader.item
283 property: "shown"
284 value: stages.shown
285 }
286 Binding {
287 target: applicationsDisplayLoader.item
288 property: "dragAreaWidth"
289 value: shell.edgeSize
290 }
291 }
292 }
294 Lockscreen {
295 id: lockscreen
296 objectName: "lockscreen"
298 readonly property int backgroundTopMargin: -panel.panelHeight
300 hides: [launcher, panel.indicators]
301 shown: false
302 enabled: true
303 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
304 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
305 y: panel.panelHeight
306 x: required ? 0 : - width
307 width: parent.width
308 height: parent.height - panel.panelHeight
309 background: shell.background
310 minPinLength: 4
311 maxPinLength: 4
313 onEntered: LightDM.Greeter.respond(passphrase);
314 onCancel: greeter.show()
316 Component.onCompleted: {
317 if (LightDM.Users.count == 1) {
318 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
319 }
320 }
321 }
323 Connections {
324 target: LightDM.Greeter
326 onShowPrompt: {
327 if (LightDM.Users.count == 1) {
328 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
329 if (text == "PIN") {
330 lockscreen.alphaNumeric = false
331 } else {
332 lockscreen.alphaNumeric = true
333 }
334 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
335 lockscreen.show();
336 }
337 }
339 onAuthenticationComplete: {
340 if (LightDM.Greeter.promptless) {
341 return;
342 }
343 if (LightDM.Greeter.authenticated) {
344 lockscreen.hide();
345 } else {
346 lockscreen.clear(true);
347 }
348 }
349 }
351 Rectangle {
352 anchors.fill: parent
353 color: "black"
354 opacity: greeterWrapper.showProgress * 0.8
355 }
357 Item {
358 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
359 id: greeterWrapper
360 x: launcher.progress
361 y: panel.panelHeight
362 width: parent.width
363 height: parent.height - panel.panelHeight
365 Behavior on x {
366 enabled: !launcher.dashSwipe
367 StandardAnimation {}
368 }
370 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
372 Greeter {
373 id: greeter
374 objectName: "greeter"
376 available: true
377 hides: [launcher, panel.indicators]
378 shown: true
380 defaultBackground: shell.background
382 width: parent.width
383 height: parent.height
385 dragHandleWidth: shell.edgeSize
387 onShownChanged: {
388 if (shown) {
389 lockscreen.reset();
390 // If there is only one user, we start authenticating with that one here.
391 // If there are more users, the Greeter will handle that
392 if (LightDM.Users.count == 1) {
393 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
394 }
395 greeter.forceActiveFocus();
396 }
397 }
399 onUnlocked: greeter.hide()
400 onSelected: {
401 // Update launcher items for new user
402 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
403 AccountsService.user = user;
404 LauncherModel.setUser(user);
405 }
407 onTease: launcher.tease()
409 Binding {
410 target: ApplicationManager
411 property: "suspended"
412 value: greeter.shown && greeterWrapper.showProgress == 1
413 }
414 }
415 }
417 InputFilterArea {
418 anchors.fill: parent
419 blockInput: ApplicationManager.focusedApplicationId.length === 0 || greeter.shown || lockscreen.shown || launcher.shown
420 || panel.indicators.shown
421 }
423 Connections {
424 id: powerConnection
425 target: Powerd
427 onDisplayPowerStateChange: {
428 // We ignore any display-off signals when the proximity sensor
429 // is active. This usually indicates something like a phone call.
430 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
431 greeter.showNow();
432 }
434 // No reason to chew demo CPU when user isn't watching
435 if (status == Powerd.Off) {
436 edgeDemo.paused = true;
437 } else if (status == Powerd.On) {
438 edgeDemo.paused = false;
439 }
440 }
441 }
443 function showHome() {
444 var animate = !greeter.shown && !stages.shown
445 greeter.hide()
446 dash.setCurrentScope("clickscope", animate, false)
447 stages.hide()
448 }
450 function hideIndicatorMenu(delay) {
451 panel.hideIndicatorMenu(delay);
452 }
454 Item {
455 id: overlay
457 anchors.fill: parent
459 Panel {
460 id: panel
461 anchors.fill: parent //because this draws indicator menus
462 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
463 indicators {
464 hides: [launcher]
465 available: edgeDemo.panelEnabled
466 contentEnabled: edgeDemo.panelContentEnabled
467 }
468 property string focusedAppId: ApplicationManager.focusedApplicationId
469 property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
470 fullscreenMode: focusedApplication && stages.fullscreen && !greeter.shown && !lockscreen.shown
472 InputFilterArea {
473 anchors {
474 top: parent.top
475 left: parent.left
476 right: parent.right
477 }
478 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
479 blockInput: true
480 }
481 }
483 InputFilterArea {
484 blockInput: launcher.shown
485 anchors {
486 top: parent.top
487 bottom: parent.bottom
488 left: parent.left
489 }
490 width: launcher.width
491 }
493 Launcher {
494 id: launcher
496 readonly property bool dashSwipe: progress > 0
498 anchors.top: parent.top
499 anchors.bottom: parent.bottom
500 width: parent.width
501 dragAreaWidth: shell.edgeSize
502 available: edgeDemo.launcherEnabled
504 onShowDashHome: {
505 if (edgeDemo.running)
506 return;
508 showHome()
509 }
510 onDash: {
511 if (stages.shown && !stages.overlayMode) {
512 if (!stages.locked) {
513 stages.hide();
514 launcher.hide();
515 }
516 }
517 if (greeter.shown) {
518 greeter.hideRight();
519 launcher.hide();
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 }
546 InputFilterArea {
547 anchors.fill: parent
548 blockInput: modalNotificationBackground.visible
549 }
550 }
552 Notifications {
553 id: notifications
555 model: NotificationBackend.Model
556 margin: units.gu(1)
558 anchors {
559 top: parent.top
560 right: parent.right
561 bottom: parent.bottom
562 topMargin: panel.panelHeight
563 }
564 states: [
565 State {
566 name: "narrow"
567 when: overlay.width <= units.gu(60)
568 AnchorChanges { target: notifications; anchors.left: parent.left }
569 },
570 State {
571 name: "wide"
572 when: overlay.width > units.gu(60)
573 AnchorChanges { target: notifications; anchors.left: undefined }
574 PropertyChanges { target: notifications; width: units.gu(38) }
575 }
576 ]
578 InputFilterArea {
579 anchors { left: parent.left; right: parent.right }
580 height: parent.contentHeight
581 blockInput: height > 0
582 }
583 }
584 }
586 focus: true
587 onFocusChanged: if (!focus) forceActiveFocus();
589 InputFilterArea {
590 anchors {
591 top: parent.top
592 bottom: parent.bottom
593 left: parent.left
594 }
595 width: shell.edgeSize
596 blockInput: true
597 }
599 InputFilterArea {
600 anchors {
601 top: parent.top
602 bottom: parent.bottom
603 right: parent.right
604 }
605 width: shell.edgeSize
606 blockInput: true
607 }
609 Binding {
610 target: i18n
611 property: "domain"
612 value: "unity8"
613 }
615 OSKController {
616 anchors.topMargin: panel.panelHeight
617 anchors.fill: parent // as needs to know the geometry of the shell
618 }
620 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
621 MouseArea {
622 anchors.bottom: parent.bottom
623 anchors.left: parent.left
624 anchors.right: parent.right
625 height: ApplicationManager.keyboardVisible ? ApplicationManager.keyboardHeight : 0
627 enabled: ApplicationManager.keyboardVisible
628 }
630 Label {
631 anchors.centerIn: parent
632 visible: ApplicationManager.fake ? ApplicationManager.fake : false
633 text: "EARLY ALPHA\nNOT READY FOR USE"
634 color: "lightgrey"
635 opacity: 0.2
636 font.weight: Font.Black
637 horizontalAlignment: Text.AlignHCenter
638 verticalAlignment: Text.AlignVCenter
639 fontSizeMode: Text.Fit
640 rotation: -45
641 scale: Math.min(parent.width, parent.height) / width
642 }
644 EdgeDemo {
645 id: edgeDemo
646 greeter: greeter
647 launcher: launcher
648 dash: dash
649 indicators: panel.indicators
650 underlay: underlay
651 }
653 Connections {
654 target: SessionBroadcast
655 onShowHome: showHome()
656 }