import React, { useEffect, useRef } from 'react'; import type { SceneMarkerBrushShape, SceneMarkerDraft, SceneMarkerState, SceneMarkerStroke, } from '../../../shared/types'; import { normalizeSceneMarkerBrushShape } from '../../../shared/types/sceneMarker'; export type SceneMarkerOverlayProps = { state: SceneMarkerState | null; viewport?: { x: number; y: number; w: number; h: number }; style?: React.CSSProperties; }; type MarkerStrokeLike = SceneMarkerStroke | SceneMarkerDraft; function stampSquare( ctx: CanvasRenderingContext2D, x: number, y: number, side: number, ): void { ctx.fillRect(x - side / 2, y - side / 2, side, side); } function drawSquareMarkerStroke( ctx: CanvasRenderingContext2D, stroke: MarkerStrokeLike, w: number, h: number, ): void { const pts = stroke.points; if (pts.length === 0) return; const side = stroke.radiusN * Math.min(w, h) * 2; for (const p of pts) { stampSquare(ctx, p.x * w, p.y * h, side); } } function drawCircleMarkerStroke( ctx: CanvasRenderingContext2D, stroke: MarkerStrokeLike, w: number, h: number, ): void { const pts = stroke.points; if (pts.length === 0) return; const r = stroke.radiusN * Math.min(w, h); if (pts.length === 1) { const p = pts[0]; if (!p) return; ctx.beginPath(); ctx.arc(p.x * w, p.y * h, r, 0, Math.PI * 2); ctx.fill(); return; } ctx.lineCap = 'round'; ctx.lineJoin = 'round'; ctx.lineWidth = r * 2; ctx.beginPath(); const first = pts[0]; if (!first) return; ctx.moveTo(first.x * w, first.y * h); for (let i = 1; i < pts.length; i += 1) { const p = pts[i]; if (!p) continue; ctx.lineTo(p.x * w, p.y * h); } ctx.stroke(); } function drawMarkerStroke( ctx: CanvasRenderingContext2D, stroke: MarkerStrokeLike, w: number, h: number, ): void { const shape: SceneMarkerBrushShape = normalizeSceneMarkerBrushShape(stroke.shape); ctx.globalCompositeOperation = 'source-over'; ctx.fillStyle = stroke.color; ctx.strokeStyle = stroke.color; if (shape === 'square') { drawSquareMarkerStroke(ctx, stroke, w, h); return; } drawCircleMarkerStroke(ctx, stroke, w, h); } export function SceneMarkerOverlay({ state, viewport, style }: SceneMarkerOverlayProps) { const canvasRef = useRef(null); useEffect(() => { const canvas = canvasRef.current; if (!canvas || !state || !viewport) return; const w = Math.max(1, Math.round(viewport.w)); const h = Math.max(1, Math.round(viewport.h)); if (canvas.width !== w) canvas.width = w; if (canvas.height !== h) canvas.height = h; const ctx = canvas.getContext('2d'); if (!ctx) return; ctx.clearRect(0, 0, w, h); for (const stroke of state.strokes) { drawMarkerStroke(ctx, stroke, w, h); } if (state.draft && state.draft.points.length > 0) { drawMarkerStroke(ctx, state.draft, w, h); } }, [state, viewport]); if (!state || !viewport) return null; return ( ); }