memlnaut-nisps/docs/redesign/manifold-export/ui_kits/memlnaut/console/CurvePad.jsx

60 lines
3 KiB
React
Raw Normal View History

/* Console 2.0 CurvePad: a square response-curve plot. Click-and-vertical-drag
anywhere reshapes the curve (drag up = bulge / ease-out, down = ease-in);
`curve` is 0..1 where ~0.43 reads as linear. Mirrors the engine's applyCurve. */
function CurvePad({ curve = 0.5, onChange, size = 116 }) {
const ref = React.useRef(null);
const drag = React.useRef({ active: false, startY: 0, startC: 0 });
React.useEffect(() => {
const cv = ref.current; if (!cv) return;
const dpr = window.devicePixelRatio || 1;
cv.width = size * dpr; cv.height = size * dpr;
const ctx = cv.getContext('2d');
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
ctx.clearRect(0, 0, size, size);
// frame + grid
ctx.strokeStyle = 'rgba(255,255,255,0.06)'; ctx.lineWidth = 1;
ctx.strokeRect(0.5, 0.5, size - 1, size - 1);
for (const t of [0.25, 0.5, 0.75]) {
ctx.beginPath(); ctx.moveTo(t * size, 0); ctx.lineTo(t * size, size); ctx.stroke();
ctx.beginPath(); ctx.moveTo(0, t * size); ctx.lineTo(size, t * size); ctx.stroke();
}
// diagonal reference (linear)
ctx.strokeStyle = 'rgba(255,255,255,0.10)'; ctx.setLineDash([3, 3]);
ctx.beginPath(); ctx.moveTo(0, size); ctx.lineTo(size, 0); ctx.stroke(); ctx.setLineDash([]);
// the curve
const css = getComputedStyle(cv);
const accent = css.getPropertyValue('--accent').trim() || '#ff6a00';
const e = 0.25 + curve * 1.75;
ctx.strokeStyle = accent; ctx.lineWidth = 2; ctx.beginPath();
const pad = 3;
for (let p = 0; p <= 80; p++) {
const xv = p / 80, yv = Math.pow(xv, e);
const px = pad + xv * (size - 2 * pad), py = (size - pad) - yv * (size - 2 * pad);
p === 0 ? ctx.moveTo(px, py) : ctx.lineTo(px, py);
}
ctx.stroke();
}, [curve, size]);
const down = (e) => { e.currentTarget.setPointerCapture?.(e.pointerId); drag.current = { active: true, startY: e.clientY, startC: curve }; };
const move = (e) => {
const d = drag.current; if (!d.active) return;
const dc = (e.clientY - d.startY) / size; // drag up (negative dy) → lower c → bulge up
onChange && onChange(Math.max(0, Math.min(1, d.startC + dc)));
};
const up = (e) => { drag.current.active = false; e.currentTarget.releasePointerCapture?.(e.pointerId); };
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span style={{ fontSize: 10, color: 'var(--fg-mute)', textTransform: 'uppercase', letterSpacing: '0.08em' }}>curve</span>
<span style={{ fontSize: 10, color: 'var(--fg-dim)', fontVariantNumeric: 'tabular-nums' }}>{(0.25 + curve * 1.75).toFixed(2)}</span>
</div>
<canvas ref={ref} onPointerDown={down} onPointerMove={move} onPointerUp={up} onPointerCancel={up}
title="Drag vertically to reshape"
style={{ width: size, height: size, display: 'block', cursor: 'ns-resize', background: 'var(--bg-1)', border: '1px solid var(--line)', borderRadius: 'var(--r-1)', touchAction: 'none' }} />
</div>
);
}
window.CurvePad = CurvePad;