on 3a2d99f help sign in
unity8/qml/Shell.qml
676 lines 22 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 "Hud"
32import "Components"
33import "Bottombar"
34import "Notifications"
35import Unity.Notifications 1.0 as NotificationBackend
37FocusScope {
38 id: shell
40 // this is only here to select the width / height of the window if not running fullscreen
41 property bool tablet: false
42 width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
43 height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
45 property real edgeSize: units.gu(2)
46 property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
47 property url background
48 readonly property real panelHeight: panel.panelHeight
50 property bool dashShown: dash.shown
52 property bool sideStageEnabled: shell.width >= units.gu(100)
53 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
55 function activateApplication(appId) {
56 if (ApplicationManager.findApplication(appId)) {
57 ApplicationManager.requestFocusApplication(appId);
58 stages.show(true);
59 if (stages.locked && ApplicationManager.focusedApplicationId == appId) {
60 applicationsDisplayLoader.item.select(appId);
61 }
62 } else {
63 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
64 ApplicationManager.startApplication(appId, execFlags);
65 stages.show(false);
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: underlayClipper
97 anchors.fill: parent
98 anchors.rightMargin: stages.overlayWidth
99 clip: stages.overlayMode && !stages.painting
101 InputFilterArea {
102 anchors.fill: parent
103 blockInput: parent.clip
104 }
106 Item {
107 id: underlay
108 objectName: "underlay"
109 anchors.fill: parent
110 anchors.rightMargin: -parent.anchors.rightMargin
112 // Whether the underlay is fully covered by opaque UI elements.
113 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
115 // Whether the user should see the topmost application surface (if there's one at all).
116 readonly property bool applicationSurfaceShouldBeSeen: stages.shown && !stages.painting && !stages.overlayMode
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 && !applicationSurfaceShouldBeSeen
122 CrossFadeImage {
123 id: backgroundImage
124 objectName: "backgroundImage"
126 anchors.fill: parent
127 source: shell.background
128 fillMode: Image.PreserveAspectCrop
129 }
131 Rectangle {
132 anchors.fill: parent
133 color: "black"
134 opacity: dash.disappearingAnimationProgress
135 }
137 Image {
138 anchors.fill: dash
139 source: shell.width > shell.height ? "Dash/graphics/paper_landscape.png" : "Dash/graphics/paper_portrait.png"
140 fillMode: Image.PreserveAspectCrop
141 horizontalAlignment: Image.AlignRight
142 verticalAlignment: Image.AlignTop
143 }
145 Dash {
146 id: dash
147 objectName: "dash"
149 available: !greeter.shown && !lockscreen.shown
150 hides: [stages, launcher, panel.indicators]
151 shown: disappearingAnimationProgress !== 1.0
152 enabled: disappearingAnimationProgress === 0.0 && edgeDemo.dashEnabled
154 anchors {
155 fill: parent
156 topMargin: panel.panelHeight
157 }
159 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
160 opacity: 1.0 - disappearingAnimationProgress
161 property real disappearingAnimationProgress: {
162 if (greeter.shown) {
163 return greeter.showProgress;
164 } else {
165 if (stages.overlayMode) {
166 return 0;
167 }
168 return stages.showProgress
169 }
170 }
172 // FIXME: only necessary because stages.showProgress and
173 // greeterRevealer.animatedProgress are not animated
174 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
175 }
176 }
177 }
179 EdgeDragArea {
180 id: stagesDragHandle
181 direction: Direction.Leftwards
183 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
184 width: shell.edgeSize
186 property real progress: stages.width
188 onTouchXChanged: {
189 if (status == DirectionalDragArea.Recognized) {
190 if (ApplicationManager.count == 0) {
191 progress = Math.max(stages.width - stagesDragHandle.width + touchX, stages.width * .3)
192 } else {
193 progress = stages.width - stagesDragHandle.width + touchX
194 }
195 }
196 }
198 onDraggingChanged: {
199 if (!dragging) {
200 if (ApplicationManager.count > 0 && progress < stages.width - units.gu(10)) {
201 stages.show(true)
202 }
203 stagesDragHandle.progress = stages.width;
204 }
205 }
206 }
208 Item {
209 id: stages
210 objectName: "stages"
211 width: parent.width
212 height: parent.height
214 x: {
215 if (shown) {
216 if (overlayMode || locked) {
217 return 0;
218 }
219 return launcher.progress
220 } else {
221 return stagesDragHandle.progress
222 }
223 }
225 Behavior on x { SmoothedAnimation { velocity: 600; duration: UbuntuAnimation.FastDuration } }
227 property bool shown: false
229 property real showProgress: overlayMode ? 0 : MathUtils.clamp(1 - x / shell.width, 0, 1)
231 property bool fullyShown: x == 0
232 property bool fullyHidden: x == width
234 property bool painting: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.painting : false
235 property bool fullscreen: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.fullscreen : false
236 property bool overlayMode: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayMode : false
237 property int overlayWidth: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayWidth : false
238 property bool locked: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.locked : false
240 function show(focusApp) {
241 shown = true;
242 panel.indicators.hide();
243 edgeDemo.stopDemo();
244 greeter.hide();
245 if (!ApplicationManager.focusedApplicationId && ApplicationManager.count > 0 && focusApp) {
246 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
247 }
248 }
250 function hide() {
251 shown = false;
252 if (ApplicationManager.focusedApplicationId) {
253 ApplicationManager.unfocusCurrentApplication();
254 }
255 }
257 Connections {
258 target: ApplicationManager
260 onFocusRequested: {
261 stages.show(true);
262 }
264 onFocusedApplicationIdChanged: {
265 if (ApplicationManager.focusedApplicationId.length > 0) {
266 stages.show(false);
267 } else {
268 if (!stages.overlayMode) {
269 stages.hide();
270 }
271 }
272 }
274 onApplicationAdded: {
275 stages.show(false);
276 }
278 onApplicationRemoved: {
279 if (ApplicationManager.focusedApplicationId.length == 0) {
280 stages.hide();
281 }
282 }
283 }
285 Loader {
286 id: applicationsDisplayLoader
287 anchors.fill: parent
289 source: shell.sideStageEnabled ? "Stages/StageWithSideStage.qml" : "Stages/PhoneStage.qml"
291 Binding {
292 target: applicationsDisplayLoader.item
293 property: "moving"
294 value: !stages.fullyShown
295 }
296 Binding {
297 target: applicationsDisplayLoader.item
298 property: "shown"
299 value: stages.shown
300 }
301 Binding {
302 target: applicationsDisplayLoader.item
303 property: "dragAreaWidth"
304 value: shell.edgeSize
305 }
306 }
307 }
309 Lockscreen {
310 id: lockscreen
311 objectName: "lockscreen"
313 readonly property int backgroundTopMargin: -panel.panelHeight
315 hides: [launcher, panel.indicators, hud]
316 shown: false
317 enabled: true
318 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
319 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
320 y: panel.panelHeight
321 x: required ? 0 : - width
322 width: parent.width
323 height: parent.height - panel.panelHeight
324 background: shell.background
325 pinLength: 4
327 onEntered: LightDM.Greeter.respond(passphrase);
328 onCancel: greeter.show()
330 Component.onCompleted: {
331 if (LightDM.Users.count == 1) {
332 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
333 }
334 }
335 }
337 Connections {
338 target: LightDM.Greeter
340 onShowPrompt: {
341 if (LightDM.Users.count == 1) {
342 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
343 if (text == "PIN") {
344 lockscreen.alphaNumeric = false
345 } else {
346 lockscreen.alphaNumeric = true
347 }
348 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
349 lockscreen.show();
350 }
351 }
353 onAuthenticationComplete: {
354 if (LightDM.Greeter.promptless) {
355 return;
356 }
357 if (LightDM.Greeter.authenticated) {
358 lockscreen.hide();
359 } else {
360 lockscreen.clear(true);
361 }
362 }
363 }
365 Greeter {
366 id: greeter
367 objectName: "greeter"
369 available: true
370 hides: [launcher, panel.indicators, hud]
371 shown: true
373 defaultBackground: shell.background
375 y: panel.panelHeight
376 width: parent.width
377 height: parent.height - panel.panelHeight
379 dragHandleWidth: shell.edgeSize
381 onShownChanged: {
382 if (shown) {
383 lockscreen.reset();
384 // If there is only one user, we start authenticating with that one here.
385 // If there are more users, the Greeter will handle that
386 if (LightDM.Users.count == 1) {
387 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
388 }
389 greeter.forceActiveFocus();
390 }
391 }
393 onUnlocked: greeter.hide()
394 onSelected: {
395 // Update launcher items for new user
396 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
397 AccountsService.user = user;
398 LauncherModel.setUser(user);
399 }
401 onLeftTeaserPressedChanged: {
402 if (leftTeaserPressed) {
403 launcher.tease();
404 }
405 }
407 Binding {
408 target: ApplicationManager
409 property: "suspended"
410 value: greeter.shown && greeter.showProgress == 1
411 }
412 }
414 InputFilterArea {
415 anchors.fill: parent
416 blockInput: ApplicationManager.focusedApplicationId.length === 0 || greeter.shown || lockscreen.shown || launcher.shown
417 || panel.indicators.shown || hud.shown
418 }
420 Connections {
421 id: powerConnection
422 target: Powerd
424 onDisplayPowerStateChange: {
425 // We ignore any display-off signals when the proximity sensor
426 // is active. This usually indicates something like a phone call.
427 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
428 greeter.showNow();
429 }
431 // No reason to chew demo CPU when user isn't watching
432 if (status == Powerd.Off) {
433 edgeDemo.paused = true;
434 } else if (status == Powerd.On) {
435 edgeDemo.paused = false;
436 }
437 }
438 }
440 function showHome() {
441 var animate = !greeter.shown && !stages.shown
442 greeter.hide()
443 dash.setCurrentScope("clickscope", animate, false)
444 stages.hide()
445 }
447 function hideIndicatorMenu(delay) {
448 panel.hideIndicatorMenu(delay);
449 }
451 Item {
452 id: overlay
454 anchors.fill: parent
456 Panel {
457 id: panel
458 anchors.fill: parent //because this draws indicator menus
459 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
460 indicators {
461 hides: [launcher]
462 available: edgeDemo.panelEnabled
463 contentEnabled: edgeDemo.panelContentEnabled
464 }
465 property string focusedAppId: ApplicationManager.focusedApplicationId
466 property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
467 fullscreenMode: focusedApplication && stages.fullscreen && !greeter.shown && !lockscreen.shown
468 searchVisible: !greeter.shown && !lockscreen.shown && dash.shown && dash.searchable
470 InputFilterArea {
471 anchors {
472 top: parent.top
473 left: parent.left
474 right: parent.right
475 }
476 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
477 blockInput: true
478 }
479 }
481 Hud {
482 id: hud
484 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
485 height: parent.height
487 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown && edgeDemo.dashEnabled
488 shown: false
489 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
490 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
492 Connections {
493 target: ApplicationManager
494 onFocusedApplicationIdChanged: hud.hide()
495 }
496 }
498 Revealer {
499 id: hudRevealer
501 enabled: hud.shown
502 width: hud.width
503 anchors.left: hud.left
504 height: parent.height
505 target: hud.revealerTarget
506 closedValue: height
507 openedValue: 0
508 direction: Qt.RightToLeft
509 orientation: Qt.Vertical
510 handleSize: hud.handleHeight
511 onCloseClicked: target.hide()
512 }
514 Bottombar {
515 id: bottombar
516 theHud: hud
517 anchors.fill: parent
518 enabled: hud.available
519 applicationIsOnForeground: ApplicationManager.focusedApplicationId
520 }
522 InputFilterArea {
523 blockInput: launcher.shown
524 anchors {
525 top: parent.top
526 bottom: parent.bottom
527 left: parent.left
528 }
529 width: launcher.width
530 }
532 Launcher {
533 id: launcher
535 readonly property bool dashSwipe: progress > 0
537 anchors.top: parent.top
538 anchors.bottom: parent.bottom
539 width: parent.width
540 dragAreaWidth: shell.edgeSize
541 available: (!greeter.shown || greeter.narrowMode) && edgeDemo.launcherEnabled
543 onShowDashHome: {
544 if (edgeDemo.running)
545 return;
547 showHome()
548 }
549 onDash: {
550 if (stages.shown && !stages.overlayMode) {
551 if (!stages.locked) {
552 stages.hide();
553 launcher.hide();
554 }
555 }
556 }
557 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
558 onLauncherApplicationSelected: {
559 if (!edgeDemo.running)
560 shell.activateApplication(appId)
561 }
562 onShownChanged: {
563 if (shown) {
564 panel.indicators.hide()
565 hud.hide()
566 bottombar.hide()
567 }
568 }
569 }
571 Notifications {
572 id: notifications
574 model: NotificationBackend.Model
575 margin: units.gu(1)
577 anchors {
578 top: parent.top
579 right: parent.right
580 bottom: parent.bottom
581 topMargin: panel.panelHeight
582 }
583 states: [
584 State {
585 name: "narrow"
586 when: overlay.width <= units.gu(60)
587 AnchorChanges { target: notifications; anchors.left: parent.left }
588 },
589 State {
590 name: "wide"
591 when: overlay.width > units.gu(60)
592 AnchorChanges { target: notifications; anchors.left: undefined }
593 PropertyChanges { target: notifications; width: units.gu(38) }
594 }
595 ]
597 InputFilterArea {
598 anchors { left: parent.left; right: parent.right }
599 height: parent.contentHeight
600 blockInput: height > 0
601 }
602 }
603 }
605 focus: true
606 onFocusChanged: if (!focus) forceActiveFocus();
608 InputFilterArea {
609 anchors {
610 top: parent.top
611 bottom: parent.bottom
612 left: parent.left
613 }
614 width: shell.edgeSize
615 blockInput: true
616 }
618 InputFilterArea {
619 anchors {
620 top: parent.top
621 bottom: parent.bottom
622 right: parent.right
623 }
624 width: shell.edgeSize
625 blockInput: true
626 }
628 Binding {
629 target: i18n
630 property: "domain"
631 value: "unity8"
632 }
634 OSKController {
635 anchors.topMargin: panel.panelHeight
636 anchors.fill: parent // as needs to know the geometry of the shell
637 }
639 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
640 MouseArea {
641 anchors.bottom: parent.bottom
642 anchors.left: parent.left
643 anchors.right: parent.right
644 height: ApplicationManager.keyboardVisible ? ApplicationManager.keyboardHeight : 0
646 enabled: ApplicationManager.keyboardVisible
647 }
649 Label {
650 anchors.centerIn: parent
651 visible: ApplicationManager.fake ? ApplicationManager.fake : false
652 text: "EARLY ALPHA\nNOT READY FOR USE"
653 color: "lightgrey"
654 opacity: 0.2
655 font.weight: Font.Black
656 horizontalAlignment: Text.AlignHCenter
657 verticalAlignment: Text.AlignVCenter
658 fontSizeMode: Text.Fit
659 rotation: -45
660 scale: Math.min(parent.width, parent.height) / width
661 }
663 EdgeDemo {
664 id: edgeDemo
665 greeter: greeter
666 launcher: launcher
667 dash: dash
668 indicators: panel.indicators
669 underlay: underlay
670 }
672 Connections {
673 target: SessionBroadcast
674 onShowHome: showHome()
675 }