import React, { useEffect, useRef } from 'react'; import type { SceneDarknessRevealStroke, SceneDarknessState, SceneDarknessStrokeMode, } from '../../../shared/types'; import { normalizeSceneDarknessStrokeMode } from '../../../shared/types/sceneDarkness'; 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 drawDarknessStroke( ctx: CanvasRenderingContext2D, stroke: SceneDarknessRevealStroke | { points: { x: number; y: number }[]; radiusN: number; mode?: SceneDarknessStrokeMode }, w: number, h: number, ): void { const pts = stroke.points; if (pts.length === 0) return; const mode = normalizeSceneDarknessStrokeMode(stroke.mode); ctx.globalCompositeOperation = mode === 'cover' ? 'source-over' : 'destination-out'; ctx.fillStyle = '#000000'; ctx.strokeStyle = '#000000'; 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++) { 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); for (const stroke of state.strokes) { drawDarknessStroke(ctx, stroke, w, h); } if (state.draft && state.draft.points.length > 0) { drawDarknessStroke(ctx, state.draft, w, h); } }, [state, viewport]); if (!state?.enabled || !viewport) return null; return ( ); }