on c71e684 help sign in
unity8/qml/Shell.qml
684 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 y: panel.panelHeight
155 width: parent.width
156 height: parent.height - panel.panelHeight
157 Behavior on y { StandardAnimation {} }
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 }
321 y: panel.panelHeight
322 x: required ? 0 : - width
323 width: parent.width
324 height: parent.height - panel.panelHeight
325 Behavior on y { StandardAnimation {} }
327 background: shell.background
328 pinLength: 4
330 onEntered: LightDM.Greeter.respond(passphrase);
331 onCancel: greeter.show()
333 Component.onCompleted: {
334 if (LightDM.Users.count == 1) {
335 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
336 }
337 }
338 }
340 Connections {
341 target: LightDM.Greeter
343 onShowPrompt: {
344 if (LightDM.Users.count == 1) {
345 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
346 if (text == "PIN") {
347 lockscreen.alphaNumeric = false
348 } else {
349 lockscreen.alphaNumeric = true
350 }
351 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
352 lockscreen.show();
353 }
354 }
356 onAuthenticationComplete: {
357 if (LightDM.Greeter.promptless) {
358 return;
359 }
360 if (LightDM.Greeter.authenticated) {
361 lockscreen.hide();
362 } else {
363 lockscreen.clear(true);
364 }
365 }
366 }
368 Greeter {
369 id: greeter
370 objectName: "greeter"
372 available: true
373 hides: [launcher, panel.indicators, hud]
374 shown: true
376 defaultBackground: shell.background
378 y: panel.panelHeight
379 width: parent.width
380 height: parent.height - panel.panelHeight
381 Behavior on y { StandardAnimation {} }
382 Behavior on height { StandardAnimation {} }
384 dragHandleWidth: shell.edgeSize
386 onShownChanged: {
387 if (shown) {
388 lockscreen.reset();
389 // If there is only one user, we start authenticating with that one here.
390 // If there are more users, the Greeter will handle that
391 if (LightDM.Users.count == 1) {
392 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
393 }
394 greeter.forceActiveFocus();
395 }
396 }
398 onUnlocked: greeter.hide()
399 onSelected: {
400 // Update launcher items for new user
401 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
402 AccountsService.user = user;
403 LauncherModel.setUser(user);
404 }
406 onLeftTeaserPressedChanged: {
407 if (leftTeaserPressed) {
408 launcher.tease();
409 }
410 }
412 Binding {
413 target: ApplicationManager
414 property: "suspended"
415 value: greeter.shown && greeter.showProgress == 1
416 }
417 }
419 InputFilterArea {
420 anchors.fill: parent
421 blockInput: ApplicationManager.focusedApplicationId.length === 0 || greeter.shown || lockscreen.shown || launcher.shown
422 || panel.indicators.shown || hud.shown
423 }
425 Connections {
426 id: powerConnection
427 target: Powerd
429 onDisplayPowerStateChange: {
430 // We ignore any display-off signals when the proximity sensor
431 // is active. This usually indicates something like a phone call.
432 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
433 greeter.showNow();
434 }
436 // No reason to chew demo CPU when user isn't watching
437 if (status == Powerd.Off) {
438 edgeDemo.paused = true;
439 } else if (status == Powerd.On) {
440 edgeDemo.paused = false;
441 }
442 }
443 }
445 function showHome() {
446 var animate = !greeter.shown && !stages.shown
447 greeter.hide()
448 dash.setCurrentScope("clickscope", animate, false)
449 stages.hide()
450 }
452 function hideIndicatorMenu(delay) {
453 panel.hideIndicatorMenu(delay);
454 }
456 Item {
457 id: overlay
459 anchors.fill: parent
461 Panel {
462 id: panel
463 anchors.fill: parent //because this draws indicator menus
464 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
465 indicators {
466 hides: [launcher]
467 available: edgeDemo.panelEnabled
468 contentEnabled: edgeDemo.panelContentEnabled
469 }
470 property string focusedAppId: ApplicationManager.focusedApplicationId
471 property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
472 fullscreenMode: focusedApplication && stages.fullscreen && !greeter.shown && !lockscreen.shown
473 searchVisible: !greeter.shown && !lockscreen.shown && dash.shown && dash.searchable
475 InputFilterArea {
476 anchors {
477 top: parent.top
478 left: parent.left
479 right: parent.right
480 }
481 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
482 blockInput: true
483 }
484 }
486 Hud {
487 id: hud
489 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
490 height: parent.height
492 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown && edgeDemo.dashEnabled
493 shown: false
494 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
495 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
497 Connections {
498 target: ApplicationManager
499 onFocusedApplicationIdChanged: hud.hide()
500 }
501 }
503 Revealer {
504 id: hudRevealer
506 enabled: hud.shown
507 width: hud.width
508 anchors.left: hud.left
509 height: parent.height
510 target: hud.revealerTarget
511 closedValue: height
512 openedValue: 0
513 direction: Qt.RightToLeft
514 orientation: Qt.Vertical
515 handleSize: hud.handleHeight
516 onCloseClicked: target.hide()
517 }
519 Bottombar {
520 id: bottombar
521 theHud: hud
522 anchors.fill: parent
523 enabled: hud.available
524 applicationIsOnForeground: ApplicationManager.focusedApplicationId
525 }
527 InputFilterArea {
528 blockInput: launcher.shown
529 anchors {
530 top: parent.top
531 bottom: parent.bottom
532 left: parent.left
533 }
534 width: launcher.width
535 }
537 Launcher {
538 id: launcher
540 readonly property bool dashSwipe: progress > 0
542 anchors.top: parent.top
543 anchors.bottom: parent.bottom
544 width: parent.width
545 dragAreaWidth: shell.edgeSize
546 available: (!greeter.shown || greeter.narrowMode) && edgeDemo.launcherEnabled
548 onShowDashHome: {
549 if (edgeDemo.running)
550 return;
552 showHome()
553 }
554 onDash: {
555 if (stages.shown && !stages.overlayMode) {
556 if (!stages.locked) {
557 stages.hide();
558 launcher.hide();
559 }
560 }
561 }
562 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
563 onLauncherApplicationSelected: {
564 if (!edgeDemo.running)
565 shell.activateApplication(appId)
566 }
567 onShownChanged: {
568 if (shown) {
569 panel.indicators.hide()
570 hud.hide()
571 bottombar.hide()
572 }
573 }
574 }
576 Notifications {
577 id: notifications
579 model: NotificationBackend.Model
580 margin: units.gu(1)
582 y: panel.panelHeight
583 width: parent.width
584 height: parent.height - panel.panelHeight
585 Behavior on y { StandardAnimation {} }
586 Behavior on height { StandardAnimation {} }
588 states: [
589 State {
590 name: "narrow"
591 when: overlay.width <= units.gu(60)
592 AnchorChanges { target: notifications; anchors.left: parent.left }
593 },
594 State {
595 name: "wide"
596 when: overlay.width > units.gu(60)
597 AnchorChanges { target: notifications; anchors.left: undefined }
598 PropertyChanges { target: notifications; width: units.gu(38) }
599 }
600 ]
602 InputFilterArea {
603 anchors { left: parent.left; right: parent.right }
604 height: parent.contentHeight
605 blockInput: height > 0
606 }
607 }
608 }
610 focus: true
611 onFocusChanged: if (!focus) forceActiveFocus();
613 InputFilterArea {
614 anchors {
615 top: parent.top
616 bottom: parent.bottom
617 left: parent.left
618 }
619 width: shell.edgeSize
620 blockInput: true
621 }
623 InputFilterArea {
624 anchors {
625 top: parent.top
626 bottom: parent.bottom
627 right: parent.right
628 }
629 width: shell.edgeSize
630 blockInput: true
631 }
633 Binding {
634 target: i18n
635 property: "domain"
636 value: "unity8"
637 }
639 OSKController {
640 y: panel.panelHeight
641 width: parent.width
642 height: parent.height - panel.panelHeight
643 Behavior on y { StandardAnimation {} }
644 Behavior on height { StandardAnimation {} }
645 }
647 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
648 MouseArea {
649 anchors.bottom: parent.bottom
650 anchors.left: parent.left
651 anchors.right: parent.right
652 height: ApplicationManager.keyboardVisible ? ApplicationManager.keyboardHeight : 0
654 enabled: ApplicationManager.keyboardVisible
655 }
657 Label {
658 anchors.centerIn: parent
659 visible: ApplicationManager.fake ? ApplicationManager.fake : false
660 text: "EARLY ALPHA\nNOT READY FOR USE"
661 color: "lightgrey"
662 opacity: 0.2
663 font.weight: Font.Black
664 horizontalAlignment: Text.AlignHCenter
665 verticalAlignment: Text.AlignVCenter
666 fontSizeMode: Text.Fit
667 rotation: -45
668 scale: Math.min(parent.width, parent.height) / width
669 }
671 EdgeDemo {
672 id: edgeDemo
673 greeter: greeter
674 launcher: launcher
675 dash: dash
676 indicators: panel.indicators
677 underlay: underlay
678 }
680 Connections {
681 target: SessionBroadcast
682 onShowHome: showHome()
683 }