diff --git a/packages/core/src/browser/shell/tab-bar-decorator.ts b/packages/core/src/browser/shell/tab-bar-decorator.ts index 4b48d848876b1..ec5ef4ba15553 100644 --- a/packages/core/src/browser/shell/tab-bar-decorator.ts +++ b/packages/core/src/browser/shell/tab-bar-decorator.ts @@ -17,9 +17,12 @@ import debounce = require('lodash.debounce'); import { Title, Widget } from '@phosphor/widgets'; import { inject, injectable, named } from 'inversify'; -import { Event, Emitter, ContributionProvider } from '../../common'; -import { WidgetDecoration } from '../widget-decoration'; +import { ContributionProvider, Emitter, Event } from '../../common'; +import { ColorRegistry } from '../color-registry'; +import { Decoration, DecorationsService, DecorationsServiceImpl } from '../decorations-service'; import { FrontendApplicationContribution } from '../frontend-application-contribution'; +import { Navigatable } from '../navigatable-types'; +import { WidgetDecoration } from '../widget-decoration'; export const TabBarDecorator = Symbol('TabBarDecorator'); @@ -53,6 +56,12 @@ export class TabBarDecoratorService implements FrontendApplicationContribution { @inject(ContributionProvider) @named(TabBarDecorator) protected readonly contributions: ContributionProvider; + @inject(DecorationsService) + protected readonly decorationsService: DecorationsServiceImpl; + + @inject(ColorRegistry) + protected readonly colors: ColorRegistry; + initialize(): void { this.contributions.getContributions().map(decorator => decorator.onDidChangeDecorations(this.fireDidChangeDecorations)); } @@ -71,6 +80,27 @@ export class TabBarDecoratorService implements FrontendApplicationContribution { const decorations = decorator.decorate(title); all = all.concat(decorations); } + if (Navigatable.is(title.owner)) { + if (title.owner.getResourceUri() !== undefined) { + const serviceDecorations = this.decorationsService.getDecoration(title.owner.getResourceUri()!, false); + all = all.concat(serviceDecorations.map(d => this.toDecorator(d))); + } + } return all; } + + protected toDecorator(decoration: Decoration): WidgetDecoration.Data { + const colorVariable = decoration.colorId && this.colors.toCssVariableName(decoration.colorId); + return { + tailDecorations: [ + { + data: decoration.letter ? decoration.letter : '', + fontData: { + color: colorVariable && `var(${colorVariable})` + }, + tooltip: decoration.tooltip ? decoration.tooltip : '' + } + ] + }; + } } diff --git a/packages/core/src/browser/shell/tab-bars.ts b/packages/core/src/browser/shell/tab-bars.ts index c43f42c73bbcf..6a9a4845f01a9 100644 --- a/packages/core/src/browser/shell/tab-bars.ts +++ b/packages/core/src/browser/shell/tab-bars.ts @@ -170,8 +170,8 @@ export class TabBarRenderer extends TabBar.Renderer { const hover = this.tabBar && (this.tabBar.orientation === 'horizontal' && this.corePreferences?.['window.tabbar.enhancedPreview'] === 'classic') ? { title: title.caption } : { - onmouseenter: this.handleMouseEnterEvent - }; + onmouseenter: this.handleMouseEnterEvent + }; return h.li( { @@ -188,6 +188,7 @@ export class TabBarRenderer extends TabBar.Renderer { { className: 'theia-tab-icon-label' }, this.renderIcon(data, isInSidePanel), this.renderLabel(data, isInSidePanel), + this.renderTailDecorations(data, isInSidePanel), this.renderBadge(data, isInSidePanel), this.renderLock(data, isInSidePanel) ), @@ -289,6 +290,35 @@ export class TabBarRenderer extends TabBar.Renderer { return h.div({ className: 'p-TabBar-tabLabel', style }, data.title.label); } + protected renderTailDecorations(renderData: SideBarRenderData, isInSidePanel?: boolean): VirtualElement[] { + const tailDecorations = this.getDecorationData(renderData.title, 'tailDecorations') + .filter(acc => acc !== undefined).reduce((acc, current) => acc!.concat(current!), []); + if (tailDecorations === undefined || tailDecorations.length === 0) { + return []; + } + let dotDecoration: WidgetDecoration.TailDecoration.AnyPartial | undefined; + const otherDecorations: WidgetDecoration.TailDecoration.AnyPartial[] = []; + tailDecorations.reverse().forEach(decoration => { + const partial = decoration as WidgetDecoration.TailDecoration.AnyPartial; + if (WidgetDecoration.TailDecoration.isDotDecoration(partial)) { + dotDecoration ||= partial; + } else if (partial.data || partial.icon || partial.iconClass) { + otherDecorations.push(partial); + } + }); + const decorationsToRender = dotDecoration ? [dotDecoration, ...otherDecorations] : otherDecorations; + return decorationsToRender.map((decoration, index) => { + const { tooltip, data, fontData, color, icon, iconClass } = decoration; + const iconToRender = icon ?? iconClass; + const className = ['p-TabBar-tail', 'flex'].join(' '); + const style = fontData ? fontData : color ? { color } : undefined; + const content = (data ? data : iconToRender + ? h.span({ className: this.getIconClass(iconToRender, iconToRender === 'circle' ? [WidgetDecoration.Styles.DECORATOR_SIZE_CLASS] : []) }) + : '') + (index !== decorationsToRender.length - 1 ? ',' : ''); + return h.span({ key: ('tailDecoration_' + index), className, style, title: tooltip ?? content }, content); + }); + } + renderBadge(data: SideBarRenderData, isInSidePanel?: boolean): VirtualElement { const totalBadge = this.getDecorationData(data.title, 'badge').reduce((sum, badge) => sum! + badge!, 0); if (!totalBadge) { diff --git a/packages/core/src/browser/style/tabs.css b/packages/core/src/browser/style/tabs.css index ef247de6fcfcd..a77ae0ad9f172 100644 --- a/packages/core/src/browser/style/tabs.css +++ b/packages/core/src/browser/style/tabs.css @@ -123,6 +123,12 @@ white-space: nowrap; } +.p-TabBar-tail { + padding-left: 5px; + text-align: center; + justify-content: center; +} + .p-TabBar.theia-app-centers .p-TabBar-tabLabelWrapper { display: flex; }