From 45531970a0c16db43799654bdd99f6fc80a23771 Mon Sep 17 00:00:00 2001 From: Niklas Rentz Date: Tue, 17 Oct 2023 20:23:22 +0200 Subject: [PATCH 1/3] implemented dark theme synthesis support --- .../klighd-cli/src/services/connection.ts | 18 ++++++- .../klighd-vscode/src/klighd-extension.ts | 18 +++++++ packages/klighd-core/src/actions/actions.ts | 47 +++++++++++++++++++ packages/klighd-core/src/diagram-server.ts | 32 +++++++++++++ 4 files changed, 114 insertions(+), 1 deletion(-) diff --git a/applications/klighd-cli/src/services/connection.ts b/applications/klighd-cli/src/services/connection.ts index f6416e8f..95e5cab4 100644 --- a/applications/klighd-cli/src/services/connection.ts +++ b/applications/klighd-cli/src/services/connection.ts @@ -161,6 +161,17 @@ export class LSPConnection implements Connection { async sendInitialize(persistedData: Record): Promise { if (!this.connection) return; + // notify the server about the preferred colors, depending on if the OS prefers light (default) or dark theme. + let foreground = "#000000" + let background = "#ffffff" + let highlight = "#000000" + if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { + // dark mode + foreground = "#cccccc" + background = "#1f1f1f" + highlight = "#cccccc" + } + const method = lsp.InitializeRequest.type.method; // The standalone view does not really has any LSP capabilities const initParams: lsp.InitializeParams = { @@ -171,7 +182,12 @@ export class LSPConnection implements Connection { capabilities: {}, initializationOptions: { clientDiagramOptions: persistedData, - } + clientColorPreferences: { + foreground, + background, + highlight, + } + }, }; console.time("lsp-init"); diff --git a/applications/klighd-vscode/src/klighd-extension.ts b/applications/klighd-vscode/src/klighd-extension.ts index 603f7f84..72fc1db8 100644 --- a/applications/klighd-vscode/src/klighd-extension.ts +++ b/applications/klighd-vscode/src/klighd-extension.ts @@ -15,6 +15,7 @@ * SPDX-License-Identifier: EPL-2.0 */ import { + ChangeColorThemeAction, KlighdFitToScreenAction, KlighdRequestExportSvgAction, RefreshDiagramAction, @@ -201,6 +202,23 @@ export class KLighDExtension extends SprottyLspVscodeExtension { * to overwrite commands and would throw an error._ */ protected override registerCommands(): void { + vscode.window.onDidChangeActiveColorTheme(() => { + // Hook into VS Code's theme change and notify the webview to check the current colors and send them to the server. + // Look for any active KLighD webview, regardless of if it is currently selected. + let webview = this.singleton + if (!webview) { + for (const possibleWebview of this.webviewMap.values()) { + if (possibleWebview.diagramPanel.active) { + webview = possibleWebview; + } + } + } + + if (webview) { + webview.dispatch(ChangeColorThemeAction.create()); + } + }) + this.context.subscriptions.push( commands.registerCommand(command.diagramOpen, async (...commandArgs: any[]) => { const identifier = await this.createDiagramIdentifier(commandArgs); diff --git a/packages/klighd-core/src/actions/actions.ts b/packages/klighd-core/src/actions/actions.ts index 4e3edea3..94834a59 100644 --- a/packages/klighd-core/src/actions/actions.ts +++ b/packages/klighd-core/src/actions/actions.ts @@ -90,6 +90,53 @@ export namespace CheckedImagesAction { } } +/** + * Sent internally to notify KLighD that the color theme has changed. Will trigger a subsequent + * ClientColorPreferencesAction to be triggered and sent. + */ +export interface ChangeColorThemeAction extends Action { + kind: typeof ChangeColorThemeAction.KIND +} + +export namespace ChangeColorThemeAction { + export const KIND = 'changeColorTheme' + + export function create(): ChangeColorThemeAction { + return { + kind: KIND, + } + } +} + +/** + * Action to notify the server about current color preferences. + */ +export interface ClientColorPreferencesAction extends Action { + kind: typeof ClientColorPreferencesAction.KIND + + clientColorPreferences: ColorPreferences +} + +export namespace ClientColorPreferencesAction { + export const KIND = 'changeClientColorPreferences' + + export function create(clientColorPreferences: ColorPreferences): ClientColorPreferencesAction { + return { + kind: KIND, + clientColorPreferences, + } + } +} + +/** + * The color preferences data class, indicating diagram colors to be used by syntheses. + */ +export interface ColorPreferences { + foreground: string, + background: string, + highlight: string, +} + /** * Sent from the client to the diagram server to perform a klighd action on the model. * Causes the server to update the diagram accordingly to the action. diff --git a/packages/klighd-core/src/diagram-server.ts b/packages/klighd-core/src/diagram-server.ts index 334822af..df4fbd25 100644 --- a/packages/klighd-core/src/diagram-server.ts +++ b/packages/klighd-core/src/diagram-server.ts @@ -47,14 +47,17 @@ import { ActionMessage, findElement, generateRequestId, + RequestModelAction, RequestPopupModelAction, SelectAction, SetPopupModelAction, UpdateModelAction, } from "sprotty-protocol"; import { + ChangeColorThemeAction, CheckedImagesAction, CheckImagesAction, + ClientColorPreferencesAction, KlighdExportSvgAction, KlighdFitToScreenAction, Pair, @@ -150,6 +153,10 @@ export class KlighdDiagramServer extends DiagramServerProxy { // In contract to the name, this should return true, if the actions should be // sent to the server. Don't know what the Sprotty folks where thinking when they named it... switch (action.kind) { + case ClientColorPreferencesAction.KIND: + return true; + case ChangeColorThemeAction.KIND: + return false; case PerformActionAction.KIND: return true; case RefreshDiagramAction.KIND: @@ -186,6 +193,8 @@ export class KlighdDiagramServer extends DiagramServerProxy { registry.register(BringToFrontAction.KIND, this); registry.register(CheckImagesAction.KIND, this); registry.register(CheckedImagesAction.KIND, this); + registry.register(ClientColorPreferencesAction.KIND, this); + registry.register(ChangeColorThemeAction.KIND, this); registry.register(DeleteLayerConstraintAction.KIND, this); registry.register(DeletePositionConstraintAction.KIND, this); registry.register(DeleteStaticConstraintAction.KIND, this); @@ -209,6 +218,18 @@ export class KlighdDiagramServer extends DiagramServerProxy { } handle(action: Action): void | ICommand | Action { + if (action.kind === RequestModelAction.KIND && getComputedStyle !== undefined) { + // On any request model action, also send the current colors with the request, so the initial + // syntheses can use the theming of VS Code. Values will be undefined outside of VS Code and should + // be ignored. + (action as RequestModelAction).options = { + ...((action as RequestModelAction).options), + clientColorPreferenceForeground: getComputedStyle(document.documentElement).getPropertyValue('--vscode-editor-foreground'), + clientColorPreferenceBackground: getComputedStyle(document.documentElement).getPropertyValue('--vscode-editor-background'), + clientColorPreferenceHighlight: getComputedStyle(document.documentElement).getPropertyValue('--vscode-focusBorder') + } + super.handle(action) + } if (action.kind === BringToFrontAction.KIND || action.kind === SwitchEditModeAction.KIND) { // Actions that should be ignored and not further handled by this diagram server @@ -217,6 +238,8 @@ export class KlighdDiagramServer extends DiagramServerProxy { if (action.kind === CheckImagesAction.KIND) { this.handleCheckImages(action as CheckImagesAction); + } else if (action.kind === ChangeColorThemeAction.KIND) { + this.handleChangeColorTheme(); } else if (action.kind === StoreImagesAction.KIND) { this.handleStoreImages(action as StoreImagesAction); } else if (action.kind === RequestPopupModelAction.KIND) { @@ -248,6 +271,15 @@ export class KlighdDiagramServer extends DiagramServerProxy { this.actionDispatcher.dispatch(CheckedImagesAction.create(notCached)); } + handleChangeColorTheme(): void { + if (getComputedStyle === undefined) return + this.actionDispatcher.dispatch(ClientColorPreferencesAction.create({ + foreground: getComputedStyle(document.documentElement).getPropertyValue('--vscode-editor-foreground'), + background: getComputedStyle(document.documentElement).getPropertyValue('--vscode-editor-background'), + highlight: getComputedStyle(document.documentElement).getPropertyValue('--vscode-focusBorder'), + })) + } + handleStoreImages(action: StoreImagesAction): void { // Put the new images in session storage. for (const imagePair of (action as StoreImagesAction).images) { From a036b1e2208c3d3a667a707e0df06c6e21db72ed Mon Sep 17 00:00:00 2001 From: Niklas Rentz Date: Wed, 18 Dec 2024 15:38:17 +0100 Subject: [PATCH 2/3] prettier and API changes for dark theme --- .../klighd-cli/src/services/connection.ts | 14 +++--- applications/klighd-vscode/src/extension.ts | 15 ++++++- packages/klighd-core/src/actions/actions.ts | 8 ++-- packages/klighd-core/src/diagram-server.ts | 43 +++++++++++-------- 4 files changed, 51 insertions(+), 29 deletions(-) diff --git a/applications/klighd-cli/src/services/connection.ts b/applications/klighd-cli/src/services/connection.ts index 26201b26..23dd20ca 100644 --- a/applications/klighd-cli/src/services/connection.ts +++ b/applications/klighd-cli/src/services/connection.ts @@ -19,7 +19,7 @@ import * as rpc from 'vscode-ws-jsonrpc' import * as lsp from 'vscode-languageserver-protocol' import { Connection, NotificationType, ActionMessage } from '@kieler/klighd-core' import { showPopup } from '../popup' -/* global WebSocket */ +/* global WebSocket, window */ type GeneralMessageParams = [string, 'info' | 'warn' | 'error'] @@ -159,14 +159,14 @@ export class LSPConnection implements Connection { if (!this.connection) return // notify the server about the preferred colors, depending on if the OS prefers light (default) or dark theme. - let foreground = "#000000" - let background = "#ffffff" - let highlight = "#000000" + let foreground = '#000000' + let background = '#ffffff' + let highlight = '#000000' if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { // dark mode - foreground = "#cccccc" - background = "#1f1f1f" - highlight = "#cccccc" + foreground = '#cccccc' + background = '#1f1f1f' + highlight = '#cccccc' } const { method } = lsp.InitializeRequest.type diff --git a/applications/klighd-vscode/src/extension.ts b/applications/klighd-vscode/src/extension.ts index 6b3b3157..d33105ec 100644 --- a/applications/klighd-vscode/src/extension.ts +++ b/applications/klighd-vscode/src/extension.ts @@ -18,7 +18,7 @@ // See https://stackoverflow.com/questions/37534890/inversify-js-reflect-hasownmetadata-is-not-a-function import 'reflect-metadata' // The other imports. -import { DebugOptions, SetRenderOptionAction } from '@kieler/klighd-core' +import { ChangeColorThemeAction, DebugOptions, SetRenderOptionAction } from '@kieler/klighd-core' import { diagramType } from '@kieler/klighd-core/lib/base/external-helpers' import { Action, ActionMessage, isAction } from 'sprotty-protocol' import { registerLspEditCommands } from 'sprotty-vscode' @@ -78,6 +78,7 @@ export function activate(context: vscode.ExtensionContext): void { registerCommands(webviewPanelManager, context) registerLspEditCommands(webviewPanelManager, context, { extensionPrefix: 'klighd-vscode' }) registerTextEditorSync(webviewPanelManager, context) + registerChangeColorTheme(webviewPanelManager) // Handle notifications that are KLighD specific extensions of the LSP for this LSClient. LspHandler.init(client) @@ -193,3 +194,15 @@ function isLanguageClient(client: unknown): client is LanguageClient { function isFileEndingsArray(array: unknown): array is string[] { return Array.isArray(array) && array.every((val) => typeof val === 'string') } +/** + * Hook into VS Code's theme change and notify the webview to check the current colors and send them to the server. + */ +function registerChangeColorTheme(manager: KLighDWebviewPanelManager) { + vscode.window.onDidChangeActiveColorTheme(() => { + for (const endpoint of manager.endpoints) { + endpoint.sendAction({ + kind: ChangeColorThemeAction.KIND, + }) + } + }) +} diff --git a/packages/klighd-core/src/actions/actions.ts b/packages/klighd-core/src/actions/actions.ts index 9b1cf72f..29ae2e5a 100644 --- a/packages/klighd-core/src/actions/actions.ts +++ b/packages/klighd-core/src/actions/actions.ts @@ -101,7 +101,7 @@ export namespace CheckedImagesAction { } /** - * Sent internally to notify KLighD that the color theme has changed. Will trigger a subsequent + * Sent internally to notify KLighD that the color theme has changed. Will trigger a subsequent * ClientColorPreferencesAction to be triggered and sent. */ export interface ChangeColorThemeAction extends Action { @@ -142,9 +142,9 @@ export namespace ClientColorPreferencesAction { * The color preferences data class, indicating diagram colors to be used by syntheses. */ export interface ColorPreferences { - foreground: string, - background: string, - highlight: string, + foreground: string + background: string + highlight: string } /** diff --git a/packages/klighd-core/src/diagram-server.ts b/packages/klighd-core/src/diagram-server.ts index d43f1fdb..0a2b72a5 100644 --- a/packages/klighd-core/src/diagram-server.ts +++ b/packages/klighd-core/src/diagram-server.ts @@ -90,6 +90,7 @@ import { ClientLayoutOption, IncrementalDiagramGeneratorOption, PreferencesRegis import { Connection, ServiceTypes, SessionStorage } from './services' import { SetSynthesisAction } from './syntheses/actions' import { UpdateDepthMapModelAction } from './update/update-depthmap-model' +/* global document, getComputedStyle */ /** * This class extends {@link DiagramServerProxy} to handle different `klighd-core` specific @@ -169,13 +170,13 @@ export class KlighdDiagramServer extends DiagramServerProxy { } handleLocally(action: Action): boolean { - // In contract to the name, this should return true, if the actions should be + // In contrast to the name, this should return true, if the actions should be // sent to the server. Don't know what the Sprotty folks where thinking when they named it... switch (action.kind) { case ClientColorPreferencesAction.KIND: - return true; + return true case ChangeColorThemeAction.KIND: - return false; + return false case PerformActionAction.KIND: return true case RefreshDiagramAction.KIND: @@ -213,8 +214,8 @@ export class KlighdDiagramServer extends DiagramServerProxy { registry.register(BringToFrontAction.KIND, this) registry.register(CheckImagesAction.KIND, this) registry.register(CheckedImagesAction.KIND, this) - registry.register(ClientColorPreferencesAction.KIND, this); - registry.register(ChangeColorThemeAction.KIND, this); + registry.register(ClientColorPreferencesAction.KIND, this) + registry.register(ChangeColorThemeAction.KIND, this) registry.register(DeleteLayerConstraintAction.KIND, this) registry.register(DeletePositionConstraintAction.KIND, this) registry.register(DeleteStaticConstraintAction.KIND, this) @@ -245,14 +246,20 @@ export class KlighdDiagramServer extends DiagramServerProxy { handle(action: Action): void | ICommand | Action { if (action.kind === RequestModelAction.KIND && getComputedStyle !== undefined) { - // On any request model action, also send the current colors with the request, so the initial + // On any request model action, also send the current colors with the request, so the initial // syntheses can use the theming of VS Code. Values will be undefined outside of VS Code and should // be ignored. - (action as RequestModelAction).options = { - ...((action as RequestModelAction).options), - clientColorPreferenceForeground: getComputedStyle(document.documentElement).getPropertyValue('--vscode-editor-foreground'), - clientColorPreferenceBackground: getComputedStyle(document.documentElement).getPropertyValue('--vscode-editor-background'), - clientColorPreferenceHighlight: getComputedStyle(document.documentElement).getPropertyValue('--vscode-focusBorder') + ;(action as RequestModelAction).options = { + ...(action as RequestModelAction).options, + clientColorPreferenceForeground: getComputedStyle(document.documentElement).getPropertyValue( + '--vscode-editor-foreground' + ), + clientColorPreferenceBackground: getComputedStyle(document.documentElement).getPropertyValue( + '--vscode-editor-background' + ), + clientColorPreferenceHighlight: getComputedStyle(document.documentElement).getPropertyValue( + '--vscode-focusBorder' + ), } super.handle(action) } @@ -265,7 +272,7 @@ export class KlighdDiagramServer extends DiagramServerProxy { if (action.kind === CheckImagesAction.KIND) { this.handleCheckImages(action as CheckImagesAction) } else if (action.kind === ChangeColorThemeAction.KIND) { - this.handleChangeColorTheme(); + this.handleChangeColorTheme() } else if (action.kind === StoreImagesAction.KIND) { this.handleStoreImages(action as StoreImagesAction) } else if (action.kind === RequestPopupModelAction.KIND) { @@ -297,11 +304,13 @@ export class KlighdDiagramServer extends DiagramServerProxy { handleChangeColorTheme(): void { if (getComputedStyle === undefined) return - this.actionDispatcher.dispatch(ClientColorPreferencesAction.create({ - foreground: getComputedStyle(document.documentElement).getPropertyValue('--vscode-editor-foreground'), - background: getComputedStyle(document.documentElement).getPropertyValue('--vscode-editor-background'), - highlight: getComputedStyle(document.documentElement).getPropertyValue('--vscode-focusBorder'), - })) + this.actionDispatcher.dispatch( + ClientColorPreferencesAction.create({ + foreground: getComputedStyle(document.documentElement).getPropertyValue('--vscode-editor-foreground'), + background: getComputedStyle(document.documentElement).getPropertyValue('--vscode-editor-background'), + highlight: getComputedStyle(document.documentElement).getPropertyValue('--vscode-focusBorder'), + }) + ) } handleStoreImages(action: StoreImagesAction): void { From ecac948f340b3262fa26ca62cbce9ac9c959eb55 Mon Sep 17 00:00:00 2001 From: Niklas Rentz Date: Fri, 20 Dec 2024 14:06:32 +0100 Subject: [PATCH 3/3] CLI supports dark theme. Initialize sends current theme type of CLI/ theme type and colors (hardcoded) of VS Code, concrete colors also on theme change (VS Code). Removes ForceLightBackground option --- .../klighd-cli/src/services/connection.ts | 18 ++--- applications/klighd-cli/src/styles/main.css | 7 +- applications/klighd-vscode/src/extension.ts | 69 +++++++++++++++++-- packages/klighd-core/src/actions/actions.ts | 33 +++++++-- packages/klighd-core/src/diagram-server.ts | 25 ++----- packages/klighd-core/src/model-viewer.ts | 20 +----- .../src/options/render-options-registry.ts | 26 ------- packages/klighd-core/src/views.tsx | 15 +++- packages/klighd-core/styles/theme.css | 16 ++++- 9 files changed, 138 insertions(+), 91 deletions(-) diff --git a/applications/klighd-cli/src/services/connection.ts b/applications/klighd-cli/src/services/connection.ts index 23dd20ca..9595cf24 100644 --- a/applications/klighd-cli/src/services/connection.ts +++ b/applications/klighd-cli/src/services/connection.ts @@ -3,7 +3,7 @@ * * http://rtsys.informatik.uni-kiel.de/kieler * - * Copyright 2021 by + * Copyright 2021-2024 by * + Kiel University * + Department of Computer Science * + Real-Time and Embedded Systems Group @@ -17,7 +17,7 @@ import * as rpc from 'vscode-ws-jsonrpc' import * as lsp from 'vscode-languageserver-protocol' -import { Connection, NotificationType, ActionMessage } from '@kieler/klighd-core' +import { Connection, NotificationType, ActionMessage, ColorThemeKind } from '@kieler/klighd-core' import { showPopup } from '../popup' /* global WebSocket, window */ @@ -159,18 +159,14 @@ export class LSPConnection implements Connection { if (!this.connection) return // notify the server about the preferred colors, depending on if the OS prefers light (default) or dark theme. - let foreground = '#000000' - let background = '#ffffff' - let highlight = '#000000' + let themeKind = ColorThemeKind.LIGHT if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { // dark mode - foreground = '#cccccc' - background = '#1f1f1f' - highlight = '#cccccc' + themeKind = ColorThemeKind.DARK } const { method } = lsp.InitializeRequest.type - // The standalone view does not really has any LSP capabilities + // The standalone view does not really have any LSP capabilities const initParams: lsp.InitializeParams = { processId: null, workspaceFolders: null, @@ -180,9 +176,7 @@ export class LSPConnection implements Connection { initializationOptions: { clientDiagramOptions: persistedData, clientColorPreferences: { - foreground, - background, - highlight, + kind: themeKind, }, }, } diff --git a/applications/klighd-cli/src/styles/main.css b/applications/klighd-cli/src/styles/main.css index 6715a36f..4e660ad6 100644 --- a/applications/klighd-cli/src/styles/main.css +++ b/applications/klighd-cli/src/styles/main.css @@ -3,7 +3,7 @@ * * http://rtsys.informatik.uni-kiel.de/kieler * - * Copyright 2021 by + * Copyright 2021-2024 by * + Kiel University * + Department of Computer Science * + Real-Time and Embedded Systems Group @@ -22,6 +22,11 @@ --color-accent-blue: #1992d4; --color-background: #f7f7f7; } +@media (prefers-color-scheme: dark) { + :root { + --color-background: #1e1e1e; + } +} body { margin: 0; diff --git a/applications/klighd-vscode/src/extension.ts b/applications/klighd-vscode/src/extension.ts index d33105ec..5cffcb55 100644 --- a/applications/klighd-vscode/src/extension.ts +++ b/applications/klighd-vscode/src/extension.ts @@ -3,7 +3,7 @@ * * http://rtsys.informatik.uni-kiel.de/kieler * - * Copyright 2021-2023 by + * Copyright 2021-2024 by * + Kiel University * + Department of Computer Science * + Real-Time and Embedded Systems Group @@ -18,7 +18,7 @@ // See https://stackoverflow.com/questions/37534890/inversify-js-reflect-hasownmetadata-is-not-a-function import 'reflect-metadata' // The other imports. -import { ChangeColorThemeAction, DebugOptions, SetRenderOptionAction } from '@kieler/klighd-core' +import { ChangeColorThemeAction, ColorThemeKind, DebugOptions, SetRenderOptionAction } from '@kieler/klighd-core' import { diagramType } from '@kieler/klighd-core/lib/base/external-helpers' import { Action, ActionMessage, isAction } from 'sprotty-protocol' import { registerLspEditCommands } from 'sprotty-vscode' @@ -58,6 +58,8 @@ export function activate(context: vscode.ExtensionContext): void { ) return } + // add color preferences to the client's init message + setColorTheme(client) const storageService = new StorageService(mementoForPersistence, client) client.onDidChangeState((stateChangedEvent) => { if (stateChangedEvent.newState === State.Running) { @@ -194,15 +196,70 @@ function isLanguageClient(client: unknown): client is LanguageClient { function isFileEndingsArray(array: unknown): array is string[] { return Array.isArray(array) && array.every((val) => typeof val === 'string') } + +/** + * Modify the initialization options to send VSCode's current theme. + */ +function setColorTheme(client: LanguageClient) { + const kind = convertColorThemeKind(vscode.window.activeColorTheme.kind) + // const foreground = new vscode.ThemeColor('editor.foreground') + // const background = new vscode.ThemeColor('editor.background') + // const highlight = new vscode.ThemeColor('focusBorder') + // there is no API to get the color of the current theme for these colors, so just hardcode these here. + // from https://github.com/microsoft/vscode/blob/main/extensions/theme-defaults/themes/light_vs.json + // and https://github.com/microsoft/vscode/blob/main/extensions/theme-defaults/themes/light_modern.json + let foreground = '#000000' // editor.foreground + let background = '#FFFFFF' // editor.background + let highlight = '#005FB8' // focusBorder + if (kind === ColorThemeKind.DARK || kind === ColorThemeKind.HIGH_CONTRAST_DARK) { + // from https://github.com/microsoft/vscode/blob/main/extensions/theme-defaults/themes/dark_vs.json + // and https://github.com/microsoft/vscode/blob/main/extensions/theme-defaults/themes/dark_modern.json + foreground = '#D4D4D4' // editor.foreground + background = '#1E1E1E' // editor.background + highlight = '#0078D4' // focusBorder + } + + // Register the current theme in the client's options. + client.clientOptions.initializationOptions = { + ...client.clientOptions.initializationOptions, + clientColorPreferences: { + kind, + foreground, + background, + highlight, + }, + } +} + /** * Hook into VS Code's theme change and notify the webview to check the current colors and send them to the server. */ function registerChangeColorTheme(manager: KLighDWebviewPanelManager) { - vscode.window.onDidChangeActiveColorTheme(() => { + // Any future color change should be sent to the KLighD webviews. + vscode.window.onDidChangeActiveColorTheme((e: vscode.ColorTheme) => { for (const endpoint of manager.endpoints) { - endpoint.sendAction({ - kind: ChangeColorThemeAction.KIND, - }) + endpoint.sendAction(ChangeColorThemeAction.create(convertColorThemeKind(e.kind))) } }) } + +/** + * Convert the vscode.ColorThemeKind to KLighDs own ColorThemeKind. + * @param kind VS Code's ColorThemeKind + * @returns KLighD'S ColorThemeKind + */ +function convertColorThemeKind(kind: vscode.ColorThemeKind): ColorThemeKind { + switch (kind) { + case vscode.ColorThemeKind.Light: + return ColorThemeKind.LIGHT + case vscode.ColorThemeKind.Dark: + return ColorThemeKind.DARK + case vscode.ColorThemeKind.HighContrast: + return ColorThemeKind.HIGH_CONTRAST_DARK + case vscode.ColorThemeKind.HighContrastLight: + return ColorThemeKind.HIGH_CONTRAST_LIGHT + default: + console.error('error in extension.ts, unknown color theme kind') + return ColorThemeKind.LIGHT + } +} diff --git a/packages/klighd-core/src/actions/actions.ts b/packages/klighd-core/src/actions/actions.ts index 29ae2e5a..6cb995bf 100644 --- a/packages/klighd-core/src/actions/actions.ts +++ b/packages/klighd-core/src/actions/actions.ts @@ -106,14 +106,16 @@ export namespace CheckedImagesAction { */ export interface ChangeColorThemeAction extends Action { kind: typeof ChangeColorThemeAction.KIND + themeKind: ColorThemeKind } export namespace ChangeColorThemeAction { export const KIND = 'changeColorTheme' - export function create(): ChangeColorThemeAction { + export function create(themeKind: ColorThemeKind): ChangeColorThemeAction { return { kind: KIND, + themeKind, } } } @@ -138,13 +140,36 @@ export namespace ClientColorPreferencesAction { } } +/** + * Kinds of color themes, as an enum similar to VS Code's ColorThemeKind. + */ +export enum ColorThemeKind { + /** + * Light color theme with light backgrounds and darker writing + */ + LIGHT = 0, + /** + * Dark color theme with dark backgrounds and lighter writing + */ + DARK = 1, + /** + * Light color theme with a higher contrast. + */ + HIGH_CONTRAST_LIGHT = 2, + /** + * Dark color theme with a higher contrast. + */ + HIGH_CONTRAST_DARK = 3, +} + /** * The color preferences data class, indicating diagram colors to be used by syntheses. */ export interface ColorPreferences { - foreground: string - background: string - highlight: string + kind: ColorThemeKind + foreground: string | undefined + background: string | undefined + highlight: string | undefined } /** diff --git a/packages/klighd-core/src/diagram-server.ts b/packages/klighd-core/src/diagram-server.ts index 0a2b72a5..a81d0c6b 100644 --- a/packages/klighd-core/src/diagram-server.ts +++ b/packages/klighd-core/src/diagram-server.ts @@ -68,6 +68,7 @@ import { CheckedImagesAction, CheckImagesAction, ClientColorPreferencesAction, + ColorThemeKind, KlighdExportSvgAction, KlighdFitToScreenAction, Pair, @@ -245,25 +246,6 @@ export class KlighdDiagramServer extends DiagramServerProxy { } handle(action: Action): void | ICommand | Action { - if (action.kind === RequestModelAction.KIND && getComputedStyle !== undefined) { - // On any request model action, also send the current colors with the request, so the initial - // syntheses can use the theming of VS Code. Values will be undefined outside of VS Code and should - // be ignored. - ;(action as RequestModelAction).options = { - ...(action as RequestModelAction).options, - clientColorPreferenceForeground: getComputedStyle(document.documentElement).getPropertyValue( - '--vscode-editor-foreground' - ), - clientColorPreferenceBackground: getComputedStyle(document.documentElement).getPropertyValue( - '--vscode-editor-background' - ), - clientColorPreferenceHighlight: getComputedStyle(document.documentElement).getPropertyValue( - '--vscode-focusBorder' - ), - } - super.handle(action) - } - if (action.kind === BringToFrontAction.KIND || action.kind === SwitchEditModeAction.KIND) { // Actions that should be ignored and not further handled by this diagram server return @@ -272,7 +254,7 @@ export class KlighdDiagramServer extends DiagramServerProxy { if (action.kind === CheckImagesAction.KIND) { this.handleCheckImages(action as CheckImagesAction) } else if (action.kind === ChangeColorThemeAction.KIND) { - this.handleChangeColorTheme() + this.handleChangeColorTheme((action as ChangeColorThemeAction).themeKind) } else if (action.kind === StoreImagesAction.KIND) { this.handleStoreImages(action as StoreImagesAction) } else if (action.kind === RequestPopupModelAction.KIND) { @@ -302,10 +284,11 @@ export class KlighdDiagramServer extends DiagramServerProxy { this.actionDispatcher.dispatch(CheckedImagesAction.create(notCached)) } - handleChangeColorTheme(): void { + handleChangeColorTheme(kind: ColorThemeKind): void { if (getComputedStyle === undefined) return this.actionDispatcher.dispatch( ClientColorPreferencesAction.create({ + kind, foreground: getComputedStyle(document.documentElement).getPropertyValue('--vscode-editor-foreground'), background: getComputedStyle(document.documentElement).getPropertyValue('--vscode-editor-background'), highlight: getComputedStyle(document.documentElement).getPropertyValue('--vscode-focusBorder'), diff --git a/packages/klighd-core/src/model-viewer.ts b/packages/klighd-core/src/model-viewer.ts index d49c4a07..c59ecba2 100644 --- a/packages/klighd-core/src/model-viewer.ts +++ b/packages/klighd-core/src/model-viewer.ts @@ -15,12 +15,12 @@ * SPDX-License-Identifier: EPL-2.0 */ -import { inject, injectable, postConstruct } from 'inversify' +import { inject, injectable } from 'inversify' import { VNode } from 'snabbdom' import { ModelViewer } from 'sprotty' import { KlighdFitToScreenAction } from './actions/actions' import { DISymbol } from './di.symbols' -import { ForceLightBackground, RenderOptionsRegistry, ResizeToFit } from './options/render-options-registry' +import { RenderOptionsRegistry, ResizeToFit } from './options/render-options-registry' /* global document */ /** @@ -38,22 +38,6 @@ export class KlighdModelViewer extends ModelViewer { @inject(DISymbol.RenderOptionsRegistry) private renderOptionsRegistry: RenderOptionsRegistry - @postConstruct() - init(): void { - // Most diagrams are not rendered with different themes in mind. In case - // a diagram is hard to read, the user can force a light background. - // This toggles such background if the user selects it. - this.renderOptionsRegistry.onChange(() => { - const baseDiv = document.querySelector(`#${this.options.baseDiv}`) - - if (this.renderOptionsRegistry.getValue(ForceLightBackground)) { - baseDiv?.classList.add('light-bg') - } else { - baseDiv?.classList.remove('light-bg') - } - }) - } - protected override onWindowResize(vdom: VNode): void { const baseDiv = document.getElementById(this.options.baseDiv) if (baseDiv !== null) { diff --git a/packages/klighd-core/src/options/render-options-registry.ts b/packages/klighd-core/src/options/render-options-registry.ts index 77c6f3d2..dfd90688 100644 --- a/packages/klighd-core/src/options/render-options-registry.ts +++ b/packages/klighd-core/src/options/render-options-registry.ts @@ -74,31 +74,6 @@ export class ResizeToFit implements RenderOption { debug = true } -/** - * Uses a light background instead of an applied theme. - */ -export class ForceLightBackground implements RenderOption { - static readonly ID: string = 'force-light-background' - - static readonly NAME: string = 'Use Light Background' - - static readonly DEFAULT: boolean = false - - readonly id: string = ForceLightBackground.ID - - readonly name: string = ForceLightBackground.NAME - - readonly type: TransformationOptionType = TransformationOptionType.CHECK - - readonly initialValue: boolean = ForceLightBackground.DEFAULT - - readonly renderCategory: string = Appearance.ID - - readonly description = 'Use light background regardless of the color scheme.' - - currentValue = ForceLightBackground.DEFAULT -} - export class ShowConstraintOption implements RenderOption { static readonly ID: string = 'show-constraints' @@ -541,7 +516,6 @@ export class RenderOptionsRegistry extends Registry { // Appearance this.register(Appearance) - this.register(ForceLightBackground) this.register(ShowConstraintOption) this.register(Shadows) diff --git a/packages/klighd-core/src/views.tsx b/packages/klighd-core/src/views.tsx index 81cb6a5e..3058913c 100644 --- a/packages/klighd-core/src/views.tsx +++ b/packages/klighd-core/src/views.tsx @@ -3,7 +3,7 @@ * * http://rtsys.informatik.uni-kiel.de/kieler * - * Copyright 2019-2023 by + * Copyright 2019-2024 by * + Kiel University * + Department of Computer Science * + Real-Time and Embedded Systems Group @@ -87,8 +87,19 @@ export class SKGraphView implements IView { } const transform = `scale(${model.zoom}) translate(${-model.scroll.x},${-model.scroll.y})` + // Look for a synthesis-defined custom background color. If none is found, use 'white' as a + // default, assuming the synthesis does not know about theming. + let background = 'white' + if ((model as any).properties && (model as any).properties['klighd.diagramBackground'] !== undefined) { + const theBackground = (model as any).properties['klighd.diagramBackground'] + const r = theBackground.red ? theBackground.red : 0 + const g = theBackground.green ? theBackground.green : 0 + const b = theBackground.blue ? theBackground.blue : 0 + background = `rgb(${r},${g},${b})` + } + return ( - + {context.renderChildren(model)} ) diff --git a/packages/klighd-core/styles/theme.css b/packages/klighd-core/styles/theme.css index 6eb89f0b..b9c59074 100644 --- a/packages/klighd-core/styles/theme.css +++ b/packages/klighd-core/styles/theme.css @@ -3,7 +3,7 @@ * * http://rtsys.informatik.uni-kiel.de/kieler * - * Copyright 2021 by + * Copyright 2021-2024 by * + Kiel University * + Department of Computer Science * + Real-Time and Embedded Systems Group @@ -35,4 +35,18 @@ --kdc-color-sidebar-font-primary: hsl(0, 0%, 12%); --kdc-color-sidebar-icon-primary: hsl(0, 0%, 24%); --kdc-color-primary: #0552b5; + +} +@media (prefers-color-scheme: dark) { + :root { + --kdc-color-sidebar-background: rgb(37, 37, 37); + --kdc-color-sidebar-trigger-background: rgb(37, 37, 37); + --kdc-color-sidebar-trigger-background-hover: rgb(4, 57, 94); + --kdc-color-sidebar-trigger-background-active: rgb(55, 55, 61); + --kdc-color-sidebar-hover-background: rgba(90, 93, 94, 0.31); + --kdc-color-sidebar-font-primary: rgb(204, 204, 204); + --kdc-color-sidebar-icon-primary: rgb(197, 197, 197); + --kdc-color-primary: rgb(0, 127, 212); + + } }