on 4ae044a help sign in
unity8/qml/Shell.qml
647 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 } else {
60 print("starting app", appId);
62 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
63 ApplicationManager.startApplication(appId, execFlags);
64 }
65 }
67 Binding {
68 target: LauncherModel
69 property: "applicationManager"
70 value: ApplicationManager
71 }
73 Component.onCompleted: {
74 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
75 }
77 GSettings {
78 id: backgroundSettings
79 schema.id: "org.gnome.desktop.background"
80 }
81 property url gSettingsPicture: backgroundSettings.pictureUri != undefined && backgroundSettings.pictureUri.length > 0 ? backgroundSettings.pictureUri : shell.defaultBackground
82 onGSettingsPictureChanged: {
83 shell.background = gSettingsPicture
84 }
86 // This is a dummy image that is needed to determine if the picture url
87 // in backgroundSettings points to a valid picture file.
88 // We can't do this with the real background image because setting a
89 // new source in onStatusChanged triggers a binding loop detection
90 // inside Image, which causes it not to render even though a valid source
91 // would be set. We don't mind about this image staying black and just
92 // use it for verification to populate the source for the real
93 // background image.
94 Image {
95 source: shell.background
96 height: 0
97 width: 0
98 sourceSize.height: 0
99 sourceSize.width: 0
100 onStatusChanged: {
101 if (status == Image.Error && source != shell.defaultBackground) {
102 shell.background = defaultBackground
103 }
104 }
105 }
107 VolumeControl {
108 id: volumeControl
109 }
111 Keys.onVolumeUpPressed: volumeControl.volumeUp()
112 Keys.onVolumeDownPressed: volumeControl.volumeDown()
114 Item {
115 id: underlay
116 objectName: "underlay"
117 anchors.fill: parent
119 // Whether the underlay is fully covered by opaque UI elements.
120 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
122 readonly property bool applicationRunning: ApplicationManager.focusedApplicationId.length > 0
124 // Whether the user should see the topmost application surface (if there's one at all).
125 readonly property bool applicationSurfaceShouldBeSeen: applicationRunning && !stages.painting
127 // NB! Application surfaces are stacked behind the shell one. So they can only be seen by the user
128 // through the translucent parts of the shell surface.
129 visible: !fullyCovered && !applicationSurfaceShouldBeSeen
131 CrossFadeImage {
132 id: backgroundImage
133 objectName: "backgroundImage"
135 anchors.fill: parent
136 source: shell.background
137 fillMode: Image.PreserveAspectCrop
138 }
140 Rectangle {
141 anchors.fill: parent
142 color: "black"
143 opacity: dash.disappearingAnimationProgress
144 }
146 Dash {
147 id: dash
148 objectName: "dash"
150 available: !greeter.shown && !lockscreen.shown
151 hides: [stages, launcher, panel.indicators]
152 shown: disappearingAnimationProgress !== 1.0
153 enabled: disappearingAnimationProgress === 0.0 && edgeDemo.dashEnabled
155 anchors {
156 fill: parent
157 topMargin: panel.panelHeight
158 }
160 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
161 opacity: 1.0 - disappearingAnimationProgress
162 property real disappearingAnimationProgress: {
163 if (greeter.shown) {
164 return greeter.showProgress;
165 } else {
166 return stagesOuterContainer.showProgress;
167 }
168 }
170 // FIXME: only necessary because stagesOuterContainer.showProgress and
171 // greeterRevealer.animatedProgress are not animated
172 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
173 }
174 }
176 Item {
177 id: stagesOuterContainer
179 width: parent.width
180 height: parent.height
181 x: launcher.progress
182 Behavior on x {SmoothedAnimation{velocity: 600}}
184 property real showProgress: MathUtils.clamp(1 - (x + stages.x) / shell.width, 0, 1)
186 Showable {
187 id: stages
188 objectName: "stages"
190 x: width
192 property bool fullyShown: shown && x == 0 && parent.x == 0
193 property bool fullyHidden: !shown && x == width
195 property bool painting: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.painting : false
196 property bool fullscreen: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.fullscreen : false
198 available: !greeter.shown
199 hides: [panel.indicators]
200 shown: false
201 opacity: 1.0
202 showAnimation: StandardAnimation { property: "x"; duration: 350; to: 0; easing.type: Easing.OutCubic }
203 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: width; easing.type: Easing.OutCubic }
205 width: parent.width
206 height: parent.height
208 property string lastFocusedAppId
209 onShownChanged: {
210 print("stages shown", shown)
211 if (shown) {
212 if (!ApplicationManager.focusedApplicationId && lastFocusedAppId) {
213 ApplicationManager.focusApplication(lastFocusedAppId);
214 }
215 } else {
216 lastFocusedAppId = ApplicationManager.focusedApplicationId;
217 ApplicationManager.unfocusCurrentApplication();
218 }
219 }
221 Connections {
222 target: ApplicationManager
223 onFocusedApplicationIdChanged: {
224 if (ApplicationManager.focusedApplicationId.length > 0) {
225 print("should show stages")
226 stages.show();
227 } else {
228 stages.hide();
229 }
230 }
232 onApplicationAdded: {
233 stages.show();
234 }
235 }
237 Loader {
238 id: applicationsDisplayLoader
239 anchors.fill: parent
241 source: shell.sideStageEnabled ? "Stages/StageWithSideStage.qml" : "Stages/PhoneStage.qml"
243 Binding {
244 target: applicationsDisplayLoader.item
245 property: "moving"
246 value: !stages.fullyShown
247 }
248 Binding {
249 target: applicationsDisplayLoader.item
250 property: "shown"
251 value: stages.shown
252 }
253 Binding {
254 target: applicationsDisplayLoader.item
255 property: "dragAreaWidth"
256 value: shell.edgeSize
257 }
258 }
260 DragHandle {
261 id: stagesDragHandle
263 anchors.top: parent.top
264 anchors.bottom: parent.bottom
265 anchors.right: parent.left
267 width: shell.edgeSize
268 direction: Direction.Leftwards
269 enabled: greeter.showProgress == 0 && edgeDemo.dashEnabled
270 property bool haveApps: ApplicationManager.count > 0
272 maxTotalDragDistance: haveApps ? parent.width : parent.width * 0.7
273 // Make autocompletion impossible when !haveApps
274 edgeDragEvaluator.minDragDistance: haveApps ? maxTotalDragDistance * 0.1 : Number.MAX_VALUE
275 }
276 }
277 }
279 Lockscreen {
280 id: lockscreen
281 objectName: "lockscreen"
283 readonly property int backgroundTopMargin: -panel.panelHeight
285 hides: [launcher, panel.indicators, hud]
286 shown: false
287 enabled: true
288 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
289 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
290 y: panel.panelHeight
291 x: required ? 0 : - width
292 width: parent.width
293 height: parent.height - panel.panelHeight
294 background: shell.background
295 pinLength: 4
297 onEntered: LightDM.Greeter.respond(passphrase);
298 onCancel: greeter.show()
300 Component.onCompleted: {
301 if (LightDM.Users.count == 1) {
302 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
303 greeter.selected(0)
304 }
305 }
306 }
308 Connections {
309 target: LightDM.Greeter
311 onShowPrompt: {
312 if (LightDM.Users.count == 1) {
313 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
314 if (text == "PIN") {
315 lockscreen.alphaNumeric = false
316 } else {
317 lockscreen.alphaNumeric = true
318 }
319 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
320 lockscreen.show();
321 }
322 }
324 onAuthenticationComplete: {
325 if (LightDM.Greeter.promptless) {
326 return;
327 }
328 if (LightDM.Greeter.authenticated) {
329 lockscreen.hide();
330 } else {
331 lockscreen.clear(true);
332 }
333 }
334 }
336 Greeter {
337 id: greeter
338 objectName: "greeter"
340 available: true
341 hides: [launcher, panel.indicators, hud]
342 shown: true
344 defaultBackground: shell.background
346 y: panel.panelHeight
347 width: parent.width
348 height: parent.height - panel.panelHeight
350 dragHandleWidth: shell.edgeSize
352 property var previousMainApp: null
353 property var previousSideApp: null
355 onShownChanged: {
356 if (shown) {
357 lockscreen.reset();
358 // If there is only one user, we start authenticating with that one here.
359 // If there are more users, the Greeter will handle that
360 if (LightDM.Users.count == 1) {
361 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
362 greeter.selected(0);
363 }
364 greeter.forceActiveFocus();
365 }
366 }
368 onUnlocked: greeter.hide()
369 onSelected: {
370 // Update launcher items for new user
371 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
372 AccountsService.user = user;
373 LauncherModel.setUser(user);
374 }
376 onLeftTeaserPressedChanged: {
377 if (leftTeaserPressed) {
378 launcher.tease();
379 }
380 }
381 }
383 InputFilterArea {
384 anchors.fill: parent
385 blockInput: ApplicationManager.focusedApplicationId.length === 0 || greeter.shown || lockscreen.shown || launcher.shown
386 || panel.indicators.shown || hud.shown
387 }
389 Connections {
390 id: powerConnection
391 target: Powerd
393 onDisplayPowerStateChange: {
394 // We ignore any display-off signals when the proximity sensor
395 // is active. This usually indicates something like a phone call.
396 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
397 greeter.showNow();
398 }
400 // No reason to chew demo CPU when user isn't watching
401 if (status == Powerd.Off) {
402 edgeDemo.paused = true;
403 } else if (status == Powerd.On) {
404 edgeDemo.paused = false;
405 }
406 }
407 }
409 function showHome() {
410 var animate = !greeter.shown && !stages.shown
411 greeter.hide()
412 dash.setCurrentScope("home.scope", animate, false)
413 stages.hide()
414 }
416 function hideIndicatorMenu(delay) {
417 panel.hideIndicatorMenu(delay);
418 }
420 Item {
421 id: overlay
423 anchors.fill: parent
425 Panel {
426 id: panel
427 anchors.fill: parent //because this draws indicator menus
428 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
429 indicators {
430 hides: [launcher]
431 available: edgeDemo.panelEnabled
432 contentEnabled: edgeDemo.panelContentEnabled
433 }
434 property string focusedAppId: ApplicationManager.focusedApplicationId
435 property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
436 fullscreenMode: stages.painting ? stages.fullscreen :
437 focusedApplication && focusedApplication.fullscreen && !greeter.shown && !lockscreen.shown
438 searchVisible: !greeter.shown && !lockscreen.shown && dash.shown
440 InputFilterArea {
441 anchors {
442 top: parent.top
443 left: parent.left
444 right: parent.right
445 }
446 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
447 blockInput: true
448 }
449 }
451 Hud {
452 id: hud
454 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
455 height: parent.height
457 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown && edgeDemo.dashEnabled
458 shown: false
459 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
460 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
462 Connections {
463 target: ApplicationManager
464 onFocusedApplicationIdChanged: hud.hide()
465 }
466 }
468 Revealer {
469 id: hudRevealer
471 enabled: hud.shown
472 width: hud.width
473 anchors.left: hud.left
474 height: parent.height
475 target: hud.revealerTarget
476 closedValue: height
477 openedValue: 0
478 direction: Qt.RightToLeft
479 orientation: Qt.Vertical
480 handleSize: hud.handleHeight
481 onCloseClicked: target.hide()
482 }
484 Bottombar {
485 id: bottombar
486 theHud: hud
487 anchors.fill: parent
488 enabled: hud.available
489 applicationIsOnForeground: ApplicationManager.focusedApplicationId
490 }
492 InputFilterArea {
493 blockInput: launcher.shown
494 anchors {
495 top: parent.top
496 bottom: parent.bottom
497 left: parent.left
498 }
499 width: launcher.width
500 }
502 Launcher {
503 id: launcher
505 readonly property bool dashSwipe: progress > 0
507 anchors.top: parent.top
508 anchors.bottom: parent.bottom
509 width: parent.width
510 dragAreaWidth: shell.edgeSize
511 available: (!greeter.shown || greeter.narrowMode) && edgeDemo.launcherEnabled
513 onShowDashHome: {
514 if (edgeDemo.running)
515 return;
517 showHome()
518 }
519 onDash: {
520 if (stages.shown) {
521 stages.hide();
522 launcher.hide();
523 }
524 }
525 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("applications.scope", false, true)
526 onLauncherApplicationSelected:{
527 if (edgeDemo.running)
528 return;
530 greeter.hide()
531 shell.activateApplication(appId)
532 }
533 onShownChanged: {
534 if (shown) {
535 panel.indicators.hide()
536 hud.hide()
537 bottombar.hide()
538 }
539 }
540 }
542 Notifications {
543 id: notifications
545 model: NotificationBackend.Model
546 margin: units.gu(1)
548 anchors {
549 top: parent.top
550 right: parent.right
551 bottom: parent.bottom
552 topMargin: panel.panelHeight
553 }
554 states: [
555 State {
556 name: "narrow"
557 when: overlay.width <= units.gu(60)
558 AnchorChanges { target: notifications; anchors.left: parent.left }
559 },
560 State {
561 name: "wide"
562 when: overlay.width > units.gu(60)
563 AnchorChanges { target: notifications; anchors.left: undefined }
564 PropertyChanges { target: notifications; width: units.gu(38) }
565 }
566 ]
568 InputFilterArea {
569 anchors { left: parent.left; right: parent.right }
570 height: parent.contentHeight
571 blockInput: height > 0
572 }
573 }
574 }
576 focus: true
577 onFocusChanged: if (!focus) forceActiveFocus();
579 InputFilterArea {
580 anchors {
581 top: parent.top
582 bottom: parent.bottom
583 left: parent.left
584 }
585 width: shell.edgeSize
586 blockInput: true
587 }
589 InputFilterArea {
590 anchors {
591 top: parent.top
592 bottom: parent.bottom
593 right: parent.right
594 }
595 width: shell.edgeSize
596 blockInput: true
597 }
599 Binding {
600 target: i18n
601 property: "domain"
602 value: "unity8"
603 }
605 OSKController {
606 anchors.topMargin: panel.panelHeight
607 anchors.fill: parent // as needs to know the geometry of the shell
608 }
610 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
611 MouseArea {
612 anchors.bottom: parent.bottom
613 anchors.left: parent.left
614 anchors.right: parent.right
615 height: ApplicationManager.keyboardVisible ? ApplicationManager.keyboardHeight : 0
617 enabled: ApplicationManager.keyboardVisible
618 }
620 Label {
621 anchors.centerIn: parent
622 visible: ApplicationManager.fake ? ApplicationManager.fake : false
623 text: "EARLY ALPHA\nNOT READY FOR USE"
624 color: "lightgrey"
625 opacity: 0.2
626 font.weight: Font.Black
627 horizontalAlignment: Text.AlignHCenter
628 verticalAlignment: Text.AlignVCenter
629 fontSizeMode: Text.Fit
630 rotation: -45
631 scale: Math.min(parent.width, parent.height) / width
632 }
634 EdgeDemo {
635 id: edgeDemo
636 greeter: greeter
637 launcher: launcher
638 dash: dash
639 indicators: panel.indicators
640 underlay: underlay
641 }
643 Connections {
644 target: SessionBroadcast
645 onShowHome: showHome()
646 }