import { fabric } from "fabric-with-all"; import { generateId } from "../utils/helper"; import { OperationType } from "../utils/layerHelper"; import { CreateSelectionCommand } from "../commands/SelectionCommands"; import { ClearSelectionCommand } from "../commands/LassoCutoutCommand"; /** * 部件选择管理器 */ export class PartManager { /** * 创建部件选择管理器 * @param {Object} options 配置选项 * @param {Object} options.canvas fabric.js画布实例 * @param {Object} options.commandManager 命令管理器实例 * @param {Object} options.layerManager 图层管理实例 */ constructor(options = {}) { this.canvas = options.canvas; this.commandManager = options.commandManager; this.layerManager = options.layerManager; // 选区状态 this.isActive = false; this.selectionType = OperationType.LASSO_RECTANGLE; // 使用常量而不是字符串 this.selectionObject = null; // 当前选区对象 this.selectionId = "selection_" + Date.now(); this.featherAmount = 0; // 羽化值 // 选区样式配置 this.selectionStyle = { stroke: "#0096ff", strokeWidth: 1, strokeDashArray: [5, 5], fill: "rgba(0, 150, 255, 0.1)", selectable: false, evented: false, excludeFromExport: true, hoverCursor: "default", moveCursor: "default", }; // 绘制状态 this.drawingObject = null; this.startPoint = null; this.selectionPath = null; // 存储选区路径数据 // 自由选区相关状态 this.drawingPoints = null; this.currentPathString = null; // 不再直接绑定事件处理函数 this._mouseDownHandler = null; this._mouseMoveHandler = null; this._mouseUpHandler = null; this._keyDownHandler = null; // 选区相关的工具类型 this.tools = [ OperationType.PART, OperationType.PART_RECTANGLE, OperationType.PART_BRUSH, OperationType.PART_ERASER, ]; // 当前工具 this.currentTool = OperationType.SELECT; // 选区状态变化回调 this.onSelectionChanged = null; // 不再自动初始化事件,改为手动控制 // this.initEvents(); } /** * 设置当前工具 * @param {String} toolId 工具ID */ setCurrentTool(toolId) { this.currentTool = toolId; // 检查是否为选区工具 const wasActive = this.isActive; this.isActive = this.tools.includes(toolId); // 如果从非选区工具切换到选区工具,初始化事件 if (!wasActive && this.isActive) { this.initEvents(); } // 如果从选区工具切换到非选区工具,清理事件和选区 else if (wasActive && !this.isActive) { this.cleanupEvents(); this.clearSelection(); } // 根据工具类型设置选区类型 if (this.isActive) { this.selectionType = toolId; } } /** * 初始化选区相关事件 */ initEvents() { if (!this.canvas || this._mouseDownHandler) return; // 避免重复初始化 // 保存实例引用,用于事件处理函数中 const self = this; // 鼠标按下事件处理 this._mouseDownHandler = (options) => { // 如果选区功能未激活,不处理事件 if (!this.isActive) return; // 如果点击的是已有对象且不是选区对象,则不处理 if ( options.target && options.target.id !== this.selectionId && options.target.selectable !== false && options.target.type !== "selection" ) { return; } // 阻止事件冒泡,避免与 CanvasEventManager 冲突 options.e.stopPropagation(); // 根据选区类型执行不同的起始操作 switch (this.selectionType) { case OperationType.LASSO: this.startFreeSelection(options); break; case OperationType.LASSO_ELLIPSE: this.startEllipseSelection(options); break; case OperationType.LASSO_RECTANGLE: this.startRectangleSelection(options); break; } }; // 鼠标移动事件处理 this._mouseMoveHandler = (options) => { // 如果选区功能未激活或没有正在绘制的对象,不处理事件 if (!this.isActive || !this.drawingObject) return; // 阻止事件冒泡 options.e.stopPropagation(); // 根据选区类型执行不同的绘制操作 switch (this.selectionType) { case OperationType.LASSO_RECTANGLE: this.drawRectangleSelection(options); break; case OperationType.LASSO_ELLIPSE: this.drawEllipseSelection(options); break; case OperationType.LASSO: this.drawFreeSelection(options); break; } }; // 鼠标抬起事件处理 this._mouseUpHandler = (options) => { // 如果选区功能未激活或没有正在绘制的对象,不处理事件 if (!this.isActive || !this.drawingObject) return; // 阻止事件冒泡 if (options && options.e) { options.e.stopPropagation(); } // 根据选区类型执行不同的完成操作 switch (this.selectionType) { case OperationType.LASSO_RECTANGLE: this.endRectangleSelection(); break; case OperationType.LASSO_ELLIPSE: this.endEllipseSelection(); break; case OperationType.LASSO: this.endFreeSelection(); break; } // 如果有命令管理器,使用命令模式记录选区创建 if (this.commandManager && this.selectionObject) { this.commandManager.execute( new CreateSelectionCommand({ canvas: this.canvas, selectionManager: this, selectionObject: this.selectionObject, selectionType: this.selectionType, }) ); } }; // 键盘事件处理 this._keyDownHandler = (event) => { // 只在选区功能激活时处理键盘事件 if (!this.isActive) return; if (event.key === "Escape") { // ESC键取消当前选区操作 if (this.drawingObject) { this.canvas.remove(this.drawingObject); this.drawingObject = null; this.startPoint = null; } // 清除已有选区 else if (this.selectionObject) { if (this.commandManager) { this.commandManager.execute( new ClearSelectionCommand({ selectionManager: this, }) ); } else { this.clearSelection(); } } } }; // 添加事件监听 this.canvas.on("mouse:down", this._mouseDownHandler); this.canvas.on("mouse:move", this._mouseMoveHandler); this.canvas.on("mouse:up", this._mouseUpHandler); // 添加键盘事件监听 document.addEventListener("keydown", this._keyDownHandler); } /** * 清理事件监听 */ cleanupEvents() { if (!this.canvas) return; // 移除事件监听 if (this._mouseDownHandler) { this.canvas.off("mouse:down", this._mouseDownHandler); this._mouseDownHandler = null; } if (this._mouseMoveHandler) { this.canvas.off("mouse:move", this._mouseMoveHandler); this._mouseMoveHandler = null; } if (this._mouseUpHandler) { this.canvas.off("mouse:up", this._mouseUpHandler); this._mouseUpHandler = null; } if (this._keyDownHandler) { document.removeEventListener("keydown", this._keyDownHandler); this._keyDownHandler = null; } } /** * 获取选区对象 * @returns {Object} 选区对象 */ getSelectionObject() { return this.selectionObject; } /** * 获取选区路径 * @returns {Array|String} 选区路径数据 */ getSelectionPath() { return this.selectionPath; } /** * 获取羽化值 * @returns {Number} 羽化值 */ getFeatherAmount() { return this.featherAmount; } /** * 设置羽化值 * @param {Number} amount 羽化值 */ setFeatherAmount(amount) { this.featherAmount = amount; return this.updateSelectionAppearance(); } /** * 设置选区对象 * @param {Object} object 选区对象 */ setSelectionObject(object) { // 如果已存在选区,先移除 if (this.selectionObject) { this.removeSelectionFromCanvas(); } // 更新选区对象 this.selectionObject = object; this.selectionPath = object.path; this.selectionId = object.id || generateId(); // 更新外观 this.updateSelectionAppearance(); // 添加到画布(确保在顶层) if (this.canvas && this.selectionObject) { this.canvas.add(this.selectionObject); this.canvas.bringToFront(this.selectionObject); this.canvas.renderAll(); } // 触发选区变化回调 if (this.onSelectionChanged && typeof this.onSelectionChanged === "function") { this.onSelectionChanged(); } return true; } /** * 从路径数据设置选区 * @param {Array|String} path 选区路径数据 */ setSelectionFromPath(path) { if (!path) return false; // 创建选区对象 const selectionObj = new fabric.Path(path, { ...this.selectionStyle, id: `selection_${Date.now()}`, name: "selection", }); // 设置选区 return this.setSelectionObject(selectionObj); } /** * 更新选区外观 */ updateSelectionAppearance() { if (!this.selectionObject) return false; // 应用基本样式 Object.assign(this.selectionObject, this.selectionStyle); // 应用羽化效果 if (this.featherAmount > 0) { this.selectionObject.shadow = new fabric.Shadow({ color: "rgba(0, 150, 255, 0.5)", blur: this.featherAmount, offsetX: 0, offsetY: 0, }); } else { this.selectionObject.shadow = null; } // 更新画布 this.canvas.renderAll(); return true; } /** * 移除选区 */ removeSelectionFromCanvas() { if (this.canvas && this.selectionObject) { this.canvas.remove(this.selectionObject); this.canvas.renderAll(); } } /** * 清除选区 */ clearSelection() { // 移除选区对象 this.removeSelectionFromCanvas(); // 重置选区状态 this.selectionObject = null; this.selectionPath = null; this.selectionId = null; this.featherAmount = 0; // 触发选区变化回调 if (this.onSelectionChanged && typeof this.onSelectionChanged === "function") { this.onSelectionChanged(); } return true; } /** * 反转选区 */ async invertSelection() { if (!this.canvas || !this.selectionObject) return false; // 获取画布范围 const canvasRect = new fabric.Rect({ left: 0, top: 0, width: this.canvas.width, height: this.canvas.height, selectable: false, }); // 创建反选路径 let invertedPath; try { invertedPath = canvasRect.subtractPathFromRect(this.selectionObject.path); } catch (error) { console.error("无法反转选区:", error); return false; } // 设置新的选区 const newSelection = new fabric.Path(invertedPath.path, { ...this.selectionStyle, id: `selection_${Date.now()}`, name: "selection", }); return this.setSelectionObject(newSelection); } /** * 添加到选区 * @param {Object} newSelection 要添加的选区对象 */ async addToSelection(newSelection) { if (!this.canvas) return false; // 如果当前没有选区,直接使用新选区 if (!this.selectionObject) { return this.setSelectionObject(newSelection); } // 合并选区 let combinedPath; try { combinedPath = this.selectionObject.union(newSelection); } catch (error) { console.error("无法添加到选区:", error); return false; } // 设置新的选区 const combinedSelection = new fabric.Path(combinedPath.path, { ...this.selectionStyle, id: `selection_${Date.now()}`, name: "selection", }); return this.setSelectionObject(combinedSelection); } /** * 从选区中移除 * @param {Object} removeSelection 要移除的选区对象 */ async removeFromSelection(removeSelection) { if (!this.canvas || !this.selectionObject) return false; // 从当前选区中减去新选区 let resultPath; try { resultPath = this.selectionObject.subtract(removeSelection); } catch (error) { console.error("无法从选区中移除:", error); return false; } // 设置新的选区 const newSelection = new fabric.Path(resultPath.path, { ...this.selectionStyle, id: `selection_${Date.now()}`, name: "selection", }); return this.setSelectionObject(newSelection); } /** * 应用羽化效果 * @param {Number} amount 羽化值 */ async featherSelection(amount) { if (!this.selectionObject) return false; // 更新羽化值 this.featherAmount = amount; // 更新选区外观 return this.updateSelectionAppearance(); } /** * 检查对象是否在选区内 * @param {Object} object 要检查的对象 * @returns {Boolean} 是否在选区内 */ isObjectInSelection(object) { if (!this.selectionObject || !object) return false; // 获取对象的边界框 const bounds = object.getBoundingRect(); const { left, top, width, height } = bounds; // 检查对象的中心点和四个角是否在选区内 const centerX = left + width / 2; const centerY = top + height / 2; // 检查中心点 if (this.isPointInSelection(centerX, centerY)) return true; // 检查四个角 if (this.isPointInSelection(left, top)) return true; if (this.isPointInSelection(left + width, top)) return true; if (this.isPointInSelection(left, top + height)) return true; if (this.isPointInSelection(left + width, top + height)) return true; return false; } /** * 检查点是否在选区内 * @param {Number} x X坐标 * @param {Number} y Y坐标 * @returns {Boolean} 是否在选区内 */ isPointInSelection(x, y) { if (!this.selectionObject) return false; // 使用fabric.js的containsPoint方法判断点是否在选区内 return this.selectionObject.containsPoint({ x, y }); } /** * 开始自由选区 * @param {Object} options 事件对象 */ startFreeSelection(options) { if (!this.canvas || !this.isActive) return; // 获取鼠标位置 const pointer = this.canvas.getPointer(options.e); this.startPoint = pointer; // 创建用于绘制轨迹的点数组 this.drawingPoints = [pointer]; // 初始化SVG路径字符串 this.currentPathString = `M ${pointer.x} ${pointer.y}`; // 创建临时路径对象用于实时显示 this.drawingObject = new fabric.Path(this.currentPathString, { stroke: this.selectionStyle.stroke, strokeWidth: this.selectionStyle.strokeWidth, strokeDashArray: this.selectionStyle.strokeDashArray, fill: "transparent", selectable: false, evented: false, strokeLineCap: "round", strokeLineJoin: "round", }); // 添加到画布 this.canvas.add(this.drawingObject); this.canvas.renderAll(); } /** * 绘制自由选区 * @param {Object} options 事件对象 */ drawFreeSelection(options) { if (!this.drawingObject || !this.drawingPoints || !this.isActive) return; // 获取鼠标位置 const pointer = this.canvas.getPointer(options.e); // 添加新的点,但避免添加过于密集的点 const lastPoint = this.drawingPoints[this.drawingPoints.length - 1]; const distance = Math.sqrt( Math.pow(pointer.x - lastPoint.x, 2) + Math.pow(pointer.y - lastPoint.y, 2) ); // 只有当距离大于2像素时才添加新点,避免路径过于复杂 if (distance > 2) { this.drawingPoints.push(pointer); // 更新路径字符串 this.currentPathString += ` L ${pointer.x} ${pointer.y}`; // 移除旧的绘制对象 this.canvas.remove(this.drawingObject); // 创建新的路径对象 this.drawingObject = new fabric.Path(this.currentPathString, { stroke: this.selectionStyle.stroke, strokeWidth: this.selectionStyle.strokeWidth, strokeDashArray: this.selectionStyle.strokeDashArray, fill: "transparent", selectable: false, evented: false, strokeLineCap: "round", strokeLineJoin: "round", }); // 重新添加到画布 this.canvas.add(this.drawingObject); this.canvas.renderAll(); } } /** * 结束自由选区 */ endFreeSelection() { if (!this.drawingObject || !this.drawingPoints || !this.isActive) return; // 检查是否有足够的点来形成选区 if (this.drawingPoints.length < 3) { // 点太少,清除绘制对象 this.canvas.remove(this.drawingObject); this.drawingObject = null; this.drawingPoints = null; this.startPoint = null; this.currentPathString = null; return; } // 自动闭合路径 - 连接最后一点到第一点 const firstPoint = this.drawingPoints[0]; const lastPoint = this.drawingPoints[this.drawingPoints.length - 1]; const closingDistance = Math.sqrt( Math.pow(firstPoint.x - lastPoint.x, 2) + Math.pow(firstPoint.y - lastPoint.y, 2) ); // 如果首尾距离较大,自动添加闭合线段 let finalPathString = this.currentPathString; if (closingDistance > 10) { finalPathString += ` L ${firstPoint.x} ${firstPoint.y}`; } finalPathString += " Z"; // 闭合路径 // 创建最终选区对象 const selectionObj = new fabric.Path(finalPathString, { ...this.selectionStyle, id: `selection_${Date.now()}`, name: "selection", fill: this.selectionStyle.fill, // 恢复填充 }); // 移除绘制中的临时对象 this.canvas.remove(this.drawingObject); // 重置绘制状态 this.drawingObject = null; this.drawingPoints = null; this.startPoint = null; this.currentPathString = null; // 设置选区 this.setSelectionObject(selectionObj); } /** * 开始矩形选区 * @param {Object} options 事件对象 */ startRectangleSelection(options) { if (!this.canvas || !this.isActive) return; // 获取鼠标位置 const pointer = this.canvas.getPointer(options.e); this.startPoint = pointer; // 创建矩形对象 this.drawingObject = new fabric.Rect({ left: pointer.x, top: pointer.y, width: 0, height: 0, ...this.selectionStyle, fill: "transparent", // 在绘制过程中不显示填充 }); // 添加到画布 this.canvas.add(this.drawingObject); this.canvas.renderAll(); } /** * 绘制矩形选区 * @param {Object} options 事件对象 */ drawRectangleSelection(options) { if (!this.drawingObject || !this.startPoint || !this.isActive) return; // 获取鼠标位置 const pointer = this.canvas.getPointer(options.e); // 计算宽度和高度 const width = Math.abs(pointer.x - this.startPoint.x); const height = Math.abs(pointer.y - this.startPoint.y); // 确定左上角坐标 const left = Math.min(this.startPoint.x, pointer.x); const top = Math.min(this.startPoint.y, pointer.y); // 更新矩形 this.drawingObject.set({ left: left, top: top, width: width, height: height, }); this.canvas.renderAll(); } /** * 结束矩形选区 */ endRectangleSelection() { if (!this.drawingObject || !this.startPoint || !this.isActive) return; // 将矩形转换为路径 const left = this.drawingObject.left; const top = this.drawingObject.top; const width = this.drawingObject.width; const height = this.drawingObject.height; // 如果矩形太小,忽略 if (width < 5 || height < 5) { this.canvas.remove(this.drawingObject); this.drawingObject = null; this.startPoint = null; return; } // 创建矩形路径字符串 const pathString = `M ${left} ${top} L ${left + width} ${top} L ${ left + width } ${top + height} L ${left} ${top + height} Z`; // 创建最终选区对象 const selectionObj = new fabric.Path(pathString, { ...this.selectionStyle, id: `selection_${Date.now()}`, name: "selection", fill: this.selectionStyle.fill, // 恢复填充 }); // 移除绘制中的临时对象 this.canvas.remove(this.drawingObject); // 重置绘制状态 this.drawingObject = null; this.startPoint = null; // 设置选区 this.setSelectionObject(selectionObj); } /** * 开始椭圆选区 * @param {Object} options 事件对象 */ startEllipseSelection(options) { if (!this.canvas || !this.isActive) return; // 获取鼠标位置 const pointer = this.canvas.getPointer(options.e); this.startPoint = pointer; // 创建椭圆对象 this.drawingObject = new fabric.Ellipse({ left: pointer.x, top: pointer.y, rx: 0, ry: 0, ...this.selectionStyle, fill: "transparent", // 在绘制过程中不显示填充 // originX: "left", // originY: "top", originX: "center", originY: "center", }); // 添加到画布 this.canvas.add(this.drawingObject); this.canvas.renderAll(); } /** * 绘制椭圆选区 * @param {Object} options 事件对象 */ drawEllipseSelection(options) { if (!this.drawingObject || !this.startPoint || !this.isActive) return; // 获取鼠标位置 const pointer = this.canvas.getPointer(options.e); // 计算半径 const rx = Math.abs(pointer.x - this.startPoint.x) / 2; const ry = Math.abs(pointer.y - this.startPoint.y) / 2; // 确定中心坐标 const left = Math.min(this.startPoint.x, pointer.x); const top = Math.min(this.startPoint.y, pointer.y); // 更新椭圆 this.drawingObject.set({ left: left, top: top, rx: rx, ry: ry, originX: "left", originY: "top", }); this.canvas.renderAll(); } /** * 结束椭圆选区 */ endEllipseSelection() { if (!this.drawingObject || !this.startPoint || !this.isActive) return; // 获取椭圆参数 const { left, top, rx, ry } = this.drawingObject; // 如果椭圆太小,忽略 if (rx < 2 || ry < 2) { this.canvas.remove(this.drawingObject); this.drawingObject = null; this.startPoint = null; return; } // 计算中心点 const cx = left + rx; const cy = top + ry; // 将椭圆转换为路径字符串 const pathString = this.ellipseToSVGPath(cx, cy, rx, ry); // 创建最终选区对象 const selectionObj = new fabric.Path(pathString, { ...this.selectionStyle, id: `selection_${Date.now()}`, name: "selection", fill: this.selectionStyle.fill, // 恢复填充 }); // 移除绘制中的临时对象 this.canvas.remove(this.drawingObject); // 重置绘制状态 this.drawingObject = null; this.startPoint = null; // 设置选区 this.setSelectionObject(selectionObj); } /** * 将椭圆转换为SVG路径字符串 * @param {Number} cx 中心点X坐标 * @param {Number} cy 中心点Y坐标 * @param {Number} rx X半径 * @param {Number} ry Y半径 * @returns {String} SVG路径字符串 */ ellipseToSVGPath(cx, cy, rx, ry) { // 使用椭圆弧命令创建完整椭圆 return `M ${cx - rx} ${cy} A ${rx} ${ry} 0 1 0 ${ cx + rx } ${cy} A ${rx} ${ry} 0 1 0 ${cx - rx} ${cy} Z`; } /** * 设置选区工具 * @param {string} type 选区类型:OperationType.LASSO, OperationType.LASSO_RECTANGLE, OperationType.LASSO_ELLIPSE */ setSelectionType(type) { this.selectionType = type; // 如果正在绘制,清除临时对象 if (this.drawingObject) { this.canvas.remove(this.drawingObject); this.drawingObject = null; this.startPoint = null; } } /** * 设置选区工具的鼠标事件 */ setupSelectionEvents() { // 选区事件现在通过 setCurrentTool 方法管理 // 这个方法现在主要用于刷新或重置事件监听 if (!this.canvas || !this.isActive) return; // 确保选区处于激活状态 if (this.tools.includes(this.currentTool)) { this.isActive = true; // 如果事件还没有初始化,初始化它们 if (!this._mouseDownHandler) { this.initEvents(); } } } /** * 清理资源 */ dispose() { this.cleanupEvents(); this.clearSelection(); this.canvas = null; this.commandManager = null; this.layerManager = null; } }