on 1eb2938 help sign in
unity8/qml/Shell.qml
699 lines 23 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 "Notifications"
35import Unity.Notifications 1.0 as NotificationBackend
37FocusScope {
38 id: shell
40 // this is only here to select the width / height of the window if not running fullscreen
41 property bool tablet: false
42 width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
43 height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
45 property real edgeSize: units.gu(2)
46 property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
47 property url background
48 readonly property real panelHeight: panel.panelHeight
50 property bool dashShown: dash.shown
52 property bool sideStageEnabled: shell.width >= units.gu(100)
53 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
55 function activateApplication(appId) {
56 if (ApplicationManager.findApplication(appId)) {
57 ApplicationManager.requestFocusApplication(appId);
58 stages.show(true);
59 if (stages.locked && ApplicationManager.focusedApplicationId == appId) {
60 applicationsDisplayLoader.item.select(appId);
61 }
62 } else {
63 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
64 ApplicationManager.startApplication(appId, execFlags);
65 stages.show(false);
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 VolumeControl {
89 id: volumeControl
90 }
92 Keys.onVolumeUpPressed: volumeControl.volumeUp()
93 Keys.onVolumeDownPressed: volumeControl.volumeDown()
95 Item {
96 id: underlayClipper
97 anchors.fill: parent
98 anchors.rightMargin: stages.overlayWidth
99 clip: stages.overlayMode && !stages.painting
101 InputFilterArea {
102 anchors.fill: parent
103 blockInput: parent.clip
104 }
106 Item {
107 id: underlay
108 objectName: "underlay"
109 anchors.fill: parent
110 anchors.rightMargin: -parent.anchors.rightMargin
112 // Whether the underlay is fully covered by opaque UI elements.
113 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicators.width
115 // Whether the user should see the topmost application surface (if there's one at all).
116 readonly property bool applicationSurfaceShouldBeSeen: stages.shown && !stages.painting && !stages.overlayMode
118 // NB! Application surfaces are stacked behind the shell one. So they can only be seen by the user
119 // through the translucent parts of the shell surface.
120 visible: !fullyCovered && !applicationSurfaceShouldBeSeen
122 CrossFadeImage {
123 id: backgroundImage
124 objectName: "backgroundImage"
126 anchors.fill: parent
127 source: shell.background
128 fillMode: Image.PreserveAspectCrop
129 }
131 Rectangle {
132 anchors.fill: parent
133 color: "black"
134 opacity: dash.disappearingAnimationProgress
135 }
137 Image {
138 anchors.fill: dash
139 source: shell.width > shell.height ? "Dash/graphics/paper_landscape.png" : "Dash/graphics/paper_portrait.png"
140 fillMode: Image.PreserveAspectCrop
141 horizontalAlignment: Image.AlignRight
142 verticalAlignment: Image.AlignTop
143 }
145 Dash {
146 id: dash
147 objectName: "dash"
149 available: !greeter.shown && !lockscreen.shown
150 hides: [stages, launcher, panel.indicators]
151 shown: disappearingAnimationProgress !== 1.0 && greeterWrapper.showProgress !== 1.0
152 enabled: disappearingAnimationProgress === 0.0 && greeterWrapper.showProgress === 0.0 && edgeDemo.dashEnabled
154 y: panel.panelHeight
155 width: parent.width
156 height: parent.height - panel.panelHeight
157 Behavior on y { StandardAnimation {} }
159 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
160 opacity: 1.0 - disappearingAnimationProgress
161 property real disappearingAnimationProgress: {
162 if (stages.overlayMode) {
163 return 0;
164 } else {
165 return stages.showProgress
166 }
167 }
169 // FIXME: only necessary because stages.showProgress is not animated
170 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
171 }
172 }
173 }
175 EdgeDragArea {
176 id: stagesDragHandle
177 direction: Direction.Leftwards
179 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
180 width: shell.edgeSize
182 property real progress: stages.width
184 onTouchXChanged: {
185 if (status == DirectionalDragArea.Recognized) {
186 if (ApplicationManager.count == 0) {
187 progress = Math.max(stages.width - stagesDragHandle.width + touchX, stages.width * .3)
188 } else {
189 progress = stages.width - stagesDragHandle.width + touchX
190 }
191 }
192 }
194 onDraggingChanged: {
195 if (!dragging) {
196 if (ApplicationManager.count > 0 && progress < stages.width - units.gu(10)) {
197 stages.show(true)
198 }
199 stagesDragHandle.progress = stages.width;
200 }
201 }
202 }
204 Item {
205 id: stages
206 objectName: "stages"
207 width: parent.width
208 height: parent.height
210 x: {
211 if (shown) {
212 if (overlayMode || locked) {
213 return 0;
214 }
215 return launcher.progress
216 } else {
217 return stagesDragHandle.progress
218 }
219 }
221 Behavior on x { SmoothedAnimation { velocity: 600; duration: UbuntuAnimation.FastDuration } }
223 property bool shown: false
225 property real showProgress: overlayMode ? 0 : MathUtils.clamp(1 - x / shell.width, 0, 1)
227 property bool fullyShown: x == 0
228 property bool fullyHidden: x == width
230 property bool painting: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.painting : false
231 property bool fullscreen: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.fullscreen : false
232 property bool overlayMode: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayMode : false
233 property int overlayWidth: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayWidth : false
234 property bool locked: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.locked : false
236 function show(focusApp) {
237 shown = true;
238 panel.indicators.hide();
239 edgeDemo.stopDemo();
240 greeter.hide();
241 if (!ApplicationManager.focusedApplicationId && ApplicationManager.count > 0 && focusApp) {
242 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
243 }
244 }
246 function hide() {
247 shown = false;
248 if (ApplicationManager.focusedApplicationId) {
249 ApplicationManager.unfocusCurrentApplication();
250 }
251 }
253 Connections {
254 target: ApplicationManager
256 onFocusRequested: {
257 stages.show(true);
258 }
260 onFocusedApplicationIdChanged: {
261 if (ApplicationManager.focusedApplicationId.length > 0) {
262 stages.show(false);
263 } else {
264 if (!stages.overlayMode) {
265 stages.hide();
266 }
267 }
268 }
270 onApplicationAdded: {
271 stages.show(false);
272 }
274 onApplicationRemoved: {
275 if (ApplicationManager.focusedApplicationId.length == 0) {
276 stages.hide();
277 }
278 }
279 }
281 Loader {
282 id: applicationsDisplayLoader
283 anchors.fill: parent
285 source: shell.sideStageEnabled ? "Stages/StageWithSideStage.qml" : "Stages/PhoneStage.qml"
287 Binding {
288 target: applicationsDisplayLoader.item
289 property: "moving"
290 value: !stages.fullyShown
291 }
292 Binding {
293 target: applicationsDisplayLoader.item
294 property: "shown"
295 value: stages.shown
296 }
297 Binding {
298 target: applicationsDisplayLoader.item
299 property: "dragAreaWidth"
300 value: shell.edgeSize
301 }
302 }
303 }
305 Lockscreen {
306 id: lockscreen
307 objectName: "lockscreen"
309 readonly property int backgroundTopMargin: -panel.panelHeight
311 hides: [launcher, panel.indicators, hud]
312 shown: false
313 enabled: true
314 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
315 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
317 y: panel.panelHeight
318 x: required ? 0 : - width
319 width: parent.width
320 height: parent.height - panel.panelHeight
321 Behavior on y { StandardAnimation {} }
323 background: shell.background
324 pinLength: 4
326 onEntered: LightDM.Greeter.respond(passphrase);
327 onCancel: greeter.show()
329 Component.onCompleted: {
330 if (LightDM.Users.count == 1) {
331 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
332 }
333 }
334 }
336 Connections {
337 target: LightDM.Greeter
339 onShowPrompt: {
340 if (LightDM.Users.count == 1) {
341 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
342 if (text == "PIN") {
343 lockscreen.alphaNumeric = false
344 } else {
345 lockscreen.alphaNumeric = true
346 }
347 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
348 lockscreen.show();
349 }
350 }
352 onAuthenticationComplete: {
353 if (LightDM.Greeter.promptless) {
354 return;
355 }
356 if (LightDM.Greeter.authenticated) {
357 lockscreen.hide();
358 } else {
359 lockscreen.clear(true);
360 }
361 }
362 }
364 Rectangle {
365 anchors.fill: parent
366 color: "black"
367 opacity: greeterWrapper.showProgress * 0.8
368 }
370 Item {
371 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
372 id: greeterWrapper
373 x: launcher.progress
374 y: panel.panelHeight
375 width: parent.width
376 height: parent.height - panel.panelHeight
377 Behavior on y { StandardAnimation {} }
378 Behavior on height { StandardAnimation {} }
380 Behavior on x {SmoothedAnimation{velocity: 600}}
382 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
384 Greeter {
385 id: greeter
386 objectName: "greeter"
388 available: true
389 hides: [launcher, panel.indicators, hud]
390 shown: true
392 defaultBackground: shell.background
394 width: parent.width
395 height: parent.height
397 dragHandleWidth: shell.edgeSize
399 onShownChanged: {
400 if (shown) {
401 lockscreen.reset();
402 // If there is only one user, we start authenticating with that one here.
403 // If there are more users, the Greeter will handle that
404 if (LightDM.Users.count == 1) {
405 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
406 }
407 greeter.forceActiveFocus();
408 }
409 }
411 onUnlocked: greeter.hide()
412 onSelected: {
413 // Update launcher items for new user
414 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
415 AccountsService.user = user;
416 LauncherModel.setUser(user);
417 }
419 onTease: launcher.tease()
421 Binding {
422 target: ApplicationManager
423 property: "suspended"
424 value: greeter.shown && greeterWrapper.showProgress == 1
425 }
426 }
427 }
429 InputFilterArea {
430 anchors.fill: parent
431 blockInput: ApplicationManager.focusedApplicationId.length === 0 || greeter.shown || lockscreen.shown || launcher.shown
432 || panel.indicators.shown || hud.shown
433 }
435 Connections {
436 id: powerConnection
437 target: Powerd
439 onDisplayPowerStateChange: {
440 // We ignore any display-off signals when the proximity sensor
441 // is active. This usually indicates something like a phone call.
442 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
443 greeter.showNow();
444 }
446 // No reason to chew demo CPU when user isn't watching
447 if (status == Powerd.Off) {
448 edgeDemo.paused = true;
449 } else if (status == Powerd.On) {
450 edgeDemo.paused = false;
451 }
452 }
453 }
455 function showHome() {
456 var animate = !greeter.shown && !stages.shown
457 greeter.hide()
458 dash.setCurrentScope("clickscope", animate, false)
459 stages.hide()
460 }
462 function hideIndicatorMenu(delay) {
463 panel.hideIndicatorMenu(delay);
464 }
466 Item {
467 id: overlay
469 anchors.fill: parent
471 Panel {
472 id: panel
473 anchors.fill: parent //because this draws indicator menus
474 indicators {
475 hides: [launcher]
476 available: edgeDemo.panelEnabled
477 contentEnabled: edgeDemo.panelContentEnabled
478 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
479 panelHeight: units.gu(3)
480 }
481 property string focusedAppId: ApplicationManager.focusedApplicationId
482 property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
483 fullscreenMode: focusedApplication && stages.fullscreen && !greeter.shown && !lockscreen.shown
484 searchVisible: !greeter.shown && !lockscreen.shown && dash.shown && dash.searchable
486 InputFilterArea {
487 anchors {
488 top: parent.top
489 left: parent.left
490 right: parent.right
491 }
492 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
493 blockInput: true
494 }
495 }
497 Hud {
498 id: hud
500 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
501 height: parent.height
503 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown && edgeDemo.dashEnabled
504 shown: false
505 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
506 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
508 Connections {
509 target: ApplicationManager
510 onFocusedApplicationIdChanged: hud.hide()
511 }
512 }
514 Revealer {
515 id: hudRevealer
517 enabled: hud.shown
518 width: hud.width
519 anchors.left: hud.left
520 height: parent.height
521 target: hud.revealerTarget
522 closedValue: height
523 openedValue: 0
524 direction: Qt.RightToLeft
525 orientation: Qt.Vertical
526 handleSize: hud.handleHeight
527 onCloseClicked: target.hide()
528 }
530 Bottombar {
531 id: bottombar
532 theHud: hud
533 anchors.fill: parent
534 enabled: hud.available
535 applicationIsOnForeground: ApplicationManager.focusedApplicationId
536 }
538 InputFilterArea {
539 blockInput: launcher.shown
540 anchors {
541 top: parent.top
542 bottom: parent.bottom
543 left: parent.left
544 }
545 width: launcher.width
546 }
548 Launcher {
549 id: launcher
551 readonly property bool dashSwipe: progress > 0
553 anchors.top: parent.top
554 anchors.bottom: parent.bottom
555 width: parent.width
556 dragAreaWidth: shell.edgeSize
557 available: edgeDemo.launcherEnabled
559 onShowDashHome: {
560 if (edgeDemo.running)
561 return;
563 showHome()
564 }
565 onDash: {
566 if (stages.shown && !stages.overlayMode) {
567 if (!stages.locked) {
568 stages.hide();
569 launcher.hide();
570 }
571 }
572 if (greeter.shown) {
573 greeter.hideRight();
574 launcher.hide();
575 }
576 }
577 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
578 onLauncherApplicationSelected: {
579 if (!edgeDemo.running)
580 shell.activateApplication(appId)
581 }
582 onShownChanged: {
583 if (shown) {
584 panel.indicators.hide()
585 hud.hide()
586 bottombar.hide()
587 }
588 }
589 }
591 Notifications {
592 id: notifications
594 model: NotificationBackend.Model
595 margin: units.gu(1)
597 y: panel.panelHeight
598 width: parent.width
599 height: parent.height - panel.panelHeight
600 Behavior on y { StandardAnimation {} }
601 Behavior on height { StandardAnimation {} }
603 states: [
604 State {
605 name: "narrow"
606 when: overlay.width <= units.gu(60)
607 AnchorChanges { target: notifications; anchors.left: parent.left }
608 },
609 State {
610 name: "wide"
611 when: overlay.width > units.gu(60)
612 AnchorChanges { target: notifications; anchors.left: undefined }
613 PropertyChanges { target: notifications; width: units.gu(38) }
614 }
615 ]
617 InputFilterArea {
618 anchors { left: parent.left; right: parent.right }
619 height: parent.contentHeight
620 blockInput: height > 0
621 }
622 }
623 }
625 focus: true
626 onFocusChanged: if (!focus) forceActiveFocus();
628 InputFilterArea {
629 anchors {
630 top: parent.top
631 bottom: parent.bottom
632 left: parent.left
633 }
634 width: shell.edgeSize
635 blockInput: true
636 }
638 InputFilterArea {
639 anchors {
640 top: parent.top
641 bottom: parent.bottom
642 right: parent.right
643 }
644 width: shell.edgeSize
645 blockInput: true
646 }
648 Binding {
649 target: i18n
650 property: "domain"
651 value: "unity8"
652 }
654 OSKController {
655 y: panel.panelHeight
656 width: parent.width
657 height: parent.height - panel.panelHeight
658 Behavior on y { StandardAnimation {} }
659 Behavior on height { StandardAnimation {} }
660 }
662 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
663 MouseArea {
664 anchors.bottom: parent.bottom
665 anchors.left: parent.left
666 anchors.right: parent.right
667 height: ApplicationManager.keyboardVisible ? ApplicationManager.keyboardHeight : 0
669 enabled: ApplicationManager.keyboardVisible
670 }
672 Label {
673 anchors.centerIn: parent
674 visible: ApplicationManager.fake ? ApplicationManager.fake : false
675 text: "EARLY ALPHA\nNOT READY FOR USE"
676 color: "lightgrey"
677 opacity: 0.2
678 font.weight: Font.Black
679 horizontalAlignment: Text.AlignHCenter
680 verticalAlignment: Text.AlignVCenter
681 fontSizeMode: Text.Fit
682 rotation: -45
683 scale: Math.min(parent.width, parent.height) / width
684 }
686 EdgeDemo {
687 id: edgeDemo
688 greeter: greeter
689 launcher: launcher
690 dash: dash
691 indicators: panel.indicators
692 underlay: underlay
693 }
695 Connections {
696 target: SessionBroadcast
697 onShowHome: showHome()
698 }