on 3f75838 help sign in
unity8/qml/Shell.qml
658 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 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 }
305 y: panel.panelHeight
306 x: required ? 0 : - width
307 width: parent.width
308 height: parent.height - panel.panelHeight
309 background: shell.background
310 minPinLength: 4
311 maxPinLength: 4
313 onEntered: LightDM.Greeter.respond(passphrase);
314 onCancel: greeter.show()
316 Component.onCompleted: {
317 if (LightDM.Users.count == 1) {
318 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole))
319 }
320 }
321 }
323 Connections {
324 target: LightDM.Greeter
326 onShowPrompt: {
327 if (LightDM.Users.count == 1) {
328 // TODO: There's no better way for now to determine if its a PIN or a passphrase.
329 if (text == "PIN") {
330 lockscreen.alphaNumeric = false
331 } else {
332 lockscreen.alphaNumeric = true
333 }
334 lockscreen.placeholderText = i18n.tr("Please enter %1").arg(text);
335 lockscreen.show();
336 }
337 }
339 onAuthenticationComplete: {
340 if (LightDM.Greeter.promptless) {
341 return;
342 }
343 if (LightDM.Greeter.authenticated) {
344 lockscreen.hide();
345 } else {
346 lockscreen.clear(true);
347 }
348 }
349 }
351 Rectangle {
352 anchors.fill: parent
353 color: "black"
354 opacity: greeterWrapper.showProgress * 0.8
355 }
357 Item {
358 // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
359 id: greeterWrapper
360 x: launcher.progress
361 y: panel.panelHeight
362 width: parent.width
363 height: parent.height - panel.panelHeight
365 Behavior on x {
366 enabled: !launcher.dashSwipe
367 StandardAnimation {}
368 }
370 readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
372 Greeter {
373 id: greeter
374 objectName: "greeter"
376 available: true
377 hides: [launcher, panel.indicators]
378 shown: true
380 defaultBackground: shell.background
382 width: parent.width
383 height: parent.height
385 dragHandleWidth: shell.edgeSize
387 onShownChanged: {
388 if (shown) {
389 lockscreen.reset();
390 // If there is only one user, we start authenticating with that one here.
391 // If there are more users, the Greeter will handle that
392 if (LightDM.Users.count == 1) {
393 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
394 }
395 greeter.forceActiveFocus();
396 }
397 }
399 onUnlocked: greeter.hide()
400 onSelected: {
401 // Update launcher items for new user
402 var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
403 AccountsService.user = user;
404 LauncherModel.setUser(user);
405 }
407 onTease: launcher.tease()
409 Binding {
410 target: ApplicationManager
411 property: "suspended"
412 value: greeter.shown && greeterWrapper.showProgress == 1
413 }
414 }
415 }
417 InputFilterArea {
418 anchors.fill: parent
419 blockInput: ApplicationManager.focusedApplicationId.length === 0 || greeter.shown || lockscreen.shown || launcher.shown
420 || panel.indicators.shown
421 }
423 Connections {
424 id: powerConnection
425 target: Powerd
427 onDisplayPowerStateChange: {
428 // We ignore any display-off signals when the proximity sensor
429 // is active. This usually indicates something like a phone call.
430 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
431 greeter.showNow();
432 }
434 // No reason to chew demo CPU when user isn't watching
435 if (status == Powerd.Off) {
436 edgeDemo.paused = true;
437 } else if (status == Powerd.On) {
438 edgeDemo.paused = false;
439 }
440 }
441 }
443 function showHome() {
444 var animate = !greeter.shown && !stages.shown
445 greeter.hide()
446 dash.setCurrentScope("clickscope", animate, false)
447 stages.hide()
448 }
450 function hideIndicatorMenu(delay) {
451 panel.hideIndicatorMenu(delay);
452 }
454 Item {
455 id: overlay
457 anchors.fill: parent
459 Panel {
460 id: panel
461 anchors.fill: parent //because this draws indicator menus
462 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
463 indicators {
464 hides: [launcher]
465 available: edgeDemo.panelEnabled
466 contentEnabled: edgeDemo.panelContentEnabled
467 }
468 property string focusedAppId: ApplicationManager.focusedApplicationId
469 property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
470 fullscreenMode: focusedApplication && stages.fullscreen && !greeter.shown && !lockscreen.shown
471 searchVisible: !greeter.shown && !lockscreen.shown && dash.shown && dash.searchable
473 InputFilterArea {
474 anchors {
475 top: parent.top
476 left: parent.left
477 right: parent.right
478 }
479 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
480 blockInput: true
481 }
482 }
484 InputFilterArea {
485 blockInput: launcher.shown
486 anchors {
487 top: parent.top
488 bottom: parent.bottom
489 left: parent.left
490 }
491 width: launcher.width
492 }
494 Launcher {
495 id: launcher
497 readonly property bool dashSwipe: progress > 0
499 anchors.top: parent.top
500 anchors.bottom: parent.bottom
501 width: parent.width
502 dragAreaWidth: shell.edgeSize
503 available: edgeDemo.launcherEnabled
505 onShowDashHome: {
506 if (edgeDemo.running)
507 return;
509 showHome()
510 }
511 onDash: {
512 if (stages.shown && !stages.overlayMode) {
513 if (!stages.locked) {
514 stages.hide();
515 launcher.hide();
516 }
517 }
518 if (greeter.shown) {
519 greeter.hideRight();
520 launcher.hide();
521 }
522 }
523 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
524 onLauncherApplicationSelected: {
525 if (!edgeDemo.running)
526 shell.activateApplication(appId)
527 }
528 onShownChanged: {
529 if (shown) {
530 panel.indicators.hide()
531 }
532 }
533 }
535 Rectangle {
536 id: modalNotificationBackground
538 visible: notifications.useModal && !greeter.shown && (notifications.state == "narrow")
539 color: "#000000"
540 anchors.fill: parent
541 opacity: 0.5
543 MouseArea {
544 anchors.fill: parent
545 }
547 InputFilterArea {
548 anchors.fill: parent
549 blockInput: modalNotificationBackground.visible
550 }
551 }
553 Notifications {
554 id: notifications
556 model: NotificationBackend.Model
557 margin: units.gu(1)
559 anchors {
560 top: parent.top
561 right: parent.right
562 bottom: parent.bottom
563 topMargin: panel.panelHeight
564 }
565 states: [
566 State {
567 name: "narrow"
568 when: overlay.width <= units.gu(60)
569 AnchorChanges { target: notifications; anchors.left: parent.left }
570 },
571 State {
572 name: "wide"
573 when: overlay.width > units.gu(60)
574 AnchorChanges { target: notifications; anchors.left: undefined }
575 PropertyChanges { target: notifications; width: units.gu(38) }
576 }
577 ]
579 InputFilterArea {
580 anchors { left: parent.left; right: parent.right }
581 height: parent.contentHeight
582 blockInput: height > 0
583 }
584 }
585 }
587 focus: true
588 onFocusChanged: if (!focus) forceActiveFocus();
590 InputFilterArea {
591 anchors {
592 top: parent.top
593 bottom: parent.bottom
594 left: parent.left
595 }
596 width: shell.edgeSize
597 blockInput: true
598 }
600 InputFilterArea {
601 anchors {
602 top: parent.top
603 bottom: parent.bottom
604 right: parent.right
605 }
606 width: shell.edgeSize
607 blockInput: true
608 }
610 Binding {
611 target: i18n
612 property: "domain"
613 value: "unity8"
614 }
616 OSKController {
617 anchors.topMargin: panel.panelHeight
618 anchors.fill: parent // as needs to know the geometry of the shell
619 }
621 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
622 MouseArea {
623 anchors.bottom: parent.bottom
624 anchors.left: parent.left
625 anchors.right: parent.right
626 height: ApplicationManager.keyboardVisible ? ApplicationManager.keyboardHeight : 0
628 enabled: ApplicationManager.keyboardVisible
629 }
631 Label {
632 anchors.centerIn: parent
633 visible: ApplicationManager.fake ? ApplicationManager.fake : false
634 text: "EARLY ALPHA\nNOT READY FOR USE"
635 color: "lightgrey"
636 opacity: 0.2
637 font.weight: Font.Black
638 horizontalAlignment: Text.AlignHCenter
639 verticalAlignment: Text.AlignVCenter
640 fontSizeMode: Text.Fit
641 rotation: -45
642 scale: Math.min(parent.width, parent.height) / width
643 }
645 EdgeDemo {
646 id: edgeDemo
647 greeter: greeter
648 launcher: launcher
649 dash: dash
650 indicators: panel.indicators
651 underlay: underlay
652 }
654 Connections {
655 target: SessionBroadcast
656 onShowHome: showHome()
657 }