Files

42 lines
3.0 KiB
JavaScript

// One control for one module setting ({type, min, max, step, options, ui, hint, unit, default, when}). Engine settings
// and anything else that edits the module-values tree draw their rows through here.
import { composer as c } from "../../composer/composer.js";
/** A `when` clause names sibling settings on the same module that must hold a value for the row to show. */
export const whenSatisfied = (values, id, when) => !when || Object.entries(when).every(([k, v]) => (Array.isArray(v) ? v : [v]).includes((values[id] || {})[k]));
/** A block list ("2-10,29-46") held to a model's blocks 0..count-1: entries past the end are cut, a range is shortened, an entry wholly past the end is dropped. */
export function clampBlocks(text, count) {
if (!Number.isInteger(count) || count < 1) return text;
const last = count - 1, out = [];
for (const part of String(text || "").split(",").map((x) => x.trim()).filter(Boolean)) {
const m = /^(\d+)(?:\s*-\s*(\d+))?$/.exec(part);
if (!m) { out.push(part); continue; } // not a number or range: left for the module to say
const lo = Number(m[1]), hi = m[2] === undefined ? lo : Number(m[2]);
const a = Math.min(lo, hi), b = Math.min(Math.max(lo, hi), last);
if (a > last) continue;
out.push(a === b ? String(a) : `${a}-${b}`);
}
return out.join(",");
}
export function settingRow(id, name, spec, values, onChange, blocks = null) {
const label = spec.label || name;
const fits = spec.model_blocks && Number.isInteger(blocks) && blocks > 0;
const hint = (spec.hint ? spec.hint + (spec.unit ? ` (${spec.unit})` : "") : spec.unit || "") + (fits ? ` This model has blocks 0-${blocks - 1}.` : "");
const now = values[id] && values[id][name];
const value = now !== undefined ? now : spec.default;
const set = (v) => onChange(id, name, fits && typeof v === "string" ? clampBlocks(v, blocks) : v);
let control;
if (spec.type === "bool") return c.toggle.default({ label, hint, checked: Boolean(value), onChange: set }); // a toggle carries its own label and hint
if (spec.type === "int" || spec.type === "float") {
const slider = (spec.ui || "slider") === "slider" && spec.min != null && spec.max != null;
const step = spec.step ?? (spec.type === "int" ? 1 : 0.01);
control = slider ? c.slider.readout({ label, value, min: spec.min, max: spec.max, step, precision: spec.type === "int" ? 0 : 2, onCommit: set })
: c.number.md({ label, value, min: spec.min, max: spec.max, step, precision: spec.type === "int" ? 0 : undefined, onChange: set });
} else if (spec.type === "enum") control = c.select.md({ label, value, options: (spec.options || []).map((o) => ({ value: o.value, label: o.label || o.value })), onChange: set });
else if (spec.type === "color") control = c.color.swatch({ label, value: value || undefined, onChange: set });
else control = (spec.type === "multiline" ? c.textarea.md({ label, value: value || "", rows: 3, onCommit: set }) : c.input.md({ label, value: value || "", onCommit: set }));
return c.settingsRow.default({ label, hint, control });
}