on b98b649 help sign in
unity8/qml/Shell.qml
618 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 Powerd 0.1
25import SessionBroadcast 0.1
26import SessionManager 0.1
27import "Dash"
28import "Launcher"
29import "Panel"
30import "Hud"
31import "Components"
32import "Bottombar"
33import "SideStage"
34import "Notifications"
35import Unity.Notifications 1.0 as NotificationBackend
37BasicShell {
38 id: shell
40 readonly property real panelHeight: panel.panelHeight
42 property bool dashShown: dash.shown
43 property bool stageScreenshotsReady: {
44 if (sideStage.shown) {
45 if (mainStage.applications.count > 0) {
46 return mainStage.usingScreenshots && sideStage.usingScreenshots;
47 } else {
48 return sideStage.usingScreenshots;
49 }
50 } else {
51 return mainStage.usingScreenshots;
52 }
53 }
55 property var applicationManager: ApplicationManagerWrapper {}
57 Binding {
58 target: LauncherModel
59 property: "applicationManager"
60 value: ApplicationManager
61 }
63 Component.onCompleted: {
64 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
66 applicationManager.sideStageEnabled = Qt.binding(function() { return sideStage.enabled })
68 // FIXME: if application focused before shell starts, shell draws on top of it only.
69 // We should detect already running applications on shell start and bring them to the front.
70 applicationManager.unfocusCurrentApplication();
71 }
73 readonly property bool applicationFocused: !!applicationManager.mainStageFocusedApplication
74 || !!applicationManager.sideStageFocusedApplication
75 // Used for autopilot testing.
76 readonly property string currentFocusedAppId: ApplicationManager.focusedApplicationId
78 readonly property bool fullscreenMode: {
79 if (mainStage.usingScreenshots) { // Window Manager animating so want to re-evaluate fullscreen mode
80 return mainStage.switchingFromFullscreenToFullscreen;
81 } else if (applicationManager.mainStageFocusedApplication) {
82 return applicationManager.mainStageFocusedApplication.fullscreen;
83 } else {
84 return false;
85 }
86 }
88 function activateApplication(appId, argument) {
89 if (applicationManager) {
90 // For newly started applications, as it takes them time to draw their first frame
91 // we add a delay before we hide the animation screenshots to compensate.
92 var addDelay = !applicationManager.getApplicationFromDesktopFile(appId);
94 var application;
95 application = applicationManager.activateApplication(appId, argument);
96 if (application == null) {
97 return;
98 }
99 if (application.stage == ApplicationInfo.MainStage || !sideStage.enabled) {
100 mainStage.activateApplication(appId, addDelay);
101 } else {
102 sideStage.activateApplication(appId, addDelay);
103 }
104 stages.show();
105 }
106 }
108 GSettings {
109 id: backgroundSettings
110 schema.id: "org.gnome.desktop.background"
111 }
112 backgroundSource: backgroundSettings.pictureUri
114 Item {
115 id: underlay
116 objectName: "underlay"
117 anchors.fill: parent
119 // Whether the underlay is fully covered by opaque UI elements.
120 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
122 readonly property bool applicationRunning: ((mainStage.applications && mainStage.applications.count > 0)
123 || (sideStage.applications && sideStage.applications.count > 0))
125 // Whether the user should see the topmost application surface (if there's one at all).
126 readonly property bool applicationSurfaceShouldBeSeen: applicationRunning && !stages.fullyHidden
127 && !mainStage.usingScreenshots // but want sideStage animating over app surface
131 // NB! Application surfaces are stacked behing the shell one. So they can only be seen by the user
132 // through the translucent parts of the shell surface.
133 visible: !fullyCovered && !applicationSurfaceShouldBeSeen
135 CrossFadeImage {
136 id: backgroundImage
137 objectName: "backgroundImage"
139 anchors.fill: parent
140 source: shell.background
141 fillMode: Image.PreserveAspectCrop
142 }
144 Rectangle {
145 anchors.fill: parent
146 color: "black"
147 opacity: dash.disappearingAnimationProgress
148 }
150 Dash {
151 id: dash
152 objectName: "dash"
154 hides: [stages, launcher, panel.indicators]
155 shown: disappearingAnimationProgress !== 1.0
156 enabled: disappearingAnimationProgress === 0.0 && edgeDemo.dashEnabled
157 // FIXME: unfocus all applications when going back to the dash
158 onEnabledChanged: {
159 if (enabled) {
160 shell.applicationManager.unfocusCurrentApplication()
161 }
162 }
164 anchors {
165 fill: parent
166 topMargin: panel.panelHeight
167 }
169 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
170 opacity: 1.0 - disappearingAnimationProgress
171 property real disappearingAnimationProgress: stagesOuterContainer.showProgress
173 // FIXME: only necessary because stagesOuterContainer.showProgress
174 // is not animated
175 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
176 }
177 }
179 Item {
180 id: stagesOuterContainer
182 width: parent.width
183 height: parent.height
184 x: launcher.progress
185 Behavior on x {SmoothedAnimation{velocity: 600}}
187 property real showProgress:
188 MathUtils.clamp(1 - (x + stages.x) / shell.width, 0, 1)
190 Showable {
191 id: stages
192 objectName: "stages"
194 x: width
196 property bool fullyShown: shown && x == 0 && parent.x == 0
197 property bool fullyHidden: !shown && x == width
198 hides: [panel.indicators]
199 shown: false
200 opacity: 1.0
201 showAnimation: StandardAnimation { property: "x"; duration: 350; to: 0; easing.type: Easing.OutCubic }
202 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: width; easing.type: Easing.OutCubic }
204 width: parent.width
205 height: parent.height
207 // close the stages when no focused application remains
208 Connections {
209 target: shell.applicationManager
210 onMainStageFocusedApplicationChanged: stages.closeIfNoApplications()
211 onSideStageFocusedApplicationChanged: stages.closeIfNoApplications()
212 ignoreUnknownSignals: true
213 }
215 function closeIfNoApplications() {
216 if (!shell.applicationManager.mainStageFocusedApplication
217 && !shell.applicationManager.sideStageFocusedApplication
218 && shell.applicationManager.mainStageApplications.count == 0
219 && shell.applicationManager.sideStageApplications.count == 0) {
220 stages.hide();
221 }
222 }
224 // show the stages when an application gets the focus
225 Connections {
226 target: shell.applicationManager
227 onMainStageFocusedApplicationChanged: {
228 if (shell.applicationManager.mainStageFocusedApplication) {
229 mainStage.show();
230 stages.show();
231 }
232 }
233 onSideStageFocusedApplicationChanged: {
234 if (shell.applicationManager.sideStageFocusedApplication) {
235 sideStage.show();
236 stages.show();
237 }
238 }
239 ignoreUnknownSignals: true
240 }
242 Stage {
243 id: mainStage
245 anchors.fill: parent
246 fullyShown: stages.fullyShown
247 fullyHidden: stages.fullyHidden
248 shouldUseScreenshots: !fullyShown
249 rightEdgeEnabled: !sideStage.enabled
251 applicationManager: shell.applicationManager
252 rightEdgeDraggingAreaWidth: shell.edgeSize
253 normalApplicationY: shell.panelHeight
255 shown: true
256 function show() {
257 stages.show();
258 }
259 function hide() {
260 }
262 // FIXME: workaround the fact that focusing a main stage application
263 // raises its surface on top of all other surfaces including the ones
264 // that belong to side stage applications.
265 onFocusedApplicationChanged: {
266 if (focusedApplication && sideStage.focusedApplication && sideStage.fullyShown) {
267 shell.applicationManager.focusApplication(sideStage.focusedApplication);
268 }
269 }
270 }
272 SideStage {
273 id: sideStage
275 applicationManager: shell.applicationManager
276 rightEdgeDraggingAreaWidth: shell.edgeSize
277 normalApplicationY: shell.panelHeight
279 onShownChanged: {
280 if (!shown && mainStage.applications.count == 0) {
281 stages.hide();
282 }
283 }
284 // FIXME: when hiding the side stage, refocus the main stage
285 // application so that it goes in front of the side stage
286 // application and hides it
287 onFullyShownChanged: {
288 if (!fullyShown && stages.fullyShown && sideStage.focusedApplication != null) {
289 shell.applicationManager.focusApplication(mainStage.focusedApplication);
290 }
291 }
293 enabled: shell.width >= units.gu(60)
294 visible: enabled
295 fullyShown: stages.fullyShown && shown
296 && sideStage[sideStageRevealer.boundProperty] == sideStageRevealer.openedValue
297 shouldUseScreenshots: !fullyShown || mainStage.usingScreenshots || sideStageRevealer.pressed
299 available: enabled
300 hides: [launcher, panel.indicators]
301 shown: false
302 showAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.openedValue; easing.type: Easing.OutQuint }
303 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.closedValue; easing.type: Easing.OutQuint }
305 width: units.gu(40)
306 height: stages.height
307 handleExpanded: sideStageRevealer.pressed
308 }
310 Revealer {
311 id: sideStageRevealer
313 enabled: mainStage.applications.count > 0 && sideStage.applications.count > 0
314 && sideStage.available
315 direction: Qt.RightToLeft
316 openedValue: parent.width - sideStage.width
317 hintDisplacement: units.gu(3)
318 /* The size of the sidestage handle needs to be bigger than the
319 typical size used for edge detection otherwise it is really
320 hard to grab.
321 */
322 handleSize: sideStage.shown ? units.gu(4) : shell.edgeSize
323 closedValue: parent.width + sideStage.handleSizeCollapsed
324 target: sideStage
325 x: parent.width - width
326 width: sideStage.width + handleSize * 0.7
327 height: sideStage.height
328 orientation: Qt.Horizontal
329 }
331 DragHandle {
332 id: stagesDragHandle
334 anchors.top: parent.top
335 anchors.bottom: parent.bottom
336 anchors.right: parent.left
338 width: shell.edgeSize
339 direction: Direction.Leftwards
340 enabled: edgeDemo.dashEnabled
341 property bool haveApps: mainStage.applications.count > 0 || sideStage.applications.count > 0
343 maxTotalDragDistance: haveApps ? parent.width : parent.width * 0.7
344 // Make autocompletion impossible when !haveApps
345 edgeDragEvaluator.minDragDistance: haveApps ? maxTotalDragDistance * 0.1 : Number.MAX_VALUE
346 }
347 }
348 }
350 InputFilterArea {
351 anchors.fill: parent
352 blockInput: !applicationFocused || launcher.shown
353 || panel.indicators.shown || hud.shown
354 }
356 Connections {
357 id: sessionConnection
358 target: SessionManager
360 property var previousMainApp: null
361 property var previousSideApp: null
363 function removeApplicationFocus() {
364 sessionConnection.previousMainApp = applicationManager.mainStageFocusedApplication;
365 sessionConnection.previousSideApp = applicationManager.sideStageFocusedApplication;
366 applicationManager.unfocusCurrentApplication();
367 }
369 function restoreApplicationFocus() {
370 if (sessionConnection.previousMainApp) {
371 applicationManager.focusApplication(sessionConnection.previousMainApp);
372 sessionConnection.previousMainApp = null;
373 }
374 if (sessionConnection.previousSideApp) {
375 applicationManager.focusApplication(sessionConnection.previousSideApp);
376 sessionConnection.previousSideApp = null;
377 }
378 }
380 onActiveChanged: {
381 if (SessionManager.active) {
382 restoreApplicationFocus();
383 } else {
384 removeApplicationFocus();
385 }
386 }
387 }
389 Connections {
390 id: powerConnection
391 target: Powerd
393 onDisplayPowerStateChange: {
394 // We ignore any display-off signals when the proximity sensor
395 // is active. This usually indicates something like a phone call.
396 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
397 SessionManager.lock();
398 }
400 // No reason to chew demo CPU when user isn't watching
401 if (status == Powerd.Off) {
402 edgeDemo.paused = true;
403 } else if (status == Powerd.On) {
404 edgeDemo.paused = false;
405 }
406 }
407 }
409 function showHome(fromLauncher) {
410 var animate = fromLauncher && !stages.shown
411 dash.setCurrentScope("home.scope", animate, false)
412 stages.hide()
413 }
415 function hideIndicatorMenu(delay) {
416 panel.hideIndicatorMenu(delay);
417 }
419 Item {
420 id: overlay
422 anchors.fill: parent
424 Panel {
425 id: panel
426 anchors.fill: parent //because this draws indicator menus
427 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
428 indicators {
429 hides: [launcher]
430 available: edgeDemo.panelEnabled
431 contentEnabled: edgeDemo.panelContentEnabled
432 }
433 fullscreenMode: shell.fullscreenMode
434 searchVisible: dash.shown
436 InputFilterArea {
437 anchors {
438 top: parent.top
439 left: parent.left
440 right: parent.right
441 }
442 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
443 blockInput: true
444 }
445 }
447 Hud {
448 id: hud
450 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
451 height: parent.height
453 available: !panel.indicators.shown && edgeDemo.dashEnabled
454 shown: false
455 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
456 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
458 Connections {
459 target: shell.applicationManager
460 onMainStageFocusedApplicationChanged: hud.hide()
461 onSideStageFocusedApplicationChanged: hud.hide()
462 }
463 }
465 Revealer {
466 id: hudRevealer
468 enabled: hud.shown
469 width: hud.width
470 anchors.left: hud.left
471 height: parent.height
472 target: hud.revealerTarget
473 closedValue: height
474 openedValue: 0
475 direction: Qt.RightToLeft
476 orientation: Qt.Vertical
477 handleSize: hud.handleHeight
478 onCloseClicked: target.hide()
479 }
481 Bottombar {
482 id: bottombar
483 theHud: hud
484 anchors.fill: parent
485 enabled: hud.available
486 applicationIsOnForeground: applicationFocused
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
509 onShowDashHome: {
510 if (edgeDemo.running)
511 return;
513 showHome(true)
514 }
516 onDash: {
517 if (stages.shown) {
518 stages.hide();
519 launcher.hide();
520 }
521 }
522 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("applications.scope", false, true)
523 onLauncherApplicationSelected:{
524 if (edgeDemo.running)
525 return;
527 shell.activateApplication(appId)
528 }
529 onShownChanged: {
530 if (shown) {
531 panel.indicators.hide()
532 hud.hide()
533 bottombar.hide()
534 }
535 }
536 }
538 Notifications {
539 id: notifications
541 model: NotificationBackend.Model
542 margin: units.gu(1)
544 anchors {
545 top: parent.top
546 right: parent.right
547 bottom: parent.bottom
548 topMargin: panel.panelHeight
549 }
550 states: [
551 State {
552 name: "narrow"
553 when: overlay.width <= units.gu(60)
554 AnchorChanges { target: notifications; anchors.left: parent.left }
555 },
556 State {
557 name: "wide"
558 when: overlay.width > units.gu(60)
559 AnchorChanges { target: notifications; anchors.left: undefined }
560 PropertyChanges { target: notifications; width: units.gu(38) }
561 }
562 ]
564 InputFilterArea {
565 anchors { left: parent.left; right: parent.right }
566 height: parent.contentHeight
567 blockInput: height > 0
568 }
569 }
570 }
572 InputFilterArea {
573 anchors {
574 top: parent.top
575 bottom: parent.bottom
576 left: parent.left
577 }
578 width: shell.edgeSize
579 blockInput: true
580 }
582 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
583 MouseArea {
584 anchors.bottom: parent.bottom
585 anchors.left: parent.left
586 anchors.right: parent.right
587 height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
589 enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
590 }
592 Label {
593 anchors.centerIn: parent
594 visible: applicationManager.fake
595 text: "EARLY ALPHA\nNOT READY FOR USE"
596 color: "lightgrey"
597 opacity: 0.2
598 font.weight: Font.Black
599 horizontalAlignment: Text.AlignHCenter
600 verticalAlignment: Text.AlignVCenter
601 fontSizeMode: Text.Fit
602 rotation: -45
603 scale: Math.min(parent.width, parent.height) / width
604 }
606 EdgeDemo {
607 id: edgeDemo
608 launcher: launcher
609 dash: dash
610 indicators: panel.indicators
611 underlay: underlay
612 }
614 Connections {
615 target: SessionBroadcast
616 onShowHome: showHome(false)
617 }