From 1f00b48456da1fc6c9b35f168fa08e105bfb1437 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loi=CC=88c=20Correnson?= <loic.correnson@cea.fr> Date: Mon, 21 Feb 2022 15:08:33 +0100 Subject: [PATCH] [ivette] move theme CSS do dome --- ivette/src/dome/main/dome.ts | 27 ++++-- ivette/src/{colors => dome/renderer}/dark.css | 0 ivette/src/dome/renderer/dome.tsx | 2 + ivette/src/dome/renderer/frame/toolbars.tsx | 23 +++-- ivette/src/dome/renderer/layout/forms.tsx | 63 +++++++++++++ .../src/{colors => dome/renderer}/light.css | 0 .../renderer/text}/dark-code.css | 0 ivette/src/dome/renderer/text/editors.tsx | 32 ++++--- ivette/src/dome/renderer/themes.tsx | 92 ++++++++++++++++++ ivette/src/frama-c/kernel/ASTinfo.tsx | 3 - ivette/src/frama-c/kernel/ASTview.tsx | 6 +- ivette/src/frama-c/kernel/SourceCode.tsx | 6 +- ivette/src/ivette/prefs.tsx | 93 ++++++++++--------- ivette/src/renderer/Application.tsx | 18 +--- ivette/src/renderer/Controller.tsx | 4 - ivette/src/renderer/Preferences.tsx | 52 +++++++---- ivette/src/renderer/index.js | 2 - 17 files changed, 298 insertions(+), 125 deletions(-) rename ivette/src/{colors => dome/renderer}/dark.css (100%) rename ivette/src/{colors => dome/renderer}/light.css (100%) rename ivette/src/{colors => dome/renderer/text}/dark-code.css (100%) create mode 100644 ivette/src/dome/renderer/themes.tsx diff --git a/ivette/src/dome/main/dome.ts b/ivette/src/dome/main/dome.ts index 5930229d41c..37078a13cfa 100644 --- a/ivette/src/dome/main/dome.ts +++ b/ivette/src/dome/main/dome.ts @@ -620,13 +620,28 @@ ipcMain.handle( ); // -------------------------------------------------------------------------- - -type themes = 'dark' | 'light' | 'system'; - -ipcMain.handle('theme-color:switch', (_evt, theme: themes) => { - nativeTheme.themeSource = theme; +// --- Native Theme +// -------------------------------------------------------------------------- + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +ipcMain.handle('dome.ipc.theme.setSource', (_evt, theme) => { + switch (theme) { + case 'dark': + case 'light': + case 'system': + nativeTheme.themeSource = theme; + return; + default: + console.warn('[dome] unknown theme', theme); + } }); -ipcMain.handle('theme-color:which-system', () => { +ipcMain.handle('dome.ipc.theme.getDefault', () => { return nativeTheme.shouldUseDarkColors ? 'dark' : 'light'; }); + +nativeTheme.on('updated', () => { + broadcast('dome.theme.updated'); +}); + +// -------------------------------------------------------------------------- diff --git a/ivette/src/colors/dark.css b/ivette/src/dome/renderer/dark.css similarity index 100% rename from ivette/src/colors/dark.css rename to ivette/src/dome/renderer/dark.css diff --git a/ivette/src/dome/renderer/dome.tsx b/ivette/src/dome/renderer/dome.tsx index 4442d4d2ca7..1770a29b7f2 100644 --- a/ivette/src/dome/renderer/dome.tsx +++ b/ivette/src/dome/renderer/dome.tsx @@ -49,6 +49,8 @@ import { ipcRenderer } from 'electron'; import SYS, * as System from 'dome/system'; import * as Json from 'dome/data/json'; import * as Settings from 'dome/data/settings'; +import './dark.css'; +import './light.css'; import './style.css'; import { State } from './data/states'; diff --git a/ivette/src/dome/renderer/frame/toolbars.tsx b/ivette/src/dome/renderer/frame/toolbars.tsx index 6f54b94f976..15746e533ea 100644 --- a/ivette/src/dome/renderer/frame/toolbars.tsx +++ b/ivette/src/dome/renderer/frame/toolbars.tsx @@ -160,23 +160,28 @@ export interface SwitchProps { enabled?: boolean; /** Defaults to `false`. */ disabled?: boolean; - /** Switch position. Default to left. */ - position?: 'left' | 'right'; + /** Switch value. + When checked, the slide is switched to « right » position. + Defaults to false. */ + checked?: boolean; /** Click callback. */ - onChange?: (newPosition: 'left' | 'right') => void; + onChange?: (newValue: boolean) => void; + /** Right Click callback. */ + onContextMenu?: () => void; } /** Toolbar Switch. */ export function Switch(props: SwitchProps) { - const { position = 'left', onChange } = props; + const { checked = false, onChange } = props; const { title = '', className = '', style } = props; const { enabled = true, disabled = false } = props; - const slide = (p: 'left' | 'right') => p === 'left' ? 'right' : 'left'; - const callback = onChange && (() => onChange(slide(position))); - const checked = position === 'right'; - if (disabled || !enabled) return <></>; + const callback = onChange && (() => onChange(!checked)); + if (disabled || !enabled) return null; return ( - <label className={'dome-xSwitch ' + className} style={style} > + <label + className={'dome-xSwitch ' + className} + style={style} + onContextMenu={props.onContextMenu}> <input type={'checkbox'} checked={checked} onChange={callback} /> <span className={'dome-xSwitch-slider'} title={title} /> </label> diff --git a/ivette/src/dome/renderer/layout/forms.tsx b/ivette/src/dome/renderer/layout/forms.tsx index d18cea9ae1d..638517f281b 100644 --- a/ivette/src/dome/renderer/layout/forms.tsx +++ b/ivette/src/dome/renderer/layout/forms.tsx @@ -1266,4 +1266,67 @@ export function SelectField(props: SelectFieldProps) { ); } +/** @category Form Fields */ +export interface MenuFieldOption<A> { + value: A; + label: string; +} + +/** @category Form Fields */ +export interface MenuFieldProps<A> extends FieldProps<A> { + /** Field label. */ + label: string; + /** Field tooltip text. */ + title?: string; + /** Field state. */ + state: FieldState<A>; + placeholder?: string; + defaultValue: A; + options: MenuFieldOption<A>[]; +} + +type ENTRY<A> = { option: JSX.Element, field: string, value: A }; + +/** + Creates a `<SelectField/>` form field with a predefine set + of (typed) options. + + @category Form Fields + */ +export function MenuField<A>(props: MenuFieldProps<A>): JSX.Element { + const entries: ENTRY<A>[] = React.useMemo(() => + props.options.map((e, k) => { + const field = `item#${k}`; + const option = <option value={field} label={e.label} />; + return { field, option, value: e.value }; + }), [props.options]); + const input = React.useCallback( + (v) => entries.find((e) => e.value === v)?.field + , [entries] + ); + const output = React.useCallback( + (f) => entries.find((e) => e.field === f)?.value ?? props.defaultValue + , [entries, props.defaultValue] + ); + const defaultField = React.useMemo( + () => input(props.defaultValue), + [input, props.defaultValue] + ); + const state = useFilter<A, string | undefined>( + props.state, + input, output, + defaultField, + ); + return ( + <SelectField + state={state} + label={props.label} + title={props.title} + placeholder={props.placeholder} > + {entries.map((e) => e.option)} + </SelectField> + ); +} + + // -------------------------------------------------------------------------- diff --git a/ivette/src/colors/light.css b/ivette/src/dome/renderer/light.css similarity index 100% rename from ivette/src/colors/light.css rename to ivette/src/dome/renderer/light.css diff --git a/ivette/src/colors/dark-code.css b/ivette/src/dome/renderer/text/dark-code.css similarity index 100% rename from ivette/src/colors/dark-code.css rename to ivette/src/dome/renderer/text/dark-code.css diff --git a/ivette/src/dome/renderer/text/editors.tsx b/ivette/src/dome/renderer/text/editors.tsx index b3cf82b06d3..43df5396e92 100644 --- a/ivette/src/dome/renderer/text/editors.tsx +++ b/ivette/src/dome/renderer/text/editors.tsx @@ -34,11 +34,13 @@ import _ from 'lodash'; import React from 'react'; import * as Dome from 'dome'; +import * as Themes from 'dome/themes'; import { Vfill } from 'dome/layout/boxes'; import CodeMirror, { EditorConfiguration } from 'codemirror/lib/codemirror'; import { RichTextBuffer, CSSMarker, Decorator } from './buffers'; import './style.css'; +import './dark-code.css'; import 'codemirror/lib/codemirror.css'; const CSS_HOVERED = 'dome-xText-hover'; @@ -487,8 +489,7 @@ class CodeMirrorWrapper extends React.Component<TextProps> { // --- Text View // -------------------------------------------------------------------------- -/** - #### Text Editor. +/** #### Text Editor. A component rendering the content of a text buffer, that shall be instances of the `Buffer` base class. @@ -506,11 +507,17 @@ class CodeMirrorWrapper extends React.Component<TextProps> { #### Themes - The CodeMirror `theme` option allow you to style your document, - especially when using modes. - Themes are only accessible if you load the associated CSS style sheet. - For instance, to use the `'ambiance'` theme provided with CodeMirror, you - shall import `'codemirror/theme/ambiance.css'` somewhere in your application. + The CodeMirror `theme` option allow you to style your document, especially + when using modes. + + By default, CodeMirror uses the `'default'` theme in _light_ theme and the + `'dark-code'` theme in _dark_ theme. The `'dark-code'` is provided by Dome, + Cf. `./dark-mode.css` in the source distribution. + + To use other custom themes, you shall load the associated CSS style + sheet. For instance, to use the `'ambiance'` theme provided with CodeMirror, + you shall import `'codemirror/theme/ambiance.css'` somewhere in your + application. #### Modes & Adds-On @@ -524,16 +531,17 @@ class CodeMirrorWrapper extends React.Component<TextProps> { You can register your own extensions directly into the global `CodeMirror` class instance. However, the correct instance must be retrieved by using `import CodeMirror from 'codemirror/lib/codemirror.js'` ; using `from - 'codemirror'` returns a different instance of `CodeMirror` class and will - not work. - */ + 'codemirror'` returns a different instance of `CodeMirror` class and will not + work. */ export function Text(props: TextProps) { - let { className, style, fontSize, ...cmprops } = props; + const [appTheme] = Themes.useColorTheme(); + let { className, style, fontSize, theme: usrTheme, ...cmprops } = props; if (fontSize !== undefined && fontSize < 4) fontSize = 4; if (fontSize !== undefined && fontSize > 48) fontSize = 48; + const theme = usrTheme ?? (appTheme === 'dark' ? 'dark-code' : 'default'); return ( <Vfill className={className} style={{ ...style, fontSize }}> - <CodeMirrorWrapper fontSize={fontSize} {...cmprops} /> + <CodeMirrorWrapper fontSize={fontSize} theme={theme} {...cmprops} /> </Vfill> ); } diff --git a/ivette/src/dome/renderer/themes.tsx b/ivette/src/dome/renderer/themes.tsx new file mode 100644 index 00000000000..8bb3e2a3bef --- /dev/null +++ b/ivette/src/dome/renderer/themes.tsx @@ -0,0 +1,92 @@ +/* ************************************************************************ */ +/* */ +/* This file is part of Frama-C. */ +/* */ +/* Copyright (C) 2007-2021 */ +/* CEA (Commissariat à l'énergie atomique et aux énergies */ +/* alternatives) */ +/* */ +/* you can redistribute it and/or modify it under the terms of the GNU */ +/* Lesser General Public License as published by the Free Software */ +/* Foundation, version 2.1. */ +/* */ +/* It is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU Lesser General Public License for more details. */ +/* */ +/* See the GNU Lesser General Public License version 2.1 */ +/* for more details (enclosed in the file licenses/LGPLv2.1). */ +/* */ +/* ************************************************************************ */ + +// -------------------------------------------------------------------------- +// --- Global Color Theme Management +// -------------------------------------------------------------------------- + +/** + @packageDocumentation + @module dome/themes + */ + +//import React from 'react'; +import * as Dome from 'dome'; +import * as Settings from 'dome/data/settings'; +import { State } from 'dome/data/states'; +import { ipcRenderer } from 'electron'; + +/* -------------------------------------------------------------------------- */ +/* --- Global Settings --- */ +/* -------------------------------------------------------------------------- */ + +export type ColorTheme = 'dark' | 'light'; +export type ColorSettings = 'dark' | 'light' | 'system'; + +export const jColorTheme = + (th: string | undefined): ColorTheme => (th === 'dark' ? 'dark' : 'light'); +export const jColorSettings = + (th: string | undefined): ColorSettings => { + switch (th) { + case 'light': + case 'dark': + case 'system': + return th; + default: + return 'system'; + } + }; + +const ColorThemeSettings = new Settings.GString('dome-color-theme', 'system'); +const NativeThemeUpdated = new Dome.Event('dome.theme.updated'); +ipcRenderer.on('dome.theme.updated', () => NativeThemeUpdated.emit()); + +async function getNativeTheme(): Promise<ColorTheme> { + const th = await ipcRenderer.invoke('dome.ipc.theme.getDefault'); + return jColorTheme(th); +} + +/* -------------------------------------------------------------------------- */ +/* --- Color Theme Hooks --- */ +/* -------------------------------------------------------------------------- */ + +export function useColorTheme(): [ColorTheme, (upd: ColorSettings) => void] { + Dome.useUpdate(NativeThemeUpdated); + const { result: current } = Dome.usePromise(getNativeTheme()); + const [pref, setPref] = Settings.useGlobalSettings(ColorThemeSettings); + const setTheme = (upd: ColorSettings): void => { + setPref(upd); + ipcRenderer.invoke('dome.ipc.theme.setSource', upd); + }; + return [current ?? jColorTheme(pref), setTheme]; +} + +export function useColorThemeSettings(): State<ColorSettings> { + const [pref, setPref] = Settings.useGlobalSettings(ColorThemeSettings); + const setTheme = (upd: ColorSettings): void => { + setPref(upd); + ipcRenderer.invoke('dome.ipc.theme.setSource', upd); + }; + return [jColorSettings(pref), setTheme]; +} + +/* -------------------------------------------------------------------------- */ diff --git a/ivette/src/frama-c/kernel/ASTinfo.tsx b/ivette/src/frama-c/kernel/ASTinfo.tsx index 9ae52f7ec89..928411368bd 100644 --- a/ivette/src/frama-c/kernel/ASTinfo.tsx +++ b/ivette/src/frama-c/kernel/ASTinfo.tsx @@ -27,7 +27,6 @@ import React from 'react'; import * as States from 'frama-c/states'; import * as Utils from 'frama-c/utils'; -import * as Preferences from 'ivette/prefs'; import { Vfill } from 'dome/layout/boxes'; import { RichTextBuffer } from 'dome/text/buffers'; @@ -40,7 +39,6 @@ import { getInfo } from 'frama-c/kernel/api/ast'; export default function ASTinfo(): JSX.Element { - const theme = Preferences.useThemeColors(); const buffer = React.useMemo(() => new RichTextBuffer(), []); const [selection, updateSelection] = States.useSelection(); const marker = selection?.current?.marker; @@ -66,7 +64,6 @@ export default function ASTinfo(): JSX.Element { <Text buffer={buffer} mode="text" - theme={theme} onSelection={onTextSelection} readOnly /> diff --git a/ivette/src/frama-c/kernel/ASTview.tsx b/ivette/src/frama-c/kernel/ASTview.tsx index cebfda57c71..d3505aba569 100644 --- a/ivette/src/frama-c/kernel/ASTview.tsx +++ b/ivette/src/frama-c/kernel/ASTview.tsx @@ -164,9 +164,8 @@ export default function ASTview() { const multipleSelections = selection?.multiple.allSelections; const theFunction = selection?.current?.fct; const theMarker = selection?.current?.marker; - const { buttons: themeButtons, theme, fontSize, wrapText } = - Preferences.useThemeButtons({ - target: 'Internal AST', + const { buttons: themeButtons, fontSize, wrapText } = + Preferences.useEditorButtons({ fontSize: Preferences.AstFontSize, wrapText: Preferences.AstWrapText, disabled: !theFunction, @@ -310,7 +309,6 @@ export default function ASTview() { <Text buffer={buffer} mode="text/x-csrc" - theme={theme} fontSize={fontSize} lineWrapping={wrapText} selection={theMarker} diff --git a/ivette/src/frama-c/kernel/SourceCode.tsx b/ivette/src/frama-c/kernel/SourceCode.tsx index 882765aa55c..4edabcf138e 100644 --- a/ivette/src/frama-c/kernel/SourceCode.tsx +++ b/ivette/src/frama-c/kernel/SourceCode.tsx @@ -80,9 +80,8 @@ export default function SourceCode(): JSX.Element { const filename = Path.parse(file).base; // Title bar buttons, along with the parameters for our text. - const { buttons: themeButtons, theme, fontSize, wrapText } = - Preferences.useThemeButtons({ - target: 'Source Code', + const { buttons: themeButtons, fontSize, wrapText } = + Preferences.useEditorButtons({ fontSize: Preferences.SourceFontSize, wrapText: Preferences.AstWrapText, disabled: !theFunction, @@ -202,7 +201,6 @@ export default function SourceCode(): JSX.Element { <Text buffer={buffer} mode="text/x-csrc" - theme={theme} fontSize={fontSize} lineWrapping={wrapText} selection={theMarker} diff --git a/ivette/src/ivette/prefs.tsx b/ivette/src/ivette/prefs.tsx index 039b544df6d..ba20eeced76 100644 --- a/ivette/src/ivette/prefs.tsx +++ b/ivette/src/ivette/prefs.tsx @@ -20,8 +20,6 @@ /* */ /* ************************************************************************ */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ - // -------------------------------------------------------------------------- // --- Main React Component rendered by './index.js' // -------------------------------------------------------------------------- @@ -32,76 +30,76 @@ */ import React from 'react'; - -import { usePromise } from 'dome/dome'; +import * as Dome from 'dome'; +import * as Themes from 'dome/themes'; +import * as Toolbar from 'dome/frame/toolbars'; import * as Settings from 'dome/data/settings'; import { IconButton } from 'dome/controls/buttons'; - import 'codemirror/mode/clike/clike'; -import '../colors/dark-code.css'; -import { ipcRenderer } from 'electron'; - -export const THEMES = [ - { id: 'light', label: 'Light' }, - { id: 'dark', label: 'Dark' }, - { id: 'system', label: 'System Defaults' }, -]; // -------------------------------------------------------------------------- // --- AST View Preferences // -------------------------------------------------------------------------- -export const ColorTheme = new Settings.GString('color-theme', 'system'); export const AstFontSize = new Settings.GNumber('ASTview.fontSize', 12); export const AstWrapText = new Settings.GFalse('ASTview.wrapText'); export const SourceFontSize = new Settings.GNumber('SourceCode.fontSize', 12); export const SourceWrapText = new Settings.GFalse('SourceCode.wrapText'); -export interface ThemeProps { - target: string; - fontSize: Settings.GlobalSettings<number>; - wrapText: Settings.GlobalSettings<boolean>; - disabled?: boolean; +/* -------------------------------------------------------------------------- */ +/* --- Theme Switcher Button --- */ +/* -------------------------------------------------------------------------- */ + +const themeEntries: Dome.PopupMenuItem[] = [ + { id: 'light', label: 'Switch to Light Theme' }, + { id: 'dark', label: 'Switch to Dark Theme' }, + { id: 'system', label: 'Switch to System Default' }, +]; + +export function ThemeSwitch(): JSX.Element { + const [theme, setTheme] = Themes.useColorTheme(); + const other = theme === 'dark' ? 'light' : 'dark'; + const title = `Switch to ${other} theme (right-click for full choice)`; + const onChange = (): void => setTheme(other); + const onPopup = (): void => Dome.popupMenu( + themeEntries, + (th) => setTheme(Themes.jColorSettings(th)) + ); + return ( + <Toolbar.Switch + disabled={!Dome.DEVEL} + title={title} + checked={theme === 'dark'} + onChange={onChange} + onContextMenu={onPopup} + /> + ); } // -------------------------------------------------------------------------- -// --- Icon Buttons +// --- Editor Icon Buttons // -------------------------------------------------------------------------- -export interface ThemeControls { +export interface EditorProps { + fontSize: Settings.GlobalSettings<number>; + wrapText: Settings.GlobalSettings<boolean>; + disabled?: boolean; +} + +export interface EditorControls { buttons: React.ReactNode; - theme: string; fontSize: number; wrapText: boolean; } -export function forceThemeUpdate(theme: string) { - ipcRenderer.invoke('theme-color:switch', theme); -} - -ipcRenderer.on('dome.ipc.settings.defaults', () => { - forceThemeUpdate('system'); -}); - -export function useThemeColors() { - const [themeColors] = Settings.useGlobalSettings(ColorTheme); - const invoke = () => ipcRenderer.invoke('theme-color:which-system'); - const { result }: { result: 'dark' | 'light' } = usePromise(invoke()); - if (themeColors === 'system') - return result === 'dark' ? 'dark-code' : 'default'; - return themeColors === 'dark' ? 'dark-code' : 'default'; -} - -export function useThemeButtons(props: ThemeProps): ThemeControls { +export function useEditorButtons(props: EditorProps): EditorControls { + const { disabled = false } = props; const [fontSize, setFontSize] = Settings.useGlobalSettings(props.fontSize); const [wrapText, setWrapText] = Settings.useGlobalSettings(props.wrapText); - const theme = useThemeColors(); - const zoomIn = () => fontSize < 48 && setFontSize(fontSize + 2); - const zoomOut = () => fontSize > 4 && setFontSize(fontSize - 2); - const flipWrapText = () => setWrapText(!wrapText); - const { disabled = false } = props; + const zoomIn = (): void => setFontSize(fontSize + 2); + const zoomOut = (): void => setFontSize(fontSize - 2); + const flipWrapText = (): void => setWrapText(!wrapText); return { - theme, fontSize, wrapText, buttons: [ @@ -109,6 +107,7 @@ export function useThemeButtons(props: ThemeProps): ThemeControls { key="zoom.out" icon="ZOOM.OUT" onClick={zoomOut} + enabled={fontSize > 4} disabled={disabled} title="Decrease font size" />, @@ -116,6 +115,7 @@ export function useThemeButtons(props: ThemeProps): ThemeControls { key="zoom.in" icon="ZOOM.IN" onClick={zoomIn} + enabled={fontSize < 48} disabled={disabled} title="Increase font size" />, @@ -123,6 +123,7 @@ export function useThemeButtons(props: ThemeProps): ThemeControls { key="wrap" icon="WRAPTEXT" selected={wrapText} + disabled={disabled} onClick={flipWrapText} title="Wrap text" />, diff --git a/ivette/src/renderer/Application.tsx b/ivette/src/renderer/Application.tsx index df336b19ed3..ea4a07a1a0b 100644 --- a/ivette/src/renderer/Application.tsx +++ b/ivette/src/renderer/Application.tsx @@ -35,9 +35,8 @@ import * as Sidebar from 'dome/frame/sidebars'; import * as Controller from './Controller'; import * as Extensions from './Extensions'; import * as Laboratory from './Laboratory'; -import * as Settings from 'dome/data/settings'; +import * as IvettePrefs from 'ivette/prefs'; import './loader'; -import * as Preferences from 'ivette/prefs'; // -------------------------------------------------------------------------- // --- Main View @@ -52,14 +51,6 @@ export default function Application(): JSX.Element { const onSelectedHints = (): void => { if (hints.length === 1) Extensions.onSearchHint(hints[0]); }; - - const [ th, setTh ] = Settings.useGlobalSettings(Preferences.ColorTheme); - const change = Preferences.forceThemeUpdate; - React.useState(() => change(th)); - const other = th === 'dark' ? 'light' : 'dark'; - const themeTitle = 'Switch to ' + other + ' theme'; - const changeColorTheme: () => void = () => { change(other); setTh(other); }; - return ( <Vfill> <Toolbar.ToolBar> @@ -79,12 +70,7 @@ export default function Application(): JSX.Element { onHint={Extensions.onSearchHint} onSelect={onSelectedHints} /> - <Toolbar.Switch - disabled={!Dome.DEVEL} - title={themeTitle} - position={th === 'dark' ? 'right' : 'left'} - onChange={changeColorTheme} - /> + <IvettePrefs.ThemeSwitch /> <Toolbar.Button icon="ITEMS.GRID" title="Customize Main View" diff --git a/ivette/src/renderer/Controller.tsx b/ivette/src/renderer/Controller.tsx index df50ecbcbc3..5d4d89e6564 100644 --- a/ivette/src/renderer/Controller.tsx +++ b/ivette/src/renderer/Controller.tsx @@ -37,8 +37,6 @@ import { LED, LEDstatus } from 'dome/controls/displays'; import { Label, Code } from 'dome/controls/labels'; import { RichTextBuffer } from 'dome/text/buffers'; import { Text } from 'dome/text/editors'; -import * as Preferences from 'ivette/prefs'; - import * as Ivette from 'ivette'; import * as Server from 'frama-c/server'; @@ -197,7 +195,6 @@ export const Control = () => { const editor = new RichTextBuffer(); const RenderConsole = () => { - const theme = Preferences.useThemeColors(); const scratch = React.useRef([] as string[]); const [cursor, setCursor] = React.useState(-1); const [isEmpty, setEmpty] = React.useState(true); @@ -332,7 +329,6 @@ const RenderConsole = () => { buffer={edited ? editor : Server.buffer} mode="text" readOnly={!edited} - theme={theme} /> </> ); diff --git a/ivette/src/renderer/Preferences.tsx b/ivette/src/renderer/Preferences.tsx index b9f5959eaf6..8056ec84f5f 100644 --- a/ivette/src/renderer/Preferences.tsx +++ b/ivette/src/renderer/Preferences.tsx @@ -39,13 +39,41 @@ import React from 'react'; import * as Settings from 'dome/data/settings'; import * as Forms from 'dome/layout/forms'; +import * as Themes from 'dome/themes'; import * as IvettePrefs from 'ivette/prefs'; // -------------------------------------------------------------------------- -// --- Font Forms +// --- Theme Fields // -------------------------------------------------------------------------- -function ThemeFields(props: IvettePrefs.ThemeProps) { +const themeOptions: Forms.MenuFieldOption<Themes.ColorSettings>[] = [ + { value: 'light', label: 'Light Theme' }, + { value: 'dark', label: 'Dark Theme' }, + { value: 'system', label: 'System Defaults' }, +]; + +function ThemeFields(): JSX.Element { + const state = Forms.useValid(Themes.useColorThemeSettings()); + return ( + <Forms.MenuField<Themes.ColorSettings> + label="Color Theme" + title="Select global color theme for the application" + state={state} + defaultValue='system' + options={themeOptions} + /> + ); +} + +// -------------------------------------------------------------------------- +// --- Editor Fields +// -------------------------------------------------------------------------- + +interface EditorFieldProps extends IvettePrefs.EditorProps { + target: string; +} + +function EditorFields(props: EditorFieldProps) { const fontsize = Forms.useValid( Settings.useGlobalSettings(props.fontSize), ); @@ -72,21 +100,7 @@ function ThemeFields(props: IvettePrefs.ThemeProps) { ); } -function ColorThemeFields() { - const [theme, setTheme] = Settings.useGlobalSettings(IvettePrefs.ColorTheme); - const elements = IvettePrefs.THEMES.map(({ id, label }) => { - return <option value={id} key={id}>{label}</option>; - }); - const set = (t: string | undefined) => { - if (t) { IvettePrefs.forceThemeUpdate(t); setTheme(t); } - }; - return ( - <Forms.SelectField label={'Color theme'} state={[theme, undefined, set]}> - {elements} - </Forms.SelectField> - ); -} // -------------------------------------------------------------------------- // --- Editor Command Forms @@ -110,17 +124,17 @@ export default function Preferences() { return ( <Forms.Page> <Forms.Section label="Theme" unfold> - <ColorThemeFields /> + <ThemeFields /> </Forms.Section> <Forms.Section label="AST View" unfold> - <ThemeFields + <EditorFields target="Internal AST" fontSize={IvettePrefs.AstFontSize} wrapText={IvettePrefs.AstWrapText} /> </Forms.Section> <Forms.Section label="Source View" unfold> - <ThemeFields + <EditorFields target="Source Code" fontSize={IvettePrefs.SourceFontSize} wrapText={IvettePrefs.SourceWrapText} diff --git a/ivette/src/renderer/index.js b/ivette/src/renderer/index.js index d65ee906500..b7fa5b20332 100644 --- a/ivette/src/renderer/index.js +++ b/ivette/src/renderer/index.js @@ -46,8 +46,6 @@ import { isApplicationWindow, isPreferencesWindow, } from 'dome' ; -import '../colors/light.css'; -import '../colors/dark.css'; // You can change the name of the main components, // provided you define the makefile variable -- GitLab