Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dark theme support #210

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
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
18 changes: 14 additions & 4 deletions applications/klighd-cli/src/services/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -17,9 +17,9 @@

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 */
/* global WebSocket, window */

type GeneralMessageParams = [string, 'info' | 'warn' | 'error']

Expand Down Expand Up @@ -158,8 +158,15 @@ export class LSPConnection implements Connection {
async sendInitialize(persistedData: Record<string, any>): Promise<void> {
if (!this.connection) return

// notify the server about the preferred colors, depending on if the OS prefers light (default) or dark theme.
let themeKind = ColorThemeKind.LIGHT
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
// dark mode
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,
Expand All @@ -168,6 +175,9 @@ export class LSPConnection implements Connection {
capabilities: {},
initializationOptions: {
clientDiagramOptions: persistedData,
clientColorPreferences: {
kind: themeKind,
},
},
}

Expand Down
7 changes: 6 additions & 1 deletion applications/klighd-cli/src/styles/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -22,6 +22,11 @@
--color-accent-blue: #1992d4;
--color-background: #f7f7f7;
}
@media (prefers-color-scheme: dark) {
:root {
--color-background: #1e1e1e;
}
}

body {
margin: 0;
Expand Down
74 changes: 72 additions & 2 deletions applications/klighd-vscode/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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, 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'
Expand Down Expand Up @@ -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) {
Expand All @@ -78,6 +80,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)
Expand Down Expand Up @@ -193,3 +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) {
// 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(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
}
}
72 changes: 72 additions & 0 deletions packages/klighd-core/src/actions/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,78 @@ 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
themeKind: ColorThemeKind
}

export namespace ChangeColorThemeAction {
export const KIND = 'changeColorTheme'

export function create(themeKind: ColorThemeKind): ChangeColorThemeAction {
return {
kind: KIND,
themeKind,
}
}
}

/**
* 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,
}
}
}

/**
* 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 {
kind: ColorThemeKind
foreground: string | undefined
background: string | undefined
highlight: string | undefined
}

/**
* 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.
Expand Down
26 changes: 25 additions & 1 deletion packages/klighd-core/src/diagram-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,11 @@ import {
ViewportResult,
} from 'sprotty-protocol'
import {
ChangeColorThemeAction,
CheckedImagesAction,
CheckImagesAction,
ClientColorPreferencesAction,
ColorThemeKind,
KlighdExportSvgAction,
KlighdFitToScreenAction,
Pair,
Expand All @@ -88,6 +91,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
Expand Down Expand Up @@ -167,9 +171,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
case ChangeColorThemeAction.KIND:
return false
case PerformActionAction.KIND:
return true
case RefreshDiagramAction.KIND:
Expand Down Expand Up @@ -207,6 +215,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)
Expand Down Expand Up @@ -243,6 +253,8 @@ export class KlighdDiagramServer extends DiagramServerProxy {

if (action.kind === CheckImagesAction.KIND) {
this.handleCheckImages(action as CheckImagesAction)
} else if (action.kind === ChangeColorThemeAction.KIND) {
this.handleChangeColorTheme((action as ChangeColorThemeAction).themeKind)
} else if (action.kind === StoreImagesAction.KIND) {
this.handleStoreImages(action as StoreImagesAction)
} else if (action.kind === RequestPopupModelAction.KIND) {
Expand Down Expand Up @@ -272,6 +284,18 @@ export class KlighdDiagramServer extends DiagramServerProxy {
this.actionDispatcher.dispatch(CheckedImagesAction.create(notCached))
}

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'),
})
)
}

handleStoreImages(action: StoreImagesAction): void {
// Put the new images in session storage.
for (const imagePair of (action as StoreImagesAction).images) {
Expand Down
20 changes: 2 additions & 18 deletions packages/klighd-core/src/model-viewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */

/**
Expand All @@ -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) {
Expand Down
Loading
Loading