Skip to content

Commit

Permalink
Added "overrideValue" and "readonly" bindings for mod settings fields
Browse files Browse the repository at this point in the history
  • Loading branch information
olegbl committed Nov 29, 2024
1 parent 9038e9a commit 8d2365d
Show file tree
Hide file tree
Showing 9 changed files with 147 additions and 45 deletions.
16 changes: 4 additions & 12 deletions src/bridge/Bindings.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export type BindingConditional<T> = [
export type BindingNot = [operator: 'not', binding: Binding<boolean>];

/**
* 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
* ```
* {
Expand All @@ -87,14 +87,10 @@ export type BindingNot = [operator: 'not', binding: Binding<boolean>];
* },
* ```
*/
export type BindingAnd = [
operator: 'and',
binding1: Binding<boolean>,
binding2: Binding<boolean>,
];
export type BindingAnd = [operator: 'and', ...bindings: Binding<boolean>[]];

/**
* Checks if either the first parameters (`boolean`) or the second parameter (`boolean`) is true.
* Checks if any of the parameters (`boolean`) are true.
* @example
* ```
* {
Expand All @@ -111,11 +107,7 @@ export type BindingAnd = [
* },
* ```
*/
export type BindingOr = [
operator: 'or',
binding1: Binding<boolean>,
binding2: Binding<boolean>,
];
export type BindingOr = [operator: 'or', ...bindings: Binding<boolean>[]];

/**
* Checks if the first parameter (`T`) is equal to the second parameter (`T`).
Expand Down
35 changes: 35 additions & 0 deletions src/bridge/ModConfig.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<null | boolean>;
}

/**
Expand All @@ -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<null | number>;
}

/**
Expand All @@ -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<null | string>;
}

/**
Expand All @@ -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<null | ModConfigSingleValue>;

/**
* The options that the user can select from.
*/
Expand Down Expand Up @@ -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<null | [number, number, number, number]>;

/**
* Whether the alpha channel should be hidden in the color picker.
*/
Expand Down
1 change: 1 addition & 0 deletions src/bridge/ModConfigValue.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* Represents the valid value of any single configuration field.
*/
export type ModConfigSingleValue =
| null
| string
| number
| boolean
Expand Down
16 changes: 7 additions & 9 deletions src/renderer/react/BindingsParser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,15 @@ export function parseBinding<T extends ModConfigSingleValue>(
}
}
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) {
Expand Down
17 changes: 15 additions & 2 deletions src/renderer/react/settings/ModSettingsCheckboxField.tsx
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -15,6 +16,11 @@ export default function ModSettingsCheckboxField({
mod,
onChange: onChangeFromProps,
}: Props): JSX.Element {
const overrideValue =
field.overrideValue == null
? null
: parseBinding<boolean | null>(field.overrideValue, mod.config) ?? null;

const value = Boolean(mod.config[field.id]);

const onChange = useCallback(
Expand All @@ -26,8 +32,15 @@ export default function ModSettingsCheckboxField({

return (
<FormControlLabel
control={<Switch checked={value} name={field.name} onChange={onChange} />}
label={value ? 'On' : 'Off'}
control={
<Switch
checked={overrideValue ?? value}
name={field.name}
onChange={onChange}
/>
}
disabled={overrideValue != null}
label={overrideValue ?? value ? 'On' : 'Off'}
/>
);
}
61 changes: 46 additions & 15 deletions src/renderer/react/settings/ModSettingsColorSelectorField.tsx
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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),
Expand All @@ -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 (
<MuiColorInput
disabled={overrideValue != null}
format={field.isAlphaHidden ? 'hex' : 'hex8'}
isAlphaHidden={field.isAlphaHidden ?? false}
onChange={onChange}
Expand Down
13 changes: 10 additions & 3 deletions src/renderer/react/settings/ModSettingsNumberField.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Mod } from 'bridge/BridgeAPI';
import type { ModConfigFieldNumber } from 'bridge/ModConfig';
import { parseBinding } from 'renderer/react/BindingsParser';
import { ChangeEvent, useCallback, useEffect, useState } from 'react';
import { TextField } from '@mui/material';

Expand Down Expand Up @@ -27,8 +28,15 @@ export default function ModSettingsNumberField({
mod,
onChange: onChangeFromProps,
}: Props): JSX.Element {
const overrideValue =
field.overrideValue == null
? null
: parseBinding<number | null>(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(
Expand Down Expand Up @@ -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);
},
Expand All @@ -80,14 +86,15 @@ export default function ModSettingsNumberField({

return (
<TextField
disabled={overrideValue != null}
inputProps={{
inputMode: 'numeric',
pattern: PATTERN,
}}
onBlur={onBlur}
onChange={onChange}
onFocus={onFocus}
value={valueString}
value={overrideValue != null ? String(overrideValue) : valueString}
variant="outlined"
/>
);
Expand Down
15 changes: 14 additions & 1 deletion src/renderer/react/settings/ModSettingsSelectField.tsx
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -15,6 +16,14 @@ export default function ModSettingsSelectField({
mod,
onChange: onChangeFromProps,
}: Props): JSX.Element {
const overrideValue =
field.overrideValue == null
? null
: parseBinding<ModConfigSingleValue | null>(
field.overrideValue,
mod.config,
) ?? null;

const value = mod.config[field.id] as string;

const onChange = useCallback(
Expand All @@ -28,7 +37,11 @@ export default function ModSettingsSelectField({
);

return (
<Select onChange={onChange} value={value}>
<Select
disabled={overrideValue != null}
onChange={onChange}
value={overrideValue ?? value}
>
{field.options.map((option) => (
<MenuItem
key={option.label}
Expand Down
18 changes: 15 additions & 3 deletions src/renderer/react/settings/ModSettingsTextField.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
import type { Mod } from 'bridge/BridgeAPI';
import type { ModConfigFieldText } from 'bridge/ModConfig';
import type { ModConfigSingleValue } from 'bridge/ModConfigValue';
import { parseBinding } from 'renderer/react/BindingsParser';
import { ChangeEvent, useCallback } from 'react';
import { TextField } from '@mui/material';

type Props = {
field: ModConfigFieldText;
mod: Mod;
onChange: (fieldID: string, value: ModConfigSingleValue) => unknown;
onChange: (fieldID: string, value: string) => unknown;
};

export default function ModSettingsTextField({
field,
mod,
onChange: onChangeFromProps,
}: Props): JSX.Element {
const overrideValue =
field.overrideValue == null
? null
: parseBinding<string | null>(field.overrideValue, mod.config) ?? null;

const value = mod.config[field.id] as string;

const onChange = useCallback(
Expand All @@ -24,5 +29,12 @@ export default function ModSettingsTextField({
[field, onChangeFromProps],
);

return <TextField onChange={onChange} value={value} variant="outlined" />;
return (
<TextField
disabled={overrideValue != null}
onChange={onChange}
value={overrideValue ?? value}
variant="outlined"
/>
);
}

0 comments on commit 8d2365d

Please sign in to comment.