feat(control): инструмент «Маркер» на области видимости
Добавлена кисть маркера на пульте и презентации с настройками в панели эффектов; штрихи привязаны к жёлтой рамке, ластик и очистка работают с отдельным сессионным слоем. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -7,8 +7,10 @@ import { USERS_BRANCH_FEATURES_ENABLED } from '../../shared/features/usersBranch
|
||||
import { ExplosionVideoOverlay } from './effects/ExplosionVideoOverlay';
|
||||
import { PixiEffectsOverlay } from './effects/PxiEffectsOverlay';
|
||||
import { SceneDarknessOverlay } from './effects/SceneDarknessOverlay';
|
||||
import { SceneMarkerOverlay } from './effects/SceneMarkerOverlay';
|
||||
import { useEffectsState } from './effects/useEffectsState';
|
||||
import { useSceneDarknessState } from './effects/useSceneDarknessState';
|
||||
import { useSceneMarkerState } from './effects/useSceneMarkerState';
|
||||
import { DEFAULT_MATERIALS_OVERLAY_LAYOUT, DEFAULT_NPCS_OVERLAY_LAYOUT, defaultLegendLayoutForMaterial } from '../../shared/types';
|
||||
import { SceneGridOverlay } from './grid/SceneGridOverlay';
|
||||
import { MaterialLegendPanel } from './materials/MaterialLegendPanel';
|
||||
@@ -57,6 +59,7 @@ export function PresentationView({
|
||||
}: PresentationViewProps) {
|
||||
const [fxState] = useEffectsState();
|
||||
const [sdState] = useSceneDarknessState();
|
||||
const [markerState] = useSceneMarkerState();
|
||||
const [sceneTraps] = useSceneTrapsState();
|
||||
const [sceneView] = useSceneViewState();
|
||||
const [materialsOverlay] = useMaterialsOverlayState();
|
||||
@@ -70,9 +73,30 @@ export function PresentationView({
|
||||
const [tokenPathSession, tokenPathApi] = useTokenPathSession();
|
||||
const [vp] = useVideoPlaybackState();
|
||||
const videoElRef = useRef<HTMLVideoElement | null>(null);
|
||||
const rootRef = useRef<HTMLDivElement | null>(null);
|
||||
const [contentRect, setContentRect] = React.useState<{ x: number; y: number; w: number; h: number } | null>(
|
||||
null,
|
||||
);
|
||||
const [markerViewport, setMarkerViewport] = React.useState<{
|
||||
x: number;
|
||||
y: number;
|
||||
w: number;
|
||||
h: number;
|
||||
} | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const el = rootRef.current;
|
||||
if (!el) return;
|
||||
const sync = () => {
|
||||
const w = Math.max(1, el.clientWidth);
|
||||
const h = Math.max(1, el.clientHeight);
|
||||
setMarkerViewport({ x: 0, y: 0, w, h });
|
||||
};
|
||||
sync();
|
||||
const ro = new ResizeObserver(sync);
|
||||
ro.observe(el);
|
||||
return () => ro.disconnect();
|
||||
}, []);
|
||||
const scene =
|
||||
session?.project && session.currentSceneId ? session.project.scenes[session.currentSceneId] : undefined;
|
||||
const project = session?.project;
|
||||
@@ -181,7 +205,7 @@ export function PresentationView({
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<div ref={rootRef} className={styles.root}>
|
||||
{shownImageUrl && scene?.previewAssetType === 'image' ? (
|
||||
<div className={styles.fill}>
|
||||
<RotatedImage
|
||||
@@ -285,6 +309,9 @@ export function PresentationView({
|
||||
contentRect ? (
|
||||
<SceneDarknessOverlay state={sdState} overlayAlpha={1} viewport={contentRect} style={{ zIndex: 30 }} />
|
||||
) : null}
|
||||
{markerViewport ? (
|
||||
<SceneMarkerOverlay state={markerState} viewport={markerViewport} style={{ zIndex: 48 }} />
|
||||
) : null}
|
||||
<SceneOverlayHost
|
||||
active={activeMaterialItems.length > 0 || activeNpcItems.length > 0}
|
||||
legendLayer={
|
||||
|
||||
Reference in New Issue
Block a user