on dda4fe1 help sign in
unity8/qml/Shell.qml
678 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(60)
54 function activateApplication(appId) {
55 if (ApplicationManager.findApplication(appId)) {
56 ApplicationManager.requestFocusApplication(appId);
57 stages.show();
58 if (stages.locked && ApplicationManager.focusedApplicationId == appId) {
59 applicationsDisplayLoader.item.select(appId);
60 }
61 } else {
62 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
63 ApplicationManager.startApplication(appId, execFlags);
64 stages.show();
65 }
66 }
68 Binding {
69 target: LauncherModel
70 property: "applicationManager"
71 value: ApplicationManager
72 }
74 Component.onCompleted: {
75 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
76 }
78 GSettings {
79 id: backgroundSettings
80 schema.id: "org.gnome.desktop.background"
81 }
82 property url gSettingsPicture: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : shell.defaultBackground
83 onGSettingsPictureChanged: {
84 shell.background = gSettingsPicture
85 }
87 // This is a dummy image that is needed to determine if the picture url
88 // in backgroundSettings points to a valid picture file.
89 // We can't do this with the real background image because setting a
90 // new source in onStatusChanged triggers a binding loop detection
91 // inside Image, which causes it not to render even though a valid source
92 // would be set. We don't mind about this image staying black and just
93 // use it for verification to populate the source for the real
94 // background image.
95 Image {
96 source: shell.background
97 height: 0
98 width: 0
99 sourceSize.height: 0
100 sourceSize.width: 0
101 onStatusChanged: {
102 if (status == Image.Error && source != shell.defaultBackground) {
103 shell.background = defaultBackground
104 }
105 }
106 }
108 VolumeControl {
109 id: volumeControl
110 }
112 Keys.onVolumeUpPressed: volumeControl.volumeUp()
113 Keys.onVolumeDownPressed: volumeControl.volumeDown()
115 Item {
116 id: underlayClipper
117 anchors.fill: parent
118 anchors.rightMargin: stages.overlayWidth
119 clip: stages.overlayMode && !stages.painting
121 Item {
122 id: underlay
123 objectName: "underlay"
124 anchors.fill: parent
125 anchors.rightMargin: -parent.anchors.rightMargin
127 // Whether the underlay is fully covered by opaque UI elements.
128 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
130 // Whether the user should see the topmost application surface (if there's one at all).
131 readonly property bool applicationSurfaceShouldBeSeen: stages.shown && !stages.painting && !stages.overlayMode
133 // NB! Application surfaces are stacked behind the shell one. So they can only be seen by the user
134 // through the translucent parts of the shell surface.
135 visible: !fullyCovered && !applicationSurfaceShouldBeSeen
137 CrossFadeImage {
138 id: backgroundImage
139 objectName: "backgroundImage"
141 anchors.fill: parent
142 source: shell.background
143 fillMode: Image.PreserveAspectCrop
144 }
146 Rectangle {
147 anchors.fill: parent
148 color: "black"
149 opacity: dash.disappearingAnimationProgress
150 }
152 Dash {
153 id: dash
154 objectName: "dash"
156 available: !greeter.shown && !lockscreen.shown
157 hides: [stages, launcher, panel.indicators]
158 shown: disappearingAnimationProgress !== 1.0
159 enabled: disappearingAnimationProgress === 0.0 && edgeDemo.dashEnabled
161 anchors {
162 fill: parent
163 topMargin: panel.panelHeight
164 }
166 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
167 opacity: 1.0 - disappearingAnimationProgress
168 property real disappearingAnimationProgress: {
169 if (greeter.shown) {
170 return greeter.showProgress;
171 } else {
172 if (stages.overlayMode) {
173 return 0;
174 }
175 return stages.showProgress
176 }
177 }
179 // FIXME: only necessary because stages.showProgress and
180 // greeterRevealer.animatedProgress are not animated
181 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
182 }
183 }
184 }
186 EdgeDragArea {
187 id: stagesDragHandle
188 direction: Direction.Leftwards
190 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
191 width: shell.edgeSize
193 property real progress: stages.width
195 onTouchXChanged: {
196 if (status == DirectionalDragArea.Recognized) {
197 if (ApplicationManager.count == 0) {
198 progress = Math.max(stages.width - stagesDragHandle.width + touchX, stages.width * .3)
199 } else {
200 progress = stages.width - stagesDragHandle.width + touchX
201 }
202 }
203 }
205 onDraggingChanged: {
206 if (!dragging) {
207 if (ApplicationManager.count > 0 && progress < stages.width - units.gu(10)) {
208 stages.show()
209 }
210 stagesDragHandle.progress = stages.width;
211 }
212 }
213 }
215 Item {
216 id: stages
217 objectName: "stages"
218 width: parent.width
219 height: parent.height
221 x: {
222 if (shown) {
223 if (overlayMode || locked) {
224 return 0;
225 }
226 return launcher.progress
227 } else {
228 return stagesDragHandle.progress
229 }
230 }
232 Behavior on x { SmoothedAnimation { velocity: 600; duration: UbuntuAnimation.FastDuration } }
234 property bool shown: false
236 property real showProgress: overlayMode ? 0 : MathUtils.clamp(1 - x / shell.width, 0, 1)
238 property bool fullyShown: x == 0
239 property bool fullyHidden: x == width
241 property bool painting: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.painting : false
242 property bool fullscreen: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.fullscreen : false
243 property bool overlayMode: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayMode : false
244 property int overlayWidth: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayWidth : false
245 property bool locked: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.locked : false
247 function show() {
248 shown = true;
249 panel.indicators.hide();
250 if (!ApplicationManager.focusedApplicationId && ApplicationManager.count > 0) {
251 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
252 }
253 }
255 function hide() {
256 shown = false;
257 if (ApplicationManager.focusedApplicationId) {
258 ApplicationManager.unfocusCurrentApplication();
259 }
260 }
262 Connections {
263 target: ApplicationManager
265 onFocusRequested: {
266 stages.show();
267 }
269 onFocusedApplicationIdChanged: {
270 if (ApplicationManager.focusedApplicationId.length > 0) {
271 stages.show();
272 } else {
273 if (!stages.overlayMode) {
274 stages.hide();
275 }
276 }
277 }
279 onApplicationAdded: {
280 stages.show();
281 }
282 }
284 Loader {
285 id: applicationsDisplayLoader
286 anchors.fill: parent
288 source: shell.sideStageEnabled ? "Stages/StageWithSideStage.qml" : "Stages/PhoneStage.qml"
290 Binding {
291 target: applicationsDisplayLoader.item
292 property: "moving"
293 value: !stages.fullyShown
294 }
295 Binding {
296 target: applicationsDisplayLoader.item
297 property: "shown"
298 value: stages.shown
299 }
300 Binding {
301 target: applicationsDisplayLoader.item
302 property: "dragAreaWidth"
303 value: shell.edgeSize
304 }
305 }
306 }
308 Lockscreen {
309 id: lockscreen
310 objectName: "lockscreen"
312 readonly property int backgroundTopMargin: -panel.panelHeight
314 hides: [launcher, panel.indicators, hud]
315 shown: false
316 enabled: true
317 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
318 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
319 y: panel.panelHeight
320 x: required ? 0 : - width
321 width: parent.width
322 height: parent.height - panel.panelHeight
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 Greeter {
365 id: greeter
366 objectName: "greeter"
368 available: true
369 hides: [launcher, panel.indicators, hud]
370 shown: true
372 defaultBackground: shell.background
374 y: panel.panelHeight
375 width: parent.width
376 height: parent.height - panel.panelHeight
378 dragHandleWidth: shell.edgeSize
380 onShownChanged: {
381 if (shown) {
382 lockscreen.reset();
383 // If there is only one user, we start authenticating with that one here.
384 // If there are more users, the Greeter will handle that
385 if (LightDM.Users.count == 1) {
386 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
387 }
388 greeter.forceActiveFocus();
389 }
390 }
392 onUnlocked: greeter.hide()
393 onSelected: {
394 // Update launcher items for new user
395 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
396 AccountsService.user = user;
397 LauncherModel.setUser(user);
398 }
400 onLeftTeaserPressedChanged: {
401 if (leftTeaserPressed) {
402 launcher.tease();
403 }
404 }
406 Binding {
407 target: ApplicationManager
408 property: "suspended"
409 value: greeter.shown && greeter.showProgress == 1
410 }
411 }
413 InputFilterArea {
414 anchors.fill: parent
415 blockInput: ApplicationManager.focusedApplicationId.length === 0 || greeter.shown || lockscreen.shown || launcher.shown
416 || panel.indicators.shown || hud.shown
417 }
419 Connections {
420 id: powerConnection
421 target: Powerd
423 onDisplayPowerStateChange: {
424 // We ignore any display-off signals when the proximity sensor
425 // is active. This usually indicates something like a phone call.
426 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
427 greeter.showNow();
428 }
430 // No reason to chew demo CPU when user isn't watching
431 if (status == Powerd.Off) {
432 edgeDemo.paused = true;
433 } else if (status == Powerd.On) {
434 edgeDemo.paused = false;
435 }
436 }
437 }
439 function showHome() {
440 var animate = !greeter.shown && !stages.shown
441 greeter.hide()
442 dash.setCurrentScope("home.scope", animate, false)
443 stages.hide()
444 }
446 function hideIndicatorMenu(delay) {
447 panel.hideIndicatorMenu(delay);
448 }
450 Item {
451 id: overlay
453 anchors.fill: parent
455 Panel {
456 id: panel
457 anchors.fill: parent //because this draws indicator menus
458 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
459 indicators {
460 hides: [launcher]
461 available: edgeDemo.panelEnabled
462 contentEnabled: edgeDemo.panelContentEnabled
463 }
464 property string focusedAppId: ApplicationManager.focusedApplicationId
465 property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
466 fullscreenMode: focusedApplication && stages.fullscreen && !greeter.shown && !lockscreen.shown
467 searchVisible: !greeter.shown && !lockscreen.shown && dash.shown && dash.searchable
469 InputFilterArea {
470 anchors {
471 top: parent.top
472 left: parent.left
473 right: parent.right
474 }
475 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
476 blockInput: true
477 }
478 }
480 Hud {
481 id: hud
483 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
484 height: parent.height
486 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown && edgeDemo.dashEnabled
487 shown: false
488 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
489 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
491 Connections {
492 target: ApplicationManager
493 onFocusedApplicationIdChanged: hud.hide()
494 }
495 }
497 Revealer {
498 id: hudRevealer
500 enabled: hud.shown
501 width: hud.width
502 anchors.left: hud.left
503 height: parent.height
504 target: hud.revealerTarget
505 closedValue: height
506 openedValue: 0
507 direction: Qt.RightToLeft
508 orientation: Qt.Vertical
509 handleSize: hud.handleHeight
510 onCloseClicked: target.hide()
511 }
513 Bottombar {
514 id: bottombar
515 theHud: hud
516 anchors.fill: parent
517 enabled: hud.available
518 applicationIsOnForeground: ApplicationManager.focusedApplicationId
519 }
521 InputFilterArea {
522 blockInput: launcher.shown
523 anchors {
524 top: parent.top
525 bottom: parent.bottom
526 left: parent.left
527 }
528 width: launcher.width
529 }
531 Launcher {
532 id: launcher
534 readonly property bool dashSwipe: progress > 0
536 anchors.top: parent.top
537 anchors.bottom: parent.bottom
538 width: parent.width
539 dragAreaWidth: shell.edgeSize
540 available: (!greeter.shown || greeter.narrowMode) && edgeDemo.launcherEnabled
542 onShowDashHome: {
543 if (edgeDemo.running)
544 return;
546 showHome()
547 }
548 onDash: {
549 if (stages.shown && !stages.overlayMode) {
550 if (!stages.locked) {
551 stages.hide();
552 launcher.hide();
553 }
554 }
555 }
556 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("applications.scope", false, true)
557 onLauncherApplicationSelected:{
558 if (edgeDemo.running)
559 return;
561 greeter.hide()
562 shell.activateApplication(appId)
563 }
564 onShownChanged: {
565 if (shown) {
566 panel.indicators.hide()
567 hud.hide()
568 bottombar.hide()
569 }
570 }
571 }
573 Notifications {
574 id: notifications
576 model: NotificationBackend.Model
577 margin: units.gu(1)
579 anchors {
580 top: parent.top
581 right: parent.right
582 bottom: parent.bottom
583 topMargin: panel.panelHeight
584 }
585 states: [
586 State {
587 name: "narrow"
588 when: overlay.width <= units.gu(60)
589 AnchorChanges { target: notifications; anchors.left: parent.left }
590 },
591 State {
592 name: "wide"
593 when: overlay.width > units.gu(60)
594 AnchorChanges { target: notifications; anchors.left: undefined }
595 PropertyChanges { target: notifications; width: units.gu(38) }
596 }
597 ]
599 InputFilterArea {
600 anchors { left: parent.left; right: parent.right }
601 height: parent.contentHeight
602 blockInput: height > 0
603 }
604 }
605 }
607 focus: true
608 onFocusChanged: if (!focus) forceActiveFocus();
610 InputFilterArea {
611 anchors {
612 top: parent.top
613 bottom: parent.bottom
614 left: parent.left
615 }
616 width: shell.edgeSize
617 blockInput: true
618 }
620 InputFilterArea {
621 anchors {
622 top: parent.top
623 bottom: parent.bottom
624 right: parent.right
625 }
626 width: shell.edgeSize
627 blockInput: true
628 }
630 Binding {
631 target: i18n
632 property: "domain"
633 value: "unity8"
634 }
636 OSKController {
637 anchors.topMargin: panel.panelHeight
638 anchors.fill: parent // as needs to know the geometry of the shell
639 }
641 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
642 MouseArea {
643 anchors.bottom: parent.bottom
644 anchors.left: parent.left
645 anchors.right: parent.right
646 height: ApplicationManager.keyboardVisible ? ApplicationManager.keyboardHeight : 0
648 enabled: ApplicationManager.keyboardVisible
649 }
651 Label {
652 anchors.centerIn: parent
653 visible: ApplicationManager.fake ? ApplicationManager.fake : false
654 text: "EARLY ALPHA\nNOT READY FOR USE"
655 color: "lightgrey"
656 opacity: 0.2
657 font.weight: Font.Black
658 horizontalAlignment: Text.AlignHCenter
659 verticalAlignment: Text.AlignVCenter
660 fontSizeMode: Text.Fit
661 rotation: -45
662 scale: Math.min(parent.width, parent.height) / width
663 }
665 EdgeDemo {
666 id: edgeDemo
667 greeter: greeter
668 launcher: launcher
669 dash: dash
670 indicators: panel.indicators
671 underlay: underlay
672 }
674 Connections {
675 target: SessionBroadcast
676 onShowHome: showHome()
677 }