on 7ffaa00 help sign in
unity8/qml/Shell.qml
649 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
123 onApplicationRunningChanged: print("***************+ application running", applicationRunning)
125 // Whether the user should see the topmost application surface (if there's one at all).
126 readonly property bool applicationSurfaceShouldBeSeen: applicationRunning && !stages.painting
128 // NB! Application surfaces are stacked behind the shell one. So they can only be seen by the user
129 // through the translucent parts of the shell surface.
130 visible: !fullyCovered && !applicationSurfaceShouldBeSeen
132 onVisibleChanged: print("hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh", visible, fullyCovered, applicationSurfaceShouldBeSeen,
133 applicationRunning, stages.fullyHidden, stages.painting)
135 CrossFadeImage {
136 id: backgroundImage
137 objectName: "backgroundImage"
139 anchors.fill: parent
140 source: shell.background
141 fillMode: Image.PreserveAspectCrop
142 }
144 Rectangle {
145 anchors.fill: parent
146 color: "black"
147 opacity: dash.disappearingAnimationProgress
148 }
150 Dash {
151 id: dash
152 objectName: "dash"
154 available: !greeter.shown && !lockscreen.shown
155 hides: [stages, launcher, panel.indicators]
156 shown: disappearingAnimationProgress !== 1.0
157 enabled: disappearingAnimationProgress === 0.0 && edgeDemo.dashEnabled
159 anchors {
160 fill: parent
161 topMargin: panel.panelHeight
162 }
164 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
165 opacity: 1.0 - disappearingAnimationProgress
166 property real disappearingAnimationProgress: {
167 if (greeter.shown) {
168 return greeter.showProgress;
169 } else {
170 return stagesOuterContainer.showProgress;
171 }
172 }
174 // FIXME: only necessary because stagesOuterContainer.showProgress and
175 // greeterRevealer.animatedProgress are not animated
176 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
177 }
178 }
180 Item {
181 id: stagesOuterContainer
183 width: parent.width
184 height: parent.height
185 x: launcher.progress
186 Behavior on x {SmoothedAnimation{velocity: 600}}
188 property real showProgress: MathUtils.clamp(1 - (x + stages.x) / shell.width, 0, 1)
190 Showable {
191 id: stages
192 objectName: "stages"
194 x: width
196 property bool fullyShown: shown && x == 0 && parent.x == 0
197 property bool fullyHidden: !shown && x == width
199 property bool painting: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.painting : false
201 available: !greeter.shown
202 hides: [panel.indicators]
203 shown: false
204 opacity: 1.0
205 showAnimation: StandardAnimation { property: "x"; duration: 350; to: 0; easing.type: Easing.OutCubic }
206 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: width; easing.type: Easing.OutCubic }
208 width: parent.width
209 height: parent.height
211 property string lastFocusedAppId
212 onShownChanged: {
213 print("stages shown", shown)
214 if (shown) {
215 if (!ApplicationManager.focusedApplicationId && lastFocusedAppId) {
216 ApplicationManager.focusApplication(lastFocusedAppId);
217 }
218 } else {
219 lastFocusedAppId = ApplicationManager.focusedApplicationId;
220 ApplicationManager.unfocusCurrentApplication();
221 }
222 }
224 Connections {
225 target: ApplicationManager
226 onFocusedApplicationIdChanged: {
227 if (ApplicationManager.focusedApplicationId.length > 0) {
228 print("should show stages")
229 stages.show();
230 } else {
231 stages.hide();
232 }
233 }
235 onApplicationAdded: {
236 stages.show();
237 }
238 }
240 Loader {
241 id: applicationsDisplayLoader
242 anchors.fill: parent
244 source: shell.sideStageEnabled ? "Stages/StageWithSideStage.qml" : "Stages/PhoneStage.qml"
246 Binding {
247 target: applicationsDisplayLoader.item
248 property: "moving"
249 value: !stages.fullyShown
250 }
251 Binding {
252 target: applicationsDisplayLoader.item
253 property: "shown"
254 value: stages.shown
255 }
256 Binding {
257 target: applicationsDisplayLoader.item
258 property: "dragAreaWidth"
259 value: shell.edgeSize
260 }
261 }
263 DragHandle {
264 id: stagesDragHandle
266 anchors.top: parent.top
267 anchors.bottom: parent.bottom
268 anchors.right: parent.left
270 width: shell.edgeSize
271 direction: Direction.Leftwards
272 enabled: greeter.showProgress == 0 && edgeDemo.dashEnabled
273 property bool haveApps: ApplicationManager.count > 0
275 maxTotalDragDistance: haveApps ? parent.width : parent.width * 0.7
276 // Make autocompletion impossible when !haveApps
277 edgeDragEvaluator.minDragDistance: haveApps ? maxTotalDragDistance * 0.1 : Number.MAX_VALUE
278 }
279 }
280 }
282 Lockscreen {
283 id: lockscreen
284 objectName: "lockscreen"
286 readonly property int backgroundTopMargin: -panel.panelHeight
288 hides: [launcher, panel.indicators, hud]
289 shown: false
290 enabled: true
291 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
292 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
293 y: panel.panelHeight
294 x: required ? 0 : - width
295 width: parent.width
296 height: parent.height - panel.panelHeight
297 background: shell.background
298 pinLength: 4
300 onEntered: LightDM.Greeter.respond(passphrase);
301 onCancel: greeter.show()
303 Component.onCompleted: {
304 if (LightDM.Users.count == 1) {
305 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
306 greeter.selected(0)
307 }
308 }
309 }
311 Connections {
312 target: LightDM.Greeter
314 onShowPrompt: {
315 if (LightDM.Users.count == 1) {
316 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
317 if (text == "PIN") {
318 lockscreen.alphaNumeric = false
319 } else {
320 lockscreen.alphaNumeric = true
321 }
322 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
323 lockscreen.show();
324 }
325 }
327 onAuthenticationComplete: {
328 if (LightDM.Greeter.promptless) {
329 return;
330 }
331 if (LightDM.Greeter.authenticated) {
332 lockscreen.hide();
333 } else {
334 lockscreen.clear(true);
335 }
336 }
337 }
339 Greeter {
340 id: greeter
341 objectName: "greeter"
343 available: true
344 hides: [launcher, panel.indicators, hud]
345 shown: true
347 defaultBackground: shell.background
349 y: panel.panelHeight
350 width: parent.width
351 height: parent.height - panel.panelHeight
353 dragHandleWidth: shell.edgeSize
355 property var previousMainApp: null
356 property var previousSideApp: null
358 onShownChanged: {
359 if (shown) {
360 lockscreen.reset();
361 // If there is only one user, we start authenticating with that one here.
362 // If there are more users, the Greeter will handle that
363 if (LightDM.Users.count == 1) {
364 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
365 greeter.selected(0);
366 }
367 greeter.forceActiveFocus();
368 }
369 }
371 onUnlocked: greeter.hide()
372 onSelected: {
373 // Update launcher items for new user
374 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
375 AccountsService.user = user;
376 LauncherModel.setUser(user);
377 }
379 onLeftTeaserPressedChanged: {
380 if (leftTeaserPressed) {
381 launcher.tease();
382 }
383 }
384 }
386 InputFilterArea {
387 anchors.fill: parent
388 blockInput: ApplicationManager.focusedApplicationId.length === 0 || greeter.shown || lockscreen.shown || launcher.shown
389 || panel.indicators.shown || hud.shown
390 }
392 Connections {
393 id: powerConnection
394 target: Powerd
396 onDisplayPowerStateChange: {
397 // We ignore any display-off signals when the proximity sensor
398 // is active. This usually indicates something like a phone call.
399 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
400 greeter.showNow();
401 }
403 // No reason to chew demo CPU when user isn't watching
404 if (status == Powerd.Off) {
405 edgeDemo.paused = true;
406 } else if (status == Powerd.On) {
407 edgeDemo.paused = false;
408 }
409 }
410 }
412 function showHome() {
413 var animate = !greeter.shown && !stages.shown
414 greeter.hide()
415 dash.setCurrentScope("home.scope", animate, false)
416 stages.hide()
417 }
419 function hideIndicatorMenu(delay) {
420 panel.hideIndicatorMenu(delay);
421 }
423 Item {
424 id: overlay
426 anchors.fill: parent
428 Panel {
429 id: panel
430 anchors.fill: parent //because this draws indicator menus
431 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
432 indicators {
433 hides: [launcher]
434 available: edgeDemo.panelEnabled
435 contentEnabled: edgeDemo.panelContentEnabled
436 }
437 property string focusedAppId: ApplicationManager.focusedApplicationId
438 property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
439 fullscreenMode: focusedApplication && focusedApplication.fullscreen && !greeter.shown && !lockscreen.shown
440 searchVisible: !greeter.shown && !lockscreen.shown && dash.shown
442 InputFilterArea {
443 anchors {
444 top: parent.top
445 left: parent.left
446 right: parent.right
447 }
448 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
449 blockInput: true
450 }
451 }
453 Hud {
454 id: hud
456 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
457 height: parent.height
459 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown && edgeDemo.dashEnabled
460 shown: false
461 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
462 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
464 Connections {
465 target: ApplicationManager
466 onFocusedApplicationIdChanged: hud.hide()
467 }
468 }
470 Revealer {
471 id: hudRevealer
473 enabled: hud.shown
474 width: hud.width
475 anchors.left: hud.left
476 height: parent.height
477 target: hud.revealerTarget
478 closedValue: height
479 openedValue: 0
480 direction: Qt.RightToLeft
481 orientation: Qt.Vertical
482 handleSize: hud.handleHeight
483 onCloseClicked: target.hide()
484 }
486 Bottombar {
487 id: bottombar
488 theHud: hud
489 anchors.fill: parent
490 enabled: hud.available
491 applicationIsOnForeground: ApplicationManager.focusedApplicationId
492 }
494 InputFilterArea {
495 blockInput: launcher.shown
496 anchors {
497 top: parent.top
498 bottom: parent.bottom
499 left: parent.left
500 }
501 width: launcher.width
502 }
504 Launcher {
505 id: launcher
507 readonly property bool dashSwipe: progress > 0
509 anchors.top: parent.top
510 anchors.bottom: parent.bottom
511 width: parent.width
512 dragAreaWidth: shell.edgeSize
513 available: (!greeter.shown || greeter.narrowMode) && edgeDemo.launcherEnabled
515 onShowDashHome: {
516 if (edgeDemo.running)
517 return;
519 showHome()
520 }
521 onDash: {
522 if (stages.shown) {
523 stages.hide();
524 launcher.hide();
525 }
526 }
527 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("applications.scope", false, true)
528 onLauncherApplicationSelected:{
529 if (edgeDemo.running)
530 return;
532 greeter.hide()
533 shell.activateApplication(appId)
534 }
535 onShownChanged: {
536 if (shown) {
537 panel.indicators.hide()
538 hud.hide()
539 bottombar.hide()
540 }
541 }
542 }
544 Notifications {
545 id: notifications
547 model: NotificationBackend.Model
548 margin: units.gu(1)
550 anchors {
551 top: parent.top
552 right: parent.right
553 bottom: parent.bottom
554 topMargin: panel.panelHeight
555 }
556 states: [
557 State {
558 name: "narrow"
559 when: overlay.width <= units.gu(60)
560 AnchorChanges { target: notifications; anchors.left: parent.left }
561 },
562 State {
563 name: "wide"
564 when: overlay.width > units.gu(60)
565 AnchorChanges { target: notifications; anchors.left: undefined }
566 PropertyChanges { target: notifications; width: units.gu(38) }
567 }
568 ]
570 InputFilterArea {
571 anchors { left: parent.left; right: parent.right }
572 height: parent.contentHeight
573 blockInput: height > 0
574 }
575 }
576 }
578 focus: true
579 onFocusChanged: if (!focus) forceActiveFocus();
581 InputFilterArea {
582 anchors {
583 top: parent.top
584 bottom: parent.bottom
585 left: parent.left
586 }
587 width: shell.edgeSize
588 blockInput: true
589 }
591 InputFilterArea {
592 anchors {
593 top: parent.top
594 bottom: parent.bottom
595 right: parent.right
596 }
597 width: shell.edgeSize
598 blockInput: true
599 }
601 Binding {
602 target: i18n
603 property: "domain"
604 value: "unity8"
605 }
607 OSKController {
608 anchors.topMargin: panel.panelHeight
609 anchors.fill: parent // as needs to know the geometry of the shell
610 }
612 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
613 MouseArea {
614 anchors.bottom: parent.bottom
615 anchors.left: parent.left
616 anchors.right: parent.right
617 height: ApplicationManager.keyboardVisible ? ApplicationManager.keyboardHeight : 0
619 enabled: ApplicationManager.keyboardVisible
620 }
622 Label {
623 anchors.centerIn: parent
624 visible: ApplicationManager.fake ? ApplicationManager.fake : false
625 text: "EARLY ALPHA\nNOT READY FOR USE"
626 color: "lightgrey"
627 opacity: 0.2
628 font.weight: Font.Black
629 horizontalAlignment: Text.AlignHCenter
630 verticalAlignment: Text.AlignVCenter
631 fontSizeMode: Text.Fit
632 rotation: -45
633 scale: Math.min(parent.width, parent.height) / width
634 }
636 EdgeDemo {
637 id: edgeDemo
638 greeter: greeter
639 launcher: launcher
640 dash: dash
641 indicators: panel.indicators
642 underlay: underlay
643 }
645 Connections {
646 target: SessionBroadcast
647 onShowHome: showHome()
648 }