on b1987f5 help sign in
unity8/qml/Shell.qml
690 lines 22 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.indicatorsMenuWidth
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 anchors {
155 fill: parent
156 topMargin: panel.panelHeight
157 }
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 }
316 y: panel.panelHeight
317 x: required ? 0 : - width
318 width: parent.width
319 height: parent.height - panel.panelHeight
320 background: shell.background
321 pinLength: 4
323 onEntered: LightDM.Greeter.respond(passphrase);
324 onCancel: greeter.show()
326 Component.onCompleted: {
327 if (LightDM.Users.count == 1) {
328 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
329 }
330 }
331 }
333 Connections {
334 target: LightDM.Greeter
336 onShowPrompt: {
337 if (LightDM.Users.count == 1) {
338 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
339 if (text == "PIN") {
340 lockscreen.alphaNumeric = false
341 } else {
342 lockscreen.alphaNumeric = true
343 }
344 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
345 lockscreen.show();
346 }
347 }
349 onAuthenticationComplete: {
350 if (LightDM.Greeter.promptless) {
351 return;
352 }
353 if (LightDM.Greeter.authenticated) {
354 lockscreen.hide();
355 } else {
356 lockscreen.clear(true);
357 }
358 }
359 }
361 Rectangle {
362 anchors.fill: parent
363 color: "black"
364 opacity: greeterWrapper.showProgress * 0.8
365 }
367 Item {
368 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
369 id: greeterWrapper
370 x: launcher.progress
371 y: panel.panelHeight
372 width: parent.width
373 height: parent.height - panel.panelHeight
375 Behavior on x {SmoothedAnimation{velocity: 600}}
377 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
379 Greeter {
380 id: greeter
381 objectName: "greeter"
383 available: true
384 hides: [launcher, panel.indicators, hud]
385 shown: true
387 defaultBackground: shell.background
389 width: parent.width
390 height: parent.height
392 dragHandleWidth: shell.edgeSize
394 onShownChanged: {
395 if (shown) {
396 lockscreen.reset();
397 // If there is only one user, we start authenticating with that one here.
398 // If there are more users, the Greeter will handle that
399 if (LightDM.Users.count == 1) {
400 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
401 }
402 greeter.forceActiveFocus();
403 }
404 }
406 onUnlocked: greeter.hide()
407 onSelected: {
408 // Update launcher items for new user
409 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
410 AccountsService.user = user;
411 LauncherModel.setUser(user);
412 }
414 onTease: launcher.tease()
416 Binding {
417 target: ApplicationManager
418 property: "suspended"
419 value: greeter.shown && greeterWrapper.showProgress == 1
420 }
421 }
422 }
424 InputFilterArea {
425 anchors.fill: parent
426 blockInput: ApplicationManager.focusedApplicationId.length === 0 || greeter.shown || lockscreen.shown || launcher.shown
427 || panel.indicators.shown || hud.shown
428 }
430 Connections {
431 id: powerConnection
432 target: Powerd
434 onDisplayPowerStateChange: {
435 // We ignore any display-off signals when the proximity sensor
436 // is active. This usually indicates something like a phone call.
437 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
438 greeter.showNow();
439 }
441 // No reason to chew demo CPU when user isn't watching
442 if (status == Powerd.Off) {
443 edgeDemo.paused = true;
444 } else if (status == Powerd.On) {
445 edgeDemo.paused = false;
446 }
447 }
448 }
450 function showHome() {
451 var animate = !greeter.shown && !stages.shown
452 greeter.hide()
453 dash.setCurrentScope("clickscope", animate, false)
454 stages.hide()
455 }
457 function hideIndicatorMenu(delay) {
458 panel.hideIndicatorMenu(delay);
459 }
461 Item {
462 id: overlay
464 anchors.fill: parent
466 Panel {
467 id: panel
468 anchors.fill: parent //because this draws indicator menus
469 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
470 indicators {
471 hides: [launcher]
472 available: edgeDemo.panelEnabled
473 contentEnabled: edgeDemo.panelContentEnabled
474 }
475 property string focusedAppId: ApplicationManager.focusedApplicationId
476 property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
477 fullscreenMode: focusedApplication && stages.fullscreen && !greeter.shown && !lockscreen.shown
478 searchVisible: !greeter.shown && !lockscreen.shown && dash.shown && dash.searchable
480 InputFilterArea {
481 anchors {
482 top: parent.top
483 left: parent.left
484 right: parent.right
485 }
486 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
487 blockInput: true
488 }
489 }
491 Hud {
492 id: hud
494 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
495 height: parent.height
497 available: !greeter.shown && !panel.indicators.shown && !lockscreen.shown && edgeDemo.dashEnabled
498 shown: false
499 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
500 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
502 Connections {
503 target: ApplicationManager
504 onFocusedApplicationIdChanged: hud.hide()
505 }
506 }
508 Revealer {
509 id: hudRevealer
511 enabled: hud.shown
512 width: hud.width
513 anchors.left: hud.left
514 height: parent.height
515 target: hud.revealerTarget
516 closedValue: height
517 openedValue: 0
518 direction: Qt.RightToLeft
519 orientation: Qt.Vertical
520 handleSize: hud.handleHeight
521 onCloseClicked: target.hide()
522 }
524 Bottombar {
525 id: bottombar
526 theHud: hud
527 anchors.fill: parent
528 enabled: hud.available
529 applicationIsOnForeground: ApplicationManager.focusedApplicationId
530 }
532 InputFilterArea {
533 blockInput: launcher.shown
534 anchors {
535 top: parent.top
536 bottom: parent.bottom
537 left: parent.left
538 }
539 width: launcher.width
540 }
542 Launcher {
543 id: launcher
545 readonly property bool dashSwipe: progress > 0
547 anchors.top: parent.top
548 anchors.bottom: parent.bottom
549 width: parent.width
550 dragAreaWidth: shell.edgeSize
551 available: edgeDemo.launcherEnabled
553 onShowDashHome: {
554 if (edgeDemo.running)
555 return;
557 showHome()
558 }
559 onDash: {
560 if (stages.shown && !stages.overlayMode) {
561 if (!stages.locked) {
562 stages.hide();
563 launcher.hide();
564 }
565 }
566 if (greeter.shown) {
567 greeter.hideRight();
568 launcher.hide();
569 }
570 }
571 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
572 onLauncherApplicationSelected: {
573 if (!edgeDemo.running)
574 shell.activateApplication(appId)
575 }
576 onShownChanged: {
577 if (shown) {
578 panel.indicators.hide()
579 hud.hide()
580 bottombar.hide()
581 }
582 }
583 }
585 Notifications {
586 id: notifications
588 model: NotificationBackend.Model
589 margin: units.gu(1)
591 anchors {
592 top: parent.top
593 right: parent.right
594 bottom: parent.bottom
595 topMargin: panel.panelHeight
596 }
597 states: [
598 State {
599 name: "narrow"
600 when: overlay.width <= units.gu(60)
601 AnchorChanges { target: notifications; anchors.left: parent.left }
602 },
603 State {
604 name: "wide"
605 when: overlay.width > units.gu(60)
606 AnchorChanges { target: notifications; anchors.left: undefined }
607 PropertyChanges { target: notifications; width: units.gu(38) }
608 }
609 ]
611 InputFilterArea {
612 anchors { left: parent.left; right: parent.right }
613 height: parent.contentHeight
614 blockInput: height > 0
615 }
616 }
617 }
619 focus: true
620 onFocusChanged: if (!focus) forceActiveFocus();
622 InputFilterArea {
623 anchors {
624 top: parent.top
625 bottom: parent.bottom
626 left: parent.left
627 }
628 width: shell.edgeSize
629 blockInput: true
630 }
632 InputFilterArea {
633 anchors {
634 top: parent.top
635 bottom: parent.bottom
636 right: parent.right
637 }
638 width: shell.edgeSize
639 blockInput: true
640 }
642 Binding {
643 target: i18n
644 property: "domain"
645 value: "unity8"
646 }
648 OSKController {
649 anchors.topMargin: panel.panelHeight
650 anchors.fill: parent // as needs to know the geometry of the shell
651 }
653 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
654 MouseArea {
655 anchors.bottom: parent.bottom
656 anchors.left: parent.left
657 anchors.right: parent.right
658 height: ApplicationManager.keyboardVisible ? ApplicationManager.keyboardHeight : 0
660 enabled: ApplicationManager.keyboardVisible
661 }
663 Label {
664 anchors.centerIn: parent
665 visible: ApplicationManager.fake ? ApplicationManager.fake : false
666 text: "EARLY ALPHA\nNOT READY FOR USE"
667 color: "lightgrey"
668 opacity: 0.2
669 font.weight: Font.Black
670 horizontalAlignment: Text.AlignHCenter
671 verticalAlignment: Text.AlignVCenter
672 fontSizeMode: Text.Fit
673 rotation: -45
674 scale: Math.min(parent.width, parent.height) / width
675 }
677 EdgeDemo {
678 id: edgeDemo
679 greeter: greeter
680 launcher: launcher
681 dash: dash
682 indicators: panel.indicators
683 underlay: underlay
684 }
686 Connections {
687 target: SessionBroadcast
688 onShowHome: showHome()
689 }