on 00a39ec 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 "Components/Math.js" as MathLocal
33import "Bottombar"
34import "SideStage"
35import "Notifications"
36import Unity.Notifications 1.0 as NotificationBackend
38BasicShell {
39 id: shell
41 readonly property real panelHeight: panel.panelHeight
43 property bool dashShown: dash.shown
44 property bool stageScreenshotsReady: {
45 if (sideStage.shown) {
46 if (mainStage.applications.count > 0) {
47 return mainStage.usingScreenshots && sideStage.usingScreenshots;
48 } else {
49 return sideStage.usingScreenshots;
50 }
51 } else {
52 return mainStage.usingScreenshots;
53 }
54 }
56 property ListModel searchHistory: SearchHistoryModel {}
58 property var applicationManager: ApplicationManagerWrapper {}
60 Binding {
61 target: LauncherModel
62 property: "applicationManager"
63 value: ApplicationManager
64 }
66 Component.onCompleted: {
67 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
69 applicationManager.sideStageEnabled = Qt.binding(function() { return sideStage.enabled })
71 // FIXME: if application focused before shell starts, shell draws on top of it only.
72 // We should detect already running applications on shell start and bring them to the front.
73 applicationManager.unfocusCurrentApplication();
74 }
76 readonly property bool applicationFocused: !!applicationManager.mainStageFocusedApplication
77 || !!applicationManager.sideStageFocusedApplication
78 // Used for autopilot testing.
79 readonly property string currentFocusedAppId: ApplicationManager.focusedApplicationId
81 readonly property bool fullscreenMode: {
82 if (mainStage.usingScreenshots) { // Window Manager animating so want to re-evaluate fullscreen mode
83 return mainStage.switchingFromFullscreenToFullscreen;
84 } else if (applicationManager.mainStageFocusedApplication) {
85 return applicationManager.mainStageFocusedApplication.fullscreen;
86 } else {
87 return false;
88 }
89 }
91 Connections {
92 target: applicationManager
93 ignoreUnknownSignals: true
94 onFocusRequested: {
95 shell.activateApplication(desktopFile);
96 }
97 }
99 function activateApplication(desktopFile, argument) {
100 if (applicationManager) {
101 // For newly started applications, as it takes them time to draw their first frame
102 // we add a delay before we hide the animation screenshots to compensate.
103 var addDelay = !applicationManager.getApplicationFromDesktopFile(desktopFile);
105 var application;
106 application = applicationManager.activateApplication(desktopFile, argument);
107 if (application == null) {
108 return;
109 }
110 if (application.stage == ApplicationInfo.MainStage || !sideStage.enabled) {
111 mainStage.activateApplication(desktopFile, addDelay);
112 } else {
113 sideStage.activateApplication(desktopFile, addDelay);
114 }
115 stages.show();
116 }
117 }
119 GSettings {
120 id: backgroundSettings
121 schema.id: "org.gnome.desktop.background"
122 }
123 backgroundSource: backgroundSettings.pictureUri
125 Item {
126 id: underlay
127 objectName: "underlay"
128 anchors.fill: parent
130 // Whether the underlay is fully covered by opaque UI elements.
131 property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
133 readonly property bool applicationRunning: ((mainStage.applications && mainStage.applications.count > 0)
134 || (sideStage.applications && sideStage.applications.count > 0))
136 // Whether the user should see the topmost application surface (if there's one at all).
137 readonly property bool applicationSurfaceShouldBeSeen: applicationRunning && !stages.fullyHidden
138 && !mainStage.usingScreenshots // but want sideStage animating over app surface
142 // NB! Application surfaces are stacked behing the shell one. So they can only be seen by the user
143 // through the translucent parts of the shell surface.
144 visible: !fullyCovered && !applicationSurfaceShouldBeSeen
146 CrossFadeImage {
147 id: backgroundImage
148 objectName: "backgroundImage"
150 anchors.fill: parent
151 source: shell.background
152 fillMode: Image.PreserveAspectCrop
153 }
155 Rectangle {
156 anchors.fill: parent
157 color: "black"
158 opacity: dash.disappearingAnimationProgress
159 }
161 Dash {
162 id: dash
163 objectName: "dash"
165 hides: [stages, launcher, panel.indicators]
166 shown: disappearingAnimationProgress !== 1.0
167 enabled: disappearingAnimationProgress === 0.0 && edgeDemo.dashEnabled
168 // FIXME: unfocus all applications when going back to the dash
169 onEnabledChanged: {
170 if (enabled) {
171 shell.applicationManager.unfocusCurrentApplication()
172 }
173 }
175 anchors {
176 fill: parent
177 topMargin: panel.panelHeight
178 }
180 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
181 opacity: 1.0 - disappearingAnimationProgress
182 property real disappearingAnimationProgress: stagesOuterContainer.showProgress
184 // FIXME: only necessary because stagesOuterContainer.showProgress
185 // is not animated
186 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
187 }
188 }
190 Item {
191 id: stagesOuterContainer
193 width: parent.width
194 height: parent.height
195 x: launcher.progress
196 Behavior on x {SmoothedAnimation{velocity: 600}}
198 property real showProgress:
199 MathLocal.clamp(1 - (x + stages.x) / shell.width, 0, 1)
201 Showable {
202 id: stages
203 objectName: "stages"
205 x: width
207 property bool fullyShown: shown && x == 0 && parent.x == 0
208 property bool fullyHidden: !shown && x == width
209 hides: [panel.indicators]
210 shown: false
211 opacity: 1.0
212 showAnimation: StandardAnimation { property: "x"; duration: 350; to: 0; easing.type: Easing.OutCubic }
213 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: width; easing.type: Easing.OutCubic }
215 width: parent.width
216 height: parent.height
218 // close the stages when no focused application remains
219 Connections {
220 target: shell.applicationManager
221 onMainStageFocusedApplicationChanged: stages.closeIfNoApplications()
222 onSideStageFocusedApplicationChanged: stages.closeIfNoApplications()
223 ignoreUnknownSignals: true
224 }
226 function closeIfNoApplications() {
227 if (!shell.applicationManager.mainStageFocusedApplication
228 && !shell.applicationManager.sideStageFocusedApplication
229 && shell.applicationManager.mainStageApplications.count == 0
230 && shell.applicationManager.sideStageApplications.count == 0) {
231 stages.hide();
232 }
233 }
235 // show the stages when an application gets the focus
236 Connections {
237 target: shell.applicationManager
238 onMainStageFocusedApplicationChanged: {
239 if (shell.applicationManager.mainStageFocusedApplication) {
240 mainStage.show();
241 stages.show();
242 }
243 }
244 onSideStageFocusedApplicationChanged: {
245 if (shell.applicationManager.sideStageFocusedApplication) {
246 sideStage.show();
247 stages.show();
248 }
249 }
250 ignoreUnknownSignals: true
251 }
253 Stage {
254 id: mainStage
256 anchors.fill: parent
257 fullyShown: stages.fullyShown
258 fullyHidden: stages.fullyHidden
259 shouldUseScreenshots: !fullyShown
260 rightEdgeEnabled: !sideStage.enabled
262 applicationManager: shell.applicationManager
263 rightEdgeDraggingAreaWidth: shell.edgeSize
264 normalApplicationY: shell.panelHeight
266 shown: true
267 function show() {
268 stages.show();
269 }
270 function hide() {
271 }
273 // FIXME: workaround the fact that focusing a main stage application
274 // raises its surface on top of all other surfaces including the ones
275 // that belong to side stage applications.
276 onFocusedApplicationChanged: {
277 if (focusedApplication && sideStage.focusedApplication && sideStage.fullyShown) {
278 shell.applicationManager.focusApplication(sideStage.focusedApplication);
279 }
280 }
281 }
283 SideStage {
284 id: sideStage
286 applicationManager: shell.applicationManager
287 rightEdgeDraggingAreaWidth: shell.edgeSize
288 normalApplicationY: shell.panelHeight
290 onShownChanged: {
291 if (!shown && mainStage.applications.count == 0) {
292 stages.hide();
293 }
294 }
295 // FIXME: when hiding the side stage, refocus the main stage
296 // application so that it goes in front of the side stage
297 // application and hides it
298 onFullyShownChanged: {
299 if (!fullyShown && stages.fullyShown && sideStage.focusedApplication != null) {
300 shell.applicationManager.focusApplication(mainStage.focusedApplication);
301 }
302 }
304 enabled: shell.width >= units.gu(60)
305 visible: enabled
306 fullyShown: stages.fullyShown && shown
307 && sideStage[sideStageRevealer.boundProperty] == sideStageRevealer.openedValue
308 shouldUseScreenshots: !fullyShown || mainStage.usingScreenshots || sideStageRevealer.pressed
310 available: enabled
311 hides: [launcher, panel.indicators]
312 shown: false
313 showAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.openedValue; easing.type: Easing.OutQuint }
314 hideAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.closedValue; easing.type: Easing.OutQuint }
316 width: units.gu(40)
317 height: stages.height
318 handleExpanded: sideStageRevealer.pressed
319 }
321 Revealer {
322 id: sideStageRevealer
324 enabled: mainStage.applications.count > 0 && sideStage.applications.count > 0
325 && sideStage.available
326 direction: Qt.RightToLeft
327 openedValue: parent.width - sideStage.width
328 hintDisplacement: units.gu(3)
329 /* The size of the sidestage handle needs to be bigger than the
330 typical size used for edge detection otherwise it is really
331 hard to grab.
332 */
333 handleSize: sideStage.shown ? units.gu(4) : shell.edgeSize
334 closedValue: parent.width + sideStage.handleSizeCollapsed
335 target: sideStage
336 x: parent.width - width
337 width: sideStage.width + handleSize * 0.7
338 height: sideStage.height
339 orientation: Qt.Horizontal
340 }
342 DragHandle {
343 id: stagesDragHandle
345 anchors.top: parent.top
346 anchors.bottom: parent.bottom
347 anchors.right: parent.left
349 width: shell.edgeSize
350 direction: Direction.Leftwards
351 enabled: edgeDemo.dashEnabled
352 property bool haveApps: mainStage.applications.count > 0 || sideStage.applications.count > 0
354 maxTotalDragDistance: haveApps ? parent.width : parent.width * 0.7
355 // Make autocompletion impossible when !haveApps
356 edgeDragEvaluator.minDragDistance: haveApps ? maxTotalDragDistance * 0.1 : Number.MAX_VALUE
357 }
358 }
359 }
361 Connections {
362 id: powerConnection
363 target: Powerd
365 property var previousMainApp: null
366 property var previousSideApp: null
368 function setFocused(focused) {
369 if (!focused) {
370 powerConnection.previousMainApp = applicationManager.mainStageFocusedApplication;
371 powerConnection.previousSideApp = applicationManager.sideStageFocusedApplication;
372 applicationManager.unfocusCurrentApplication();
373 } else {
374 if (powerConnection.previousMainApp) {
375 applicationManager.focusApplication(powerConnection.previousMainApp);
376 powerConnection.previousMainApp = null;
377 }
378 if (powerConnection.previousSideApp) {
379 applicationManager.focusApplication(powerConnection.previousSideApp);
380 powerConnection.previousSideApp = null;
381 }
382 }
383 }
385 onDisplayPowerStateChange: {
386 // We ignore any display-off signals when the proximity sensor
387 // is active. This usually indicates something like a phone call.
388 if (status == Powerd.Off && (flags & Powerd.UseProximity) == 0) {
389 powerConnection.setFocused(false);
390 SessionManager.lock();
391 } else if (status == Powerd.On) {
392 powerConnection.setFocused(true);
393 }
395 // No reason to chew demo CPU when user isn't watching
396 if (status == Powerd.Off) {
397 edgeDemo.paused = true;
398 } else if (status == Powerd.On) {
399 edgeDemo.paused = false;
400 }
401 }
402 }
404 function showHome() {
405 // Animate if moving between application and dash
406 if (!stages.shown) {
407 dash.setCurrentScope("home.scope", true, false)
408 } else {
409 dash.setCurrentScope("home.scope", false, false)
410 }
411 stages.hide()
412 }
414 Item {
415 id: overlay
417 anchors.fill: parent
419 Panel {
420 id: panel
421 anchors.fill: parent //because this draws indicator menus
422 indicatorsMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
423 indicators {
424 hides: [launcher]
425 available: edgeDemo.panelEnabled
426 contentEnabled: edgeDemo.panelContentEnabled
427 }
428 fullscreenMode: shell.fullscreenMode
430 InputFilterArea {
431 anchors {
432 top: parent.top
433 left: parent.left
434 right: parent.right
435 }
436 height: (panel.fullscreenMode) ? shell.edgeSize : panel.panelHeight
437 blockInput: true
438 }
439 }
441 Hud {
442 id: hud
444 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
445 height: parent.height
447 available: !panel.indicators.shown && edgeDemo.dashEnabled
448 shown: false
449 showAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: 0; easing.type: Easing.Linear }
450 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
452 Connections {
453 target: shell.applicationManager
454 onMainStageFocusedApplicationChanged: hud.hide()
455 onSideStageFocusedApplicationChanged: hud.hide()
456 }
457 }
459 Revealer {
460 id: hudRevealer
462 enabled: hud.shown
463 width: hud.width
464 anchors.left: hud.left
465 height: parent.height
466 target: hud.revealerTarget
467 closedValue: height
468 openedValue: 0
469 direction: Qt.RightToLeft
470 orientation: Qt.Vertical
471 handleSize: hud.handleHeight
472 onCloseClicked: target.hide()
473 }
475 Bottombar {
476 theHud: hud
477 anchors.fill: parent
478 enabled: hud.available
479 applicationIsOnForeground: applicationFocused
480 }
482 InputFilterArea {
483 blockInput: launcher.shown
484 anchors {
485 top: parent.top
486 bottom: parent.bottom
487 left: parent.left
488 }
489 width: launcher.width
490 }
492 Launcher {
493 id: launcher
495 anchors.top: parent.top
496 anchors.bottom: parent.bottom
497 width: parent.width
498 dragAreaWidth: shell.edgeSize
499 available: edgeDemo.launcherEnabled
500 onDashItemSelected: showHome()
501 onDash: {
502 if (stages.shown) {
503 dash.setCurrentScope("applications.scope", true, false)
504 stages.hide();
505 launcher.hide();
506 }
507 }
508 onLauncherApplicationSelected:{
509 shell.activateApplication(appId)
510 }
511 onShownChanged: {
512 if (shown) {
513 panel.indicators.hide()
514 hud.hide()
515 }
516 }
517 }
519 Notifications {
520 id: notifications
522 model: NotificationBackend.Model
523 anchors {
524 top: parent.top
525 right: parent.right
526 bottom: parent.bottom
527 leftMargin: units.gu(1)
528 rightMargin: units.gu(1)
529 topMargin: panel.panelHeight + units.gu(1)
530 }
531 states: [
532 State {
533 name: "narrow"
534 when: overlay.width <= units.gu(60)
535 AnchorChanges { target: notifications; anchors.left: parent.left }
536 },
537 State {
538 name: "wide"
539 when: overlay.width > units.gu(60)
540 AnchorChanges { target: notifications; anchors.left: undefined }
541 PropertyChanges { target: notifications; width: units.gu(38) }
542 }
543 ]
544 }
545 }
547 focus: true
549 InputFilterArea {
550 anchors {
551 top: parent.top
552 bottom: parent.bottom
553 left: parent.left
554 }
555 width: shell.edgeSize
556 blockInput: true
557 }
559 Binding {
560 target: i18n
561 property: "domain"
562 value: "unity8"
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()
600 }