on 2b5e5ca 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 function activateApplication(appId) {
54 if (ApplicationManager.findApplication(appId)) {
55 print("Shell.qml: activating app", appId);
56 ApplicationManager.activateApplication(appId);
57 } else {
58 print("starting app", appId);
60 // FIXME:
61// var execFlags = sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
62 var execFlags = ApplicationManager.ForceMainStage;
64 ApplicationManager.startApplication(appId, execFlags);
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: underlay
117 objectName: "underlay"
118 anchors.fill: parent
120 // Whether the underlay is fully covered by opaque UI elements.
121 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
123 readonly property bool applicationRunning: ApplicationManager.focusedApplicationId.length > 0
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 behing 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.usingScreenshots)
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 }
231 }
233 onApplicationAdded: {
234 stages.show();
235 }
236 }
238 Loader {
239 id: applicationsDisplayLoader
240 anchors.fill: parent
242// source: "Stages/StageWithSideStage.qml"
243 source: "Stages/PhoneStage.qml"
245 Binding {
246 target: applicationsDisplayLoader.item
247 property: "moving"
248 value: !stages.fullyShown
249 }
250 Binding {
251 target: applicationsDisplayLoader.item
252 property: "shown"
253 value: stages.shown
254 }
255 Binding {
256 target: applicationsDisplayLoader.item
257 property: "dragAreaWidth"
258 value: shell.edgeSize
259 }
260 }
262 DragHandle {
263 id: stagesDragHandle
265 anchors.top: parent.top
266 anchors.bottom: parent.bottom
267 anchors.right: parent.left
269 width: shell.edgeSize
270 direction: Direction.Leftwards
271 enabled: greeter.showProgress == 0 && edgeDemo.dashEnabled
272 property bool haveApps: ApplicationManager.count > 0
274 maxTotalDragDistance: haveApps ? parent.width : parent.width * 0.7
275 // Make autocompletion impossible when !haveApps
276 edgeDragEvaluator.minDragDistance: haveApps ? maxTotalDragDistance * 0.1 : Number.MAX_VALUE
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 && focusedApplication.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)
548 fullHeight: parent.height - panel.panelHeight
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 }