import React, { useEffect, useRef } from 'react'; import type { SceneDarknessRevealStroke, SceneDarknessState } from '../../../shared/types'; export type SceneDarknessOverlayProps = { state: SceneDarknessState | null; viewport?: { x: number; y: number; w: number; h: number }; /** 1.0 — полностью чёрный (Presentation); 0.5 — полупрозрачный (Control). */ overlayAlpha: number; style?: React.CSSProperties; }; function drawRevealStroke( ctx: CanvasRenderingContext2D, stroke: SceneDarknessRevealStroke | { points: { x: number; y: number }[]; radiusN: number }, 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.strokeStyle = 'rgba(0,0,0,1)'; 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++) { const p = pts[i]; if (!p) continue; ctx.lineTo(p.x * w, p.y * h); } ctx.stroke(); } export function SceneDarknessOverlay({ state, viewport, overlayAlpha, style, }: SceneDarknessOverlayProps) { const canvasRef = useRef(null); useEffect(() => { const canvas = canvasRef.current; if (!canvas || !state?.enabled || !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.globalCompositeOperation = 'source-over'; ctx.fillStyle = '#000000'; ctx.fillRect(0, 0, w, h); ctx.globalCompositeOperation = 'destination-out'; for (const stroke of state.strokes) { drawRevealStroke(ctx, stroke, w, h); } if (state.draft && state.draft.points.length > 0) { drawRevealStroke(ctx, state.draft, w, h); } }, [state, viewport]); if (!state?.enabled || !viewport) return null; return ( ); }