From 766dfd7740f038af0bc2bc04f84a7acc9b5b42a2 Mon Sep 17 00:00:00 2001 From: Anton Kosyakov Date: Thu, 31 Oct 2019 10:22:36 +0000 Subject: [PATCH] [theming] #4831: color contribution point - Theia extensions can register new colors - All colors are translated into css variables with `.` replaced by `-` and `--theia` prefix - Theia extensions can use registered colors via css or programatically from `ColorRegistrsy.getCurrentColor` Signed-off-by: Anton Kosyakov --- .../browser/color-application-contribution.ts | 75 +++++++++++++++++++ packages/core/src/browser/color-registry.ts | 16 ++++ .../browser/frontend-application-module.ts | 5 ++ .../src/browser/monaco-color-registry.ts | 8 +- packages/monaco/src/typings/monaco/index.d.ts | 7 ++ .../webview/webview-theme-data-provider.ts | 6 +- 6 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 packages/core/src/browser/color-application-contribution.ts diff --git a/packages/core/src/browser/color-application-contribution.ts b/packages/core/src/browser/color-application-contribution.ts new file mode 100644 index 0000000000000..9f03df91e57a1 --- /dev/null +++ b/packages/core/src/browser/color-application-contribution.ts @@ -0,0 +1,75 @@ +/******************************************************************************** + * Copyright (C) 2019 TypeFox and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { injectable, inject, named } from 'inversify'; +import { ColorRegistry } from './color-registry'; +import { Emitter } from '../common/event'; +import { ThemeService } from './theming'; +import { FrontendApplicationContribution } from './frontend-application'; +import { ContributionProvider } from '../common/contribution-provider'; +import { Disposable, DisposableCollection } from '../common/disposable'; + +export const ColorContribution = Symbol('ColorContribution'); +export interface ColorContribution { + registerColors(colors: ColorRegistry): void; +} + +@injectable() +export class ColorApplicationContribution implements FrontendApplicationContribution { + + protected readonly onDidChangeEmitter = new Emitter(); + readonly onDidChange = this.onDidChangeEmitter.event; + + @inject(ColorRegistry) + protected readonly colors: ColorRegistry; + + @inject(ContributionProvider) @named(ColorContribution) + protected readonly colorContributions: ContributionProvider; + + onStart(): void { + for (const contribution of this.colorContributions.getContributions()) { + contribution.registerColors(this.colors); + } + + this.update(); + ThemeService.get().onThemeChange(() => this.update()); + } + + protected readonly toUpdate = new DisposableCollection(); + protected update(): void { + if (!document) { + return; + } + this.toUpdate.dispose(); + const theme = 'theia-' + ThemeService.get().getCurrentTheme().type; + document.body.classList.add(theme); + this.toUpdate.push(Disposable.create(() => document.body.classList.remove(theme))); + + const documentElement = document.documentElement; + if (documentElement) { + for (const id of this.colors.getColors()) { + const color = this.colors.getCurrentColor(id); + if (color) { + const propertyName = `--theia-${id.replace('.', '-')}`; + documentElement.style.setProperty(propertyName, color); + this.toUpdate.push(Disposable.create(() => documentElement.style.removeProperty(propertyName))); + } + } + } + this.onDidChangeEmitter.fire(undefined); + } + +} diff --git a/packages/core/src/browser/color-registry.ts b/packages/core/src/browser/color-registry.ts index 128c528470de0..03fcc2ec19530 100644 --- a/packages/core/src/browser/color-registry.ts +++ b/packages/core/src/browser/color-registry.ts @@ -15,6 +15,18 @@ ********************************************************************************/ import { injectable } from 'inversify'; +import { Disposable } from '../common/disposable'; + +export interface ColorDefaults { + light?: string + dark?: string + hc?: string +} + +export interface ColorOptions { + defaults?: ColorDefaults + description: string +} /** * It should be implemented by an extension, e.g. by the monaco extension. @@ -28,4 +40,8 @@ export class ColorRegistry { return undefined; } + register(id: string, options: ColorOptions): Disposable { + return Disposable.NULL; + } + } diff --git a/packages/core/src/browser/frontend-application-module.ts b/packages/core/src/browser/frontend-application-module.ts index e87520db6a401..6d8eb7f7883f9 100644 --- a/packages/core/src/browser/frontend-application-module.ts +++ b/packages/core/src/browser/frontend-application-module.ts @@ -84,6 +84,7 @@ import { TabBarDecoratorService, TabBarDecorator } from './shell/tab-bar-decorat import { ContextMenuContext } from './menu/context-menu-context'; import { bindResourceProvider, bindMessageService, bindPreferenceService } from './frontend-application-bindings'; import { ColorRegistry } from './color-registry'; +import { ColorContribution, ColorApplicationContribution } from './color-application-contribution'; export { bindResourceProvider, bindMessageService, bindPreferenceService }; @@ -91,7 +92,11 @@ export const frontendApplicationModule = new ContainerModule((bind, unbind, isBo const themeService = ThemeService.get(); themeService.register(...BuiltinThemeProvider.themes); themeService.startupTheme(); + bind(ColorRegistry).toSelf().inSingletonScope(); + bindContributionProvider(bind, ColorContribution); + bind(ColorApplicationContribution).toSelf().inSingletonScope(); + bind(FrontendApplicationContribution).toService(ColorApplicationContribution); bind(FrontendApplication).toSelf().inSingletonScope(); bind(FrontendApplicationStateService).toSelf().inSingletonScope(); diff --git a/packages/monaco/src/browser/monaco-color-registry.ts b/packages/monaco/src/browser/monaco-color-registry.ts index fd4f544d448ba..f776ae4d9a721 100644 --- a/packages/monaco/src/browser/monaco-color-registry.ts +++ b/packages/monaco/src/browser/monaco-color-registry.ts @@ -15,7 +15,8 @@ ********************************************************************************/ import { injectable } from 'inversify'; -import { ColorRegistry } from '@theia/core/lib/browser/color-registry'; +import { ColorRegistry, ColorOptions } from '@theia/core/lib/browser/color-registry'; +import { Disposable } from '@theia/core/lib/common/disposable'; @injectable() export class MonacoColorRegistry implements ColorRegistry { @@ -34,4 +35,9 @@ export class MonacoColorRegistry implements ColorRegistry { return color && color.toString(); } + register(id: string, options: ColorOptions): Disposable { + const identifier = this.monacoColorRegistry.registerColor(id, options.defaults, options.description); + return Disposable.create(() => this.monacoColorRegistry.deregisterColor(identifier)); + } + } diff --git a/packages/monaco/src/typings/monaco/index.d.ts b/packages/monaco/src/typings/monaco/index.d.ts index 320cdc0211a73..c71fe10fa5f73 100644 --- a/packages/monaco/src/typings/monaco/index.d.ts +++ b/packages/monaco/src/typings/monaco/index.d.ts @@ -542,8 +542,15 @@ declare module monaco.color { export interface ColorContribution { readonly id: string; } + export interface ColorDefaults { + ligh?: string; + dark?: string; + hc?: string; + } export interface IColorRegistry { getColors(): ColorContribution[]; + registerColor(id: string, defaults: ColorDefaults | undefined, description: string): string; + deregisterColor(id: string): void; } export function getColorRegistry(): IColorRegistry; } diff --git a/packages/plugin-ext/src/main/browser/webview/webview-theme-data-provider.ts b/packages/plugin-ext/src/main/browser/webview/webview-theme-data-provider.ts index b15ce8d569283..081cad8ec4066 100644 --- a/packages/plugin-ext/src/main/browser/webview/webview-theme-data-provider.ts +++ b/packages/plugin-ext/src/main/browser/webview/webview-theme-data-provider.ts @@ -24,6 +24,7 @@ import { Emitter } from '@theia/core/lib/common/event'; import { EditorPreferences, EditorConfiguration } from '@theia/editor/lib/browser/editor-preferences'; import { ThemeService } from '@theia/core/lib/browser/theming'; import { ColorRegistry } from '@theia/core/lib/browser/color-registry'; +import { ColorApplicationContribution } from '@theia/core/lib/browser/color-application-contribution'; export type WebviewThemeType = 'vscode-light' | 'vscode-dark' | 'vscode-high-contrast'; export interface WebviewThemeData { @@ -43,6 +44,9 @@ export class WebviewThemeDataProvider { @inject(ColorRegistry) protected readonly colorRegistry: ColorRegistry; + @inject(ColorApplicationContribution) + protected readonly colorContribution: ColorApplicationContribution; + protected themeData: WebviewThemeData | undefined; protected readonly editorStyles = new Map([ @@ -53,7 +57,7 @@ export class WebviewThemeDataProvider { @postConstruct() protected init(): void { - ThemeService.get().onThemeChange(() => this.reset()); + this.colorContribution.onDidChange(() => this.reset()); this.editorPreferences.onPreferenceChanged(e => { if (this.editorStyles.has(e.preferenceName)) {