on eeea6cf help sign in
unity8/qml/Shell.qml
663 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 "Components"
32import "Notifications"
33import Unity.Notifications 1.0 as NotificationBackend
35FocusScope {
36 id: shell
38 // this is only here to select the width / height of the window if not running fullscreen
39 property bool tablet: false
40 width: tablet ? units.gu(160) : applicationArguments.hasGeometry() ? applicationArguments.width() : units.gu(40)
41 height: tablet ? units.gu(100) : applicationArguments.hasGeometry() ? applicationArguments.height() : units.gu(71)
43 property real edgeSize: units.gu(2)
44 property url defaultBackground: Qt.resolvedUrl(shell.width >= units.gu(60) ? "graphics/tablet_background.jpg" : "graphics/phone_background.jpg")
45 property url background
46 readonly property real panelHeight: panel.panelHeight
48 property bool dashShown: dash.shown
50 property bool sideStageEnabled: shell.width >= units.gu(100)
51 readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
53 function activateApplication(appId) {
54 if (ApplicationManager.findApplication(appId)) {
55 ApplicationManager.requestFocusApplication(appId);
56 stages.show(true);
57 if (stages.locked && ApplicationManager.focusedApplicationId == appId) {
58 applicationsDisplayLoader.item.select(appId);
59 }
60 } else {
61 var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
62 ApplicationManager.startApplication(appId, execFlags);
63 stages.show(false);
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 VolumeControl {
87 id: volumeControl
88 }
90 Keys.onVolumeUpPressed: volumeControl.volumeUp()
91 Keys.onVolumeDownPressed: volumeControl.volumeDown()
93 Item {
94 id: underlayClipper
95 anchors.fill: parent
96 anchors.rightMargin: stages.overlayWidth
97 clip: stages.overlayMode && !stages.painting
99 InputFilterArea {
100 anchors.fill: parent
101 blockInput: parent.clip
102 }
104 Item {
105 id: underlay
106 objectName: "underlay"
107 anchors.fill: parent
108 anchors.rightMargin: -parent.anchors.rightMargin
110 // Whether the underlay is fully covered by opaque UI elements.
111 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
113 // Whether the user should see the topmost application surface (if there's one at all).
114 readonly property bool applicationSurfaceShouldBeSeen: stages.shown && !stages.painting && !stages.overlayMode
116 // NB! Application surfaces are stacked behind the shell one. So they can only be seen by the user
117 // through the translucent parts of the shell surface.
118 visible: !fullyCovered && !applicationSurfaceShouldBeSeen
120 CrossFadeImage {
121 id: backgroundImage
122 objectName: "backgroundImage"
124 anchors.fill: parent
125 source: shell.background
126 fillMode: Image.PreserveAspectCrop
127 }
129 Rectangle {
130 anchors.fill: parent
131 color: "black"
132 opacity: dash.disappearingAnimationProgress
133 }
135 Image {
136 anchors.fill: dash
137 source: shell.width > shell.height ? "Dash/graphics/paper_landscape.png" : "Dash/graphics/paper_portrait.png"
138 fillMode: Image.PreserveAspectCrop
139 horizontalAlignment: Image.AlignRight
140 verticalAlignment: Image.AlignTop
141 }
143 Dash {
144 id: dash
145 objectName: "dash"
147 available: !greeter.shown && !lockscreen.shown
148 hides: [stages, launcher, panel.indicators]
149 shown: disappearingAnimationProgress !== 1.0 && greeterWrapper.showProgress !== 1.0
150 enabled: disappearingAnimationProgress === 0.0 && greeterWrapper.showProgress === 0.0 && edgeDemo.dashEnabled
152 anchors {
153 fill: parent
154 topMargin: panel.panelHeight
155 }
157 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
158 opacity: 1.0 - disappearingAnimationProgress
159 property real disappearingAnimationProgress: {
160 if (stages.overlayMode) {
161 return 0;
162 } else {
163 return stages.showProgress
164 }
165 }
167 // FIXME: only necessary because stages.showProgress is not animated
168 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
169 }
170 }
171 }
173 EdgeDragArea {
174 id: stagesDragHandle
175 direction: Direction.Leftwards
177 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
178 width: shell.edgeSize
180 property real progress: stages.width
182 onTouchXChanged: {
183 if (status == DirectionalDragArea.Recognized) {
184 if (ApplicationManager.count == 0) {
185 progress = Math.max(stages.width - stagesDragHandle.width + touchX, stages.width * .3)
186 } else {
187 progress = stages.width - stagesDragHandle.width + touchX
188 }
189 }
190 }
192 onDraggingChanged: {
193 if (!dragging) {
194 if (ApplicationManager.count > 0 && progress < stages.width - units.gu(10)) {
195 stages.show(true)
196 }
197 stagesDragHandle.progress = stages.width;
198 }
199 }
200 }
202 Item {
203 id: stages
204 objectName: "stages"
205 width: parent.width
206 height: parent.height
208 x: {
209 if (shown) {
210 if (overlayMode || locked) {
211 return 0;
212 }
213 return launcher.progress
214 } else {
215 return stagesDragHandle.progress
216 }
217 }
219 Behavior on x { SmoothedAnimation { velocity: 600; duration: UbuntuAnimation.FastDuration } }
221 property bool shown: false
223 property real showProgress: overlayMode ? 0 : MathUtils.clamp(1 - x / shell.width, 0, 1)
225 property bool fullyShown: x == 0
226 property bool fullyHidden: x == width
228 property bool painting: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.painting : false
229 property bool fullscreen: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.fullscreen : false
230 property bool overlayMode: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayMode : false
231 property int overlayWidth: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayWidth : false
232 property bool locked: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.locked : false
234 function show(focusApp) {
235 shown = true;
236 panel.indicators.hide();
237 edgeDemo.stopDemo();
238 greeter.hide();
239 if (!ApplicationManager.focusedApplicationId && ApplicationManager.count > 0 && focusApp) {
240 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
241 }
242 }
244 function hide() {
245 shown = false;
246 if (ApplicationManager.focusedApplicationId) {
247 ApplicationManager.unfocusCurrentApplication();
248 }
249 }
251 Connections {
252 target: ApplicationManager
254 onFocusRequested: {
255 stages.show(true);
256 }
258 onFocusedApplicationIdChanged: {
259 if (ApplicationManager.focusedApplicationId.length > 0) {
260 stages.show(false);
261 } else {
262 if (!stages.overlayMode) {
263 stages.hide();
264 }
265 }
266 }
268 onApplicationAdded: {
269 stages.show(false);
270 }
272 onApplicationRemoved: {
273 if (ApplicationManager.focusedApplicationId.length == 0) {
274 stages.hide();
275 }
276 }
277 }
279 Loader {
280 id: applicationsDisplayLoader
281 anchors.fill: parent
283 source: shell.sideStageEnabled ? "Stages/StageWithSideStage.qml" : "Stages/PhoneStage.qml"
285 Binding {
286 target: applicationsDisplayLoader.item
287 property: "moving"
288 value: !stages.fullyShown
289 }
290 Binding {
291 target: applicationsDisplayLoader.item
292 property: "shown"
293 value: stages.shown
294 }
295 Binding {
296 target: applicationsDisplayLoader.item
297 property: "dragAreaWidth"
298 value: shell.edgeSize
299 }
300 }
301 }
303 Lockscreen {
304 id: lockscreen
305 objectName: "lockscreen"
307 readonly property int backgroundTopMargin: -panel.panelHeight
309 hides: [launcher, panel.indicators]
310 shown: false
311 enabled: true
312 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
313 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
314 y: panel.panelHeight
315 x: required ? 0 : - width
316 width: parent.width
317 height: parent.height - panel.panelHeight
318 background: shell.background
319 pinLength: 4
321 onEntered: LightDM.Greeter.respond(passphrase);
322 onCancel: greeter.show()
324 Component.onCompleted: {
325 if (LightDM.Users.count == 1) {
326 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
327 }
328 }
329 }
331 Connections {
332 target: LightDM.Greeter
334 onShowPrompt: {
335 if (LightDM.Users.count == 1) {
336 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
337 if (text == "PIN") {
338 lockscreen.alphaNumeric = false
339 } else {
340 lockscreen.alphaNumeric = true
341 }
342 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
343 lockscreen.show();
344 }
345 }
347 onAuthenticationComplete: {
348 if (LightDM.Greeter.promptless) {
349 return;
350 }
351 if (LightDM.Greeter.authenticated) {
352 lockscreen.hide();
353 } else {
354 lockscreen.clear(true);
355 }
356 }
357 }
359 Rectangle {
360 anchors.fill: parent
361 color: "black"
362 opacity: greeterWrapper.showProgress * 0.8
363 }
365 Item {
366 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
367 id: greeterWrapper
368 x: launcher.progress
369 y: panel.panelHeight
370 width: parent.width
371 height: parent.height - panel.panelHeight
373 Behavior on x {SmoothedAnimation{velocity: 600}}
375 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
377 Greeter {
378 id: greeter
379 objectName: "greeter"
381 available: true
382 hides: [launcher, panel.indicators]
383 shown: true
385 defaultBackground: shell.background
387 width: parent.width
388 height: parent.height
390 dragHandleWidth: shell.edgeSize
392 onShownChanged: {
393 if (shown) {
394 lockscreen.reset();
395 // If there is only one user, we start authenticating with that one here.
396 // If there are more users, the Greeter will handle that
397 if (LightDM.Users.count == 1) {
398 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
399 }
400 greeter.forceActiveFocus();
401 }
402 }
404 onUnlocked: greeter.hide()
405 onSelected: {
406 // Update launcher items for new user
407 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
408 AccountsService.user = user;
409 LauncherModel.setUser(user);
410 }
412 onTease: launcher.tease()
414 Binding {
415 target: ApplicationManager
416 property: "suspended"
417 value: greeter.shown && greeterWrapper.showProgress == 1
418 }
419 }
420 }
422 InputFilterArea {
423 anchors.fill: parent
424 blockInput: ApplicationManager.focusedApplicationId.length === 0 || greeter.shown || lockscreen.shown || launcher.shown
425 || panel.indicators.shown
426 }
428 Connections {
429 id: powerConnection
430 target: Powerd
432 onDisplayPowerStateChange: {
433 // We ignore any display-off signals when the proximity sensor
434 // is active. This usually indicates something like a phone call.
435 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
436 greeter.showNow();
437 }
439 // No reason to chew demo CPU when user isn't watching
440 if (status == Powerd.Off) {
441 edgeDemo.paused = true;
442 } else if (status == Powerd.On) {
443 edgeDemo.paused = false;
444 }
445 }
446 }
448 function showHome() {
449 var animate = !greeter.shown && !stages.shown
450 greeter.hide()
451 dash.setCurrentScope("clickscope", animate, false)
452 stages.hide()
453 }
455 function hideIndicatorMenu(delay) {
456 panel.hideIndicatorMenu(delay);
457 }
459 Item {
460 id: overlay
462 anchors.fill: parent
464 Panel {
465 id: panel
466 anchors.fill: parent //because this draws indicator menus
467 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
468 indicators {
469 hides: [launcher]
470 available: edgeDemo.panelEnabled
471 contentEnabled: edgeDemo.panelContentEnabled
472 }
473 property string focusedAppId: ApplicationManager.focusedApplicationId
474 property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
475 fullscreenMode: focusedApplication && stages.fullscreen && !greeter.shown && !lockscreen.shown
476 searchVisible: !greeter.shown && !lockscreen.shown && dash.shown && dash.searchable
478 InputFilterArea {
479 anchors {
480 top: parent.top
481 left: parent.left
482 right: parent.right
483 }
484 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
485 blockInput: true
486 }
487 }
489 InputFilterArea {
490 blockInput: launcher.shown
491 anchors {
492 top: parent.top
493 bottom: parent.bottom
494 left: parent.left
495 }
496 width: launcher.width
497 }
499 Launcher {
500 id: launcher
502 readonly property bool dashSwipe: progress > 0
504 anchors.top: parent.top
505 anchors.bottom: parent.bottom
506 width: parent.width
507 dragAreaWidth: shell.edgeSize
508 available: edgeDemo.launcherEnabled
510 onShowDashHome: {
511 if (edgeDemo.running)
512 return;
514 showHome()
515 }
516 onDash: {
517 if (stages.shown && !stages.overlayMode) {
518 if (!stages.locked) {
519 stages.hide();
520 launcher.hide();
521 }
522 }
523 if (greeter.shown) {
524 greeter.hideRight();
525 launcher.hide();
526 }
527 }
528 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
529 onLauncherApplicationSelected: {
530 if (!edgeDemo.running)
531 shell.activateApplication(appId)
532 }
533 onShownChanged: {
534 if (shown) {
535 panel.indicators.hide()
536 }
537 }
538 }
540 Rectangle {
541 id: modalNotificationBackground
543 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
544 color: "#000000"
545 anchors.fill: parent
546 opacity: 0.5
548 MouseArea {
549 anchors.fill: parent
550 }
552 InputFilterArea {
553 anchors.fill: parent
554 blockInput: modalNotificationBackground.visible
555 }
556 }
558 Notifications {
559 id: notifications
561 model: NotificationBackend.Model
562 margin: units.gu(1)
564 anchors {
565 top: parent.top
566 right: parent.right
567 bottom: parent.bottom
568 topMargin: panel.panelHeight
569 }
570 states: [
571 State {
572 name: "narrow"
573 when: overlay.width <= units.gu(60)
574 AnchorChanges { target: notifications; anchors.left: parent.left }
575 },
576 State {
577 name: "wide"
578 when: overlay.width > units.gu(60)
579 AnchorChanges { target: notifications; anchors.left: undefined }
580 PropertyChanges { target: notifications; width: units.gu(38) }
581 }
582 ]
584 InputFilterArea {
585 anchors { left: parent.left; right: parent.right }
586 height: parent.contentHeight
587 blockInput: height > 0
588 }
589 }
590 }
592 focus: true
593 onFocusChanged: if (!focus) forceActiveFocus();
595 InputFilterArea {
596 anchors {
597 top: parent.top
598 bottom: parent.bottom
599 left: parent.left
600 }
601 width: shell.edgeSize
602 blockInput: true
603 }
605 InputFilterArea {
606 anchors {
607 top: parent.top
608 bottom: parent.bottom
609 right: parent.right
610 }
611 width: shell.edgeSize
612 blockInput: true
613 }
615 Binding {
616 target: i18n
617 property: "domain"
618 value: "unity8"
619 }
621 OSKController {
622 anchors.topMargin: panel.panelHeight
623 anchors.fill: parent // as needs to know the geometry of the shell
624 }
626 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
627 MouseArea {
628 anchors.bottom: parent.bottom
629 anchors.left: parent.left
630 anchors.right: parent.right
631 height: ApplicationManager.keyboardVisible ? ApplicationManager.keyboardHeight : 0
633 enabled: ApplicationManager.keyboardVisible
634 }
636 Label {
637 anchors.centerIn: parent
638 visible: ApplicationManager.fake ? ApplicationManager.fake : false
639 text: "EARLY ALPHA\nNOT READY FOR USE"
640 color: "lightgrey"
641 opacity: 0.2
642 font.weight: Font.Black
643 horizontalAlignment: Text.AlignHCenter
644 verticalAlignment: Text.AlignVCenter
645 fontSizeMode: Text.Fit
646 rotation: -45
647 scale: Math.min(parent.width, parent.height) / width
648 }
650 EdgeDemo {
651 id: edgeDemo
652 greeter: greeter
653 launcher: launcher
654 dash: dash
655 indicators: panel.indicators
656 underlay: underlay
657 }
659 Connections {
660 target: SessionBroadcast
661 onShowHome: showHome()
662 }