feat(control): история-аккордеон и форма кисти затемнения
Переименовать блок сюжетной линии в «История» и сделать его сворачиваемым. Добавить выбор круглой/квадратной формы для кистей открытия и закрытия с сохранением в штрихах и сбросом при смене инструмента или сцены без затемнения. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,11 +1,15 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
|
||||
import type {
|
||||
SceneDarknessBrushShape,
|
||||
SceneDarknessRevealStroke,
|
||||
SceneDarknessState,
|
||||
SceneDarknessStrokeMode,
|
||||
} from '../../../shared/types';
|
||||
import { normalizeSceneDarknessStrokeMode } from '../../../shared/types/sceneDarkness';
|
||||
import {
|
||||
normalizeSceneDarknessBrushShape,
|
||||
normalizeSceneDarknessStrokeMode,
|
||||
} from '../../../shared/types/sceneDarkness';
|
||||
|
||||
export type SceneDarknessOverlayProps = {
|
||||
state: SceneDarknessState | null;
|
||||
@@ -15,18 +19,44 @@ export type SceneDarknessOverlayProps = {
|
||||
style?: React.CSSProperties;
|
||||
};
|
||||
|
||||
function drawDarknessStroke(
|
||||
type DarknessStrokeLike = SceneDarknessRevealStroke | {
|
||||
points: { x: number; y: number }[];
|
||||
radiusN: number;
|
||||
mode?: SceneDarknessStrokeMode;
|
||||
shape?: SceneDarknessBrushShape;
|
||||
};
|
||||
|
||||
function stampSquare(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
stroke: SceneDarknessRevealStroke | { points: { x: number; y: number }[]; radiusN: number; mode?: SceneDarknessStrokeMode },
|
||||
x: number,
|
||||
y: number,
|
||||
side: number,
|
||||
): void {
|
||||
ctx.fillRect(x - side / 2, y - side / 2, side, side);
|
||||
}
|
||||
|
||||
function drawSquareDarknessStroke(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
stroke: DarknessStrokeLike,
|
||||
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 drawCircleDarknessStroke(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
stroke: DarknessStrokeLike,
|
||||
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];
|
||||
@@ -51,6 +81,24 @@ function drawDarknessStroke(
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
function drawDarknessStroke(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
stroke: DarknessStrokeLike,
|
||||
w: number,
|
||||
h: number,
|
||||
): void {
|
||||
const mode = normalizeSceneDarknessStrokeMode(stroke.mode);
|
||||
const shape = normalizeSceneDarknessBrushShape(stroke.shape);
|
||||
ctx.globalCompositeOperation = mode === 'cover' ? 'source-over' : 'destination-out';
|
||||
ctx.fillStyle = '#000000';
|
||||
ctx.strokeStyle = '#000000';
|
||||
if (shape === 'square') {
|
||||
drawSquareDarknessStroke(ctx, stroke, w, h);
|
||||
return;
|
||||
}
|
||||
drawCircleDarknessStroke(ctx, stroke, w, h);
|
||||
}
|
||||
|
||||
export function SceneDarknessOverlay({
|
||||
state,
|
||||
viewport,
|
||||
|
||||
Reference in New Issue
Block a user