Skip to content

Commit

Permalink
themes - first cut ColorScheme support (#105715) (#105716)
Browse files Browse the repository at this point in the history
  • Loading branch information
bpasero committed Sep 7, 2020
1 parent 5a2431a commit 4a2b990
Show file tree
Hide file tree
Showing 14 changed files with 148 additions and 39 deletions.
4 changes: 2 additions & 2 deletions src/vs/code/electron-browser/workbench/workbench.js
Expand Up @@ -76,7 +76,7 @@ bootstrapWindow.load([
/**
* @param {{
* partsSplashPath?: string,
* highContrast?: boolean,
* colorScheme: (1 | 2 | 3),
* autoDetectHighContrast?: boolean,
* extensionDevelopmentPath?: string[],
* folderUri?: object,
Expand All @@ -96,7 +96,7 @@ function showPartsSplash(configuration) {
}

// high contrast mode has been turned on from the outside, e.g. OS -> ignore stored colors and layouts
const isHighContrast = configuration.highContrast && configuration.autoDetectHighContrast;
const isHighContrast = configuration.colorScheme === 3 /* ColorScheme.HIGH_CONTRAST */ && configuration.autoDetectHighContrast;
if (data && isHighContrast && data.baseTheme !== 'hc-black') {
data = undefined;
}
Expand Down
4 changes: 2 additions & 2 deletions src/vs/code/electron-main/window.ts
Expand Up @@ -15,7 +15,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv';
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import product from 'vs/platform/product/common/product';
import { IWindowSettings, MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility, zoomLevelToZoomFactor, INativeWindowConfiguration } from 'vs/platform/windows/common/windows';
import { IWindowSettings, MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility, zoomLevelToZoomFactor, INativeWindowConfiguration, ColorScheme } from 'vs/platform/windows/common/windows';
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
import { ICodeWindow, IWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows';
Expand Down Expand Up @@ -784,7 +784,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
windowConfiguration.fullscreen = this.isFullScreen;

// Set Accessibility Config
windowConfiguration.highContrast = nativeTheme.shouldUseInvertedColorScheme || nativeTheme.shouldUseHighContrastColors;
windowConfiguration.colorScheme = (nativeTheme.shouldUseInvertedColorScheme || nativeTheme.shouldUseHighContrastColors) ? ColorScheme.HIGH_CONTRAST : nativeTheme.shouldUseDarkColors ? ColorScheme.DARK : ColorScheme.DEFAULT;
windowConfiguration.autoDetectHighContrast = windowConfig?.autoDetectHighContrast ?? true;
windowConfiguration.accessibilitySupport = app.accessibilitySupportEnabled;

Expand Down
4 changes: 3 additions & 1 deletion src/vs/platform/electron/common/electron.ts
Expand Up @@ -5,7 +5,7 @@

import { Event } from 'vs/base/common/event';
import { MessageBoxOptions, MessageBoxReturnValue, OpenDevToolsOptions, SaveDialogOptions, OpenDialogOptions, OpenDialogReturnValue, SaveDialogReturnValue, MouseInputEvent } from 'vs/base/parts/sandbox/common/electronTypes';
import { IOpenedWindow, IWindowOpenable, IOpenEmptyWindowOptions, IOpenWindowOptions } from 'vs/platform/windows/common/windows';
import { IOpenedWindow, IWindowOpenable, IOpenEmptyWindowOptions, IOpenWindowOptions, ColorScheme } from 'vs/platform/windows/common/windows';
import { INativeOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs';
import { ISerializableCommandAction } from 'vs/platform/actions/common/actions';

Expand Down Expand Up @@ -34,6 +34,8 @@ export interface ICommonElectronService {

readonly onOSResume: Event<unknown>;

readonly onColorSchemeChange: Event<ColorScheme>;

// Window
getWindows(): Promise<IOpenedWindow[]>;
getWindowCount(): Promise<number>;
Expand Down
26 changes: 23 additions & 3 deletions src/vs/platform/electron/electron-main/electronMainService.ts
Expand Up @@ -3,12 +3,12 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { Event } from 'vs/base/common/event';
import { Emitter, Event } from 'vs/base/common/event';
import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows';
import { MessageBoxOptions, MessageBoxReturnValue, shell, OpenDevToolsOptions, SaveDialogOptions, SaveDialogReturnValue, OpenDialogOptions, OpenDialogReturnValue, Menu, BrowserWindow, app, clipboard, powerMonitor } from 'electron';
import { MessageBoxOptions, MessageBoxReturnValue, shell, OpenDevToolsOptions, SaveDialogOptions, SaveDialogReturnValue, OpenDialogOptions, OpenDialogReturnValue, Menu, BrowserWindow, app, clipboard, powerMonitor, nativeTheme } from 'electron';
import { OpenContext } from 'vs/platform/windows/node/window';
import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import { IOpenedWindow, IOpenWindowOptions, IWindowOpenable, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows';
import { IOpenedWindow, IOpenWindowOptions, IWindowOpenable, IOpenEmptyWindowOptions, ColorScheme } from 'vs/platform/windows/common/windows';
import { INativeOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs';
import { isMacintosh, isWindows, isRootUser } from 'vs/base/common/platform';
import { ICommonElectronService, IOSProperties } from 'vs/platform/electron/common/electron';
Expand Down Expand Up @@ -38,8 +38,25 @@ export class ElectronMainService implements IElectronMainService {
@IEnvironmentService private readonly environmentService: INativeEnvironmentService,
@ITelemetryService private readonly telemetryService: ITelemetryService
) {
this.registerListeners();
}

private registerListeners(): void {

// Color Scheme changes
nativeTheme.on('updated', () => {
let colorScheme = ColorScheme.DEFAULT;
if (nativeTheme.shouldUseInvertedColorScheme || nativeTheme.shouldUseHighContrastColors) {
colorScheme = ColorScheme.HIGH_CONTRAST;
} else if (nativeTheme.shouldUseDarkColors) {
colorScheme = ColorScheme.DARK;
}

this._onColorSchemeChange.fire(colorScheme);
});
}


//#region Properties

get windowId(): never { throw new Error('Not implemented in electron-main'); }
Expand All @@ -61,6 +78,9 @@ export class ElectronMainService implements IElectronMainService {

readonly onOSResume = Event.fromNodeEventEmitter(powerMonitor, 'resume');

private readonly _onColorSchemeChange = new Emitter<ColorScheme>();
readonly onColorSchemeChange = this._onColorSchemeChange.event;

//#endregion

//#region Window
Expand Down
20 changes: 19 additions & 1 deletion src/vs/platform/windows/common/windows.ts
Expand Up @@ -211,7 +211,7 @@ export interface IWindowConfiguration {

remoteAuthority?: string;

highContrast?: boolean;
colorScheme: ColorScheme;
autoDetectHighContrast?: boolean;

filesToOpenOrCreate?: IPath[];
Expand Down Expand Up @@ -253,3 +253,21 @@ export interface INativeWindowConfiguration extends IWindowConfiguration, Native
export function zoomLevelToZoomFactor(zoomLevel = 0): number {
return Math.pow(1.2, zoomLevel);
}

export enum ColorScheme {

/**
* The window should use standard colors.
*/
DEFAULT = 1,

/**
* The window should use dark colors.
*/
DARK = 2,

/**
* The window should use high contrast colors.
*/
HIGH_CONTRAST = 3
}
13 changes: 1 addition & 12 deletions src/vs/platform/windows/electron-main/windowsMainService.ts
Expand Up @@ -14,7 +14,7 @@ import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/envi
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { IStateService } from 'vs/platform/state/node/state';
import { CodeWindow, defaultWindowState } from 'vs/code/electron-main/window';
import { screen, BrowserWindow, MessageBoxOptions, Display, app, nativeTheme } from 'electron';
import { screen, BrowserWindow, MessageBoxOptions, Display, app } from 'electron';
import { ILifecycleMainService, UnloadReason, LifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILogService } from 'vs/platform/log/common/log';
Expand Down Expand Up @@ -209,17 +209,6 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic

private registerListeners(): void {

// React to HC color scheme changes (Windows, macOS)
if (isWindows || isMacintosh) {
nativeTheme.on('updated', () => {
if (nativeTheme.shouldUseInvertedColorScheme || nativeTheme.shouldUseHighContrastColors) {
this.sendToAll('vscode:enterHighContrast');
} else {
this.sendToAll('vscode:leaveHighContrast');
}
});
}

// When a window looses focus, save all windows state. This allows to
// prevent loss of window-state data when OS is restarted without properly
// shutting down the application (https://github.com/microsoft/vscode/issues/87171)
Expand Down
13 changes: 4 additions & 9 deletions src/vs/workbench/electron-sandbox/window.ts
Expand Up @@ -13,7 +13,7 @@ import { IFileService } from 'vs/platform/files/common/files';
import { toResource, IUntitledTextResourceEditorInput, SideBySideEditor, pathsToEditors } from 'vs/workbench/common/editor';
import { IEditorService, IResourceEditorInputType } from 'vs/workbench/services/editor/common/editorService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IOpenFileRequest, IWindowsConfiguration, getTitleBarStyle, IAddFoldersRequest, INativeRunActionInWindowRequest, INativeRunKeybindingInWindowRequest, INativeOpenFileRequest } from 'vs/platform/windows/common/windows';
import { IOpenFileRequest, IWindowsConfiguration, getTitleBarStyle, IAddFoldersRequest, INativeRunActionInWindowRequest, INativeRunKeybindingInWindowRequest, INativeOpenFileRequest, ColorScheme } from 'vs/platform/windows/common/windows';
import { ITitleService } from 'vs/workbench/services/title/common/titleService';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { applyZoom } from 'vs/platform/windows/electron-sandbox/window';
Expand Down Expand Up @@ -199,15 +199,10 @@ export class NativeWindow extends Disposable {
});

// High Contrast Events
ipcRenderer.on('vscode:enterHighContrast', async () => {
this._register(this.electronService.onColorSchemeChange(async scheme => {
await this.lifecycleService.when(LifecyclePhase.Ready);
this.themeService.setOSHighContrast(true);
});

ipcRenderer.on('vscode:leaveHighContrast', async () => {
await this.lifecycleService.when(LifecyclePhase.Ready);
this.themeService.setOSHighContrast(false);
});
this.themeService.setOSHighContrast(scheme === ColorScheme.HIGH_CONTRAST);
}));

// accessibility support changed event
ipcRenderer.on('vscode:accessibilitySupportChanged', (event: unknown, accessibilitySupportEnabled: boolean) => {
Expand Down
Expand Up @@ -8,7 +8,7 @@ import { joinPath } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { generateUuid } from 'vs/base/common/uuid';
import { BACKUPS, IExtensionHostDebugParams } from 'vs/platform/environment/common/environment';
import { IPath } from 'vs/platform/windows/common/windows';
import { ColorScheme, IPath } from 'vs/platform/windows/common/windows';
import { IWorkbenchEnvironmentService, IWorkbenchConfiguration } from 'vs/workbench/services/environment/common/environmentService';
import { IWorkbenchConstructionOptions as IWorkbenchOptions } from 'vs/workbench/workbench.web.api';
import product from 'vs/platform/product/common/product';
Expand Down Expand Up @@ -74,8 +74,13 @@ class BrowserWorkbenchConfiguration implements IWorkbenchConfiguration {
return undefined;
}

get highContrast() {
return false; // could investigate to detect high contrast theme automatically
// TODO@martin TODO@ben this currently does not support high-contrast theme preference (no browser support yet)
get colorScheme() {
if (window.matchMedia(`(prefers-color-scheme: dark)`).matches) {
return ColorScheme.DARK;
}

return ColorScheme.DEFAULT;
}
}

Expand Down
26 changes: 26 additions & 0 deletions src/vs/workbench/services/host/browser/browserHostService.ts
Expand Up @@ -84,6 +84,8 @@ export class BrowserHostService extends Disposable implements IHostService {
}
}

//#region Focus

@memoize
get onDidChangeFocus(): Event<boolean> {
const focusTracker = this._register(trackFocus(window));
Expand All @@ -107,6 +109,11 @@ export class BrowserHostService extends Disposable implements IHostService {
window.focus();
}

//#endregion


//#region Window

openWindow(options?: IOpenEmptyWindowOptions): Promise<void>;
openWindow(toOpen: IWindowOpenable[], options?: IOpenWindowOptions): Promise<void>;
openWindow(arg1?: IOpenEmptyWindowOptions | IWindowOpenable[], arg2?: IOpenWindowOptions): Promise<void> {
Expand Down Expand Up @@ -320,13 +327,32 @@ export class BrowserHostService extends Disposable implements IHostService {
}
}

//#endregion


//#region Color Scheme

// TODO@martin TODO@ben this currently does not support detecting changes
onDidChangeColorScheme = Event.None;

get colorScheme() {
return this.environmentService.configuration.colorScheme;
}

//#endregion


//#region Lifecycle

async restart(): Promise<void> {
this.reload();
}

async reload(): Promise<void> {
window.location.reload();
}

//#endregion
}

registerSingleton(IHostService, BrowserHostService, true);
12 changes: 11 additions & 1 deletion src/vs/workbench/services/host/browser/host.ts
Expand Up @@ -5,14 +5,15 @@

import { Event } from 'vs/base/common/event';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IWindowOpenable, IOpenWindowOptions, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows';
import { IWindowOpenable, IOpenWindowOptions, IOpenEmptyWindowOptions, ColorScheme } from 'vs/platform/windows/common/windows';

export const IHostService = createDecorator<IHostService>('hostService');

export interface IHostService {

readonly _serviceBrand: undefined;


//#region Focus

/**
Expand Down Expand Up @@ -65,6 +66,15 @@ export interface IHostService {
//#endregion


//#region Color Scheme

readonly colorScheme: ColorScheme;

readonly onDidChangeColorScheme: Event<void>;

//#endregion


//#region Lifecycle

/**
Expand Down
Expand Up @@ -3,13 +3,13 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { Event } from 'vs/base/common/event';
import { Emitter, Event } from 'vs/base/common/event';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { ILabelService } from 'vs/platform/label/common/label';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IWindowOpenable, IOpenWindowOptions, isFolderToOpen, isWorkspaceToOpen, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows';
import { IWindowOpenable, IOpenWindowOptions, isFolderToOpen, isWorkspaceToOpen, IOpenEmptyWindowOptions, ColorScheme } from 'vs/platform/windows/common/windows';
import { Disposable } from 'vs/base/common/lifecycle';

export class NativeHostService extends Disposable implements IHostService {
Expand All @@ -22,8 +22,23 @@ export class NativeHostService extends Disposable implements IHostService {
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService
) {
super();

this.registerListeners();
}

private registerListeners(): void {

// Color Scheme
this._register(this.electronService.onColorSchemeChange(scheme => {
this._colorScheme = scheme;

this._onDidChangeColorScheme.fire();
}));
}


//#region Focus

get onDidChangeFocus(): Event<boolean> { return this._onDidChangeFocus; }
private _onDidChangeFocus: Event<boolean> = Event.latch(Event.any(
Event.map(Event.filter(this.electronService.onWindowFocus, id => id === this.electronService.windowId), () => this.hasFocus),
Expand All @@ -44,6 +59,11 @@ export class NativeHostService extends Disposable implements IHostService {
return activeWindowId === this.electronService.windowId;
}

//#endregion


//#region Window

openWindow(options?: IOpenEmptyWindowOptions): Promise<void>;
openWindow(toOpen: IWindowOpenable[], options?: IOpenWindowOptions): Promise<void>;
openWindow(arg1?: IOpenEmptyWindowOptions | IWindowOpenable[], arg2?: IOpenWindowOptions): Promise<void> {
Expand Down Expand Up @@ -82,6 +102,22 @@ export class NativeHostService extends Disposable implements IHostService {
return this.electronService.toggleFullScreen();
}

//#endregion


//#region Color Scheme

private readonly _onDidChangeColorScheme = this._register(new Emitter<void>());
readonly onDidChangeColorScheme = this._onDidChangeColorScheme.event;

private _colorScheme: ColorScheme = this.environmentService.configuration.colorScheme;
get colorScheme() { return this._colorScheme; }

//#endregion


//#region Lifecycle

focus(options?: { force: boolean }): Promise<void> {
return this.electronService.focusWindow(options);
}
Expand All @@ -93,6 +129,8 @@ export class NativeHostService extends Disposable implements IHostService {
reload(): Promise<void> {
return this.electronService.reload();
}

//#endregion
}

registerSingleton(IHostService, NativeHostService, true);

0 comments on commit 4a2b990

Please sign in to comment.