Skip to content

Commit e5bf5a7

Browse files
authored
button: switch to theming by css variables (microsoft#165515)
* button: switch to themeing by css variables * make options mandatory, don't mixin default styles * merge fixes
1 parent 9984da1 commit e5bf5a7

File tree

21 files changed

+159
-221
lines changed

21 files changed

+159
-221
lines changed

src/vs/base/browser/ui/button/button.ts

Lines changed: 58 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import { Color } from 'vs/base/common/color';
1414
import { Emitter, Event as BaseEvent } from 'vs/base/common/event';
1515
import { KeyCode } from 'vs/base/common/keyCodes';
1616
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
17-
import { mixin } from 'vs/base/common/objects';
1817
import { localize } from 'vs/nls';
1918
import 'vs/css!./button';
2019

@@ -24,22 +23,28 @@ export interface IButtonOptions extends IButtonStyles {
2423
readonly secondary?: boolean;
2524
}
2625

26+
export type CSSValueString = string;
27+
2728
export interface IButtonStyles {
28-
buttonBackground?: Color;
29-
buttonHoverBackground?: Color;
30-
buttonForeground?: Color;
31-
buttonSeparator?: Color;
32-
buttonSecondaryBackground?: Color;
33-
buttonSecondaryHoverBackground?: Color;
34-
buttonSecondaryForeground?: Color;
35-
buttonBorder?: Color;
29+
readonly buttonBackground?: CSSValueString;
30+
readonly buttonHoverBackground?: CSSValueString;
31+
readonly buttonForeground?: CSSValueString;
32+
readonly buttonSeparator?: CSSValueString;
33+
readonly buttonSecondaryBackground?: CSSValueString;
34+
readonly buttonSecondaryHoverBackground?: CSSValueString;
35+
readonly buttonSecondaryForeground?: CSSValueString;
36+
readonly buttonBorder?: CSSValueString;
3637
}
3738

38-
const defaultOptions: IButtonStyles = {
39-
buttonBackground: Color.fromHex('#0E639C'),
40-
buttonHoverBackground: Color.fromHex('#006BB3'),
41-
buttonSeparator: Color.white,
42-
buttonForeground: Color.white
39+
export const defaultOptions: IButtonStyles = {
40+
buttonBackground: '#0E639C',
41+
buttonHoverBackground: '#006BB3',
42+
buttonSeparator: Color.white.toString(),
43+
buttonForeground: Color.white.toString(),
44+
buttonBorder: undefined,
45+
buttonSecondaryBackground: undefined,
46+
buttonSecondaryForeground: undefined,
47+
buttonSecondaryHoverBackground: undefined
4348
};
4449

4550
export interface IButton extends IDisposable {
@@ -48,7 +53,6 @@ export interface IButton extends IDisposable {
4853
label: string;
4954
icon: CSSIcon;
5055
enabled: boolean;
51-
style(styles: IButtonStyles): void;
5256
focus(): void;
5357
hasFocus(): boolean;
5458
}
@@ -62,40 +66,31 @@ export class Button extends Disposable implements IButton {
6266
protected _element: HTMLElement;
6367
protected options: IButtonOptions;
6468

65-
private buttonBackground: Color | undefined;
66-
private buttonHoverBackground: Color | undefined;
67-
private buttonForeground: Color | undefined;
68-
private buttonSecondaryBackground: Color | undefined;
69-
private buttonSecondaryHoverBackground: Color | undefined;
70-
private buttonSecondaryForeground: Color | undefined;
71-
private buttonBorder: Color | undefined;
72-
7369
private _onDidClick = this._register(new Emitter<Event>());
7470
get onDidClick(): BaseEvent<Event> { return this._onDidClick.event; }
7571

7672
private focusTracker: IFocusTracker;
7773

78-
constructor(container: HTMLElement, options?: IButtonOptions) {
74+
constructor(container: HTMLElement, options: IButtonOptions) {
7975
super();
8076

81-
this.options = options || Object.create(null);
82-
mixin(this.options, defaultOptions, false);
83-
84-
this.buttonForeground = this.options.buttonForeground;
85-
this.buttonBackground = this.options.buttonBackground;
86-
this.buttonHoverBackground = this.options.buttonHoverBackground;
87-
88-
this.buttonSecondaryForeground = this.options.buttonSecondaryForeground;
89-
this.buttonSecondaryBackground = this.options.buttonSecondaryBackground;
90-
this.buttonSecondaryHoverBackground = this.options.buttonSecondaryHoverBackground;
91-
92-
this.buttonBorder = this.options.buttonBorder;
77+
this.options = options;
9378

9479
this._element = document.createElement('a');
9580
this._element.classList.add('monaco-button');
9681
this._element.tabIndex = 0;
9782
this._element.setAttribute('role', 'button');
9883

84+
const background = options.secondary ? options.buttonSecondaryBackground : options.buttonBackground;
85+
const foreground = options.secondary ? options.buttonSecondaryForeground : options.buttonForeground;
86+
const border = options.buttonBorder;
87+
88+
this._element.style.color = foreground || '';
89+
this._element.style.backgroundColor = background || '';
90+
if (border) {
91+
this._element.style.border = `1px solid ${border}`;
92+
}
93+
9994
container.appendChild(this._element);
10095

10196
this._register(Gesture.addTarget(this._element));
@@ -129,65 +124,29 @@ export class Button extends Disposable implements IButton {
129124

130125
this._register(addDisposableListener(this._element, EventType.MOUSE_OVER, e => {
131126
if (!this._element.classList.contains('disabled')) {
132-
this.setHoverBackground();
127+
this.updateBackground(true);
133128
}
134129
}));
135130

136131
this._register(addDisposableListener(this._element, EventType.MOUSE_OUT, e => {
137-
this.applyStyles(); // restore standard styles
132+
this.updateBackground(false); // restore standard styles
138133
}));
139134

140135
// Also set hover background when button is focused for feedback
141136
this.focusTracker = this._register(trackFocus(this._element));
142-
this._register(this.focusTracker.onDidFocus(() => { if (this.enabled) { this.setHoverBackground(); } }));
143-
this._register(this.focusTracker.onDidBlur(() => { if (this.enabled) { this.applyStyles(); } }));
144-
145-
this.applyStyles();
137+
this._register(this.focusTracker.onDidFocus(() => { if (this.enabled) { this.updateBackground(true); } }));
138+
this._register(this.focusTracker.onDidBlur(() => { if (this.enabled) { this.updateBackground(false); } }));
146139
}
147140

148-
private setHoverBackground(): void {
149-
let hoverBackground;
141+
private updateBackground(hover: boolean): void {
142+
let background;
150143
if (this.options.secondary) {
151-
hoverBackground = this.buttonSecondaryHoverBackground ? this.buttonSecondaryHoverBackground.toString() : null;
144+
background = hover ? this.options.buttonSecondaryHoverBackground : this.options.buttonSecondaryBackground;
152145
} else {
153-
hoverBackground = this.buttonHoverBackground ? this.buttonHoverBackground.toString() : null;
154-
}
155-
if (hoverBackground) {
156-
this._element.style.backgroundColor = hoverBackground;
146+
background = hover ? this.options.buttonHoverBackground : this.options.buttonBackground;
157147
}
158-
}
159-
160-
style(styles: IButtonStyles): void {
161-
this.buttonForeground = styles.buttonForeground;
162-
this.buttonBackground = styles.buttonBackground;
163-
this.buttonHoverBackground = styles.buttonHoverBackground;
164-
this.buttonSecondaryForeground = styles.buttonSecondaryForeground;
165-
this.buttonSecondaryBackground = styles.buttonSecondaryBackground;
166-
this.buttonSecondaryHoverBackground = styles.buttonSecondaryHoverBackground;
167-
this.buttonBorder = styles.buttonBorder;
168-
169-
this.applyStyles();
170-
}
171-
172-
private applyStyles(): void {
173-
if (this._element) {
174-
let background, foreground;
175-
if (this.options.secondary) {
176-
foreground = this.buttonSecondaryForeground ? this.buttonSecondaryForeground.toString() : '';
177-
background = this.buttonSecondaryBackground ? this.buttonSecondaryBackground.toString() : '';
178-
} else {
179-
foreground = this.buttonForeground ? this.buttonForeground.toString() : '';
180-
background = this.buttonBackground ? this.buttonBackground.toString() : '';
181-
}
182-
183-
const border = this.buttonBorder ? this.buttonBorder.toString() : '';
184-
185-
this._element.style.color = foreground;
148+
if (background) {
186149
this._element.style.backgroundColor = background;
187-
188-
this._element.style.borderWidth = border ? '1px' : '';
189-
this._element.style.borderStyle = border ? 'solid' : '';
190-
this._element.style.borderColor = border;
191150
}
192151
}
193152

@@ -293,6 +252,21 @@ export class ButtonWithDropdown extends Disposable implements IButton {
293252
this.separatorContainer.appendChild(this.separator);
294253
this.element.appendChild(this.separatorContainer);
295254

255+
// Separator styles
256+
const border = options.buttonBorder;
257+
if (border) {
258+
this.separatorContainer.style.borderTopWidth = '1px';
259+
this.separatorContainer.style.borderTopStyle = 'solid';
260+
this.separatorContainer.style.borderTopColor = border;
261+
262+
this.separatorContainer.style.borderBottomWidth = '1px';
263+
this.separatorContainer.style.borderBottomStyle = 'solid';
264+
this.separatorContainer.style.borderBottomColor = border;
265+
}
266+
this.separatorContainer.style.backgroundColor = options.buttonBackground?.toString() ?? '';
267+
this.separator.style.backgroundColor = options.buttonSeparator?.toString() ?? '';
268+
269+
296270
this.dropdownButton = this._register(new Button(this.element, { ...options, title: false, supportIcons: true }));
297271
this.dropdownButton.element.title = localize("button dropdown more actions", 'More Actions...');
298272
this.dropdownButton.element.setAttribute('aria-haspopup', 'true');
@@ -330,25 +304,6 @@ export class ButtonWithDropdown extends Disposable implements IButton {
330304
return this.button.enabled;
331305
}
332306

333-
style(styles: IButtonStyles): void {
334-
this.button.style(styles);
335-
this.dropdownButton.style(styles);
336-
337-
// Separator
338-
const border = styles.buttonBorder ? styles.buttonBorder.toString() : '';
339-
340-
this.separatorContainer.style.borderTopWidth = border ? '1px' : '';
341-
this.separatorContainer.style.borderTopStyle = border ? 'solid' : '';
342-
this.separatorContainer.style.borderTopColor = border;
343-
344-
this.separatorContainer.style.borderBottomWidth = border ? '1px' : '';
345-
this.separatorContainer.style.borderBottomStyle = border ? 'solid' : '';
346-
this.separatorContainer.style.borderBottomColor = border;
347-
348-
this.separatorContainer.style.backgroundColor = styles.buttonBackground?.toString() ?? '';
349-
this.separator.style.backgroundColor = styles.buttonSeparator?.toString() ?? '';
350-
}
351-
352307
focus(): void {
353308
this.button.focus();
354309
}
@@ -363,7 +318,7 @@ export class ButtonWithDescription extends Button implements IButtonWithDescript
363318
private _labelElement: HTMLElement;
364319
private _descriptionElement: HTMLElement;
365320

366-
constructor(container: HTMLElement, options?: IButtonOptions) {
321+
constructor(container: HTMLElement, options: IButtonOptions) {
367322
super(container, options);
368323

369324
this._element.classList.add('monaco-description-button');
@@ -412,13 +367,13 @@ export class ButtonBar extends Disposable {
412367
return this._buttons;
413368
}
414369

415-
addButton(options?: IButtonOptions): IButton {
370+
addButton(options: IButtonOptions): IButton {
416371
const button = this._register(new Button(this.container, options));
417372
this.pushButton(button);
418373
return button;
419374
}
420375

421-
addButtonWithDescription(options?: IButtonOptions): IButtonWithDescription {
376+
addButtonWithDescription(options: IButtonOptions): IButtonWithDescription {
422377
const button = this._register(new ButtonWithDescription(this.container, options));
423378
this.pushButton(button);
424379
return button;

src/vs/base/browser/ui/dialog/dialog.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -427,8 +427,6 @@ export class Dialog extends Disposable {
427427
this.element.style.backgroundColor = bgColor?.toString() ?? '';
428428
this.element.style.border = border;
429429

430-
this.buttonBar?.buttons.forEach(button => button.style(style));
431-
432430
this.checkbox?.style(style);
433431

434432
if (fgColor && bgColor) {

src/vs/base/parts/quickinput/browser/quickInput.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1295,14 +1295,14 @@ export class QuickInputController extends Disposable {
12951295
const count = new CountBadge(countContainer, { countFormat: localize({ key: 'quickInput.countSelected', comment: ['This tells the user how many items are selected in a list of items to select from. The items can be anything.'] }, "{0} Selected") });
12961296

12971297
const okContainer = dom.append(headerContainer, $('.quick-input-action'));
1298-
const ok = new Button(okContainer);
1298+
const ok = new Button(okContainer, this.styles.button);
12991299
ok.label = localize('ok', "OK");
13001300
this._register(ok.onDidClick(e => {
13011301
this.onDidAcceptEmitter.fire();
13021302
}));
13031303

13041304
const customButtonContainer = dom.append(headerContainer, $('.quick-input-action'));
1305-
const customButton = new Button(customButtonContainer);
1305+
const customButton = new Button(customButtonContainer, this.styles.button);
13061306
customButton.label = localize('custom', "Custom");
13071307
this._register(customButton.onDidClick(e => {
13081308
this.onDidCustomEmitter.fire();
@@ -1825,8 +1825,6 @@ export class QuickInputController extends Disposable {
18251825
this.ui.container.style.boxShadow = widgetShadow ? `0 0 8px 2px ${widgetShadow}` : '';
18261826
this.ui.inputBox.style(this.styles.inputBox);
18271827
this.ui.count.style(this.styles.countBadge);
1828-
this.ui.ok.style(this.styles.button);
1829-
this.ui.customButton.style(this.styles.button);
18301828
this.ui.list.style(this.styles.list);
18311829

18321830
const content: string[] = [];

src/vs/code/electron-sandbox/issue/issueReporterMain.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import 'vs/css!./media/issueReporter';
77
import 'vs/base/browser/ui/codicons/codiconStyles'; // make sure codicon css is loaded
88
import { localize } from 'vs/nls';
99
import { $, reset, safeInnerHtml, windowOpenNoOpener } from 'vs/base/browser/dom';
10-
import { Button } from 'vs/base/browser/ui/button/button';
10+
import { Button, defaultOptions } from 'vs/base/browser/ui/button/button';
1111
import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels';
1212
import { Delayer } from 'vs/base/common/async';
1313
import { Codicon } from 'vs/base/common/codicons';
@@ -88,7 +88,7 @@ export class IssueReporter extends Disposable {
8888

8989
const issueReporterElement = this.getElementById('issue-reporter');
9090
if (issueReporterElement) {
91-
this.previewButton = new Button(issueReporterElement);
91+
this.previewButton = new Button(issueReporterElement, defaultOptions);
9292
this.updatePreviewButtonState();
9393
}
9494

src/vs/platform/quickinput/browser/quickInput.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ import { IWorkbenchListOptions, WorkbenchList } from 'vs/platform/list/browser/l
1616
import { QuickAccessController } from 'vs/platform/quickinput/browser/quickAccess';
1717
import { IQuickAccessController } from 'vs/platform/quickinput/common/quickAccess';
1818
import { IInputBox, IInputOptions, IKeyMods, IPickOptions, IQuickInputButton, IQuickInputService, IQuickNavigateConfiguration, IQuickPick, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput';
19-
import { getProgressBarStyles } from 'vs/platform/theme/browser/defaultStyles';
20-
import { activeContrastBorder, badgeBackground, badgeForeground, buttonBackground, buttonForeground, buttonHoverBackground, contrastBorder, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, keybindingLabelBackground, keybindingLabelBorder, keybindingLabelBottomBorder, keybindingLabelForeground, pickerGroupBorder, pickerGroupForeground, quickInputBackground, quickInputForeground, quickInputListFocusBackground, quickInputListFocusForeground, quickInputListFocusIconForeground, quickInputTitleBackground, widgetShadow } from 'vs/platform/theme/common/colorRegistry';
19+
import { defaultButtonStyles, getProgressBarStyles } from 'vs/platform/theme/browser/defaultStyles';
20+
import { activeContrastBorder, badgeBackground, badgeForeground, contrastBorder, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, keybindingLabelBackground, keybindingLabelBorder, keybindingLabelBottomBorder, keybindingLabelForeground, pickerGroupBorder, pickerGroupForeground, quickInputBackground, quickInputForeground, quickInputListFocusBackground, quickInputListFocusForeground, quickInputListFocusIconForeground, quickInputTitleBackground, widgetShadow } from 'vs/platform/theme/common/colorRegistry';
2121
import { computeStyles } from 'vs/platform/theme/common/styler';
2222
import { IThemeService, Themable } from 'vs/platform/theme/common/themeService';
2323

@@ -213,12 +213,7 @@ export class QuickInputService extends Themable implements IQuickInputService {
213213
badgeForeground,
214214
badgeBorder: contrastBorder
215215
}),
216-
button: computeStyles(this.theme, {
217-
buttonForeground,
218-
buttonBackground,
219-
buttonHoverBackground,
220-
buttonBorder: contrastBorder
221-
}),
216+
button: defaultButtonStyles,
222217
progressBar: getProgressBarStyles(), // default uses progressBarBackground
223218
keybindingLabel: computeStyles(this.theme, {
224219
keybindingLabelBackground,

src/vs/platform/theme/browser/defaultStyles.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
* Copyright (c) Microsoft Corporation. All rights reserved.
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
5+
import { IButtonStyles } from 'vs/base/browser/ui/button/button';
56
import { IKeybindingLabelStyles } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel';
7+
import { ColorIdentifier, keybindingLabelBackground, keybindingLabelBorder, keybindingLabelBottomBorder, keybindingLabelForeground, asCssValue, widgetShadow, buttonForeground, buttonSeparator, buttonBackground, buttonHoverBackground, buttonSecondaryForeground, buttonSecondaryBackground, buttonSecondaryHoverBackground, buttonBorder, progressBarBackground } from 'vs/platform/theme/common/colorRegistry';
68
import { IProgressBarStyles } from 'vs/base/browser/ui/progressbar/progressbar';
7-
import { ColorIdentifier, keybindingLabelBackground, keybindingLabelBorder, keybindingLabelBottomBorder, keybindingLabelForeground, asCssValue, widgetShadow, progressBarBackground } from 'vs/platform/theme/common/colorRegistry';
89
import { IStyleOverrides } from 'vs/platform/theme/common/styler';
910

1011

@@ -26,6 +27,33 @@ export function getKeybindingLabelStyles(style?: IKeybindingLabelStyleOverrides)
2627
};
2728
}
2829

30+
export interface IButtonStyleOverrides extends IStyleOverrides {
31+
readonly buttonForeground?: ColorIdentifier;
32+
readonly buttonSeparator?: ColorIdentifier;
33+
readonly buttonBackground?: ColorIdentifier;
34+
readonly buttonHoverBackground?: ColorIdentifier;
35+
readonly buttonSecondaryForeground?: ColorIdentifier;
36+
readonly buttonSecondaryBackground?: ColorIdentifier;
37+
readonly buttonSecondaryHoverBackground?: ColorIdentifier;
38+
readonly buttonBorder?: ColorIdentifier;
39+
}
40+
41+
42+
export const defaultButtonStyles: IButtonStyles = getButtonStyles({});
43+
44+
export function getButtonStyles(style: IButtonStyleOverrides): IButtonStyles {
45+
return {
46+
buttonForeground: asCssValue(style.buttonForeground || buttonForeground),
47+
buttonSeparator: asCssValue(style.buttonSeparator || buttonSeparator),
48+
buttonBackground: asCssValue(style.buttonBackground || buttonBackground),
49+
buttonHoverBackground: asCssValue(style.buttonHoverBackground || buttonHoverBackground),
50+
buttonSecondaryForeground: asCssValue(style.buttonSecondaryForeground || buttonSecondaryForeground),
51+
buttonSecondaryBackground: asCssValue(style.buttonSecondaryBackground || buttonSecondaryBackground),
52+
buttonSecondaryHoverBackground: asCssValue(style.buttonSecondaryHoverBackground || buttonSecondaryHoverBackground),
53+
buttonBorder: asCssValue(style.buttonBorder || buttonBorder),
54+
};
55+
}
56+
2957
export interface IProgressBarStyleOverrides extends IStyleOverrides {
3058
progressBarBackground?: ColorIdentifier;
3159
}

0 commit comments

Comments
 (0)