Feature/editor optimization (#251)
* refactor: 编辑器/运行时架构拆分与构建系统升级 * feat(core): 层级系统重构与UI变换矩阵修复 * refactor: 移除 ecs-components 聚合包并修复跨包组件查找问题 * fix(physics): 修复跨包组件类引用问题 * feat: 统一运行时架构与浏览器运行支持 * feat(asset): 实现浏览器运行时资产加载系统 * fix: 修复文档、CodeQL安全问题和CI类型检查错误 * fix: 修复文档、CodeQL安全问题和CI类型检查错误 * fix: 修复文档、CodeQL安全问题、CI类型检查和测试错误 * test: 补齐核心模块测试用例,修复CI构建配置 * fix: 修复测试用例中的类型错误和断言问题 * fix: 修复 turbo build:npm 任务的依赖顺序问题 * fix: 修复 CI 构建错误并优化构建性能
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
import { React, useRef, useCallback, forwardRef, useState, useEffect } from '@esengine/editor-runtime';
|
||||
import { useCanvasInteraction } from '../../hooks/useCanvasInteraction';
|
||||
import { EditorConfig } from '../../types';
|
||||
import { GridBackground } from './GridBackground';
|
||||
|
||||
/**
|
||||
* 画布组件属性
|
||||
*/
|
||||
interface BehaviorTreeCanvasProps {
|
||||
/**
|
||||
* 编辑器配置
|
||||
*/
|
||||
config: EditorConfig;
|
||||
|
||||
/**
|
||||
* 子组件
|
||||
*/
|
||||
children: React.ReactNode;
|
||||
|
||||
/**
|
||||
* 画布点击事件
|
||||
*/
|
||||
onClick?: (e: React.MouseEvent) => void;
|
||||
|
||||
/**
|
||||
* 画布双击事件
|
||||
*/
|
||||
onDoubleClick?: (e: React.MouseEvent) => void;
|
||||
|
||||
/**
|
||||
* 画布右键事件
|
||||
*/
|
||||
onContextMenu?: (e: React.MouseEvent) => void;
|
||||
|
||||
/**
|
||||
* 鼠标移动事件
|
||||
*/
|
||||
onMouseMove?: (e: React.MouseEvent) => void;
|
||||
|
||||
/**
|
||||
* 鼠标按下事件
|
||||
*/
|
||||
onMouseDown?: (e: React.MouseEvent) => void;
|
||||
|
||||
/**
|
||||
* 鼠标抬起事件
|
||||
*/
|
||||
onMouseUp?: (e: React.MouseEvent) => void;
|
||||
|
||||
/**
|
||||
* 鼠标离开事件
|
||||
*/
|
||||
onMouseLeave?: (e: React.MouseEvent) => void;
|
||||
|
||||
/**
|
||||
* 拖放事件
|
||||
*/
|
||||
onDrop?: (e: React.DragEvent) => void;
|
||||
|
||||
/**
|
||||
* 拖动悬停事件
|
||||
*/
|
||||
onDragOver?: (e: React.DragEvent) => void;
|
||||
|
||||
/**
|
||||
* 拖动进入事件
|
||||
*/
|
||||
onDragEnter?: (e: React.DragEvent) => void;
|
||||
|
||||
/**
|
||||
* 拖动离开事件
|
||||
*/
|
||||
onDragLeave?: (e: React.DragEvent) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 行为树画布组件
|
||||
* 负责画布的渲染、缩放、平移等基础功能
|
||||
*/
|
||||
export const BehaviorTreeCanvas = forwardRef<HTMLDivElement, BehaviorTreeCanvasProps>(({
|
||||
config,
|
||||
children,
|
||||
onClick,
|
||||
onDoubleClick,
|
||||
onContextMenu,
|
||||
onMouseMove,
|
||||
onMouseDown,
|
||||
onMouseUp,
|
||||
onMouseLeave,
|
||||
onDrop,
|
||||
onDragOver,
|
||||
onDragEnter,
|
||||
onDragLeave
|
||||
}, forwardedRef) => {
|
||||
const internalRef = useRef<HTMLDivElement>(null);
|
||||
const canvasRef = (forwardedRef as React.RefObject<HTMLDivElement>) || internalRef;
|
||||
|
||||
const [canvasSize, setCanvasSize] = useState({ width: 0, height: 0 });
|
||||
|
||||
const {
|
||||
canvasOffset,
|
||||
canvasScale,
|
||||
isPanning,
|
||||
handleWheel,
|
||||
startPanning,
|
||||
updatePanning,
|
||||
stopPanning
|
||||
} = useCanvasInteraction();
|
||||
|
||||
// 监听画布尺寸变化
|
||||
useEffect(() => {
|
||||
const updateSize = () => {
|
||||
if (canvasRef.current) {
|
||||
const rect = canvasRef.current.getBoundingClientRect();
|
||||
setCanvasSize({
|
||||
width: rect.width,
|
||||
height: rect.height
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
updateSize();
|
||||
|
||||
const resizeObserver = new ResizeObserver(updateSize);
|
||||
if (canvasRef.current) {
|
||||
resizeObserver.observe(canvasRef.current);
|
||||
}
|
||||
|
||||
return () => {
|
||||
resizeObserver.disconnect();
|
||||
};
|
||||
}, [canvasRef]);
|
||||
|
||||
const handleMouseDown = useCallback((e: React.MouseEvent) => {
|
||||
if (e.button === 1 || (e.button === 0 && e.altKey)) {
|
||||
e.preventDefault();
|
||||
startPanning(e.clientX, e.clientY);
|
||||
}
|
||||
|
||||
onMouseDown?.(e);
|
||||
}, [startPanning, onMouseDown]);
|
||||
|
||||
const handleMouseMove = useCallback((e: React.MouseEvent) => {
|
||||
if (isPanning) {
|
||||
updatePanning(e.clientX, e.clientY);
|
||||
}
|
||||
|
||||
onMouseMove?.(e);
|
||||
}, [isPanning, updatePanning, onMouseMove]);
|
||||
|
||||
const handleMouseUp = useCallback((e: React.MouseEvent) => {
|
||||
if (isPanning) {
|
||||
stopPanning();
|
||||
}
|
||||
|
||||
onMouseUp?.(e);
|
||||
}, [isPanning, stopPanning, onMouseUp]);
|
||||
|
||||
const handleContextMenu = useCallback((e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
onContextMenu?.(e);
|
||||
}, [onContextMenu]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={canvasRef}
|
||||
className="behavior-tree-canvas"
|
||||
style={{
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
overflow: 'hidden',
|
||||
cursor: isPanning ? 'grabbing' : 'default',
|
||||
backgroundColor: '#1a1a1a'
|
||||
}}
|
||||
onWheel={handleWheel}
|
||||
onClick={onClick}
|
||||
onDoubleClick={onDoubleClick}
|
||||
onContextMenu={handleContextMenu}
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseUp={handleMouseUp}
|
||||
onMouseLeave={onMouseLeave}
|
||||
onDrop={onDrop}
|
||||
onDragOver={onDragOver}
|
||||
onDragEnter={onDragEnter}
|
||||
onDragLeave={onDragLeave}
|
||||
>
|
||||
{/* 网格背景 */}
|
||||
{config.showGrid && canvasSize.width > 0 && canvasSize.height > 0 && (
|
||||
<GridBackground
|
||||
canvasOffset={canvasOffset}
|
||||
canvasScale={canvasScale}
|
||||
width={canvasSize.width}
|
||||
height={canvasSize.height}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 内容容器(应用变换) */}
|
||||
<div
|
||||
className="canvas-content"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
transformOrigin: '0 0',
|
||||
transform: `translate(${canvasOffset.x}px, ${canvasOffset.y}px) scale(${canvasScale})`,
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
BehaviorTreeCanvas.displayName = 'BehaviorTreeCanvas';
|
||||
Reference in New Issue
Block a user