Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions src/vs/base/browser/ui/iconLabel/iconLabel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface IIconLabelValueOptions {
suffix?: string;
hideIcon?: boolean;
extraClasses?: readonly string[];
bgColorClassName?: string;
bold?: boolean;
italic?: boolean;
strikethrough?: boolean;
Expand Down Expand Up @@ -106,6 +107,7 @@ export class IconLabel extends Disposable {

private readonly hoverDelegate: IHoverDelegate;
private readonly customHovers: Map<HTMLElement, IDisposable> = new Map();
private currentBgColorClassName: string | undefined;

constructor(container: HTMLElement, options?: IIconLabelCreationOptions) {
super();
Expand Down Expand Up @@ -189,6 +191,11 @@ export class IconLabel extends Disposable {
}

this.domNode.classNames = labelClasses;
this.clearBgColorClassName();
if (options?.bgColorClassName) {
this.currentBgColorClassName = options.bgColorClassName;
this.getDecorationContainer()?.classList.add(this.currentBgColorClassName ?? '');
}
this.domNode.element.setAttribute('aria-label', ariaLabel);
this.labelContainer.classList.value = '';
this.labelContainer.classList.add(...containerClasses);
Expand Down Expand Up @@ -242,13 +249,37 @@ export class IconLabel extends Disposable {
}

public override dispose() {
this.clearBgColorClassName();
super.dispose();
for (const disposable of this.customHovers.values()) {
disposable.dispose();
}
this.customHovers.clear();
}

private clearBgColorClassName(): void {
if (!this.currentBgColorClassName) {
return;
}
this.getDecorationContainer()?.classList.remove(this.currentBgColorClassName);
this.currentBgColorClassName = undefined;
}

private getDecorationContainer(): HTMLElement | undefined {
const listRow = this.domNode.element.closest('.monaco-list-row');
if (!listRow) {
return undefined;
}

for (const child of listRow.children) {
if (dom.isHTMLElement(child) && child.classList.contains('monaco-tl-decoration-container')) {
return child;
}
}

return undefined;
}

private getOrCreateSuffixNode() {
if (!this.suffixNode) {
const suffixContainer = this._register(new FastLabelNode(dom.after(this.nameContainer, dom.$('span.monaco-icon-suffix-container'))));
Expand Down
1 change: 1 addition & 0 deletions src/vs/base/browser/ui/tree/abstractTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,7 @@ export class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListR
}

renderTemplate(container: HTMLElement): ITreeListTemplateData<TTemplateData> {
append(container, $('.monaco-tl-decoration-container'));
const el = append(container, $('.monaco-tl-row'));
const indent = append(el, $('.monaco-tl-indent'));
const twistie = append(el, $('.monaco-tl-twistie'));
Expand Down
11 changes: 11 additions & 0 deletions src/vs/base/browser/ui/tree/media/tree.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@
.monaco-tl-row.disabled {
cursor: default;
}

.monaco-tl-decoration-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0.3;
pointer-events: none;
}

.monaco-tl-indent {
height: 100%;
position: absolute;
Expand Down
3 changes: 2 additions & 1 deletion src/vs/workbench/api/browser/mainThreadDecorations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,12 @@ export class MainThreadDecorations implements MainThreadDecorationsShape {
if (!data) {
return undefined;
}
const [bubble, tooltip, letter, themeColor] = data;
const [bubble, tooltip, letter, themeColor, backgroundColor] = data;
return {
weight: 10,
bubble: bubble ?? false,
color: themeColor?.id,
backgroundColor: backgroundColor?.id,
tooltip,
letter
};
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/api/common/extHost.protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3171,7 +3171,7 @@ export interface DecorationRequest {
readonly uri: UriComponents;
}

export type DecorationData = [boolean, string, string | ThemeIcon, ThemeColor];
export type DecorationData = [boolean, string, string | ThemeIcon, ThemeColor, ThemeColor | undefined];
export type DecorationReply = { [id: number]: DecorationData };

export interface ExtHostDecorationsShape {
Expand Down
2 changes: 1 addition & 1 deletion src/vs/workbench/api/common/extHostDecorations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export class ExtHostDecorations implements ExtHostDecorationsShape {
if (data.badge && typeof data.badge !== 'string') {
checkProposedApiEnabled(extensionId, 'codiconDecoration');
}
result[id] = <DecorationData>[data.propagate, data.tooltip, data.badge, data.color];
result[id] = <DecorationData>[data.propagate, data.tooltip, data.badge, data.color, data.backgroundColor];
} catch (e) {
this._logService.warn(`INVALID decoration from extension '${extensionId.identifier.value}': ${e}`);
}
Expand Down
6 changes: 4 additions & 2 deletions src/vs/workbench/api/common/extHostTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2592,7 +2592,7 @@ export class FileDecoration {
throw new Error(`The 'badge'-property is not a valid ThemeIcon`);
}
}
if (!d.color && !d.badge && !d.tooltip) {
if (!d.color && !d.backgroundColor && !d.badge && !d.tooltip) {
throw new Error(`The decoration is empty`);
}
return true;
Expand All @@ -2601,12 +2601,14 @@ export class FileDecoration {
badge?: string | vscode.ThemeIcon;
tooltip?: string;
color?: vscode.ThemeColor;
backgroundColor?: vscode.ThemeColor;
propagate?: boolean;

constructor(badge?: string | ThemeIcon, tooltip?: string, color?: ThemeColor) {
constructor(badge?: string | ThemeIcon, tooltip?: string, color?: ThemeColor, backgroundColor?: ThemeColor) {
this.badge = badge;
this.tooltip = tooltip;
this.color = color;
this.backgroundColor = backgroundColor;
}
}

Expand Down
8 changes: 7 additions & 1 deletion src/vs/workbench/browser/labels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -705,7 +705,13 @@ class ResourceLabelWidget extends IconLabel {
}

if (this.options.fileDecorations.colors) {
iconLabelOptions.extraClasses.push(decoration.labelClassName);
if (decoration.labelClassName) {
iconLabelOptions.extraClasses.push(decoration.labelClassName);
}

if (decoration.bgColorClassName) {
iconLabelOptions.bgColorClassName = decoration.bgColorClassName;
}
}

if (this.options.fileDecorations.badges) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ class DecorationRule {
if (Array.isArray(data)) {
return data.map(DecorationRule.keyOf).join(',');
} else {
const { color, letter } = data;
const { color, backgroundColor, letter } = data;
if (ThemeIcon.isThemeIcon(letter)) {
return `${color}+${letter.id}`;
return `${color}+${backgroundColor}+${letter.id}`;
} else {
return `${color}/${letter}`;
return `${color}/${backgroundColor}/${letter}`;
}
}
}
Expand All @@ -44,6 +44,7 @@ class DecorationRule {

readonly data: IDecorationData | IDecorationData[];
readonly itemColorClassName: string;
readonly itemBgColorClassName: string;
readonly itemBadgeClassName: string;
readonly iconBadgeClassName: string;
readonly bubbleBadgeClassName: string;
Expand All @@ -54,6 +55,7 @@ class DecorationRule {
this.data = data;
const suffix = hash(key).toString(36);
this.itemColorClassName = `${DecorationRule._classNamesPrefix}-itemColor-${suffix}`;
this.itemBgColorClassName = `${DecorationRule._classNamesPrefix}-itemBgColor-${suffix}`;
this.itemBadgeClassName = `${DecorationRule._classNamesPrefix}-itemBadge-${suffix}`;
this.bubbleBadgeClassName = `${DecorationRule._classNamesPrefix}-bubbleBadge-${suffix}`;
this.iconBadgeClassName = `${DecorationRule._classNamesPrefix}-iconBadge-${suffix}`;
Expand All @@ -76,9 +78,9 @@ class DecorationRule {
}

private _appendForOne(data: IDecorationData, element: HTMLStyleElement): void {
const { color, letter } = data;
const { color, backgroundColor, letter } = data;
// label
createCSSRule(`.${this.itemColorClassName}`, `color: ${getColor(color)};`, element);
this._appendColorCSSRule(element, color, backgroundColor);
if (ThemeIcon.isThemeIcon(letter)) {
this._createIconCSSRule(letter, color, element);
} else if (letter) {
Expand All @@ -89,7 +91,8 @@ class DecorationRule {
private _appendForMany(data: IDecorationData[], element: HTMLStyleElement): void {
// label
const { color } = data.find(d => !!d.color) ?? data[0];
createCSSRule(`.${this.itemColorClassName}`, `color: ${getColor(color)};`, element);
const { backgroundColor } = data.find(d => !!d.backgroundColor) ?? data[0];
this._appendColorCSSRule(element, color, backgroundColor);

// badge or icon
const letters: string[] = [];
Expand Down Expand Up @@ -121,6 +124,15 @@ class DecorationRule {
}
}

private _appendColorCSSRule(element: HTMLStyleElement, color: string | undefined, backgroundColor: string | undefined): void {
createCSSRule(`.${this.itemColorClassName}`, `color: ${getColor(color)};`, element);

if (backgroundColor) {
const backgroundColorRule = backgroundColor ? `background-color: ${getColor(backgroundColor)};` : '';
createCSSRule(`.${this.itemBgColorClassName}`, `${backgroundColorRule}`, element);
}
}

private _createIconCSSRule(icon: ThemeIcon, color: string | undefined, element: HTMLStyleElement) {

const modifier = ThemeIcon.getModifier(icon);
Expand Down Expand Up @@ -151,6 +163,7 @@ class DecorationRule {

removeCSSRules(element: HTMLStyleElement): void {
removeCSSRulesContainingSelector(this.itemColorClassName, element);
removeCSSRulesContainingSelector(this.itemBgColorClassName, element);
removeCSSRulesContainingSelector(this.itemBadgeClassName, element);
removeCSSRulesContainingSelector(this.bubbleBadgeClassName, element);
removeCSSRulesContainingSelector(this.iconBadgeClassName, element);
Expand Down Expand Up @@ -188,6 +201,7 @@ class DecorationStyles {
rule.acquire();

const labelClassName = rule.itemColorClassName;
const bgColorClassName = data.some(d => !!d.backgroundColor) ? rule.itemBgColorClassName : undefined;
let badgeClassName = rule.itemBadgeClassName;
const iconClassName = rule.iconBadgeClassName;
let tooltip = distinct(data.filter(d => !isFalsyOrWhitespace(d.tooltip)).map(d => d.tooltip)).join(' • ');
Expand All @@ -201,6 +215,7 @@ class DecorationStyles {

return {
labelClassName,
bgColorClassName,
badgeClassName,
iconClassName,
strikethrough,
Expand Down
2 changes: 2 additions & 0 deletions src/vs/workbench/services/decorations/common/decorations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const IDecorationsService = createDecorator<IDecorationsService>('IFileDe
export interface IDecorationData {
readonly weight?: number;
readonly color?: ColorIdentifier;
readonly backgroundColor?: ColorIdentifier;
readonly letter?: string | ThemeIcon;
readonly tooltip?: string;
readonly strikethrough?: boolean;
Expand All @@ -26,6 +27,7 @@ export interface IDecoration extends IDisposable {
readonly tooltip: string;
readonly strikethrough: boolean;
readonly labelClassName: string;
readonly bgColorClassName?: string;
readonly badgeClassName: string;
readonly iconClassName: string;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,37 @@ suite('DecorationsService', function () {
reg.dispose();
});

test('Background color class name is optional', function () {

const uri = URI.parse('foo:bar');

const regNoBackground = service.registerDecorationsProvider({
label: 'NoBackground',
onDidChange: Event.None,
provideDecorations() {
return { color: 'someBlue', tooltip: 'No background' };
}
});

const noBackground = service.getDecoration(uri, false)!;
assert.strictEqual(noBackground.bgColorClassName, undefined);
noBackground.dispose();
regNoBackground.dispose();

const regWithBackground = service.registerDecorationsProvider({
label: 'WithBackground',
onDidChange: Event.None,
provideDecorations() {
return { color: 'someBlue', backgroundColor: 'someGreen', tooltip: 'With background' };
}
});

const withBackground = service.getDecoration(uri, false)!;
assert.ok(withBackground.bgColorClassName);
withBackground.dispose();
regWithBackground.dispose();
});

test('Clear decorations on provider dispose', async function () {
return runWithFakedTimers({}, async function () {

Expand Down
8 changes: 7 additions & 1 deletion src/vscode-dts/vscode.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8262,6 +8262,11 @@ declare module 'vscode' {
*/
color?: ThemeColor;

/**
* The background color of this decoration.
*/
backgroundColor?: ThemeColor;

/**
* A flag expressing that this decoration should be
* propagated to its parents.
Expand All @@ -8274,8 +8279,9 @@ declare module 'vscode' {
* @param badge A letter that represents the decoration.
* @param tooltip The tooltip of the decoration.
* @param color The color of the decoration.
* @param backgroundColor The background color of the decoration.
*/
constructor(badge?: string, tooltip?: string, color?: ThemeColor);
constructor(badge?: string, tooltip?: string, color?: ThemeColor, backgroundColor?: ThemeColor);
}

/**
Expand Down
5 changes: 5 additions & 0 deletions src/vscode-dts/vscode.proposed.codiconDecoration.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ declare module 'vscode' {
*/
color?: ThemeColor;

/**
* The background color of this decoration.
*/
backgroundColor?: ThemeColor;

/**
* A flag expressing that this decoration should be
* propagated to its parents.
Expand Down