on ef7bab5 help sign in
unity8/qml/Shell.qml
644 lines 20 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 "SideStage"
35import "Notifications"
36import Unity.Notifications 1.0 as NotificationBackend
38FocusScope {
39 id: shell
41 // this is only here to select the width / height of the window if not running fullscreen
42 property bool tablet: false
43 width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
44 height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
46 property real edgeSize: units.gu(2)
47 property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
48 property url background
49 readonly property real panelHeight: panel.panelHeight
51 property bool dashShown: dash.shown
53 property bool sideStageEnabled: shell.width >= units.gu(60)
55 function activateApplication(appId) {
56 if (ApplicationManager.findApplication(appId)) {
57 print("Shell.qml: activating app", appId);
58 ApplicationManager.activateApplication(appId);
59 stages.show();
60 } else {
61 print("starting app", appId);
63 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
64 ApplicationManager.startApplication(appId, execFlags);
65 stages.show();
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 // This is a dummy image that is needed to determine if the picture url
89 // in backgroundSettings points to a valid picture file.
90 // We can't do this with the real background image because setting a
91 // new source in onStatusChanged triggers a binding loop detection
92 // inside Image, which causes it not to render even though a valid source
93 // would be set. We don't mind about this image staying black and just
94 // use it for verification to populate the source for the real
95 // background image.
96 Image {
97 source: shell.background
98 height: 0
99 width: 0
100 sourceSize.height: 0
101 sourceSize.width: 0
102 onStatusChanged: {
103 if (status == Image.Error && source != shell.defaultBackground) {
104 shell.background = defaultBackground
105 }
106 }
107 }
109 VolumeControl {
110 id: volumeControl
111 }
113 Keys.onVolumeUpPressed: volumeControl.volumeUp()
114 Keys.onVolumeDownPressed: volumeControl.volumeDown()
116 Item {
117 id: underlay
118 objectName: "underlay"
119 anchors.fill: parent
121 // Whether the underlay is fully covered by opaque UI elements.
122 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
124 readonly property bool applicationRunning: ApplicationManager.focusedApplicationId.length > 0
126 // Whether the user should see the topmost application surface (if there's one at all).
127 readonly property bool applicationSurfaceShouldBeSeen: stages.shown && !stages.painting
129 // NB! Application surfaces are stacked behind the shell one. So they can only be seen by the user
130 // through the translucent parts of the shell surface.
131 visible: !fullyCovered && !applicationSurfaceShouldBeSeen
133 CrossFadeImage {
134 id: backgroundImage
135 objectName: "backgroundImage"
137 anchors.fill: parent
138 source: shell.background
139 fillMode: Image.PreserveAspectCrop
140 }
142 Rectangle {
143 anchors.fill: parent
144 color: "black"
145 opacity: dash.disappearingAnimationProgress
146 }
148 Dash {
149 id: dash
150 objectName: "dash"
152 available: !greeter.shown && !lockscreen.shown
153 hides: [stages, launcher, panel.indicators]
154 shown: disappearingAnimationProgress !== 1.0
155 enabled: disappearingAnimationProgress === 0.0 && edgeDemo.dashEnabled
157 anchors {
158 fill: parent
159 topMargin: panel.panelHeight
160 }
162 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
163 opacity: 1.0 - disappearingAnimationProgress
164 property real disappearingAnimationProgress: {
165 if (greeter.shown) {
166 return greeter.showProgress;
167 } else {
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 }
178 EdgeDragArea {
179 id: stagesDragHandle
180 direction: Direction.LeftWards
182 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
183 width: shell.edgeSize
185 property real progress: stages.width
187 onTouchXChanged: {
188 if (status == DirectionalDragArea.Recognized) {
189 if (ApplicationManager.count == 0) {
190 progress = Math.max(stages.width + touchX * 2, stages.width * .7)
191 } else {
192 progress = stages.width + touchX
193 }
194 }
195 }
197 onDraggingChanged: {
198 if (!dragging) {
199 if (ApplicationManager.count > 0 && progress < stages.width - units.gu(10)) {
200 stages.show()
201 }
202 stagesDragHandle.progress = stages.width;
203 }
204 }
205 }
207 Item {
208 id: stages
209 objectName: "stages"
210 width: parent.width
211 height: parent.height
213 x: shown ? launcher.progress : stagesDragHandle.progress
215 Behavior on x { SmoothedAnimation { velocity: 600; duration: UbuntuAnimation.SlowDuration } }
217 property bool shown: false
219 property real showProgress: MathUtils.clamp(1 - x / shell.width, 0, 1)
221 property bool fullyShown: x == 0
222 property bool fullyHidden: x == width
224 property bool painting: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.painting : false
225 property bool fullscreen: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.fullscreen : false
227 function show() {
228 shown = true;
229 panel.indicators.hide();
230 if (!ApplicationManager.focusedApplicationId && ApplicationManager.count > 0) {
231 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
232 }
233 }
235 function hide() {
236 shown = false;
237 ApplicationManager.unfocusCurrentApplication();
238 }
240 Connections {
241 target: ApplicationManager
242 onFocusedApplicationIdChanged: {
243 if (ApplicationManager.focusedApplicationId.length > 0) {
244 stages.show();
245 }
246 }
248 onApplicationAdded: {
249 stages.show();
250 }
251 }
253 Loader {
254 id: applicationsDisplayLoader
255 anchors.fill: parent
257 source: shell.sideStageEnabled ? "Stages/StageWithSideStage.qml" : "Stages/PhoneStage.qml"
259 Binding {
260 target: applicationsDisplayLoader.item
261 property: "moving"
262 value: !stages.fullyShown
263 }
264 Binding {
265 target: applicationsDisplayLoader.item
266 property: "shown"
267 value: stages.shown
268 }
269 Binding {
270 target: applicationsDisplayLoader.item
271 property: "dragAreaWidth"
272 value: shell.edgeSize
273 }
274 }
275 }
277 Lockscreen {
278 id: lockscreen
279 objectName: "lockscreen"
281 readonly property int backgroundTopMargin: -panel.panelHeight
283 hides: [launcher, panel.indicators, hud]
284 shown: false
285 enabled: true
286 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
287 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
288 y: panel.panelHeight
289 x: required ? 0 : - width
290 width: parent.width
291 height: parent.height - panel.panelHeight
292 background: shell.background
293 pinLength: 4
295 onEntered: LightDM.Greeter.respond(passphrase);
296 onCancel: greeter.show()
298 Component.onCompleted: {
299 if (LightDM.Users.count == 1) {
300 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
301 greeter.selected(0)
302 }
303 }
304 }
306 Connections {
307 target: LightDM.Greeter
309 onShowPrompt: {
310 if (LightDM.Users.count == 1) {
311 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
312 if (text == "PIN") {
313 lockscreen.alphaNumeric = false
314 } else {
315 lockscreen.alphaNumeric = true
316 }
317 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
318 lockscreen.show();
319 }
320 }
322 onAuthenticationComplete: {
323 if (LightDM.Greeter.promptless) {
324 return;
325 }
326 if (LightDM.Greeter.authenticated) {
327 lockscreen.hide();
328 } else {
329 lockscreen.clear(true);
330 }
331 }
332 }
334 Greeter {
335 id: greeter
336 objectName: "greeter"
338 available: true
339 hides: [launcher, panel.indicators, hud]
340 shown: true
342 defaultBackground: shell.background
344 y: panel.panelHeight
345 width: parent.width
346 height: parent.height - panel.panelHeight
348 dragHandleWidth: shell.edgeSize
350 property var previousMainApp: null
351 property var previousSideApp: null
353 onShownChanged: {
354 if (shown) {
355 lockscreen.reset();
356 // If there is only one user, we start authenticating with that one here.
357 // If there are more users, the Greeter will handle that
358 if (LightDM.Users.count == 1) {
359 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
360 greeter.selected(0);
361 }
362 greeter.forceActiveFocus();
363 }
364 }
366 onUnlocked: greeter.hide()
367 onSelected: {
368 // Update launcher items for new user
369 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
370 AccountsService.user = user;
371 LauncherModel.setUser(user);
372 }
374 onLeftTeaserPressedChanged: {
375 if (leftTeaserPressed) {
376 launcher.tease();
377 }
378 }
379 }
381 InputFilterArea {
382 anchors.fill: parent
383 blockInput: ApplicationManager.focusedApplicationId.length === 0 || greeter.shown || lockscreen.shown || launcher.shown
384 || panel.indicators.shown || hud.shown
385 }
387 Connections {
388 id: powerConnection
389 target: Powerd
391 onDisplayPowerStateChange: {
392 // We ignore any display-off signals when the proximity sensor
393 // is active. This usually indicates something like a phone call.
394 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
395 greeter.showNow();
396 }
398 // No reason to chew demo CPU when user isn't watching
399 if (status == Powerd.Off) {
400 edgeDemo.paused = true;
401 } else if (status == Powerd.On) {
402 edgeDemo.paused = false;
403 }
404 }
405 }
407 function showHome() {
408 var animate = !greeter.shown && !stages.shown
409 greeter.hide()
410 dash.setCurrentScope("home.scope", animate, false)
411 stages.hide()
412 }
414 function hideIndicatorMenu(delay) {
415 panel.hideIndicatorMenu(delay);
416 }
418 Item {
419 id: overlay
421 anchors.fill: parent
423 Panel {
424 id: panel
425 anchors.fill: parent //because this draws indicator menus
426 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
427 indicators {
428 hides: [launcher]
429 available: edgeDemo.panelEnabled
430 contentEnabled: edgeDemo.panelContentEnabled
431 }
432 property string focusedAppId: ApplicationManager.focusedApplicationId
433 property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
434 fullscreenMode: focusedApplication && stages.fullscreen && !greeter.shown && !lockscreen.shown
435 searchVisible: !greeter.shown && !lockscreen.shown && dash.shown
437 InputFilterArea {
438 anchors {
439 top: parent.top
440 left: parent.left
441 right: parent.right
442 }
443 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
444 blockInput: true
445 }
446 }
448 Hud {
449 id: hud
451 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
452 height: parent.height
454 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown && edgeDemo.dashEnabled
455 shown: false
456 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
457 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
459 Connections {
460 target: ApplicationManager
461 onFocusedApplicationIdChanged: hud.hide()
462 }
463 }
465 Revealer {
466 id: hudRevealer
468 enabled: hud.shown
469 width: hud.width
470 anchors.left: hud.left
471 height: parent.height
472 target: hud.revealerTarget
473 closedValue: height
474 openedValue: 0
475 direction: Qt.RightToLeft
476 orientation: Qt.Vertical
477 handleSize: hud.handleHeight
478 onCloseClicked: target.hide()
479 }
481 Bottombar {
482 id: bottombar
483 theHud: hud
484 anchors.fill: parent
485 enabled: hud.available
486 applicationIsOnForeground: ApplicationManager.focusedApplicationId
487 }
489 InputFilterArea {
490 blockInput: launcher.shown
491 anchors {
492 top: parent.top
493 bottom: parent.bottom
494 left: parent.left
495 }
496 width: launcher.width
497 }
499 Launcher {
500 id: launcher
502 readonly property bool dashSwipe: progress > 0
504 anchors.top: parent.top
505 anchors.bottom: parent.bottom
506 width: parent.width
507 dragAreaWidth: shell.edgeSize
508 available: (!greeter.shown || greeter.narrowMode) && edgeDemo.launcherEnabled
510 onShowDashHome: {
511 if (edgeDemo.running)
512 return;
514 showHome()
515 }
516 onDash: {
517 if (stages.shown) {
518 stages.hide();
519 launcher.hide();
520 }
521 }
522 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("applications.scope", false, true)
523 onLauncherApplicationSelected:{
524 if (edgeDemo.running)
525 return;
527 greeter.hide()
528 shell.activateApplication(appId)
529 }
530 onShownChanged: {
531 if (shown) {
532 panel.indicators.hide()
533 hud.hide()
534 bottombar.hide()
535 }
536 }
537 }
539 Notifications {
540 id: notifications
542 model: NotificationBackend.Model
543 margin: units.gu(1)
545 anchors {
546 top: parent.top
547 right: parent.right
548 bottom: parent.bottom
549 topMargin: panel.panelHeight
550 }
551 states: [
552 State {
553 name: "narrow"
554 when: overlay.width <= units.gu(60)
555 AnchorChanges { target: notifications; anchors.left: parent.left }
556 },
557 State {
558 name: "wide"
559 when: overlay.width > units.gu(60)
560 AnchorChanges { target: notifications; anchors.left: undefined }
561 PropertyChanges { target: notifications; width: units.gu(38) }
562 }
563 ]
565 InputFilterArea {
566 anchors { left: parent.left; right: parent.right }
567 height: parent.contentHeight
568 blockInput: height > 0
569 }
570 }
571 }
573 focus: true
574 onFocusChanged: if (!focus) forceActiveFocus();
576 InputFilterArea {
577 anchors {
578 top: parent.top
579 bottom: parent.bottom
580 left: parent.left
581 }
582 width: shell.edgeSize
583 blockInput: true
584 }
586 InputFilterArea {
587 anchors {
588 top: parent.top
589 bottom: parent.bottom
590 right: parent.right
591 }
592 width: shell.edgeSize
593 blockInput: true
594 }
596 Binding {
597 target: i18n
598 property: "domain"
599 value: "unity8"
600 }
602 OSKController {
603 anchors.topMargin: panel.panelHeight
604 anchors.fill: parent // as needs to know the geometry of the shell
605 }
607 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
608 MouseArea {
609 anchors.bottom: parent.bottom
610 anchors.left: parent.left
611 anchors.right: parent.right
612 height: ApplicationManager.keyboardVisible ? ApplicationManager.keyboardHeight : 0
614 enabled: ApplicationManager.keyboardVisible
615 }
617 Label {
618 anchors.centerIn: parent
619 visible: ApplicationManager.fake ? ApplicationManager.fake : false
620 text: "EARLY ALPHA\nNOT READY FOR USE"
621 color: "lightgrey"
622 opacity: 0.2
623 font.weight: Font.Black
624 horizontalAlignment: Text.AlignHCenter
625 verticalAlignment: Text.AlignVCenter
626 fontSizeMode: Text.Fit
627 rotation: -45
628 scale: Math.min(parent.width, parent.height) / width
629 }
631 EdgeDemo {
632 id: edgeDemo
633 greeter: greeter
634 launcher: launcher
635 dash: dash
636 indicators: panel.indicators
637 underlay: underlay
638 }
640 Connections {
641 target: SessionBroadcast
642 onShowHome: showHome()
643 }