OSC updates and video resolution fixes
Some checks failed
CI / Native Windows Build And Tests (push) Failing after 7s
CI / React UI Build (push) Has been cancelled
CI / Windows Release Package (push) Has been cancelled

This commit is contained in:
2026-05-03 14:33:33 +10:00
parent bfc12a1aea
commit 7dc4b552a5
20 changed files with 842 additions and 124 deletions

View File

@@ -1,7 +1,64 @@
import { Copy } from "lucide-react";
import { useThrottledParameterValue } from "../hooks/useThrottledParameterValue";
import { ParameterValueDisplay } from "./ParameterValueDisplay";
export function ParameterField({ parameter, onParameterChange }) {
function ParameterHeader({ layer, parameter }) {
const layerKey = layer.shaderId || layer.shaderName || layer.id;
const oscRoute = `/VideoShaderToys/${layerKey}/${parameter.id}`;
function copyRoute() {
if (navigator.clipboard) {
navigator.clipboard.writeText(oscRoute);
}
}
return (
<div className="parameter__header">
<label>{parameter.label}</label>
<button
type="button"
className="parameter__osc"
title="Copy OSC route"
aria-label={`Copy OSC route ${oscRoute}`}
onClick={copyRoute}
>
<span>{oscRoute}</span>
<Copy size={13} strokeWidth={1.75} aria-hidden="true" />
</button>
</div>
);
}
function clamp01(value) {
return Math.max(0, Math.min(1, Number(value) || 0));
}
function colorComponentToHex(value) {
return Math.round(clamp01(value) * 255)
.toString(16)
.padStart(2, "0");
}
function colorValueToHex(value) {
const values = [...(value ?? [])];
while (values.length < 3) {
values.push(0);
}
return `#${colorComponentToHex(values[0])}${colorComponentToHex(values[1])}${colorComponentToHex(values[2])}`;
}
function hexToColorValue(hex, alpha) {
const sanitized = /^#[0-9a-fA-F]{6}$/.test(hex) ? hex.slice(1) : "000000";
return [
parseInt(sanitized.slice(0, 2), 16) / 255,
parseInt(sanitized.slice(2, 4), 16) / 255,
parseInt(sanitized.slice(4, 6), 16) / 255,
clamp01(alpha ?? 1),
];
}
export function ParameterField({ layer, parameter, onParameterChange }) {
const {
appliedValue,
beginInteraction,
@@ -12,12 +69,12 @@ export function ParameterField({ parameter, onParameterChange }) {
sendValue,
} = useThrottledParameterValue(parameter, onParameterChange);
const label = <label>{parameter.label}</label>;
const header = <ParameterHeader layer={layer} parameter={parameter} />;
if (parameter.type === "float") {
return (
<section className="parameter">
{label}
{header}
<div className="parameter__pair">
<input
type="range"
@@ -52,18 +109,17 @@ export function ParameterField({ parameter, onParameterChange }) {
);
}
if (parameter.type === "vec2" || parameter.type === "color") {
const componentCount = parameter.type === "color" ? 4 : 2;
if (parameter.type === "vec2") {
const values = [...(draftValue ?? [])];
while (values.length < componentCount) {
while (values.length < 2) {
values.push(0);
}
return (
<section className="parameter">
{label}
{header}
<div className="parameter__pair">
{Array.from({ length: componentCount }, (_, index) => (
{Array.from({ length: 2 }, (_, index) => (
<input
key={index}
type="number"
@@ -86,10 +142,50 @@ export function ParameterField({ parameter, onParameterChange }) {
);
}
if (parameter.type === "color") {
const values = [...(draftValue ?? [])];
while (values.length < 4) {
values.push(values.length === 3 ? 1 : 0);
}
return (
<section className="parameter">
{header}
<div className="parameter__color-row">
<input
type="color"
value={colorValueToHex(values)}
onFocus={beginInteraction}
onChange={(event) => sendValue(hexToColorValue(event.target.value, values[3]))}
onBlur={endInteraction}
/>
<label className="parameter__alpha">
<span>Alpha</span>
<input
type="number"
min={parameter.min?.[3] ?? 0}
max={parameter.max?.[3] ?? 1}
step={parameter.step?.[3] ?? 0.01}
value={values[3]}
onFocus={beginInteraction}
onChange={(event) => {
const next = [...values];
next[3] = Number(event.target.value);
sendValue(next);
}}
onBlur={endInteraction}
/>
</label>
</div>
<ParameterValueDisplay parameterType={parameter.type} value={appliedValue} pending={isPending} />
</section>
);
}
if (parameter.type === "bool") {
return (
<section className="parameter">
{label}
{header}
<label className="toggle toggle--field">
<input
type="checkbox"
@@ -108,7 +204,7 @@ export function ParameterField({ parameter, onParameterChange }) {
if (parameter.type === "enum") {
return (
<section className="parameter">
{label}
{header}
<select
value={draftValue}
onFocus={beginInteraction}