import { useId } from "react"; import type { CurveChannel, CurvePoint } from "../control-types"; import { ChannelTabs as SharedChannelTabs, channelMeta as sharedChannelMeta, } from "../channel-tabs"; import { type CurveInterpolation, curveGraphMax, curveGraphSize, curveGridStops, curveInset, defaultCurveInterpolation, getCurvePath, mapPointToSvg, normalizeCurvePoints, } from "./curve-geometry"; export const curveChannels = [ "RGB", "R", "G", "B", ] as const satisfies readonly CurveChannel[]; export const singleCurveChannels = ["RGB"] as const satisfies readonly CurveChannel[]; export const channelMeta = sharedChannelMeta; const curveGraphRadius = 6; const curvePointHandleViewportSize = 20; const curvePointHandleViewportRadius = curvePointHandleViewportSize / 2; export function ChannelTabs({ name, onValueChange, value, }: { name: string; onValueChange: (value: CurveChannel) => void; value: CurveChannel; }): React.JSX.Element { return ( ); } export function CurveGraph({ activeChannel, activePoints, ariaLabel = "Color curves editor", channels = curveChannels, draggingPointIndex, graphRef, interpolation = defaultCurveInterpolation, onBackgroundPointerDown, onPointDoubleClick, onPointKeyDown, onPointPointerDown, onPointPointerUp, points, selectedPointIndex, }: CurveGraphProps): React.JSX.Element { const clipPathId = `curve-graph-${useId().replaceAll(":", "")}`; const showReferenceLine = shouldShowReferenceLine(points, channels); return ( ); } type CurveGraphProps = { activeChannel: CurveChannel; activePoints: readonly CurvePoint[]; ariaLabel?: string; channels?: readonly CurveChannel[]; draggingPointIndex: number | null; graphRef: React.RefObject; interpolation?: CurveInterpolation; onBackgroundPointerDown: React.PointerEventHandler; onPointDoubleClick: ( index: number, event: React.MouseEvent, ) => void; onPointKeyDown: ( index: number, event: React.KeyboardEvent, ) => void; onPointPointerDown: ( index: number, event: React.PointerEvent, ) => void; onPointPointerUp: ( index: number, event: React.PointerEvent, ) => void; points: Record; selectedPointIndex: number | null; }; function shouldShowReferenceLine( points: Record, channels: readonly CurveChannel[] = curveChannels, ): boolean { return channels.every((channel) => !isIdentityCurve(points[channel])); } function isIdentityCurve(points: readonly CurvePoint[]): boolean { const normalizedPoints = normalizeCurvePoints(points); return ( normalizedPoints.length > 0 && normalizedPoints.every((point) => Math.abs(point.x - point.y) < 0.0001) ); } function CurveGrid({ clipPathId, showReferenceLine, }: { clipPathId: string; showReferenceLine: boolean; }): React.JSX.Element { return ( {curveGridStops.map((stop) => { const position = curveInset + stop * curveGraphSize; return ( ); })} {showReferenceLine ? ( ) : null} ); } function CurvePaths({ activeChannel, channels, clipPathId, interpolation, points, }: { activeChannel: CurveChannel; channels: readonly CurveChannel[]; clipPathId: string; interpolation: CurveInterpolation; points: Record; }): React.JSX.Element { return ( {channels.map((channel) => ( ))} ); } function CurvePointHandles({ activeChannel, activePoints, draggingPointIndex, onPointDoubleClick, onPointKeyDown, onPointPointerDown, onPointPointerUp, selectedPointIndex, }: { activeChannel: CurveChannel; activePoints: readonly CurvePoint[]; draggingPointIndex: number | null; onPointDoubleClick: ( index: number, event: React.MouseEvent, ) => void; onPointKeyDown: ( index: number, event: React.KeyboardEvent, ) => void; onPointPointerDown: ( index: number, event: React.PointerEvent, ) => void; onPointPointerUp: ( index: number, event: React.PointerEvent, ) => void; selectedPointIndex: number | null; }): React.JSX.Element { const handlesVisible = selectedPointIndex !== null || draggingPointIndex !== null; return ( {activePoints.map((point, index) => ( ))} ); } function CurvePointHandle({ activeChannel, dragging, index, onPointDoubleClick, onPointKeyDown, onPointPointerDown, onPointPointerUp, point, selected, }: { activeChannel: CurveChannel; dragging: boolean; index: number; onPointDoubleClick: ( index: number, event: React.MouseEvent, ) => void; onPointKeyDown: ( index: number, event: React.KeyboardEvent, ) => void; onPointPointerDown: ( index: number, event: React.PointerEvent, ) => void; onPointPointerUp: ( index: number, event: React.PointerEvent, ) => void; point: CurvePoint; selected: boolean; }): React.JSX.Element { const [pointX, pointY] = mapPointToSvg(point); const color = channelMeta[activeChannel].color; return ( onPointDoubleClick(index, event)} onKeyDown={(event) => onPointKeyDown(index, event)} onPointerDown={(event) => onPointPointerDown(index, event)} onPointerUp={(event) => onPointPointerUp(index, event)} style={{ borderColor: selected ? "var(--foreground)" : color }} type="button" /> ); }