on b2c7059 help sign in
unity8/qml/Shell.qml
692 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.indicators.width
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 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 (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 anchors {
318 top: parent.top
319 right: parent.right
320 bottom: parent.bottom
321 topMargin: panel.panelHeight
322 }
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
378 Behavior on x {SmoothedAnimation{velocity: 600}}
380 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
382 Greeter {
383 id: greeter
384 objectName: "greeter"
386 available: true
387 hides: [launcher, panel.indicators, hud]
388 shown: true
390 defaultBackground: shell.background
392 width: parent.width
393 height: parent.height
395 dragHandleWidth: shell.edgeSize
397 onShownChanged: {
398 if (shown) {
399 lockscreen.reset();
400 // If there is only one user, we start authenticating with that one here.
401 // If there are more users, the Greeter will handle that
402 if (LightDM.Users.count == 1) {
403 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
404 }
405 greeter.forceActiveFocus();
406 }
407 }
409 onUnlocked: greeter.hide()
410 onSelected: {
411 // Update launcher items for new user
412 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
413 AccountsService.user = user;
414 LauncherModel.setUser(user);
415 }
417 onTease: launcher.tease()
419 Binding {
420 target: ApplicationManager
421 property: "suspended"
422 value: greeter.shown && greeterWrapper.showProgress == 1
423 }
424 }
425 }
427 InputFilterArea {
428 anchors.fill: parent
429 blockInput: ApplicationManager.focusedApplicationId.length === 0 || greeter.shown || lockscreen.shown || launcher.shown
430 || panel.indicators.shown || hud.shown
431 }
433 Connections {
434 id: powerConnection
435 target: Powerd
437 onDisplayPowerStateChange: {
438 // We ignore any display-off signals when the proximity sensor
439 // is active. This usually indicates something like a phone call.
440 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
441 greeter.showNow();
442 }
444 // No reason to chew demo CPU when user isn't watching
445 if (status == Powerd.Off) {
446 edgeDemo.paused = true;
447 } else if (status == Powerd.On) {
448 edgeDemo.paused = false;
449 }
450 }
451 }
453 function showHome() {
454 var animate = !greeter.shown && !stages.shown
455 greeter.hide()
456 dash.setCurrentScope("clickscope", animate, false)
457 stages.hide()
458 }
460 function hideIndicatorMenu(delay) {
461 panel.hideIndicatorMenu(delay);
462 }
464 Item {
465 id: overlay
467 anchors.fill: parent
469 Panel {
470 id: panel
471 anchors.fill: parent //because this draws indicator menus
472 indicators {
473 hides: [launcher]
474 available: edgeDemo.panelEnabled
475 contentEnabled: edgeDemo.panelContentEnabled
476 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
477 panelHeight: units.gu(3)
478 }
479 property string focusedAppId: ApplicationManager.focusedApplicationId
480 property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
481 fullscreenMode: focusedApplication && stages.fullscreen && !greeter.shown && !lockscreen.shown
482 searchVisible: !greeter.shown && !lockscreen.shown && dash.shown && dash.searchable
484 InputFilterArea {
485 anchors {
486 top: parent.top
487 left: parent.left
488 right: parent.right
489 }
490 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
491 blockInput: true
492 }
493 }
495 Hud {
496 id: hud
498 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
499 height: parent.height
501 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown && edgeDemo.dashEnabled
502 shown: false
503 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
504 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
506 Connections {
507 target: ApplicationManager
508 onFocusedApplicationIdChanged: hud.hide()
509 }
510 }
512 Revealer {
513 id: hudRevealer
515 enabled: hud.shown
516 width: hud.width
517 anchors.left: hud.left
518 height: parent.height
519 target: hud.revealerTarget
520 closedValue: height
521 openedValue: 0
522 direction: Qt.RightToLeft
523 orientation: Qt.Vertical
524 handleSize: hud.handleHeight
525 onCloseClicked: target.hide()
526 }
528 Bottombar {
529 id: bottombar
530 theHud: hud
531 anchors.fill: parent
532 enabled: hud.available
533 applicationIsOnForeground: ApplicationManager.focusedApplicationId
534 }
536 InputFilterArea {
537 blockInput: launcher.shown
538 anchors {
539 top: parent.top
540 bottom: parent.bottom
541 left: parent.left
542 }
543 width: launcher.width
544 }
546 Launcher {
547 id: launcher
549 readonly property bool dashSwipe: progress > 0
551 anchors.top: parent.top
552 anchors.bottom: parent.bottom
553 width: parent.width
554 dragAreaWidth: shell.edgeSize
555 available: edgeDemo.launcherEnabled
557 onShowDashHome: {
558 if (edgeDemo.running)
559 return;
561 showHome()
562 }
563 onDash: {
564 if (stages.shown && !stages.overlayMode) {
565 if (!stages.locked) {
566 stages.hide();
567 launcher.hide();
568 }
569 }
570 if (greeter.shown) {
571 greeter.hideRight();
572 launcher.hide();
573 }
574 }
575 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
576 onLauncherApplicationSelected: {
577 if (!edgeDemo.running)
578 shell.activateApplication(appId)
579 }
580 onShownChanged: {
581 if (shown) {
582 panel.indicators.hide()
583 hud.hide()
584 bottombar.hide()
585 }
586 }
587 }
589 Notifications {
590 id: notifications
592 model: NotificationBackend.Model
593 margin: units.gu(1)
595 y: panel.panelHeight
596 width: parent.width
597 height: parent.height - panel.panelHeight
599 states: [
600 State {
601 name: "narrow"
602 when: overlay.width <= units.gu(60)
603 AnchorChanges { target: notifications; anchors.left: parent.left }
604 },
605 State {
606 name: "wide"
607 when: overlay.width > units.gu(60)
608 AnchorChanges { target: notifications; anchors.left: undefined }
609 PropertyChanges { target: notifications; width: units.gu(38) }
610 }
611 ]
613 InputFilterArea {
614 anchors { left: parent.left; right: parent.right }
615 height: parent.contentHeight
616 blockInput: height > 0
617 }
618 }
619 }
621 focus: true
622 onFocusChanged: if (!focus) forceActiveFocus();
624 InputFilterArea {
625 anchors {
626 top: parent.top
627 bottom: parent.bottom
628 left: parent.left
629 }
630 width: shell.edgeSize
631 blockInput: true
632 }
634 InputFilterArea {
635 anchors {
636 top: parent.top
637 bottom: parent.bottom
638 right: parent.right
639 }
640 width: shell.edgeSize
641 blockInput: true
642 }
644 Binding {
645 target: i18n
646 property: "domain"
647 value: "unity8"
648 }
650 OSKController {
651 anchors.topMargin: panel.panelHeight
652 anchors.fill: parent // as needs to know the geometry of the shell
653 }
655 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
656 MouseArea {
657 anchors.bottom: parent.bottom
658 anchors.left: parent.left
659 anchors.right: parent.right
660 height: ApplicationManager.keyboardVisible ? ApplicationManager.keyboardHeight : 0
662 enabled: ApplicationManager.keyboardVisible
663 }
665 Label {
666 anchors.centerIn: parent
667 visible: ApplicationManager.fake ? ApplicationManager.fake : false
668 text: "EARLY ALPHA\nNOT READY FOR USE"
669 color: "lightgrey"
670 opacity: 0.2
671 font.weight: Font.Black
672 horizontalAlignment: Text.AlignHCenter
673 verticalAlignment: Text.AlignVCenter
674 fontSizeMode: Text.Fit
675 rotation: -45
676 scale: Math.min(parent.width, parent.height) / width
677 }
679 EdgeDemo {
680 id: edgeDemo
681 greeter: greeter
682 launcher: launcher
683 dash: dash
684 indicators: panel.indicators
685 underlay: underlay
686 }
688 Connections {
689 target: SessionBroadcast
690 onShowHome: showHome()
691 }