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 26203c35a..6bd4c674a 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, @@ -40,12 +41,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); @@ -58,9 +88,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()); @@ -146,25 +175,37 @@ 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; - fitToCanvas(); - }, [activeImageId, fitToCanvas, logicalSize]); + const element = viewportElementRef.current; + const canvasSize = { + width: element?.clientWidth || canvasSizeRef.current.width, + height: element?.clientHeight || canvasSizeRef.current.height, + }; + setViewport( + fitEditorViewport( + { x: 0, y: 0, width: logicalSize.width, height: logicalSize.height }, + canvasSize, + ), + ); + }, [ + activeImageId, + activePreviewUrl, + canvasSize.height, + canvasSize.width, + logicalSize, + setViewport, + ]); const scaleViewportFromCenter = useCallback( (nextScale: number) => { @@ -243,15 +284,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, @@ -385,45 +426,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(); } }} > - +
) : ( @@ -518,7 +530,7 @@ export function PreviewWorkspace({ /> ) : null}
{ if ( event.target instanceof Element && @@ -527,6 +539,7 @@ export function PreviewWorkspace({ event.preventDefault(); } }} + onDragStart={(event) => event.preventDefault()} >
)} -
); }