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