From d953fb9838e6fdf4b20f21cfbfa15704c2d177d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Sat, 12 Sep 2026 20:47:06 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20UI=20=E9=A2=84=E8=A7=88?= =?UTF-8?q?=E7=94=BB=E5=B8=83=E4=BA=A4=E4=BA=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为无限视口添加跟随平移和缩放的点阵背景 恢复画布快捷缩放与初始适配比例控制 保留节点平移选择并屏蔽浏览器原生拖拽选择 --- .../components/preview/PreviewWorkspace.tsx | 144 ++++++++---------- 1 file changed, 60 insertions(+), 84 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 8ec753d09..737c0dbc9 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 @@ -1,4 +1,5 @@ import { + CANVAS_WORLD_SIZE, CANVAS_ZOOM_IN_FACTOR, CANVAS_ZOOM_OUT_FACTOR, canvasDisplayScaleToViewportScale, @@ -39,12 +40,41 @@ import { import { type UiEditorRenderMode, UiTreeRenderer } from './UiTreeRenderer'; import { useNodeTransformInteraction } from './useNodeTransformInteraction'; +/** 画布初始适配比例;调小可为浮动面板预留更多可视空间。 */ +export const UI_EDITOR_FIT_DEGREE = 0.7; + +function fitEditorViewport( + bounds: { x: number; y: number; width: number; height: number }, + canvasSize: { width: number; height: number }, +): CanvasViewport { + const fit = fitViewportToBounds({ bounds, canvasSize }); + const scale = fit.scale * UI_EDITOR_FIT_DEGREE; + return { + scale, + x: canvasSize.width / 2 - (bounds.x + bounds.width / 2) * scale, + y: canvasSize.height / 2 - (bounds.y + bounds.height / 2) * scale, + }; +} + +export type { UiEditorRenderMode } from './UiTreeRenderer'; + export function PreviewWorkspace({ canvas, + renderMode: controlledRenderMode, + onRenderModeChange: _onRenderModeChange, + showFrame: controlledShowFrame, + onShowFrameChange: _onShowFrameChange, }: { canvas: UiEditorCanvasProjection; + renderMode?: UiEditorRenderMode; + onRenderModeChange?: (mode: UiEditorRenderMode) => void; + showFrame?: boolean; + onShowFrameChange?: (show: boolean) => void; }) { const { activeImage, activeImageId, previewUrls } = canvas; + const activePreviewUrl = activeImageId + ? previewUrls[activeImageId] + : undefined; const viewportElementRef = useRef(null); const viewportRef = useRef({ x: 0, y: 0, scale: 0.5 }); const handledFocusRequestIdRef = useRef(null); @@ -57,9 +87,8 @@ export function PreviewWorkspace({ const [canvasSize, setCanvasSize] = useState({ width: 900, height: 640 }); const canvasSizeRef = useRef(canvasSize); const [spaceHeld, setSpaceHeld] = useState(false); - const [renderMode, setRenderMode] = - useState('editor-overlay'); - const [showFrame, setShowFrame] = useState(false); + const renderMode = controlledRenderMode ?? 'editor-overlay'; + const showFrame = controlledShowFrame ?? false; const [previewTransforms, setPreviewTransforms] = useState< ReadonlyMap >(new Map()); @@ -145,40 +174,32 @@ export function PreviewWorkspace({ if (!logicalSize) return; const element = viewportElementRef.current; setViewport( - fitViewportToBounds({ - bounds: { - x: 0, - y: 0, - width: logicalSize.width, - height: logicalSize.height, - }, - canvasSize: { + fitEditorViewport( + { x: 0, y: 0, width: logicalSize.width, height: logicalSize.height }, + { width: element?.clientWidth || canvasSizeRef.current.width, height: element?.clientHeight || canvasSizeRef.current.height, }, - }), + ), ); }, [logicalSize, setViewport]); useEffect(() => { if (!activeImageId || !logicalSize) return; const element = viewportElementRef.current; + const canvasSize = { + width: element?.clientWidth || canvasSizeRef.current.width, + height: element?.clientHeight || canvasSizeRef.current.height, + }; setViewport( - fitViewportToBounds({ - bounds: { - x: 0, - y: 0, - width: logicalSize.width, - height: logicalSize.height, - }, - canvasSize: { - width: element?.clientWidth || canvasSizeRef.current.width, - height: element?.clientHeight || canvasSizeRef.current.height, - }, - }), + fitEditorViewport( + { x: 0, y: 0, width: logicalSize.width, height: logicalSize.height }, + canvasSize, + ), ); }, [ activeImageId, + activePreviewUrl, canvasSize.height, canvasSize.width, logicalSize, @@ -262,15 +283,15 @@ export function PreviewWorkspace({ }; handledFocusRequestIdRef.current = request.requestId; setViewport( - fitViewportToBounds({ - bounds: { + fitEditorViewport( + { x: context.rect.min[0], y: context.rect.min[1], width: context.rect.max[0] - context.rect.min[0], height: context.rect.max[1] - context.rect.min[1], }, - canvasSize: size, - }), + size, + ), ); }, [ activeImageId, @@ -404,45 +425,18 @@ export function PreviewWorkspace({ }, [renderMode]); return ( -
+
{activeImage && logicalSize && (renderMode === 'final-preview' || previewUrls[activeImageId ?? '']) ? (
-
- {renderMode === 'final-preview' ? ( - - ) : null} -
- - -
-
event.preventDefault()} onClick={(event) => { if (!(event.target as Element).closest('[data-node-id]')) { canvas.clearNodeSelection(); } }} > - +
) : ( @@ -537,7 +529,7 @@ export function PreviewWorkspace({ /> ) : null}
{ if ( event.target instanceof Element && @@ -546,6 +538,7 @@ export function PreviewWorkspace({ event.preventDefault(); } }} + onDragStart={(event) => event.preventDefault()} >
)} -
); }