on 7f75348 help sign in
unity8/qml/Shell.qml
648 lines 21 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.FastDuration } }
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 if (ApplicationManager.focusedApplicationId) {
238 ApplicationManager.unfocusCurrentApplication();
239 }
240 }
242 Connections {
243 target: ApplicationManager
244 onFocusedApplicationIdChanged: {
245 if (ApplicationManager.focusedApplicationId.length > 0) {
246 stages.show();
247 } else {
248 stages.hide();
249 }
250 }
252 onApplicationAdded: {
253 stages.show();
254 }
255 }
257 Loader {
258 id: applicationsDisplayLoader
259 anchors.fill: parent
261 source: shell.sideStageEnabled ? "Stages/StageWithSideStage.qml" : "Stages/PhoneStage.qml"
263 Binding {
264 target: applicationsDisplayLoader.item
265 property: "moving"
266 value: !stages.fullyShown
267 }
268 Binding {
269 target: applicationsDisplayLoader.item
270 property: "shown"
271 value: stages.shown
272 }
273 Binding {
274 target: applicationsDisplayLoader.item
275 property: "dragAreaWidth"
276 value: shell.edgeSize
277 }
278 }
279 }
281 Lockscreen {
282 id: lockscreen
283 objectName: "lockscreen"
285 readonly property int backgroundTopMargin: -panel.panelHeight
287 hides: [launcher, panel.indicators, hud]
288 shown: false
289 enabled: true
290 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
291 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
292 y: panel.panelHeight
293 x: required ? 0 : - width
294 width: parent.width
295 height: parent.height - panel.panelHeight
296 background: shell.background
297 pinLength: 4
299 onEntered: LightDM.Greeter.respond(passphrase);
300 onCancel: greeter.show()
302 Component.onCompleted: {
303 if (LightDM.Users.count == 1) {
304 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
305 greeter.selected(0)
306 }
307 }
308 }
310 Connections {
311 target: LightDM.Greeter
313 onShowPrompt: {
314 if (LightDM.Users.count == 1) {
315 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
316 if (text == "PIN") {
317 lockscreen.alphaNumeric = false
318 } else {
319 lockscreen.alphaNumeric = true
320 }
321 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
322 lockscreen.show();
323 }
324 }
326 onAuthenticationComplete: {
327 if (LightDM.Greeter.promptless) {
328 return;
329 }
330 if (LightDM.Greeter.authenticated) {
331 lockscreen.hide();
332 } else {
333 lockscreen.clear(true);
334 }
335 }
336 }
338 Greeter {
339 id: greeter
340 objectName: "greeter"
342 available: true
343 hides: [launcher, panel.indicators, hud]
344 shown: true
346 defaultBackground: shell.background
348 y: panel.panelHeight
349 width: parent.width
350 height: parent.height - panel.panelHeight
352 dragHandleWidth: shell.edgeSize
354 property var previousMainApp: null
355 property var previousSideApp: null
357 onShownChanged: {
358 if (shown) {
359 lockscreen.reset();
360 // If there is only one user, we start authenticating with that one here.
361 // If there are more users, the Greeter will handle that
362 if (LightDM.Users.count == 1) {
363 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
364 greeter.selected(0);
365 }
366 greeter.forceActiveFocus();
367 }
368 }
370 onUnlocked: greeter.hide()
371 onSelected: {
372 // Update launcher items for new user
373 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
374 AccountsService.user = user;
375 LauncherModel.setUser(user);
376 }
378 onLeftTeaserPressedChanged: {
379 if (leftTeaserPressed) {
380 launcher.tease();
381 }
382 }
383 }
385 InputFilterArea {
386 anchors.fill: parent
387 blockInput: ApplicationManager.focusedApplicationId.length === 0 || greeter.shown || lockscreen.shown || launcher.shown
388 || panel.indicators.shown || hud.shown
389 }
391 Connections {
392 id: powerConnection
393 target: Powerd
395 onDisplayPowerStateChange: {
396 // We ignore any display-off signals when the proximity sensor
397 // is active. This usually indicates something like a phone call.
398 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
399 greeter.showNow();
400 }
402 // No reason to chew demo CPU when user isn't watching
403 if (status == Powerd.Off) {
404 edgeDemo.paused = true;
405 } else if (status == Powerd.On) {
406 edgeDemo.paused = false;
407 }
408 }
409 }
411 function showHome() {
412 var animate = !greeter.shown && !stages.shown
413 greeter.hide()
414 dash.setCurrentScope("home.scope", animate, false)
415 stages.hide()
416 }
418 function hideIndicatorMenu(delay) {
419 panel.hideIndicatorMenu(delay);
420 }
422 Item {
423 id: overlay
425 anchors.fill: parent
427 Panel {
428 id: panel
429 anchors.fill: parent //because this draws indicator menus
430 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
431 indicators {
432 hides: [launcher]
433 available: edgeDemo.panelEnabled
434 contentEnabled: edgeDemo.panelContentEnabled
435 }
436 property string focusedAppId: ApplicationManager.focusedApplicationId
437 property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
438 fullscreenMode: focusedApplication && stages.fullscreen && !greeter.shown && !lockscreen.shown
439 searchVisible: !greeter.shown && !lockscreen.shown && dash.shown
441 InputFilterArea {
442 anchors {
443 top: parent.top
444 left: parent.left
445 right: parent.right
446 }
447 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
448 blockInput: true
449 }
450 }
452 Hud {
453 id: hud
455 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
456 height: parent.height
458 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown && edgeDemo.dashEnabled
459 shown: false
460 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
461 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
463 Connections {
464 target: ApplicationManager
465 onFocusedApplicationIdChanged: hud.hide()
466 }
467 }
469 Revealer {
470 id: hudRevealer
472 enabled: hud.shown
473 width: hud.width
474 anchors.left: hud.left
475 height: parent.height
476 target: hud.revealerTarget
477 closedValue: height
478 openedValue: 0
479 direction: Qt.RightToLeft
480 orientation: Qt.Vertical
481 handleSize: hud.handleHeight
482 onCloseClicked: target.hide()
483 }
485 Bottombar {
486 id: bottombar
487 theHud: hud
488 anchors.fill: parent
489 enabled: hud.available
490 applicationIsOnForeground: ApplicationManager.focusedApplicationId
491 }
493 InputFilterArea {
494 blockInput: launcher.shown
495 anchors {
496 top: parent.top
497 bottom: parent.bottom
498 left: parent.left
499 }
500 width: launcher.width
501 }
503 Launcher {
504 id: launcher
506 readonly property bool dashSwipe: progress > 0
508 anchors.top: parent.top
509 anchors.bottom: parent.bottom
510 width: parent.width
511 dragAreaWidth: shell.edgeSize
512 available: (!greeter.shown || greeter.narrowMode) && edgeDemo.launcherEnabled
514 onShowDashHome: {
515 if (edgeDemo.running)
516 return;
518 showHome()
519 }
520 onDash: {
521 if (stages.shown) {
522 stages.hide();
523 launcher.hide();
524 }
525 }
526 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("applications.scope", false, true)
527 onLauncherApplicationSelected:{
528 if (edgeDemo.running)
529 return;
531 greeter.hide()
532 shell.activateApplication(appId)
533 }
534 onShownChanged: {
535 if (shown) {
536 panel.indicators.hide()
537 hud.hide()
538 bottombar.hide()
539 }
540 }
541 }
543 Notifications {
544 id: notifications
546 model: NotificationBackend.Model
547 margin: units.gu(1)
549 anchors {
550 top: parent.top
551 right: parent.right
552 bottom: parent.bottom
553 topMargin: panel.panelHeight
554 }
555 states: [
556 State {
557 name: "narrow"
558 when: overlay.width <= units.gu(60)
559 AnchorChanges { target: notifications; anchors.left: parent.left }
560 },
561 State {
562 name: "wide"
563 when: overlay.width > units.gu(60)
564 AnchorChanges { target: notifications; anchors.left: undefined }
565 PropertyChanges { target: notifications; width: units.gu(38) }
566 }
567 ]
569 InputFilterArea {
570 anchors { left: parent.left; right: parent.right }
571 height: parent.contentHeight
572 blockInput: height > 0
573 }
574 }
575 }
577 focus: true
578 onFocusChanged: if (!focus) forceActiveFocus();
580 InputFilterArea {
581 anchors {
582 top: parent.top
583 bottom: parent.bottom
584 left: parent.left
585 }
586 width: shell.edgeSize
587 blockInput: true
588 }
590 InputFilterArea {
591 anchors {
592 top: parent.top
593 bottom: parent.bottom
594 right: parent.right
595 }
596 width: shell.edgeSize
597 blockInput: true
598 }
600 Binding {
601 target: i18n
602 property: "domain"
603 value: "unity8"
604 }
606 OSKController {
607 anchors.topMargin: panel.panelHeight
608 anchors.fill: parent // as needs to know the geometry of the shell
609 }
611 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
612 MouseArea {
613 anchors.bottom: parent.bottom
614 anchors.left: parent.left
615 anchors.right: parent.right
616 height: ApplicationManager.keyboardVisible ? ApplicationManager.keyboardHeight : 0
618 enabled: ApplicationManager.keyboardVisible
619 }
621 Label {
622 anchors.centerIn: parent
623 visible: ApplicationManager.fake ? ApplicationManager.fake : false
624 text: "EARLY ALPHA\nNOT READY FOR USE"
625 color: "lightgrey"
626 opacity: 0.2
627 font.weight: Font.Black
628 horizontalAlignment: Text.AlignHCenter
629 verticalAlignment: Text.AlignVCenter
630 fontSizeMode: Text.Fit
631 rotation: -45
632 scale: Math.min(parent.width, parent.height) / width
633 }
635 EdgeDemo {
636 id: edgeDemo
637 greeter: greeter
638 launcher: launcher
639 dash: dash
640 indicators: panel.indicators
641 underlay: underlay
642 }
644 Connections {
645 target: SessionBroadcast
646 onShowHome: showHome()
647 }