on 4e86d64 help sign in
unity8/qml/Shell.qml
660 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.indicators.width
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 Rectangle {
121 anchors.fill: parent
122 color: "black"
123 opacity: dash.disappearingAnimationProgress
124 }
126 Image {
127 anchors.fill: dash
128 source: shell.width > shell.height ? "Dash/graphics/paper_landscape.png" : "Dash/graphics/paper_portrait.png"
129 fillMode: Image.PreserveAspectCrop
130 horizontalAlignment: Image.AlignRight
131 verticalAlignment: Image.AlignTop
132 }
134 Dash {
135 id: dash
136 objectName: "dash"
138 available: !greeter.shown && !lockscreen.shown
139 hides: [stages, launcher, panel.indicators]
140 shown: disappearingAnimationProgress !== 1.0 && greeterWrapper.showProgress !== 1.0
141 enabled: disappearingAnimationProgress === 0.0 && greeterWrapper.showProgress === 0.0 && edgeDemo.dashEnabled
143 anchors {
144 fill: parent
145 topMargin: panel.panelHeight
146 }
148 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
149 opacity: 1.0 - disappearingAnimationProgress
150 property real disappearingAnimationProgress: {
151 if (stages.overlayMode) {
152 return 0;
153 } else {
154 return stages.showProgress
155 }
156 }
158 // FIXME: only necessary because stages.showProgress is not animated
159 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
160 }
161 }
162 }
164 EdgeDragArea {
165 id: stagesDragHandle
166 direction: Direction.Leftwards
168 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
169 width: shell.edgeSize
171 property real progress: stages.width
173 onTouchXChanged: {
174 if (status == DirectionalDragArea.Recognized) {
175 if (ApplicationManager.count == 0) {
176 progress = Math.max(stages.width - stagesDragHandle.width + touchX, stages.width * .3)
177 } else {
178 progress = stages.width - stagesDragHandle.width + touchX
179 }
180 }
181 }
183 onDraggingChanged: {
184 if (!dragging) {
185 if (ApplicationManager.count > 0 && progress < stages.width - units.gu(10)) {
186 stages.show(true)
187 }
188 stagesDragHandle.progress = stages.width;
189 }
190 }
191 }
193 Item {
194 id: stages
195 objectName: "stages"
196 width: parent.width
197 height: parent.height
199 x: {
200 if (shown) {
201 if (overlayMode || locked) {
202 return 0;
203 }
204 return launcher.progress
205 } else {
206 return stagesDragHandle.progress
207 }
208 }
210 Behavior on x { SmoothedAnimation { velocity: 600; duration: UbuntuAnimation.FastDuration } }
212 property bool shown: false
214 property real showProgress: overlayMode ? 0 : MathUtils.clamp(1 - x / shell.width, 0, 1)
216 property bool fullyShown: x == 0
217 property bool fullyHidden: x == width
219 property bool painting: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.painting : false
220 property bool fullscreen: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.fullscreen : false
221 property bool overlayMode: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayMode : false
222 property int overlayWidth: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayWidth : false
223 property bool locked: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.locked : false
225 function show(focusApp) {
226 shown = true;
227 panel.indicators.hide();
228 edgeDemo.stopDemo();
229 greeter.hide();
230 if (!ApplicationManager.focusedApplicationId && ApplicationManager.count > 0 && focusApp) {
231 ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
232 }
233 }
235 function hide() {
236 shown = false;
237 if (ApplicationManager.focusedApplicationId) {
238 ApplicationManager.unfocusCurrentApplication();
239 }
240 }
242 Connections {
243 target: ApplicationManager
245 onFocusRequested: {
246 stages.show(true);
247 }
249 onFocusedApplicationIdChanged: {
250 if (ApplicationManager.focusedApplicationId.length > 0) {
251 stages.show(false);
252 } else {
253 if (!stages.overlayMode) {
254 stages.hide();
255 }
256 }
257 }
259 onApplicationAdded: {
260 stages.show(false);
261 }
263 onApplicationRemoved: {
264 if (ApplicationManager.focusedApplicationId.length == 0) {
265 stages.hide();
266 }
267 }
268 }
270 Loader {
271 id: applicationsDisplayLoader
272 anchors.fill: parent
274 source: shell.sideStageEnabled ? "Stages/StageWithSideStage.qml" : "Stages/PhoneStage.qml"
276 Binding {
277 target: applicationsDisplayLoader.item
278 property: "moving"
279 value: !stages.fullyShown
280 }
281 Binding {
282 target: applicationsDisplayLoader.item
283 property: "shown"
284 value: stages.shown
285 }
286 Binding {
287 target: applicationsDisplayLoader.item
288 property: "dragAreaWidth"
289 value: shell.edgeSize
290 }
291 }
292 }
294 Lockscreen {
295 id: lockscreen
296 objectName: "lockscreen"
298 readonly property int backgroundTopMargin: -panel.panelHeight
300 hides: [launcher, panel.indicators]
301 shown: false
302 enabled: true
303 showAnimation: StandardAnimation { property: "opacity"; to: 1 }
304 hideAnimation: StandardAnimation { property: "opacity"; to: 0 }
306 anchors {
307 top: parent.top
308 right: parent.right
309 bottom: parent.bottom
310 topMargin: panel.panelHeight
311 }
312 background: shell.background
313 minPinLength: 4
314 maxPinLength: 4
316 onEntered: LightDM.Greeter.respond(passphrase);
317 onCancel: greeter.show()
319 Component.onCompleted: {
320 if (LightDM.Users.count == 1) {
321 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
322 }
323 }
324 }
326 Connections {
327 target: LightDM.Greeter
329 onShowPrompt: {
330 if (LightDM.Users.count == 1) {
331 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
332 if (text == "PIN") {
333 lockscreen.alphaNumeric = false
334 } else {
335 lockscreen.alphaNumeric = true
336 }
337 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
338 lockscreen.show();
339 }
340 }
342 onAuthenticationComplete: {
343 if (LightDM.Greeter.promptless) {
344 return;
345 }
346 if (LightDM.Greeter.authenticated) {
347 lockscreen.hide();
348 } else {
349 lockscreen.clear(true);
350 }
351 }
352 }
354 Rectangle {
355 anchors.fill: parent
356 color: "black"
357 opacity: greeterWrapper.showProgress * 0.8
358 }
360 Item {
361 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
362 id: greeterWrapper
363 x: launcher.progress
364 y: panel.panelHeight
365 width: parent.width
366 height: parent.height - panel.panelHeight
368 Behavior on x {
369 enabled: !launcher.dashSwipe
370 StandardAnimation {}
371 }
373 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
375 Greeter {
376 id: greeter
377 objectName: "greeter"
379 available: true
380 hides: [launcher, panel.indicators]
381 shown: true
383 defaultBackground: shell.background
385 width: parent.width
386 height: parent.height
388 dragHandleWidth: shell.edgeSize
390 onShownChanged: {
391 if (shown) {
392 lockscreen.reset();
393 // If there is only one user, we start authenticating with that one here.
394 // If there are more users, the Greeter will handle that
395 if (LightDM.Users.count == 1) {
396 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
397 }
398 greeter.forceActiveFocus();
399 }
400 }
402 onUnlocked: greeter.hide()
403 onSelected: {
404 // Update launcher items for new user
405 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
406 AccountsService.user = user;
407 LauncherModel.setUser(user);
408 }
410 onTease: launcher.tease()
412 Binding {
413 target: ApplicationManager
414 property: "suspended"
415 value: greeter.shown && greeterWrapper.showProgress == 1
416 }
417 }
418 }
420 InputFilterArea {
421 anchors.fill: parent
422 blockInput: ApplicationManager.focusedApplicationId.length === 0 || greeter.shown || lockscreen.shown || launcher.shown
423 || panel.indicators.shown
424 }
426 Connections {
427 id: powerConnection
428 target: Powerd
430 onDisplayPowerStateChange: {
431 // We ignore any display-off signals when the proximity sensor
432 // is active. This usually indicates something like a phone call.
433 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
434 greeter.showNow();
435 }
437 // No reason to chew demo CPU when user isn't watching
438 if (status == Powerd.Off) {
439 edgeDemo.paused = true;
440 } else if (status == Powerd.On) {
441 edgeDemo.paused = false;
442 }
443 }
444 }
446 function showHome() {
447 var animate = !greeter.shown && !stages.shown
448 greeter.hide()
449 dash.setCurrentScope("clickscope", animate, false)
450 stages.hide()
451 }
453 function hideIndicatorMenu(delay) {
454 panel.hideIndicatorMenu(delay);
455 }
457 Item {
458 id: overlay
460 anchors.fill: parent
462 Panel {
463 id: panel
464 anchors.fill: parent //because this draws indicator menus
465 indicators {
466 hides: [launcher]
467 available: edgeDemo.panelEnabled
468 contentEnabled: edgeDemo.panelContentEnabled
469 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
470 panelHeight: units.gu(3)
471 }
472 property string focusedAppId: ApplicationManager.focusedApplicationId
473 property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
474 fullscreenMode: focusedApplication && stages.fullscreen && !greeter.shown && !lockscreen.shown
475 searchVisible: !greeter.shown && !lockscreen.shown && dash.shown && dash.searchable
477 InputFilterArea {
478 anchors {
479 top: parent.top
480 left: parent.left
481 right: parent.right
482 }
483 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
484 blockInput: true
485 }
486 }
488 InputFilterArea {
489 blockInput: launcher.shown
490 anchors {
491 top: parent.top
492 bottom: parent.bottom
493 left: parent.left
494 }
495 width: launcher.width
496 }
498 Launcher {
499 id: launcher
501 readonly property bool dashSwipe: progress > 0
503 anchors.top: parent.top
504 anchors.bottom: parent.bottom
505 width: parent.width
506 dragAreaWidth: shell.edgeSize
507 available: edgeDemo.launcherEnabled
509 onShowDashHome: {
510 if (edgeDemo.running)
511 return;
513 showHome()
514 }
515 onDash: {
516 if (stages.shown && !stages.overlayMode) {
517 if (!stages.locked) {
518 stages.hide();
519 launcher.hide();
520 }
521 }
522 if (greeter.shown) {
523 greeter.hideRight();
524 launcher.hide();
525 }
526 }
527 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
528 onLauncherApplicationSelected: {
529 if (!edgeDemo.running)
530 shell.activateApplication(appId)
531 }
532 onShownChanged: {
533 if (shown) {
534 panel.indicators.hide()
535 }
536 }
537 }
539 Rectangle {
540 id: modalNotificationBackground
542 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
543 color: "#000000"
544 anchors.fill: parent
545 opacity: 0.5
547 MouseArea {
548 anchors.fill: parent
549 }
551 InputFilterArea {
552 anchors.fill: parent
553 blockInput: modalNotificationBackground.visible
554 }
555 }
557 Notifications {
558 id: notifications
560 model: NotificationBackend.Model
561 margin: units.gu(1)
563 y: panel.panelHeight
564 width: parent.width
565 height: parent.height - panel.panelHeight
567 states: [
568 State {
569 name: "narrow"
570 when: overlay.width <= units.gu(60)
571 AnchorChanges { target: notifications; anchors.left: parent.left }
572 },
573 State {
574 name: "wide"
575 when: overlay.width > units.gu(60)
576 AnchorChanges { target: notifications; anchors.left: undefined }
577 PropertyChanges { target: notifications; width: units.gu(38) }
578 }
579 ]
581 InputFilterArea {
582 anchors { left: parent.left; right: parent.right }
583 height: parent.contentHeight
584 blockInput: height > 0
585 }
586 }
587 }
589 focus: true
590 onFocusChanged: if (!focus) forceActiveFocus();
592 InputFilterArea {
593 anchors {
594 top: parent.top
595 bottom: parent.bottom
596 left: parent.left
597 }
598 width: shell.edgeSize
599 blockInput: true
600 }
602 InputFilterArea {
603 anchors {
604 top: parent.top
605 bottom: parent.bottom
606 right: parent.right
607 }
608 width: shell.edgeSize
609 blockInput: true
610 }
612 Binding {
613 target: i18n
614 property: "domain"
615 value: "unity8"
616 }
618 OSKController {
619 anchors.topMargin: panel.panelHeight
620 anchors.fill: parent // as needs to know the geometry of the shell
621 }
623 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
624 MouseArea {
625 anchors.bottom: parent.bottom
626 anchors.left: parent.left
627 anchors.right: parent.right
628 height: ApplicationManager.keyboardVisible ? ApplicationManager.keyboardHeight : 0
630 enabled: ApplicationManager.keyboardVisible
631 }
633 Label {
634 anchors.centerIn: parent
635 visible: ApplicationManager.fake ? ApplicationManager.fake : false
636 text: "EARLY ALPHA\nNOT READY FOR USE"
637 color: "lightgrey"
638 opacity: 0.2
639 font.weight: Font.Black
640 horizontalAlignment: Text.AlignHCenter
641 verticalAlignment: Text.AlignVCenter
642 fontSizeMode: Text.Fit
643 rotation: -45
644 scale: Math.min(parent.width, parent.height) / width
645 }
647 EdgeDemo {
648 id: edgeDemo
649 greeter: greeter
650 launcher: launcher
651 dash: dash
652 indicators: panel.indicators
653 underlay: underlay
654 }
656 Connections {
657 target: SessionBroadcast
658 onShowHome: showHome()
659 }