111
This commit is contained in:
@@ -94,6 +94,10 @@
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
partManager: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
layerManager: {
|
||||
type: Object,
|
||||
required: true,
|
||||
|
||||
@@ -166,19 +166,19 @@ const normalToolsList = ref([
|
||||
icon: { name: "CFont", size: "20" },
|
||||
class: "text-btn",
|
||||
},
|
||||
// {
|
||||
// id: OperationType.PART,
|
||||
// title: t("Canvas.GarmentPartSelector"),
|
||||
// action: () => selectTool(OperationType.PART),
|
||||
// icon: { name: "CPart", size: "28" },
|
||||
// class: "part-btn",
|
||||
// activeList: [
|
||||
// OperationType.PART,
|
||||
// OperationType.PART_RECTANGLE,
|
||||
// OperationType.PART_BRUSH,
|
||||
// OperationType.PART_ERASER,
|
||||
// ],
|
||||
// },
|
||||
{
|
||||
id: OperationType.PART,
|
||||
title: t("Canvas.GarmentPartSelector"),
|
||||
action: () => selectTool(OperationType.PART),
|
||||
icon: { name: "CPart", size: "28" },
|
||||
class: "part-btn",
|
||||
activeList: [
|
||||
OperationType.PART,
|
||||
OperationType.PART_RECTANGLE,
|
||||
OperationType.PART_BRUSH,
|
||||
OperationType.PART_ERASER,
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "help",
|
||||
title: t("Canvas.help"),
|
||||
|
||||
@@ -17,6 +17,7 @@ import { KeyboardManager } from "./managers/events/KeyboardManager.js";
|
||||
import CanvasConfig from "./config/canvasConfig.js";
|
||||
import { LiquifyManager } from "./managers/liquify/LiquifyManager";
|
||||
import { SelectionManager } from "./managers/selection/SelectionManager";
|
||||
import { PartManager } from "./managers/PartManager";
|
||||
import { RedGreenModeManager } from "./managers/RedGreenModeManager";
|
||||
import texturePresetManager from "./managers/brushes/TexturePresetManager";
|
||||
import { BrushStore } from "./store/BrushStore";
|
||||
@@ -185,6 +186,7 @@ let keyboardManager = null;
|
||||
let toolManager = null;
|
||||
let liquifyManager = null;
|
||||
let selectionManager = null;
|
||||
let partManager = null;
|
||||
let redGreenModeManager = null;
|
||||
|
||||
// 快捷键帮助模态框状态
|
||||
@@ -226,6 +228,7 @@ function handleCanvasInit(isLoadJson = false) {
|
||||
keyboardManager,
|
||||
liquifyManager,
|
||||
selectionManager,
|
||||
partManager,
|
||||
redGreenModeManager,
|
||||
});
|
||||
}
|
||||
@@ -378,6 +381,13 @@ onMounted(async () => {
|
||||
});
|
||||
canvasManager.setSelectionManager(selectionManager);
|
||||
|
||||
// 初始化部件选择管理器
|
||||
partManager = new PartManager({
|
||||
canvas: canvasManager.canvas,
|
||||
layerManager,
|
||||
});
|
||||
canvasManager.setPartManager(partManager);
|
||||
|
||||
if (props.canvasJSON) {
|
||||
// 如果传入了初始JSON数据,加载到画布上
|
||||
if (typeof props.canvasJSON === "string") {
|
||||
@@ -537,6 +547,7 @@ onBeforeUnmount(async () => {
|
||||
toolManager?.dispose?.();
|
||||
liquifyManager?.dispose?.();
|
||||
selectionManager?.dispose?.();
|
||||
partManager?.dispose?.();
|
||||
redGreenModeManager?.dispose?.();
|
||||
// minimapManager?.dispose?.();
|
||||
canvasManager = null;
|
||||
@@ -546,6 +557,7 @@ onBeforeUnmount(async () => {
|
||||
toolManager = null;
|
||||
liquifyManager = null;
|
||||
selectionManager = null;
|
||||
partManager = null;
|
||||
redGreenModeManager = null;
|
||||
// fabric.Object.prototype.controls.deleteControl = undefined;
|
||||
|
||||
@@ -1254,16 +1266,17 @@ defineExpose({
|
||||
/>
|
||||
|
||||
<!-- 部件选取面板 -->
|
||||
<!-- <PartSelectorPanel
|
||||
<PartSelectorPanel
|
||||
v-if="canvasManagerLoaded && !enabledRedGreenMode"
|
||||
:canvas="canvasManager && canvasManager.canvas"
|
||||
:commandManager="commandManager"
|
||||
:selectionManager="selectionManager"
|
||||
:partManager="partManager"
|
||||
:layerManager="layerManager"
|
||||
:canvasManager="canvasManager"
|
||||
:toolManager="toolManager"
|
||||
:activeTool="activeTool"
|
||||
/> -->
|
||||
/>
|
||||
|
||||
<!-- 文本编辑面板 -->
|
||||
<TextEditorPanel
|
||||
|
||||
@@ -304,6 +304,19 @@ export class CanvasManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置部件选择管理器
|
||||
* @param {Object} partManager 部件选择管理器实例
|
||||
*/
|
||||
setPartManager(partManager) {
|
||||
this.partManager = partManager;
|
||||
|
||||
// 如果已创建事件管理器,更新它的部件选择管理器引用
|
||||
if (this.eventManager) {
|
||||
this.eventManager.partManager = this.partManager;
|
||||
}
|
||||
}
|
||||
|
||||
// 设置红绿图模式管理器
|
||||
setRedGreenModeManager(redGreenModeManager) {
|
||||
this.redGreenModeManager = redGreenModeManager;
|
||||
|
||||
941
src/component/Canvas/CanvasEditor/managers/PartManager.js
Normal file
941
src/component/Canvas/CanvasEditor/managers/PartManager.js
Normal file
@@ -0,0 +1,941 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -190,30 +190,26 @@ export class ToolManager {
|
||||
[OperationType.PART]: {
|
||||
name: "部件选取工具",
|
||||
icon: "part",
|
||||
cursor: "crosshair",
|
||||
// setup: this.setupLassoTool.bind(this),
|
||||
specialLayerDisabled: true,
|
||||
cursor: "default",
|
||||
setup: this.setupPartTool.bind(this),
|
||||
},
|
||||
[OperationType.PART_RECTANGLE]: {
|
||||
name: "部件选取工具-矩形",
|
||||
icon: "part",
|
||||
cursor: "crosshair",
|
||||
// setup: this.setupRectangleLassoTool.bind(this),
|
||||
specialLayerDisabled: true,
|
||||
cursor: "default",
|
||||
setup: this.setupPartTool.bind(this),
|
||||
},
|
||||
[OperationType.PART_BRUSH]: {
|
||||
name: "部件选取工具-画笔",
|
||||
icon: "part",
|
||||
cursor: "crosshair",
|
||||
// setup: this.setupEllipseLassoTool.bind(this),
|
||||
specialLayerDisabled: true,
|
||||
cursor: "default",
|
||||
setup: this.setupPartTool.bind(this),
|
||||
},
|
||||
[OperationType.PART_ERASER]: {
|
||||
name: "部件选取工具-橡皮擦",
|
||||
icon: "part",
|
||||
cursor: "crosshair",
|
||||
// setup: this.setupEllipseLassoTool.bind(this),
|
||||
specialLayerDisabled: true,
|
||||
cursor: "default",
|
||||
setup: this.setupPartTool.bind(this),
|
||||
},
|
||||
|
||||
// 红绿图模式专用工具
|
||||
@@ -704,6 +700,20 @@ export class ToolManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置部件选取工具
|
||||
*/
|
||||
setupPartTool() {
|
||||
if (!this.canvas) return;
|
||||
if (this.checkToolCanOperateSelectedObject()) return;
|
||||
this.canvas.isDrawingMode = false;
|
||||
this.canvas.selection = false;
|
||||
|
||||
if (this.canvasManager && this.canvasManager.partManager) {
|
||||
this.canvasManager.partManager.setCurrentTool(OperationType.PART);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置波浪工具
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user