OSC updates and video resolution fixes
This commit is contained in:
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user