From 184d88dbb8bf259060b7e6dc884aaf2a4b315f87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Mon, 14 Sep 2026 18:56:03 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=A4=9A=E6=A0=91=E9=A2=84?= =?UTF-8?q?=E8=A7=88=E5=8F=97=E5=B7=A6=E4=BE=A7=E9=80=89=E4=B8=AD=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E9=99=90=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 移除 activeImageId 对预览拖动和缩放手势的门控 按树 wrapper 提供几何上下文并允许任意树操作 保留 activeImageId 仅用于左侧图片与图片级检查 --- .../components/preview/PreviewWorkspace.tsx | 69 +++++---------- .../components/preview/UiTreeRenderer.tsx | 6 +- .../preview/useNodeTransformInteraction.ts | 83 ++++++++++--------- 3 files changed, 69 insertions(+), 89 deletions(-) diff --git a/apps/ai-game-creator-shell/src/view/ui-editor/components/preview/PreviewWorkspace.tsx b/apps/ai-game-creator-shell/src/view/ui-editor/components/preview/PreviewWorkspace.tsx index 5527e7687..c9cc82224 100644 --- a/apps/ai-game-creator-shell/src/view/ui-editor/components/preview/PreviewWorkspace.tsx +++ b/apps/ai-game-creator-shell/src/view/ui-editor/components/preview/PreviewWorkspace.tsx @@ -30,6 +30,7 @@ import { import { findNodePageContext } from '../../../../features/ui-editor/nodeTransformGeometry'; import type { Node } from '../../../../features/ui-editor/types/Node'; +import type { Node as UiNode } from '../../../../features/ui-editor/types/Node'; import type { NodeId } from '../../../../features/ui-editor/types/NodeId'; import type { UiEditorCanvasProjection } from '../../useUiEditorPage'; import { UiNodeContextMenu } from '../UiNodeContextMenu'; @@ -68,8 +69,7 @@ export function PreviewWorkspace({ showOriginImage: boolean; showComponent: boolean; }) { - const { activeImage, activeImageId, previewUrls, uiTrees, images } = canvas; - const tree = canvas.tree ?? null; + const { previewUrls, uiTrees, images } = canvas; const viewportElementRef = useRef(null); const viewportRef = useRef({ x: 0, y: 0, scale: 0.5 }); const handledFocusRequestIdRef = useRef(null); @@ -116,34 +116,6 @@ export function PreviewWorkspace({ }, [], ); - useEffect(() => { - setPreviewTransforms(new Map()); - }, [activeImageId]); - const activeImagePixelWidth = activeImage?.pixel_size[0]; - const activeImagePixelHeight = activeImage?.pixel_size[1]; - const activeImagePixelsPerUnit = activeImage?.pixels_per_unit; - const logicalSize = useMemo(() => { - if ( - activeImagePixelWidth === undefined || - activeImagePixelHeight === undefined || - activeImagePixelsPerUnit === undefined - ) { - return null; - } - const ppu = activeImagePixelsPerUnit; - if (!Number.isFinite(ppu) || ppu <= 0) return null; - const width = activeImagePixelWidth / ppu; - const height = activeImagePixelHeight / ppu; - if ( - !Number.isFinite(width) || - !Number.isFinite(height) || - width <= 0 || - height <= 0 - ) { - return null; - } - return { width, height }; - }, [activeImagePixelHeight, activeImagePixelsPerUnit, activeImagePixelWidth]); const treeLayouts = useMemo( () => uiTrees.map((item) => { @@ -176,13 +148,22 @@ export function PreviewWorkspace({ ); return { x: minX, y: minY, width: maxX - minX, height: maxY - minY }; }, [treeLayouts]); + const logicalSizes = useMemo( + () => + new Map( + treeLayouts.map( + ({ tree: item, width, height }) => + [item.src_ui_design, { width, height }] as const, + ), + ), + [treeLayouts], + ); const nodeInteractions = useNodeTransformInteraction({ - activeImageId, canvas, - logicalSize, + trees: uiTrees, + logicalSizes, spaceHeld, - tree, keepChildrenUnchanged: canvas.keepChildrenUnchanged, viewportRef, onPreviewTransform: updatePreviewTransform, @@ -285,15 +266,12 @@ export function PreviewWorkspace({ useEffect(() => { const request = canvas.focusRequest; - if ( - !request || - request.requestId === handledFocusRequestIdRef.current || - request.treeId !== activeImageId || - !tree || - !logicalSize - ) { + if (!request || request.requestId === handledFocusRequestIdRef.current) { return; } + const tree = uiTrees.find((item) => item.src_ui_design === request.treeId); + const logicalSize = logicalSizes.get(request.treeId); + if (!tree || !logicalSize) return; const context = findNodePageContext(tree.root, request.nodeId, { min: [0, 0], max: [logicalSize.width, logicalSize.height], @@ -317,13 +295,12 @@ export function PreviewWorkspace({ ), ); }, [ - activeImageId, canvasSize.height, canvasSize.width, canvas.focusRequest, - logicalSize, + logicalSizes, setViewport, - tree, + uiTrees, ]); useEffect(() => { @@ -350,7 +327,7 @@ export function PreviewWorkspace({ handlePreviewZoomKeyDown( event, { - hasZoomableViewport: logicalSize !== null, + hasZoomableViewport: allBounds !== null, isFocused: previewFocusedRef.current, isHovered: previewHoveredRef.current, usesMetaModifier, @@ -374,7 +351,7 @@ export function PreviewWorkspace({ window.removeEventListener('keyup', onKeyUp); window.removeEventListener('blur', onWindowBlur); }; - }, [logicalSize, resetToActualSize, zoomIn, zoomOut]); + }, [allBounds, resetToActualSize, zoomIn, zoomOut]); const handlePointerDown = (event: ReactPointerEvent) => { if (event.button === 0 && !isPreviewZoomInteractiveTarget(event.target)) { @@ -479,7 +456,7 @@ export function PreviewWorkspace({ const handleNodeContextMenu = ( event: ReactMouseEvent, - node: NonNullable['root'], + node: UiNode, isPageRoot: boolean, treeId?: string, ) => { diff --git a/apps/ai-game-creator-shell/src/view/ui-editor/components/preview/UiTreeRenderer.tsx b/apps/ai-game-creator-shell/src/view/ui-editor/components/preview/UiTreeRenderer.tsx index cc068c8d3..f92a19aad 100644 --- a/apps/ai-game-creator-shell/src/view/ui-editor/components/preview/UiTreeRenderer.tsx +++ b/apps/ai-game-creator-shell/src/view/ui-editor/components/preview/UiTreeRenderer.tsx @@ -23,6 +23,7 @@ export type { ResizeHandle } from '../../../../features/ui-editor/nodeTransformG type NodePointerDown = ( event: ReactPointerEvent, node: UiNode, + treeId?: string, ) => void; type UiTreeRendererProps = { @@ -50,6 +51,7 @@ type UiTreeRendererProps = { event: ReactPointerEvent, node: UiNode, handle: ResizeHandle, + treeId?: string, ) => void; onNodeResizePointerMove: (event: ReactPointerEvent) => void; onNodeResizePointerUp: (event: ReactPointerEvent) => void; @@ -178,7 +180,7 @@ function RenderNode({ }} onPointerDown={ receivesPointerGesture - ? (event) => onNodePointerDown(event, node) + ? (event) => onNodePointerDown(event, node, treeId) : undefined } onPointerMove={receivesPointerGesture ? onNodePointerMove : undefined} @@ -255,7 +257,7 @@ function RenderNode({ touchAction: 'none', }} onPointerDown={(event) => - onNodeResizePointerDown(event, node, handle.id) + onNodeResizePointerDown(event, node, handle.id, treeId) } onPointerMove={onNodeResizePointerMove} onPointerUp={onNodeResizePointerUp} diff --git a/apps/ai-game-creator-shell/src/view/ui-editor/components/preview/useNodeTransformInteraction.ts b/apps/ai-game-creator-shell/src/view/ui-editor/components/preview/useNodeTransformInteraction.ts index 9b79de2d7..37ad66c0e 100644 --- a/apps/ai-game-creator-shell/src/view/ui-editor/components/preview/useNodeTransformInteraction.ts +++ b/apps/ai-game-creator-shell/src/view/ui-editor/components/preview/useNodeTransformInteraction.ts @@ -176,22 +176,20 @@ function emitPreviewTransforms( } export function useNodeTransformInteraction({ - activeImageId, canvas, - logicalSize, + trees, + logicalSizes, spaceHeld, - tree, keepChildrenUnchanged, viewportRef, onPreviewTransform, previewRef, selectedNodeId, }: { - activeImageId: UiEditorCanvasProjection['activeImageId']; canvas: Pick; - logicalSize: { width: number; height: number } | null; + trees: readonly UITree[]; + logicalSizes: ReadonlyMap; spaceHeld: boolean; - tree: UITree | null; keepChildrenUnchanged: boolean; viewportRef: RefObject; onPreviewTransform?: ( @@ -229,36 +227,30 @@ export function useNodeTransformInteraction({ return () => window.removeEventListener('blur', cancelGesture); }, [cancelGesture]); - useEffect(() => { - const gesture = activeGestureRef.current; - if ( - gesture && - (gesture.treeId !== activeImageId || - tree?.src_ui_design !== gesture.treeId) - ) { - cancelGesture(); - } - }, [activeImageId, cancelGesture, tree]); - const acceptsGestureEvent = useCallback( (event: ReactPointerEvent) => { const gesture = activeGestureRef.current; - return ( - gesture !== null && - gesture.pointerId === event.pointerId && - gesture.treeId === activeImageId - ); + return gesture !== null && gesture.pointerId === event.pointerId; }, - [activeImageId], + [], ); const onNodePointerDown = useCallback( - (event: ReactPointerEvent, node: UiNode) => { + ( + event: ReactPointerEvent, + node: UiNode, + treeId?: string, + ) => { + const tree = treeId + ? (trees.find((candidate) => candidate.src_ui_design === treeId) ?? + null) + : null; + const logicalSize = treeId ? (logicalSizes.get(treeId) ?? null) : null; if ( activeGestureRef.current !== null || event.button !== 0 || spaceHeld || - !activeImageId || + !treeId || node.id === tree?.root.id || !isFiniteTransform(node.layout.transform) ) { @@ -275,7 +267,7 @@ export function useNodeTransformInteraction({ event.preventDefault(); activeGestureRef.current = { kind: 'drag', - treeId: activeImageId, + treeId, nodeId: dragNode.id, pointerId: event.pointerId, target: event.currentTarget, @@ -292,14 +284,7 @@ export function useNodeTransformInteraction({ ), }; }, - [ - activeImageId, - keepChildrenUnchanged, - logicalSize, - spaceHeld, - selectedNodeId, - tree, - ], + [keepChildrenUnchanged, logicalSizes, spaceHeld, selectedNodeId, trees], ); const onNodePointerMove = useCallback( @@ -309,6 +294,11 @@ export function useNodeTransformInteraction({ return; } event.stopPropagation(); + const tree = + trees.find((candidate) => candidate.src_ui_design === gesture.treeId) ?? + null; + const logicalSize = logicalSizes.get(gesture.treeId) ?? null; + if (!tree || !logicalSize) return; if (!gesture.hasMoved && !passedDragThreshold(gesture, event)) return; const scale = viewportRef.current?.scale; if (!Number.isFinite(scale) || scale <= 0) { @@ -346,8 +336,8 @@ export function useNodeTransformInteraction({ acceptsGestureEvent, cancelGesture, keepChildrenUnchanged, - logicalSize, - tree, + logicalSizes, + trees, viewportRef, ], ); @@ -389,12 +379,18 @@ export function useNodeTransformInteraction({ event: ReactPointerEvent, node: UiNode, handle: ResizeHandle, + treeId?: string, ) => { + const tree = treeId + ? (trees.find((candidate) => candidate.src_ui_design === treeId) ?? + null) + : null; + const logicalSize = treeId ? (logicalSizes.get(treeId) ?? null) : null; if ( activeGestureRef.current !== null || event.button !== 0 || spaceHeld || - !activeImageId || + !treeId || node.id === tree?.root.id || !tree || !logicalSize || @@ -424,7 +420,7 @@ export function useNodeTransformInteraction({ suppressNextNodeClickRef.current = false; activeGestureRef.current = { kind: 'resize', - treeId: activeImageId, + treeId, nodeId: node.id, pointerId: event.pointerId, target: event.currentTarget, @@ -445,7 +441,7 @@ export function useNodeTransformInteraction({ ), }; }, - [activeImageId, keepChildrenUnchanged, logicalSize, spaceHeld, tree], + [keepChildrenUnchanged, logicalSizes, spaceHeld, trees], ); const onNodeResizePointerMove = useCallback( @@ -459,6 +455,11 @@ export function useNodeTransformInteraction({ return; } event.stopPropagation(); + const tree = + trees.find((candidate) => candidate.src_ui_design === gesture.treeId) ?? + null; + const logicalSize = logicalSizes.get(gesture.treeId) ?? null; + if (!tree || !logicalSize) return; if (!gesture.hasMoved && !passedDragThreshold(gesture, event)) return; const scale = viewportRef.current?.scale; if (!Number.isFinite(scale) || scale <= 0) { @@ -517,8 +518,8 @@ export function useNodeTransformInteraction({ acceptsGestureEvent, cancelGesture, keepChildrenUnchanged, - logicalSize, - tree, + logicalSizes, + trees, viewportRef, ], );