on 7c3ece7 help sign in
unity8/qml/Shell.qml
698 lines 23 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 && greeterWrapper.showProgress !== 1.0
152 enabled: disappearingAnimationProgress === 0.0 && greeterWrapper.showProgress === 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 (stages.overlayMode) {
163 return 0;
164 } else {
165 return stages.showProgress
166 }
167 }
169 // FIXME: only necessary because stages.showProgress is not animated
170 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
171 }
172 }
173 }
175 EdgeDragArea {
176 id: stagesDragHandle
177 direction: Direction.Leftwards
179 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
180 width: shell.edgeSize
182 property real progress: stages.width
184 onTouchXChanged: {
185 if (status == DirectionalDragArea.Recognized) {
186 if (ApplicationManager.count == 0) {
187 progress = Math.max(stages.width - stagesDragHandle.width + touchX, stages.width * .3)
188 } else {
189 progress = stages.width - stagesDragHandle.width + touchX
190 }
191 }
192 }
194 onDraggingChanged: {
195 if (!dragging) {
196 if (ApplicationManager.count > 0 && progress < stages.width - units.gu(10)) {
197 stages.show(true)
198 }
199 stagesDragHandle.progress = stages.width;
200 }
201 }
202 }
204 Item {
205 id: stages
206 objectName: "stages"
207 width: parent.width
208 height: parent.height
210 x: {
211 if (shown) {
212 if (overlayMode || locked) {
213 return 0;
214 }
215 return launcher.progress
216 } else {
217 return stagesDragHandle.progress
218 }
219 }
221 Behavior on x { SmoothedAnimation { velocity: 600; duration: UbuntuAnimation.FastDuration } }
223 property bool shown: false
225 property real showProgress: overlayMode ? 0 : MathUtils.clamp(1 - x / shell.width, 0, 1)
227 property bool fullyShown: x == 0
228 property bool fullyHidden: x == width
230 property bool painting: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.painting : false
231 property bool fullscreen: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.fullscreen : false
232 property bool overlayMode: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayMode : false
233 property int overlayWidth: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayWidth : false
234 property bool locked: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.locked : false
236 function show(focusApp) {
237 shown = true;
238 panel.indicators.hide();
239 edgeDemo.stopDemo();
240 greeter.hide();
241 if (!ApplicationManager.focusedApplicationId && ApplicationManager.count > 0 && focusApp) {
242 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
243 }
244 }
246 function hide() {
247 shown = false;
248 if (ApplicationManager.focusedApplicationId) {
249 ApplicationManager.unfocusCurrentApplication();
250 }
251 }
253 Connections {
254 target: ApplicationManager
256 onFocusRequested: {
257 stages.show(true);
258 }
260 onFocusedApplicationIdChanged: {
261 if (ApplicationManager.focusedApplicationId.length > 0) {
262 stages.show(false);
263 } else {
264 if (!stages.overlayMode) {
265 stages.hide();
266 }
267 }
268 }
270 onApplicationAdded: {
271 stages.show(false);
272 }
274 onApplicationRemoved: {
275 if (ApplicationManager.focusedApplicationId.length == 0) {
276 stages.hide();
277 }
278 }
279 }
281 Loader {
282 id: applicationsDisplayLoader
283 anchors.fill: parent
285 source: shell.sideStageEnabled ? "Stages/StageWithSideStage.qml" : "Stages/PhoneStage.qml"
287 Binding {
288 target: applicationsDisplayLoader.item
289 property: "moving"
290 value: !stages.fullyShown
291 }
292 Binding {
293 target: applicationsDisplayLoader.item
294 property: "shown"
295 value: stages.shown
296 }
297 Binding {
298 target: applicationsDisplayLoader.item
299 property: "dragAreaWidth"
300 value: shell.edgeSize
301 }
302 }
303 }
305 Lockscreen {
306 id: lockscreen
307 objectName: "lockscreen"
309 readonly property int backgroundTopMargin: -panel.panelHeight
311 hides: [launcher, panel.indicators, hud]
312 shown: false
313 enabled: true
314 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
315 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
317 y: panel.panelHeight
318 x: required ? 0 : - width
319 width: parent.width
320 height: parent.height - panel.panelHeight
321 Behavior on y { StandardAnimation {} }
323 background: shell.background
324 pinLength: 4
326 onEntered: LightDM.Greeter.respond(passphrase);
327 onCancel: greeter.show()
329 Component.onCompleted: {
330 if (LightDM.Users.count == 1) {
331 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
332 }
333 }
334 }
336 Connections {
337 target: LightDM.Greeter
339 onShowPrompt: {
340 if (LightDM.Users.count == 1) {
341 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
342 if (text == "PIN") {
343 lockscreen.alphaNumeric = false
344 } else {
345 lockscreen.alphaNumeric = true
346 }
347 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
348 lockscreen.show();
349 }
350 }
352 onAuthenticationComplete: {
353 if (LightDM.Greeter.promptless) {
354 return;
355 }
356 if (LightDM.Greeter.authenticated) {
357 lockscreen.hide();
358 } else {
359 lockscreen.clear(true);
360 }
361 }
362 }
364 Rectangle {
365 anchors.fill: parent
366 color: "black"
367 opacity: greeterWrapper.showProgress * 0.8
368 }
370 Item {
371 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
372 id: greeterWrapper
373 x: launcher.progress
374 y: panel.panelHeight
375 width: parent.width
376 height: parent.height - panel.panelHeight
377 Behavior on y { StandardAnimation {} }
378 Behavior on height { StandardAnimation {} }
380 Behavior on x {SmoothedAnimation{velocity: 600}}
382 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
384 Greeter {
385 id: greeter
386 objectName: "greeter"
388 available: true
389 hides: [launcher, panel.indicators, hud]
390 shown: true
392 defaultBackground: shell.background
394 width: parent.width
395 height: parent.height
397 dragHandleWidth: shell.edgeSize
399 onShownChanged: {
400 if (shown) {
401 lockscreen.reset();
402 // If there is only one user, we start authenticating with that one here.
403 // If there are more users, the Greeter will handle that
404 if (LightDM.Users.count == 1) {
405 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
406 }
407 greeter.forceActiveFocus();
408 }
409 }
411 onUnlocked: greeter.hide()
412 onSelected: {
413 // Update launcher items for new user
414 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
415 AccountsService.user = user;
416 LauncherModel.setUser(user);
417 }
419 onTease: launcher.tease()
421 Binding {
422 target: ApplicationManager
423 property: "suspended"
424 value: greeter.shown && greeterWrapper.showProgress == 1
425 }
426 }
427 }
429 InputFilterArea {
430 anchors.fill: parent
431 blockInput: ApplicationManager.focusedApplicationId.length === 0 || greeter.shown || lockscreen.shown || launcher.shown
432 || panel.indicators.shown || hud.shown
433 }
435 Connections {
436 id: powerConnection
437 target: Powerd
439 onDisplayPowerStateChange: {
440 // We ignore any display-off signals when the proximity sensor
441 // is active. This usually indicates something like a phone call.
442 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
443 greeter.showNow();
444 }
446 // No reason to chew demo CPU when user isn't watching
447 if (status == Powerd.Off) {
448 edgeDemo.paused = true;
449 } else if (status == Powerd.On) {
450 edgeDemo.paused = false;
451 }
452 }
453 }
455 function showHome() {
456 var animate = !greeter.shown && !stages.shown
457 greeter.hide()
458 dash.setCurrentScope("clickscope", animate, false)
459 stages.hide()
460 }
462 function hideIndicatorMenu(delay) {
463 panel.hideIndicatorMenu(delay);
464 }
466 Item {
467 id: overlay
469 anchors.fill: parent
471 Panel {
472 id: panel
473 anchors.fill: parent //because this draws indicator menus
474 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
475 indicators {
476 hides: [launcher]
477 available: edgeDemo.panelEnabled
478 contentEnabled: edgeDemo.panelContentEnabled
479 }
480 property string focusedAppId: ApplicationManager.focusedApplicationId
481 property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
482 fullscreenMode: focusedApplication && stages.fullscreen && !greeter.shown && !lockscreen.shown
483 searchVisible: !greeter.shown && !lockscreen.shown && dash.shown && dash.searchable
485 InputFilterArea {
486 anchors {
487 top: parent.top
488 left: parent.left
489 right: parent.right
490 }
491 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
492 blockInput: true
493 }
494 }
496 Hud {
497 id: hud
499 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
500 height: parent.height
502 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown && edgeDemo.dashEnabled
503 shown: false
504 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
505 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
507 Connections {
508 target: ApplicationManager
509 onFocusedApplicationIdChanged: hud.hide()
510 }
511 }
513 Revealer {
514 id: hudRevealer
516 enabled: hud.shown
517 width: hud.width
518 anchors.left: hud.left
519 height: parent.height
520 target: hud.revealerTarget
521 closedValue: height
522 openedValue: 0
523 direction: Qt.RightToLeft
524 orientation: Qt.Vertical
525 handleSize: hud.handleHeight
526 onCloseClicked: target.hide()
527 }
529 Bottombar {
530 id: bottombar
531 theHud: hud
532 anchors.fill: parent
533 enabled: hud.available
534 applicationIsOnForeground: ApplicationManager.focusedApplicationId
535 }
537 InputFilterArea {
538 blockInput: launcher.shown
539 anchors {
540 top: parent.top
541 bottom: parent.bottom
542 left: parent.left
543 }
544 width: launcher.width
545 }
547 Launcher {
548 id: launcher
550 readonly property bool dashSwipe: progress > 0
552 anchors.top: parent.top
553 anchors.bottom: parent.bottom
554 width: parent.width
555 dragAreaWidth: shell.edgeSize
556 available: edgeDemo.launcherEnabled
558 onShowDashHome: {
559 if (edgeDemo.running)
560 return;
562 showHome()
563 }
564 onDash: {
565 if (stages.shown && !stages.overlayMode) {
566 if (!stages.locked) {
567 stages.hide();
568 launcher.hide();
569 }
570 }
571 if (greeter.shown) {
572 greeter.hideRight();
573 launcher.hide();
574 }
575 }
576 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
577 onLauncherApplicationSelected: {
578 if (!edgeDemo.running)
579 shell.activateApplication(appId)
580 }
581 onShownChanged: {
582 if (shown) {
583 panel.indicators.hide()
584 hud.hide()
585 bottombar.hide()
586 }
587 }
588 }
590 Notifications {
591 id: notifications
593 model: NotificationBackend.Model
594 margin: units.gu(1)
596 y: panel.panelHeight
597 width: parent.width
598 height: parent.height - panel.panelHeight
599 Behavior on y { StandardAnimation {} }
600 Behavior on height { StandardAnimation {} }
602 states: [
603 State {
604 name: "narrow"
605 when: overlay.width <= units.gu(60)
606 AnchorChanges { target: notifications; anchors.left: parent.left }
607 },
608 State {
609 name: "wide"
610 when: overlay.width > units.gu(60)
611 AnchorChanges { target: notifications; anchors.left: undefined }
612 PropertyChanges { target: notifications; width: units.gu(38) }
613 }
614 ]
616 InputFilterArea {
617 anchors { left: parent.left; right: parent.right }
618 height: parent.contentHeight
619 blockInput: height > 0
620 }
621 }
622 }
624 focus: true
625 onFocusChanged: if (!focus) forceActiveFocus();
627 InputFilterArea {
628 anchors {
629 top: parent.top
630 bottom: parent.bottom
631 left: parent.left
632 }
633 width: shell.edgeSize
634 blockInput: true
635 }
637 InputFilterArea {
638 anchors {
639 top: parent.top
640 bottom: parent.bottom
641 right: parent.right
642 }
643 width: shell.edgeSize
644 blockInput: true
645 }
647 Binding {
648 target: i18n
649 property: "domain"
650 value: "unity8"
651 }
653 OSKController {
654 y: panel.panelHeight
655 width: parent.width
656 height: parent.height - panel.panelHeight
657 Behavior on y { StandardAnimation {} }
658 Behavior on height { StandardAnimation {} }
659 }
661 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
662 MouseArea {
663 anchors.bottom: parent.bottom
664 anchors.left: parent.left
665 anchors.right: parent.right
666 height: ApplicationManager.keyboardVisible ? ApplicationManager.keyboardHeight : 0
668 enabled: ApplicationManager.keyboardVisible
669 }
671 Label {
672 anchors.centerIn: parent
673 visible: ApplicationManager.fake ? ApplicationManager.fake : false
674 text: "EARLY ALPHA\nNOT READY FOR USE"
675 color: "lightgrey"
676 opacity: 0.2
677 font.weight: Font.Black
678 horizontalAlignment: Text.AlignHCenter
679 verticalAlignment: Text.AlignVCenter
680 fontSizeMode: Text.Fit
681 rotation: -45
682 scale: Math.min(parent.width, parent.height) / width
683 }
685 EdgeDemo {
686 id: edgeDemo
687 greeter: greeter
688 launcher: launcher
689 dash: dash
690 indicators: panel.indicators
691 underlay: underlay
692 }
694 Connections {
695 target: SessionBroadcast
696 onShowHome: showHome()
697 }