feat(control): инструмент «Маркер» на области видимости
Добавлена кисть маркера на пульте и презентации с настройками в панели эффектов; штрихи привязаны к жёлтой рамке, ластик и очистка работают с отдельным сессионным слоем. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
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<HTMLCanvasElement | null>(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 (
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
aria-hidden
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: viewport.x,
|
||||
top: viewport.y,
|
||||
width: viewport.w,
|
||||
height: viewport.h,
|
||||
pointerEvents: 'none',
|
||||
zIndex: 3,
|
||||
...style,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user