From 8d2365dd2e31406b2a601c09c928963135c1f4a6 Mon Sep 17 00:00:00 2001 From: Oleg Lokhvitsky Date: Fri, 29 Nov 2024 12:11:13 -0800 Subject: [PATCH] Added "overrideValue" and "readonly" bindings for mod settings fields --- src/bridge/Bindings.d.ts | 16 ++--- src/bridge/ModConfig.d.ts | 35 +++++++++++ src/bridge/ModConfigValue.d.ts | 1 + src/renderer/react/BindingsParser.tsx | 16 +++-- .../settings/ModSettingsCheckboxField.tsx | 17 +++++- .../ModSettingsColorSelectorField.tsx | 61 ++++++++++++++----- .../react/settings/ModSettingsNumberField.tsx | 13 +++- .../react/settings/ModSettingsSelectField.tsx | 15 ++++- .../react/settings/ModSettingsTextField.tsx | 18 +++++- 9 files changed, 147 insertions(+), 45 deletions(-) diff --git a/src/bridge/Bindings.d.ts b/src/bridge/Bindings.d.ts index 74e4a4b..1ec78d7 100644 --- a/src/bridge/Bindings.d.ts +++ b/src/bridge/Bindings.d.ts @@ -70,7 +70,7 @@ export type BindingConditional = [ export type BindingNot = [operator: 'not', binding: Binding]; /** - * Checks if both the first parameters (`boolean`) and the second parameter (`boolean`) are both true. + * Checks if all of the parameters (`boolean`) are true. * @example * ``` * { @@ -87,14 +87,10 @@ export type BindingNot = [operator: 'not', binding: Binding]; * }, * ``` */ -export type BindingAnd = [ - operator: 'and', - binding1: Binding, - binding2: Binding, -]; +export type BindingAnd = [operator: 'and', ...bindings: Binding[]]; /** - * Checks if either the first parameters (`boolean`) or the second parameter (`boolean`) is true. + * Checks if any of the parameters (`boolean`) are true. * @example * ``` * { @@ -111,11 +107,7 @@ export type BindingAnd = [ * }, * ``` */ -export type BindingOr = [ - operator: 'or', - binding1: Binding, - binding2: Binding, -]; +export type BindingOr = [operator: 'or', ...bindings: Binding[]]; /** * Checks if the first parameter (`T`) is equal to the second parameter (`T`). diff --git a/src/bridge/ModConfig.d.ts b/src/bridge/ModConfig.d.ts index 2b8df8e..92155f5 100644 --- a/src/bridge/ModConfig.d.ts +++ b/src/bridge/ModConfig.d.ts @@ -131,6 +131,13 @@ export interface ModConfigFieldCheckbox extends ModConfigFieldBase { * The default value of the checkbox field. */ defaultValue: boolean; + + /** + * The override value of the checkbox field. + * If this value is anything other than `null`, it will override the current value. + * If the value is overridden, it will also be read only. + */ + overrideValue?: Binding; } /** @@ -157,6 +164,13 @@ export interface ModConfigFieldNumber extends ModConfigFieldBase { * The maximum value that the user can input. */ maxValue?: number; + + /** + * The override value of the number field. + * If this value is anything other than `null`, it will override the current value. + * If the value is overridden, it will also be read only. + */ + overrideValue?: Binding; } /** @@ -173,6 +187,13 @@ export interface ModConfigFieldText extends ModConfigFieldBase { * The default value of the text field. */ defaultValue: string; + + /** + * The override value of the text field. + * If this value is anything other than `null`, it will override the current value. + * If the value is overridden, it will also be read only. + */ + overrideValue?: Binding; } /** @@ -190,6 +211,13 @@ export interface ModConfigFieldSelect extends ModConfigFieldBase { */ defaultValue: ModConfigSingleValue; + /** + * The override value of the select field. + * If this value is anything other than `null`, it will override the current value. + * If the value is overridden, it will also be read only. + */ + overrideValue?: Binding; + /** * The options that the user can select from. */ @@ -226,6 +254,13 @@ export interface ModConfigFieldColor extends ModConfigFieldBase { */ defaultValue: [number, number, number, number]; + /** + * The override value of the color field. + * If this value is anything other than `null`, it will override the current value. + * If the value is overridden, it will also be read only. + */ + overrideValue?: Binding; + /** * Whether the alpha channel should be hidden in the color picker. */ diff --git a/src/bridge/ModConfigValue.d.ts b/src/bridge/ModConfigValue.d.ts index cd6beb9..aa936cf 100644 --- a/src/bridge/ModConfigValue.d.ts +++ b/src/bridge/ModConfigValue.d.ts @@ -2,6 +2,7 @@ * Represents the valid value of any single configuration field. */ export type ModConfigSingleValue = + | null | string | number | boolean diff --git a/src/renderer/react/BindingsParser.tsx b/src/renderer/react/BindingsParser.tsx index 6f0512f..5564c27 100644 --- a/src/renderer/react/BindingsParser.tsx +++ b/src/renderer/react/BindingsParser.tsx @@ -32,17 +32,15 @@ export function parseBinding( } } if (op === 'and' && value.length === 3) { - const arg1 = parseBinding(value[1], config); - const arg2 = parseBinding(value[2], config); - if (typeof arg1 === 'boolean' && typeof arg2 === 'boolean') { - return (arg1 && arg2) as T; + const args = value.slice(1).map((arg) => parseBinding(arg, config)); + if (args.every((arg) => typeof arg === 'boolean')) { + return args.every((arg) => arg) as T; } } - if (op === 'or' && value.length === 3) { - const arg1 = parseBinding(value[1], config); - const arg2 = parseBinding(value[2], config); - if (typeof arg1 === 'boolean' && typeof arg2 === 'boolean') { - return (arg1 || arg2) as T; + if (op === 'or' && value.length >= 3) { + const args = value.slice(1).map((arg) => parseBinding(arg, config)); + if (args.every((arg) => typeof arg === 'boolean')) { + return args.some((arg) => arg) as T; } } if (op === 'eq' && value.length === 3) { diff --git a/src/renderer/react/settings/ModSettingsCheckboxField.tsx b/src/renderer/react/settings/ModSettingsCheckboxField.tsx index c4e6db7..1d3b53c 100644 --- a/src/renderer/react/settings/ModSettingsCheckboxField.tsx +++ b/src/renderer/react/settings/ModSettingsCheckboxField.tsx @@ -1,6 +1,7 @@ import type { Mod } from 'bridge/BridgeAPI'; import type { ModConfigFieldCheckbox } from 'bridge/ModConfig'; import type { ModConfigSingleValue } from 'bridge/ModConfigValue'; +import { parseBinding } from 'renderer/react/BindingsParser'; import { useCallback } from 'react'; import { FormControlLabel, Switch } from '@mui/material'; @@ -15,6 +16,11 @@ export default function ModSettingsCheckboxField({ mod, onChange: onChangeFromProps, }: Props): JSX.Element { + const overrideValue = + field.overrideValue == null + ? null + : parseBinding(field.overrideValue, mod.config) ?? null; + const value = Boolean(mod.config[field.id]); const onChange = useCallback( @@ -26,8 +32,15 @@ export default function ModSettingsCheckboxField({ return ( } - label={value ? 'On' : 'Off'} + control={ + + } + disabled={overrideValue != null} + label={overrideValue ?? value ? 'On' : 'Off'} /> ); } diff --git a/src/renderer/react/settings/ModSettingsColorSelectorField.tsx b/src/renderer/react/settings/ModSettingsColorSelectorField.tsx index 936868a..f00e59f 100644 --- a/src/renderer/react/settings/ModSettingsColorSelectorField.tsx +++ b/src/renderer/react/settings/ModSettingsColorSelectorField.tsx @@ -1,10 +1,39 @@ import type { Mod } from 'bridge/BridgeAPI'; import type { ModConfigFieldColor } from 'bridge/ModConfig'; import type { ModConfigSingleValue } from 'bridge/ModConfigValue'; +import { parseBinding } from 'renderer/react/BindingsParser'; import debounce from 'renderer/utils/debounce'; import { MuiColorInput, MuiColorInputColors } from 'mui-color-input'; import { useCallback, useMemo, useState, useTransition } from 'react'; +type ColorArray = [number, number, number, number]; +type ColorObject = { + r: number; + g: number; + b: number; + a: number; +}; +type ColorHex = [string, string, string, string]; + +function colorArrayToObject(color: ColorArray): ColorObject { + return { + r: color[0], + g: color[1], + b: color[2], + a: color[3], + }; +} + +function colorObjectToHex(color: ColorObject): ColorHex { + const hexR = color.r.toString(16).padStart(2, '0'); + const hexG = color.g.toString(16).padStart(2, '0'); + const hexB = color.b.toString(16).padStart(2, '0'); + const hexA = Math.round(color.a * 255) + .toString(16) + .padStart(2, '0'); + return [hexR, hexG, hexB, hexA]; +} + type Props = { field: ModConfigFieldColor; mod: Mod; @@ -18,15 +47,19 @@ export default function ModSettingsColorSelectorField({ }: Props): JSX.Element { const [_isTransitioning, startTransition] = useTransition(); - const [value, setValue] = useState(() => { - const [r, g, b, a] = mod.config[field.id] as [ - number, - number, - number, - number, - ]; - return { r, g, b, a }; - }); + const overrideValue = + field.overrideValue == null + ? null + : parseBinding<[number, number, number, number] | null>( + field.overrideValue, + mod.config, + ) ?? null; + + const [value, setValue] = useState(() => + colorArrayToObject( + mod.config[field.id] as [number, number, number, number], + ), + ); const onChangeFromPropsDebounced = useMemo( () => debounce(onChangeFromProps, 1000), @@ -52,15 +85,13 @@ export default function ModSettingsColorSelectorField({ [field, onChangeFromPropsDebounced], ); - const hexR = value.r.toString(16).padStart(2, '0'); - const hexG = value.g.toString(16).padStart(2, '0'); - const hexB = value.b.toString(16).padStart(2, '0'); - const hexA = Math.round(value.a * 255) - .toString(16) - .padStart(2, '0'); + const [hexR, hexG, hexB, hexA] = colorObjectToHex( + overrideValue != null ? colorArrayToObject(overrideValue) : value, + ); return ( (field.overrideValue, mod.config) ?? null; + const value = mod.config[field.id] as number; + const [valueString, setValueString] = useState(String(value)); + const [isFocused, onFocus, onBlur] = useIsFocused(); const onChange = useCallback( @@ -58,8 +66,6 @@ export default function ModSettingsNumberField({ newValue = Math.min(newValue, field.maxValue); } - console.log('set value', event.target.value, newValue); - setValueString(event.target.value); onChangeFromProps(field.id, newValue); }, @@ -80,6 +86,7 @@ export default function ModSettingsNumberField({ return ( ); diff --git a/src/renderer/react/settings/ModSettingsSelectField.tsx b/src/renderer/react/settings/ModSettingsSelectField.tsx index f7da9d3..cd50191 100644 --- a/src/renderer/react/settings/ModSettingsSelectField.tsx +++ b/src/renderer/react/settings/ModSettingsSelectField.tsx @@ -1,6 +1,7 @@ import type { Mod } from 'bridge/BridgeAPI'; import type { ModConfigFieldSelect } from 'bridge/ModConfig'; import type { ModConfigSingleValue } from 'bridge/ModConfigValue'; +import { parseBinding } from 'renderer/react/BindingsParser'; import { useCallback } from 'react'; import { Box, MenuItem, Select, SelectChangeEvent } from '@mui/material'; @@ -15,6 +16,14 @@ export default function ModSettingsSelectField({ mod, onChange: onChangeFromProps, }: Props): JSX.Element { + const overrideValue = + field.overrideValue == null + ? null + : parseBinding( + field.overrideValue, + mod.config, + ) ?? null; + const value = mod.config[field.id] as string; const onChange = useCallback( @@ -28,7 +37,11 @@ export default function ModSettingsSelectField({ ); return ( - {field.options.map((option) => ( unknown; + onChange: (fieldID: string, value: string) => unknown; }; export default function ModSettingsTextField({ @@ -15,6 +15,11 @@ export default function ModSettingsTextField({ mod, onChange: onChangeFromProps, }: Props): JSX.Element { + const overrideValue = + field.overrideValue == null + ? null + : parseBinding(field.overrideValue, mod.config) ?? null; + const value = mod.config[field.id] as string; const onChange = useCallback( @@ -24,5 +29,12 @@ export default function ModSettingsTextField({ [field, onChangeFromProps], ); - return ; + return ( + + ); }