on 5529866 help sign in
unity8/Shell.qml
601 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 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 ListModel searchHistory: SearchHistoryModel {}
57 property var applicationManager: ApplicationManagerWrapper {}
59 Binding {
60 target: LauncherModel
61 property: "applicationManager"
62 value: ApplicationManager
63 }
65 Component.onCompleted: {
66 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
68 applicationManager.sideStageEnabled = Qt.binding(function() { return sideStage.enabled })
70 // FIXME: if application focused before shell starts, shell draws on top of it only.
71 // We should detect already running applications on shell start and bring them to the front.
72 applicationManager.unfocusCurrentApplication();
73 }
75 readonly property bool applicationFocused: !!applicationManager.mainStageFocusedApplication
76 || !!applicationManager.sideStageFocusedApplication
77 // Used for autopilot testing.
78 readonly property string currentFocusedAppId: ApplicationManager.focusedApplicationId
80 readonly property bool fullscreenMode: {
81 if (mainStage.usingScreenshots) { // Window Manager animating so want to re-evaluate fullscreen mode
82 return mainStage.switchingFromFullscreenToFullscreen;
83 } else if (applicationManager.mainStageFocusedApplication) {
84 return applicationManager.mainStageFocusedApplication.fullscreen;
85 } else {
86 return false;
87 }
88 }
90 function activateApplication(desktopFile, argument) {
91 if (applicationManager) {
92 // For newly started applications, as it takes them time to draw their first frame
93 // we add a delay before we hide the animation screenshots to compensate.
94 var addDelay = !applicationManager.getApplicationFromDesktopFile(desktopFile);
96 var application;
97 application = applicationManager.activateApplication(desktopFile, argument);
98 if (application == null) {
99 return;
100 }
101 if (application.stage == ApplicationInfo.MainStage || !sideStage.enabled) {
102 mainStage.activateApplication(desktopFile, addDelay);
103 } else {
104 sideStage.activateApplication(desktopFile, addDelay);
105 }
106 stages.show();
107 }
108 }
110 GSettings {
111 id: backgroundSettings
112 schema.id: "org.gnome.desktop.background"
113 }
114 backgroundSource: backgroundSettings.pictureUri
116 Item {
117 id: underlay
118 objectName: "underlay"
119 anchors.fill: parent
121 // Whether the underlay is fully covered by opaque UI elements.
122 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
124 readonly property bool applicationRunning: ((mainStage.applications && mainStage.applications.count > 0)
125 || (sideStage.applications && sideStage.applications.count > 0))
127 // Whether the user should see the topmost application surface (if there's one at all).
128 readonly property bool applicationSurfaceShouldBeSeen: applicationRunning && !stages.fullyHidden
129 && !mainStage.usingScreenshots // but want sideStage animating over app surface
133 // NB! Application surfaces are stacked behing the shell one. So they can only be seen by the user
134 // through the translucent parts of the shell surface.
135 visible: !fullyCovered && !applicationSurfaceShouldBeSeen
137 CrossFadeImage {
138 id: backgroundImage
139 objectName: "backgroundImage"
141 anchors.fill: parent
142 source: shell.background
143 fillMode: Image.PreserveAspectCrop
144 }
146 Rectangle {
147 anchors.fill: parent
148 color: "black"
149 opacity: dash.disappearingAnimationProgress
150 }
152 Dash {
153 id: dash
154 objectName: "dash"
156 hides: [stages, launcher, panel.indicators]
157 shown: disappearingAnimationProgress !== 1.0
158 enabled: disappearingAnimationProgress === 0.0 && edgeDemo.dashEnabled
159 // FIXME: unfocus all applications when going back to the dash
160 onEnabledChanged: {
161 if (enabled) {
162 shell.applicationManager.unfocusCurrentApplication()
163 }
164 }
166 anchors {
167 fill: parent
168 topMargin: panel.panelHeight
169 }
171 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
172 opacity: 1.0 - disappearingAnimationProgress
173 property real disappearingAnimationProgress: stagesOuterContainer.showProgress
175 // FIXME: only necessary because stagesOuterContainer.showProgress
176 // is not animated
177 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
178 }
179 }
181 Item {
182 id: stagesOuterContainer
184 width: parent.width
185 height: parent.height
186 x: launcher.progress
187 Behavior on x {SmoothedAnimation{velocity: 600}}
189 property real showProgress:
190 MathUtils.clamp(1 - (x + stages.x) / shell.width, 0, 1)
192 Showable {
193 id: stages
194 objectName: "stages"
196 x: width
198 property bool fullyShown: shown && x == 0 && parent.x == 0
199 property bool fullyHidden: !shown && x == width
200 hides: [panel.indicators]
201 shown: false
202 opacity: 1.0
203 showAnimation: StandardAnimation { property: "x"; duration: 350; to: 0; easing.type: Easing.OutCubic }
204 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: width; easing.type: Easing.OutCubic }
206 width: parent.width
207 height: parent.height
209 // close the stages when no focused application remains
210 Connections {
211 target: shell.applicationManager
212 onMainStageFocusedApplicationChanged: stages.closeIfNoApplications()
213 onSideStageFocusedApplicationChanged: stages.closeIfNoApplications()
214 ignoreUnknownSignals: true
215 }
217 function closeIfNoApplications() {
218 if (!shell.applicationManager.mainStageFocusedApplication
219 && !shell.applicationManager.sideStageFocusedApplication
220 && shell.applicationManager.mainStageApplications.count == 0
221 && shell.applicationManager.sideStageApplications.count == 0) {
222 stages.hide();
223 }
224 }
226 // show the stages when an application gets the focus
227 Connections {
228 target: shell.applicationManager
229 onMainStageFocusedApplicationChanged: {
230 if (shell.applicationManager.mainStageFocusedApplication) {
231 mainStage.show();
232 stages.show();
233 }
234 }
235 onSideStageFocusedApplicationChanged: {
236 if (shell.applicationManager.sideStageFocusedApplication) {
237 sideStage.show();
238 stages.show();
239 }
240 }
241 ignoreUnknownSignals: true
242 }
244 Stage {
245 id: mainStage
247 anchors.fill: parent
248 fullyShown: stages.fullyShown
249 fullyHidden: stages.fullyHidden
250 shouldUseScreenshots: !fullyShown
251 rightEdgeEnabled: !sideStage.enabled
253 applicationManager: shell.applicationManager
254 rightEdgeDraggingAreaWidth: shell.edgeSize
255 normalApplicationY: shell.panelHeight
257 shown: true
258 function show() {
259 stages.show();
260 }
261 function hide() {
262 }
264 // FIXME: workaround the fact that focusing a main stage application
265 // raises its surface on top of all other surfaces including the ones
266 // that belong to side stage applications.
267 onFocusedApplicationChanged: {
268 if (focusedApplication && sideStage.focusedApplication && sideStage.fullyShown) {
269 shell.applicationManager.focusApplication(sideStage.focusedApplication);
270 }
271 }
272 }
274 SideStage {
275 id: sideStage
277 applicationManager: shell.applicationManager
278 rightEdgeDraggingAreaWidth: shell.edgeSize
279 normalApplicationY: shell.panelHeight
281 onShownChanged: {
282 if (!shown && mainStage.applications.count == 0) {
283 stages.hide();
284 }
285 }
286 // FIXME: when hiding the side stage, refocus the main stage
287 // application so that it goes in front of the side stage
288 // application and hides it
289 onFullyShownChanged: {
290 if (!fullyShown && stages.fullyShown && sideStage.focusedApplication != null) {
291 shell.applicationManager.focusApplication(mainStage.focusedApplication);
292 }
293 }
295 enabled: shell.width >= units.gu(60)
296 visible: enabled
297 fullyShown: stages.fullyShown && shown
298 && sideStage[sideStageRevealer.boundProperty] == sideStageRevealer.openedValue
299 shouldUseScreenshots: !fullyShown || mainStage.usingScreenshots || sideStageRevealer.pressed
301 available: enabled
302 hides: [launcher, panel.indicators]
303 shown: false
304 showAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.openedValue; easing.type: Easing.OutQuint }
305 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.closedValue; easing.type: Easing.OutQuint }
307 width: units.gu(40)
308 height: stages.height
309 handleExpanded: sideStageRevealer.pressed
310 }
312 Revealer {
313 id: sideStageRevealer
315 enabled: mainStage.applications.count > 0 && sideStage.applications.count > 0
316 && sideStage.available
317 direction: Qt.RightToLeft
318 openedValue: parent.width - sideStage.width
319 hintDisplacement: units.gu(3)
320 /* The size of the sidestage handle needs to be bigger than the
321 typical size used for edge detection otherwise it is really
322 hard to grab.
323 */
324 handleSize: sideStage.shown ? units.gu(4) : shell.edgeSize
325 closedValue: parent.width + sideStage.handleSizeCollapsed
326 target: sideStage
327 x: parent.width - width
328 width: sideStage.width + handleSize * 0.7
329 height: sideStage.height
330 orientation: Qt.Horizontal
331 }
333 DragHandle {
334 id: stagesDragHandle
336 anchors.top: parent.top
337 anchors.bottom: parent.bottom
338 anchors.right: parent.left
340 width: shell.edgeSize
341 direction: Direction.Leftwards
342 enabled: edgeDemo.dashEnabled
343 property bool haveApps: mainStage.applications.count > 0 || sideStage.applications.count > 0
345 maxTotalDragDistance: haveApps ? parent.width : parent.width * 0.7
346 // Make autocompletion impossible when !haveApps
347 edgeDragEvaluator.minDragDistance: haveApps ? maxTotalDragDistance * 0.1 : Number.MAX_VALUE
348 }
349 }
350 }
352 Connections {
353 id: sessionConnection
354 target: SessionManager
356 property var previousMainApp: null
357 property var previousSideApp: null
359 function removeApplicationFocus() {
360 sessionConnection.previousMainApp = applicationManager.mainStageFocusedApplication;
361 sessionConnection.previousSideApp = applicationManager.sideStageFocusedApplication;
362 applicationManager.unfocusCurrentApplication();
363 }
365 function restoreApplicationFocus() {
366 if (sessionConnection.previousMainApp) {
367 applicationManager.focusApplication(sessionConnection.previousMainApp);
368 sessionConnection.previousMainApp = null;
369 }
370 if (sessionConnection.previousSideApp) {
371 applicationManager.focusApplication(sessionConnection.previousSideApp);
372 sessionConnection.previousSideApp = null;
373 }
374 }
376 onActiveChanged: {
377 if (SessionManager.active) {
378 restoreApplicationFocus();
379 } else {
380 removeApplicationFocus();
381 }
382 }
383 }
385 Connections {
386 id: powerConnection
387 target: Powerd
389 onDisplayPowerStateChange: {
390 // We ignore any display-off signals when the proximity sensor
391 // is active. This usually indicates something like a phone call.
392 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
393 SessionManager.lock();
394 }
396 // No reason to chew demo CPU when user isn't watching
397 if (status == Powerd.Off) {
398 edgeDemo.paused = true;
399 } else if (status == Powerd.On) {
400 edgeDemo.paused = false;
401 }
402 }
403 }
405 function showHome(fromLauncher) {
406 var animate = fromLauncher && !stages.shown
407 dash.setCurrentScope("home.scope", animate, false)
408 stages.hide()
409 }
411 Item {
412 id: overlay
414 anchors.fill: parent
416 Panel {
417 id: panel
418 anchors.fill: parent //because this draws indicator menus
419 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
420 indicators {
421 hides: [launcher]
422 available: edgeDemo.panelEnabled
423 contentEnabled: edgeDemo.panelContentEnabled
424 }
425 fullscreenMode: shell.fullscreenMode
426 searchVisible: dash.shown
428 InputFilterArea {
429 anchors {
430 top: parent.top
431 left: parent.left
432 right: parent.right
433 }
434 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
435 blockInput: true
436 }
437 }
439 Hud {
440 id: hud
442 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
443 height: parent.height
445 available: !panel.indicators.shown && edgeDemo.dashEnabled
446 shown: false
447 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
448 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
450 Connections {
451 target: shell.applicationManager
452 onMainStageFocusedApplicationChanged: hud.hide()
453 onSideStageFocusedApplicationChanged: hud.hide()
454 }
455 }
457 Revealer {
458 id: hudRevealer
460 enabled: hud.shown
461 width: hud.width
462 anchors.left: hud.left
463 height: parent.height
464 target: hud.revealerTarget
465 closedValue: height
466 openedValue: 0
467 direction: Qt.RightToLeft
468 orientation: Qt.Vertical
469 handleSize: hud.handleHeight
470 onCloseClicked: target.hide()
471 }
473 Bottombar {
474 id: bottombar
475 theHud: hud
476 anchors.fill: parent
477 enabled: hud.available
478 applicationIsOnForeground: applicationFocused
479 }
481 InputFilterArea {
482 blockInput: launcher.shown
483 anchors {
484 top: parent.top
485 bottom: parent.bottom
486 left: parent.left
487 }
488 width: launcher.width
489 }
491 Launcher {
492 id: launcher
494 readonly property bool dashSwipe: progress > 0
496 anchors.top: parent.top
497 anchors.bottom: parent.bottom
498 width: parent.width
499 dragAreaWidth: shell.edgeSize
500 available: edgeDemo.launcherEnabled
501 onDashItemSelected: showHome(true)
502 onDash: {
503 if (stages.shown) {
504 stages.hide();
505 launcher.hide();
506 }
507 }
508 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("applications.scope", false, true)
509 onLauncherApplicationSelected:{
510 shell.activateApplication(appId)
511 }
512 onShownChanged: {
513 if (shown) {
514 panel.indicators.hide()
515 hud.hide()
516 bottombar.hide()
517 }
518 }
519 }
521 Notifications {
522 id: notifications
524 model: NotificationBackend.Model
525 anchors {
526 top: parent.top
527 right: parent.right
528 bottom: parent.bottom
529 leftMargin: units.gu(1)
530 rightMargin: units.gu(1)
531 topMargin: panel.panelHeight + units.gu(1)
532 }
533 states: [
534 State {
535 name: "narrow"
536 when: overlay.width <= units.gu(60)
537 AnchorChanges { target: notifications; anchors.left: parent.left }
538 },
539 State {
540 name: "wide"
541 when: overlay.width > units.gu(60)
542 AnchorChanges { target: notifications; anchors.left: undefined }
543 PropertyChanges { target: notifications; width: units.gu(38) }
544 }
545 ]
547 InputFilterArea {
548 anchors { left: parent.left; right: parent.right }
549 height: parent.contentHeight
550 blockInput: height > 0
551 }
552 }
553 }
555 InputFilterArea {
556 anchors {
557 top: parent.top
558 bottom: parent.bottom
559 left: parent.left
560 }
561 width: shell.edgeSize
562 blockInput: true
563 }
565 //FIXME: This should be handled in the input stack, keyboard shouldnt propagate
566 MouseArea {
567 anchors.bottom: parent.bottom
568 anchors.left: parent.left
569 anchors.right: parent.right
570 height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
572 enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
573 }
575 Label {
576 anchors.centerIn: parent
577 visible: applicationManager.fake
578 text: "EARLY ALPHA\nNOT READY FOR USE"
579 color: "lightgrey"
580 opacity: 0.2
581 font.weight: Font.Black
582 horizontalAlignment: Text.AlignHCenter
583 verticalAlignment: Text.AlignVCenter
584 fontSizeMode: Text.Fit
585 rotation: -45
586 scale: Math.min(parent.width, parent.height) / width
587 }
589 EdgeDemo {
590 id: edgeDemo
591 launcher: launcher
592 dash: dash
593 indicators: panel.indicators
594 underlay: underlay
595 }
597 Connections {
598 target: SessionBroadcast
599 onShowHome: showHome(false)
600 }