Refactor/clean architecture phase1 (#215)
* refactor(editor): 建立Clean Architecture领域模型层 * refactor(editor): 实现应用层架构 - 命令模式、用例和状态管理 * refactor(editor): 实现展示层核心Hooks * refactor(editor): 实现基础设施层和展示层组件 * refactor(editor): 迁移画布和连接渲染到 Clean Architecture 组件 * feat(editor): 集成应用层架构和命令模式,实现撤销/重做功能 * refactor(editor): UI组件拆分 * refactor(editor): 提取快速创建菜单逻辑 * refactor(editor): 重构BehaviorTreeEditor,提取组件和Hook * refactor(editor): 提取端口连接和键盘事件Hook * refactor(editor): 提取拖放处理Hook * refactor(editor): 提取画布交互Hook和工具函数 * refactor(editor): 完成核心重构 * fix(editor): 修复节点无法创建和连接 * refactor(behavior-tree,editor): 重构节点子节点约束系统,实现元数据驱动的架构
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
import { RefObject } from 'react';
|
||||
import { BehaviorTreeNode, ROOT_NODE_ID } from '../../stores/behaviorTreeStore';
|
||||
|
||||
interface QuickCreateMenuState {
|
||||
visible: boolean;
|
||||
position: { x: number; y: number };
|
||||
searchText: string;
|
||||
selectedIndex: number;
|
||||
mode: 'create' | 'replace';
|
||||
replaceNodeId: string | null;
|
||||
}
|
||||
|
||||
interface UseCanvasMouseEventsParams {
|
||||
canvasRef: RefObject<HTMLDivElement>;
|
||||
canvasOffset: { x: number; y: number };
|
||||
canvasScale: number;
|
||||
connectingFrom: string | null;
|
||||
connectingToPos: { x: number; y: number } | null;
|
||||
isBoxSelecting: boolean;
|
||||
boxSelectStart: { x: number; y: number } | null;
|
||||
boxSelectEnd: { x: number; y: number } | null;
|
||||
nodes: BehaviorTreeNode[];
|
||||
selectedNodeIds: string[];
|
||||
quickCreateMenu: QuickCreateMenuState;
|
||||
setConnectingToPos: (pos: { x: number; y: number } | null) => void;
|
||||
setIsBoxSelecting: (isSelecting: boolean) => void;
|
||||
setBoxSelectStart: (pos: { x: number; y: number } | null) => void;
|
||||
setBoxSelectEnd: (pos: { x: number; y: number } | null) => void;
|
||||
setSelectedNodeIds: (ids: string[]) => void;
|
||||
setSelectedConnection: (connection: { from: string; to: string } | null) => void;
|
||||
setQuickCreateMenu: (menu: QuickCreateMenuState) => void;
|
||||
clearConnecting: () => void;
|
||||
clearBoxSelect: () => void;
|
||||
}
|
||||
|
||||
export function useCanvasMouseEvents(params: UseCanvasMouseEventsParams) {
|
||||
const {
|
||||
canvasRef,
|
||||
canvasOffset,
|
||||
canvasScale,
|
||||
connectingFrom,
|
||||
connectingToPos,
|
||||
isBoxSelecting,
|
||||
boxSelectStart,
|
||||
boxSelectEnd,
|
||||
nodes,
|
||||
selectedNodeIds,
|
||||
quickCreateMenu,
|
||||
setConnectingToPos,
|
||||
setIsBoxSelecting,
|
||||
setBoxSelectStart,
|
||||
setBoxSelectEnd,
|
||||
setSelectedNodeIds,
|
||||
setSelectedConnection,
|
||||
setQuickCreateMenu,
|
||||
clearConnecting,
|
||||
clearBoxSelect
|
||||
} = params;
|
||||
|
||||
const handleCanvasMouseMove = (e: React.MouseEvent) => {
|
||||
if (connectingFrom && canvasRef.current && !quickCreateMenu.visible) {
|
||||
const rect = canvasRef.current.getBoundingClientRect();
|
||||
const canvasX = (e.clientX - rect.left - canvasOffset.x) / canvasScale;
|
||||
const canvasY = (e.clientY - rect.top - canvasOffset.y) / canvasScale;
|
||||
setConnectingToPos({
|
||||
x: canvasX,
|
||||
y: canvasY
|
||||
});
|
||||
}
|
||||
|
||||
if (isBoxSelecting && boxSelectStart) {
|
||||
const rect = canvasRef.current?.getBoundingClientRect();
|
||||
if (!rect) return;
|
||||
|
||||
const canvasX = (e.clientX - rect.left - canvasOffset.x) / canvasScale;
|
||||
const canvasY = (e.clientY - rect.top - canvasOffset.y) / canvasScale;
|
||||
setBoxSelectEnd({ x: canvasX, y: canvasY });
|
||||
}
|
||||
};
|
||||
|
||||
const handleCanvasMouseUp = (e: React.MouseEvent) => {
|
||||
if (quickCreateMenu.visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (connectingFrom && connectingToPos) {
|
||||
setQuickCreateMenu({
|
||||
visible: true,
|
||||
position: {
|
||||
x: e.clientX,
|
||||
y: e.clientY
|
||||
},
|
||||
searchText: '',
|
||||
selectedIndex: 0,
|
||||
mode: 'create',
|
||||
replaceNodeId: null
|
||||
});
|
||||
setConnectingToPos(null);
|
||||
return;
|
||||
}
|
||||
|
||||
clearConnecting();
|
||||
|
||||
if (isBoxSelecting && boxSelectStart && boxSelectEnd) {
|
||||
const minX = Math.min(boxSelectStart.x, boxSelectEnd.x);
|
||||
const maxX = Math.max(boxSelectStart.x, boxSelectEnd.x);
|
||||
const minY = Math.min(boxSelectStart.y, boxSelectEnd.y);
|
||||
const maxY = Math.max(boxSelectStart.y, boxSelectEnd.y);
|
||||
|
||||
const selectedInBox = nodes
|
||||
.filter((node: BehaviorTreeNode) => {
|
||||
if (node.id === ROOT_NODE_ID) return false;
|
||||
|
||||
const nodeElement = canvasRef.current?.querySelector(`[data-node-id="${node.id}"]`);
|
||||
if (!nodeElement) {
|
||||
return node.position.x >= minX && node.position.x <= maxX &&
|
||||
node.position.y >= minY && node.position.y <= maxY;
|
||||
}
|
||||
|
||||
const rect = nodeElement.getBoundingClientRect();
|
||||
const canvasRect = canvasRef.current!.getBoundingClientRect();
|
||||
|
||||
const nodeLeft = (rect.left - canvasRect.left - canvasOffset.x) / canvasScale;
|
||||
const nodeRight = (rect.right - canvasRect.left - canvasOffset.x) / canvasScale;
|
||||
const nodeTop = (rect.top - canvasRect.top - canvasOffset.y) / canvasScale;
|
||||
const nodeBottom = (rect.bottom - canvasRect.top - canvasOffset.y) / canvasScale;
|
||||
|
||||
return nodeRight > minX && nodeLeft < maxX && nodeBottom > minY && nodeTop < maxY;
|
||||
})
|
||||
.map((node: BehaviorTreeNode) => node.id);
|
||||
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
const newSet = new Set([...selectedNodeIds, ...selectedInBox]);
|
||||
setSelectedNodeIds(Array.from(newSet));
|
||||
} else {
|
||||
setSelectedNodeIds(selectedInBox);
|
||||
}
|
||||
}
|
||||
|
||||
clearBoxSelect();
|
||||
};
|
||||
|
||||
const handleCanvasMouseDown = (e: React.MouseEvent) => {
|
||||
if (e.button === 0 && !e.altKey) {
|
||||
const rect = canvasRef.current?.getBoundingClientRect();
|
||||
if (!rect) return;
|
||||
|
||||
const canvasX = (e.clientX - rect.left - canvasOffset.x) / canvasScale;
|
||||
const canvasY = (e.clientY - rect.top - canvasOffset.y) / canvasScale;
|
||||
|
||||
setIsBoxSelecting(true);
|
||||
setBoxSelectStart({ x: canvasX, y: canvasY });
|
||||
setBoxSelectEnd({ x: canvasX, y: canvasY });
|
||||
|
||||
if (!e.ctrlKey && !e.metaKey) {
|
||||
setSelectedNodeIds([]);
|
||||
setSelectedConnection(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
handleCanvasMouseMove,
|
||||
handleCanvasMouseUp,
|
||||
handleCanvasMouseDown
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user