1242 lines
33 KiB
JavaScript
1242 lines
33 KiB
JavaScript
import { BrushStore } from "../store/BrushStore";
|
||
import { BrushManager } from "./brushes/brushManager";
|
||
import { ToolCommand } from "../commands/ToolCommands";
|
||
import { OperationType } from "../utils/layerHelper";
|
||
import CanvasConfig from "../config/canvasConfig";
|
||
import { fabric } from "fabric-with-all";
|
||
import { InitLiquifyToolCommand } from "../commands/LiquifyCommands";
|
||
import { RasterizeLayerCommand } from "../commands/GroupCommands";
|
||
import { message, Modal } from "ant-design-vue";
|
||
import { h } from "vue";
|
||
|
||
/**
|
||
* 工具管理器
|
||
* 负责管理编辑器中的各种工具
|
||
*/
|
||
export class ToolManager {
|
||
/**
|
||
* 创建工具管理器
|
||
* @param {Object} options 配置选项
|
||
* @param {Object} options.canvas fabric.js画布实例
|
||
* @param {Object} options.commandManager 命令管理器实例
|
||
* @param {Object} options.canvasManager 画布管理实例
|
||
* @param {Object} options.layerManager 图层管理实例
|
||
* @param {Object} options.brushManager 画笔管理器实例(可选,如果不提供会创建一个)
|
||
* @param {Object} options.activeTool 当前活动工具的响应式引用
|
||
*/
|
||
constructor(options = {}) {
|
||
this.canvas = options.canvas;
|
||
this.commandManager = options.commandManager;
|
||
this.canvasManager = options.canvasManager;
|
||
this.layerManager = options.layerManager;
|
||
this.activeTool = options.activeTool || {
|
||
value: OperationType.SELECT,
|
||
};
|
||
|
||
// 红绿图模式状态
|
||
this.isRedGreenMode = false;
|
||
this.redGreenModeManager = null;
|
||
|
||
// 使用传入的brushManager或创建新的实例
|
||
this.brushManager =
|
||
options.brushManager ||
|
||
new BrushManager({
|
||
canvas: this.canvas,
|
||
brushSize: options.brushSize,
|
||
layerManager: this.layerManager, // 传入图层管理器引用
|
||
});
|
||
|
||
// 观察者列表
|
||
this.observers = [];
|
||
|
||
// 工具列表 - 与OperationType保持一致
|
||
this.tools = {
|
||
// 基础工具
|
||
[OperationType.SELECT]: {
|
||
name: "选择工具",
|
||
icon: "select",
|
||
cursor: "default",
|
||
shortcut: "V",
|
||
setup: this.setupSelectTool.bind(this),
|
||
allowedInRedGreen: false,
|
||
},
|
||
[OperationType.DRAW]: {
|
||
name: "画笔工具",
|
||
icon: "brush",
|
||
cursor: "crosshair",
|
||
shortcut: "B",
|
||
setup: this.setupBrushTool.bind(this),
|
||
allowedInRedGreen: false,
|
||
},
|
||
[OperationType.ERASER]: {
|
||
name: "橡皮擦",
|
||
icon: "eraser",
|
||
cursor: "crosshair",
|
||
shortcut: "E",
|
||
setup: this.setupEraserTool.bind(this),
|
||
allowedInRedGreen: true, // 红绿图模式允许橡皮擦
|
||
},
|
||
[OperationType.EYEDROPPER]: {
|
||
name: "吸色工具",
|
||
icon: "eyedropper",
|
||
cursor: "crosshair",
|
||
shortcut: "I",
|
||
setup: this.setupEyedropperTool.bind(this),
|
||
allowedInRedGreen: false,
|
||
},
|
||
[OperationType.PAN]: {
|
||
name: "移动画布",
|
||
icon: "hand",
|
||
cursor: "grab",
|
||
shortcut: "H",
|
||
setup: this.setupHandTool.bind(this),
|
||
allowedInRedGreen: false, // 红绿图模式不允许PAN
|
||
},
|
||
|
||
// 套索工具
|
||
[OperationType.LASSO]: {
|
||
name: "套索工具",
|
||
icon: "lasso",
|
||
cursor: "crosshair",
|
||
shortcut: "L",
|
||
setup: this.setupLassoTool.bind(this),
|
||
allowedInRedGreen: false,
|
||
},
|
||
[OperationType.LASSO_RECTANGLE]: {
|
||
name: "矩形套索工具",
|
||
icon: "lasso",
|
||
cursor: "crosshair",
|
||
// shortcut: "L",
|
||
altKey: true,
|
||
setup: this.setupRectangleLassoTool.bind(this),
|
||
allowedInRedGreen: false,
|
||
},
|
||
[OperationType.LASSO_ELLIPSE]: {
|
||
name: "椭圆形套索工具",
|
||
icon: "lasso",
|
||
cursor: "crosshair",
|
||
// shortcut: "L",
|
||
altKey: true,
|
||
setup: this.setupEllipseLassoTool.bind(this),
|
||
allowedInRedGreen: false,
|
||
},
|
||
|
||
// 选区工具 - 只需要矩形选区
|
||
[OperationType.AREA_RECTANGLE]: {
|
||
name: "矩形选区工具",
|
||
icon: "area-rectangle",
|
||
cursor: "crosshair",
|
||
shortcut: "M",
|
||
altKey: true,
|
||
setup: this.setupRectangleAreaTool.bind(this),
|
||
allowedInRedGreen: false,
|
||
},
|
||
|
||
// 特效工具
|
||
[OperationType.WAVE]: {
|
||
name: "波浪工具",
|
||
icon: "wave",
|
||
cursor: "crosshair",
|
||
shortcut: "W",
|
||
setup: this.setupWaveTool.bind(this),
|
||
allowedInRedGreen: false,
|
||
},
|
||
[OperationType.LIQUIFY]: {
|
||
name: "液化工具",
|
||
icon: "liquify",
|
||
cursor: "crosshair",
|
||
shortcut: "J",
|
||
setup: this.setupLiquifyTool.bind(this),
|
||
allowedInRedGreen: false, // 红绿图模式不允许液化
|
||
},
|
||
[OperationType.TEXT]: {
|
||
name: "文本工具",
|
||
icon: "text",
|
||
cursor: "text",
|
||
shortcut: "T",
|
||
setup: this.setupTextTool.bind(this),
|
||
allowedInRedGreen: false, // 红绿图模式不允许文本
|
||
},
|
||
|
||
// 红绿图模式专用工具
|
||
[OperationType.RED_BRUSH]: {
|
||
name: "红色笔刷",
|
||
icon: "brush",
|
||
cursor: "crosshair",
|
||
shortcut: "R",
|
||
setup: this.setupRedBrushTool.bind(this),
|
||
allowedInRedGreen: true,
|
||
redGreenOnly: true, // 只在红绿图模式显示
|
||
},
|
||
[OperationType.GREEN_BRUSH]: {
|
||
name: "绿色笔刷",
|
||
icon: "brush",
|
||
cursor: "crosshair",
|
||
shortcut: "G",
|
||
setup: this.setupGreenBrushTool.bind(this),
|
||
allowedInRedGreen: true,
|
||
redGreenOnly: true, // 只在红绿图模式显示
|
||
},
|
||
};
|
||
|
||
// 记录先前的工具
|
||
this.previousTool = null;
|
||
|
||
// 初始化默认工具
|
||
this.setTool(this.activeTool.value);
|
||
|
||
// 初始化工具快捷键
|
||
this.initKeyboardShortcuts();
|
||
|
||
// 设置文本编辑事件
|
||
this.setupTextEditingEvents();
|
||
|
||
// 添加观察者
|
||
// this.addObserver(this.brushManager);
|
||
this.addObserver(this.layerManager);
|
||
this.addObserver(this.canvasManager);
|
||
}
|
||
|
||
/**
|
||
* 添加观察者
|
||
* @param {Object} observer 观察者对象,必须实现toolChanged方法
|
||
*/
|
||
addObserver(observer) {
|
||
if (typeof observer.toolChanged === "function") {
|
||
this.observers.push(observer);
|
||
} else {
|
||
console.warn("Observer must implement toolChanged method");
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 移除观察者
|
||
* @param {Object} observer 要移除的观察者对象
|
||
*/
|
||
removeObserver(observer) {
|
||
this.observers = this.observers.filter((obs) => obs !== observer);
|
||
}
|
||
|
||
/**
|
||
* 通知所有观察者工具已更改
|
||
* @param {String} toolId 工具ID
|
||
*/
|
||
notifyObservers(toolId) {
|
||
this.observers.forEach((observer) => {
|
||
observer.toolChanged(toolId, this.tools[toolId]);
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 初始化工具快捷键
|
||
*/
|
||
initKeyboardShortcuts() {
|
||
// 可以在这里设置工具快捷键的全局监听
|
||
// 如需要由外部统一管理键盘事件,则不需要此方法
|
||
}
|
||
|
||
/**
|
||
* 处理快捷键事件
|
||
* @param {KeyboardEvent} event 键盘事件
|
||
*/
|
||
handleKeyboardShortcut(event) {
|
||
const key = event.key.toUpperCase();
|
||
const altKey = event.altKey;
|
||
const ctrlKey = event.ctrlKey;
|
||
const shiftKey = event.shiftKey;
|
||
|
||
// 当处于输入状态时不触发快捷键
|
||
if (
|
||
event.target.tagName === "INPUT" ||
|
||
event.target.tagName === "TEXTAREA"
|
||
) {
|
||
return;
|
||
}
|
||
|
||
// 在红绿图模式下,只允许特定快捷键
|
||
if (this.isRedGreenMode) {
|
||
const allowedKeys = ["E", "R", "G"]; // 橡皮擦、红色笔刷、绿色笔刷
|
||
if (!allowedKeys.includes(key)) {
|
||
return; // 忽略不允许的快捷键
|
||
}
|
||
}
|
||
|
||
// 查找匹配的工具
|
||
for (const [toolId, tool] of Object.entries(this.tools)) {
|
||
if (tool.shortcut && tool.shortcut.toUpperCase() === key) {
|
||
// 检查可能的辅助键要求
|
||
if (
|
||
(tool.altKey && !altKey) ||
|
||
(tool.ctrlKey && !ctrlKey) ||
|
||
(tool.shiftKey && !shiftKey)
|
||
) {
|
||
continue;
|
||
}
|
||
|
||
// 在红绿图模式下检查工具可用性
|
||
if (
|
||
this.isRedGreenMode &&
|
||
!this.isToolAvailableInRedGreenMode(toolId)
|
||
) {
|
||
continue;
|
||
}
|
||
|
||
// 切换到该工具
|
||
this.setToolWithCommand(toolId);
|
||
event.preventDefault();
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取所有可用工具列表
|
||
* @returns {Array} 工具列表
|
||
*/
|
||
getTools() {
|
||
return Object.keys(this.tools).map((key) => {
|
||
return {
|
||
id: key,
|
||
...this.tools[key],
|
||
};
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 获取当前工具
|
||
* @returns {String} 当前工具ID
|
||
*/
|
||
getCurrentTool() {
|
||
return this.activeTool.value;
|
||
}
|
||
|
||
/**
|
||
* 设置当前活动工具
|
||
* @param {String} toolId 工具ID
|
||
*/
|
||
setTool(toolId) {
|
||
// 检查工具是否存在
|
||
if (!this.tools[toolId]) {
|
||
console.error(`工具 '${toolId}' 不存在`);
|
||
return;
|
||
}
|
||
|
||
// 在红绿图模式下检查工具可用性
|
||
if (this.isRedGreenMode && !this.isToolAvailableInRedGreenMode(toolId)) {
|
||
console.warn(`工具 '${toolId}' 在红绿图模式下不可用`);
|
||
return;
|
||
}
|
||
|
||
// 在普通模式下检查是否为红绿图专用工具
|
||
if (!this.isRedGreenMode && this.isRedGreenOnlyTool(toolId)) {
|
||
console.warn(`工具 '${toolId}' 只能在红绿图模式下使用`);
|
||
return;
|
||
}
|
||
|
||
// 保存先前的工具
|
||
this.previousTool = this.activeTool.value;
|
||
|
||
// 设置新工具
|
||
this.activeTool.value = toolId;
|
||
|
||
// 设置光标
|
||
if (this.canvas) {
|
||
this.canvas.defaultCursor = this.tools[toolId].cursor;
|
||
}
|
||
|
||
// 设置工具特定的状态
|
||
const tool = this.tools[toolId];
|
||
if (tool && typeof tool.setup === "function") {
|
||
tool.setup();
|
||
}
|
||
|
||
// 通知选区管理器工具已改变
|
||
if (this.canvasManager && this.canvasManager.selectionManager) {
|
||
this.canvasManager.selectionManager.setCurrentTool(toolId);
|
||
}
|
||
|
||
// 通知观察者
|
||
this.notifyObservers(toolId);
|
||
|
||
return this.activeTool.value;
|
||
}
|
||
|
||
/**
|
||
* 通过命令模式设置工具
|
||
* @param {String} toolId 工具ID
|
||
*/
|
||
setToolWithCommand(toolId, options = {}) {
|
||
if (!this.commandManager) {
|
||
this.setTool(toolId);
|
||
return;
|
||
}
|
||
|
||
// 创建工具切换命令
|
||
const command = new ToolCommand({
|
||
toolManager: this,
|
||
tool: toolId,
|
||
previousTool: this.activeTool.value,
|
||
});
|
||
|
||
command.undoable = options.undoable !== undefined ? options.undoable : true;
|
||
|
||
// 执行命令
|
||
this.commandManager.execute(command, { ...options });
|
||
}
|
||
|
||
/**
|
||
* 恢复当前工具的选择状态
|
||
* 在拖拽结束时调用,确保canvas.selection状态与当前工具一致
|
||
*/
|
||
restoreSelectionState() {
|
||
if (!this.canvas) return;
|
||
|
||
const currentTool = this.activeTool.value;
|
||
const tool = this.tools[currentTool];
|
||
|
||
// 根据当前工具设置selection状态
|
||
if (currentTool === OperationType.SELECT) {
|
||
this.canvas.selection = true;
|
||
} else {
|
||
// 对于大多数工具,selection应该是false
|
||
this.canvas.selection = false;
|
||
}
|
||
|
||
// 如有必要,可以调用当前工具的setup方法来全面恢复状态
|
||
if (tool && typeof tool.setup === "function") {
|
||
tool.setup();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 切换回先前使用的工具
|
||
*/
|
||
togglePreviousTool() {
|
||
if (this.previousTool && this.previousTool !== this.activeTool.value) {
|
||
this.setTool(this.previousTool);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 设置选择工具
|
||
*/
|
||
setupSelectTool() {
|
||
if (!this.canvas) return;
|
||
this.canvas.isDrawingMode = false;
|
||
this.canvas.selection = true;
|
||
}
|
||
|
||
/**
|
||
* 设置画笔工具
|
||
*/
|
||
setupBrushTool() {
|
||
if (!this.canvas) return;
|
||
|
||
this.canvas.isDrawingMode = true;
|
||
this.canvas.selection = false;
|
||
|
||
// 确保有笔刷管理器
|
||
if (this.brushManager) {
|
||
// 检查画笔是否正在更新中
|
||
if (this.brushManager.isUpdatingBrush) {
|
||
console.warn("画笔正在更新中,请稍候...");
|
||
return;
|
||
}
|
||
|
||
if (BrushStore) {
|
||
// 同步基本属性
|
||
this.brushManager.setBrushSize(BrushStore.state.size);
|
||
this.brushManager.setBrushColor(BrushStore.state.color);
|
||
this.brushManager.setBrushOpacity(BrushStore.state.opacity);
|
||
|
||
// 同步笔刷类型 - 修复方法名,使用正确的setBrushType方法
|
||
this.brushManager.setBrushType(BrushStore.state.type);
|
||
|
||
// 同步材质设置
|
||
if (BrushStore.state.textureEnabled && BrushStore.state.texturePath) {
|
||
this.brushManager.setTexturePath(BrushStore.state.texturePath);
|
||
this.brushManager.setTextureScale(BrushStore.state.textureScale);
|
||
}
|
||
}
|
||
|
||
// 更新应用到画布
|
||
this.brushManager.updateBrush();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 设置橡皮擦工具
|
||
*/
|
||
setupEraserTool() {
|
||
if (!this.canvas) return;
|
||
this.canvas.isDrawingMode = true;
|
||
this.canvas.selection = false;
|
||
|
||
// 确保有笔刷管理器
|
||
if (this.brushManager) {
|
||
this.brushManager.createEraser();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 设置吸色工具
|
||
*/
|
||
setupEyedropperTool() {
|
||
if (!this.canvas || !this.brushManager) return;
|
||
|
||
this.canvas.isDrawingMode = false;
|
||
this.canvas.selection = false;
|
||
|
||
// 保存当前工具,以便吸色完成后还原
|
||
const currentTool = this.activeTool.value;
|
||
|
||
// 使用吸色工具
|
||
this.brushManager.createEyedropper((color) => {
|
||
// 设置画笔颜色
|
||
this.brushManager.setBrushColor(color);
|
||
|
||
// 吸色完成后,恢复到之前的工具
|
||
this.setTool(currentTool);
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 设置移动画布工具
|
||
*/
|
||
setupHandTool() {
|
||
if (!this.canvas) return;
|
||
|
||
this.canvas.isDrawingMode = false;
|
||
this.canvas.selection = false;
|
||
|
||
// 设置画布为可拖动状态
|
||
this.canvas.defaultCursor = "grab";
|
||
}
|
||
|
||
/**
|
||
* 设置套索工具
|
||
*/
|
||
setupLassoTool() {
|
||
if (!this.canvas) return;
|
||
|
||
this.canvas.isDrawingMode = false;
|
||
this.canvas.selection = false;
|
||
|
||
// 通知选区管理器切换到自由套索工具
|
||
if (this.canvasManager && this.canvasManager.selectionManager) {
|
||
this.canvasManager.selectionManager.setCurrentTool(OperationType.LASSO);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 设置矩形套索工具
|
||
*/
|
||
setupRectangleLassoTool() {
|
||
if (!this.canvas) return;
|
||
|
||
this.canvas.isDrawingMode = false;
|
||
this.canvas.selection = false;
|
||
|
||
// 通知选区管理器切换到矩形套索工具
|
||
if (this.canvasManager && this.canvasManager.selectionManager) {
|
||
this.canvasManager.selectionManager.setCurrentTool(
|
||
OperationType.LASSO_RECTANGLE
|
||
);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 设置椭圆形套索工具
|
||
*/
|
||
setupEllipseLassoTool() {
|
||
if (!this.canvas) return;
|
||
|
||
this.canvas.isDrawingMode = false;
|
||
this.canvas.selection = false;
|
||
|
||
// 通知选区管理器切换到椭圆套索工具
|
||
if (this.canvasManager && this.canvasManager.selectionManager) {
|
||
this.canvasManager.selectionManager.setCurrentTool(
|
||
OperationType.LASSO_ELLIPSE
|
||
);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 设置自由选区工具
|
||
*/
|
||
setupCustomAreaTool() {
|
||
if (!this.canvas) return;
|
||
|
||
this.canvas.isDrawingMode = false;
|
||
this.canvas.selection = false;
|
||
|
||
// 通知选区管理器切换到椭圆套索工具
|
||
if (this.canvasManager && this.canvasManager.selectionManager) {
|
||
this.canvasManager.selectionManager.setCurrentTool(
|
||
OperationType.AREA_CUSTOM
|
||
);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 设置矩形选区工具
|
||
*/
|
||
setupRectangleAreaTool() {
|
||
if (!this.canvas) return;
|
||
|
||
this.canvas.isDrawingMode = false;
|
||
this.canvas.selection = false;
|
||
|
||
// // 设置矩形选区模式
|
||
// // 这里需要具体的矩形选区工具实现
|
||
console.log("矩形选区工具已激活");
|
||
|
||
if (this.canvasManager && this.canvasManager.selectionManager) {
|
||
this.canvasManager.selectionManager.setCurrentTool(
|
||
OperationType.AREA_RECTANGLE
|
||
);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 设置波浪工具
|
||
*/
|
||
setupWaveTool() {
|
||
if (!this.canvas) return;
|
||
|
||
this.canvas.isDrawingMode = false;
|
||
this.canvas.selection = false;
|
||
}
|
||
|
||
/**
|
||
* 设置液化工具
|
||
*/
|
||
setupLiquifyTool() {
|
||
if (!this.canvas || !this.layerManager) return;
|
||
|
||
this.canvas.isDrawingMode = false;
|
||
this.canvas.selection = false;
|
||
|
||
// 获取当前活动图层
|
||
const activeLayerId = this.layerManager.getActiveLayerId();
|
||
|
||
// 准备液化面板显示的详情信息
|
||
let panelDetail = {
|
||
activeLayerId: activeLayerId,
|
||
layerStatus: null,
|
||
canLiquify: false,
|
||
targetObject: null,
|
||
originalImageData: null,
|
||
};
|
||
|
||
// 如果有活动图层,检查其状态
|
||
if (activeLayerId) {
|
||
const liquifyManager = this.canvasManager?.liquifyManager;
|
||
if (liquifyManager) {
|
||
// 检查图层状态
|
||
const checkResult = liquifyManager.checkLayerForLiquify(activeLayerId);
|
||
panelDetail.layerStatus = checkResult;
|
||
|
||
// 获取图层对象
|
||
const layer = this.layerManager.getLayerById(activeLayerId);
|
||
|
||
// 检查图层是否为空
|
||
if (!checkResult.isEmpty) {
|
||
// 图层不为空,判断是否可直接液化或需要栅格化
|
||
if (checkResult.valid) {
|
||
// 可以直接液化 (单个图像对象)
|
||
panelDetail.canLiquify = true;
|
||
|
||
// 设置目标对象
|
||
if (layer) {
|
||
if (layer.isBackground || layer.type === "background") {
|
||
panelDetail.targetObject = layer.fabricObject;
|
||
} else if (
|
||
layer.fabricObjects &&
|
||
layer.fabricObjects.length > 0
|
||
) {
|
||
panelDetail.targetObject = layer.fabricObjects[0];
|
||
}
|
||
|
||
// 准备液化环境,获取原始图像数据
|
||
if (panelDetail.targetObject && liquifyManager) {
|
||
liquifyManager.initialize({
|
||
canvas: this.canvas,
|
||
layerManager: this.layerManager,
|
||
});
|
||
|
||
// 异步获取原始图像数据
|
||
liquifyManager
|
||
.prepareForLiquify(panelDetail.targetObject)
|
||
.then((result) => {
|
||
if (result && result.originalImageData) {
|
||
// 当获取到原始图像数据后触发更新
|
||
const updatedDetail = {
|
||
...panelDetail,
|
||
originalImageData: result.originalImageData,
|
||
};
|
||
|
||
// 重新触发液化面板显示事件,这次包含原始图像数据
|
||
document.dispatchEvent(
|
||
new CustomEvent("showLiquifyPanel", {
|
||
detail: updatedDetail,
|
||
})
|
||
);
|
||
}
|
||
})
|
||
.catch((err) => {
|
||
console.error("准备液化环境失败:", err);
|
||
});
|
||
}
|
||
}
|
||
} else if (checkResult.needsRasterization) {
|
||
// 需要栅格化 (多个对象或组)
|
||
// 使用Modal询问用户是否要栅格化
|
||
this._showRasterizeConfirmModal(checkResult.isGroup, activeLayerId);
|
||
return; // 等待用户确认,不继续执行
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 总是触发液化面板显示事件,不论图层状态如何
|
||
document.dispatchEvent(
|
||
new CustomEvent("showLiquifyPanel", {
|
||
detail: panelDetail,
|
||
})
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 显示栅格化确认Modal对话框
|
||
* @param {Boolean} isGroup 是否为组对象
|
||
* @param {String} layerId 图层ID
|
||
* @private
|
||
*/
|
||
_showRasterizeConfirmModal(isGroup, layerId) {
|
||
const title = "栅格化图层";
|
||
const content = "需要先栅格化才能进行液化操作,是否立即栅格化?";
|
||
|
||
Modal.confirm({
|
||
title,
|
||
content,
|
||
okText: "确定栅格化",
|
||
cancelText: "取消",
|
||
centered: true,
|
||
icon: h("span", { style: "color: #faad14;" }, "⚠️"),
|
||
onOk: () => {
|
||
// 用户确认栅格化,执行栅格化操作
|
||
this._rasterizeLayerForLiquify(layerId);
|
||
},
|
||
onCancel: () => {
|
||
console.log("用户取消了栅格化操作");
|
||
// 用户取消,触发液化面板显示事件但不能液化
|
||
document.dispatchEvent(
|
||
new CustomEvent("showLiquifyPanel", {
|
||
detail: {
|
||
activeLayerId: layerId,
|
||
layerStatus: { needsRasterization: true, isGroup },
|
||
canLiquify: false,
|
||
targetObject: null,
|
||
originalImageData: null,
|
||
},
|
||
})
|
||
);
|
||
},
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 栅格化图层用于液化操作
|
||
* @param {String} layerId 图层ID
|
||
* @private
|
||
*/
|
||
async _rasterizeLayerForLiquify(layerId) {
|
||
if (!this.commandManager || !this.layerManager) return;
|
||
|
||
try {
|
||
// 显示加载Modal
|
||
const loadingModal = Modal.info({
|
||
title: "正在栅格化",
|
||
content: "正在栅格化图层,请稍候...",
|
||
okButtonProps: { style: { display: "none" } },
|
||
centered: true,
|
||
closable: false,
|
||
maskClosable: false,
|
||
});
|
||
|
||
// 创建栅格化命令
|
||
const rasterizeCommand = new RasterizeLayerCommand({
|
||
canvas: this.canvas,
|
||
layerManager: this.layerManager,
|
||
layerId: layerId,
|
||
layers: this.layerManager.layers,
|
||
activeLayerId: this.layerManager.activeLayerId,
|
||
});
|
||
|
||
// 执行命令
|
||
const result = await this.commandManager.execute(rasterizeCommand);
|
||
|
||
// 关闭加载Modal
|
||
loadingModal.destroy();
|
||
|
||
if (result) {
|
||
// 栅格化成功,启动液化
|
||
message.success("图层已成功栅格化,可以进行液化操作");
|
||
this._startLiquify(layerId);
|
||
} else {
|
||
// 栅格化失败
|
||
Modal.error({
|
||
title: "栅格化失败",
|
||
content: "栅格化失败,无法进行液化操作",
|
||
okText: "确定",
|
||
centered: true,
|
||
});
|
||
}
|
||
} catch (error) {
|
||
console.error("栅格化图层失败:", error);
|
||
Modal.error({
|
||
title: "栅格化错误",
|
||
content: `栅格化失败:${error.message}`,
|
||
okText: "确定",
|
||
centered: true,
|
||
});
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 开始液化操作
|
||
* @param {String} layerId 图层ID
|
||
* @private
|
||
*/
|
||
async _startLiquify(layerId) {
|
||
// 获取图层信息
|
||
const layer = this.layerManager.getLayerById(layerId);
|
||
if (!layer) {
|
||
Modal.error({
|
||
title: "图层错误",
|
||
content: "图层不存在",
|
||
okText: "确定",
|
||
centered: true,
|
||
});
|
||
return;
|
||
}
|
||
|
||
// 检查图层是否为空
|
||
let targetObject = null;
|
||
if (layer.isBackground) {
|
||
// 背景图层使用 fabricObject (单数)
|
||
if (!layer.fabricObject) {
|
||
Modal.warning({
|
||
title: "背景图层为空",
|
||
content: "背景图层为空,无法进行液化操作",
|
||
okText: "确定",
|
||
centered: true,
|
||
});
|
||
return;
|
||
}
|
||
targetObject = layer.fabricObject;
|
||
} else {
|
||
// 普通图层使用 fabricObjects (复数)
|
||
if (!layer.fabricObjects || layer.fabricObjects.length === 0) {
|
||
Modal.warning({
|
||
title: "图层为空",
|
||
content: "图层为空,无法进行液化操作",
|
||
okText: "确定",
|
||
centered: true,
|
||
});
|
||
return;
|
||
}
|
||
targetObject = layer.fabricObjects[0];
|
||
}
|
||
|
||
// 确保liquifyManager可用
|
||
const liquifyManager = this.canvasManager?.liquifyManager;
|
||
if (!liquifyManager) {
|
||
Modal.error({
|
||
title: "液化管理器错误",
|
||
content: "液化管理器未初始化",
|
||
okText: "确定",
|
||
centered: true,
|
||
});
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// 显示准备中的Modal
|
||
const preparingModal = Modal.info({
|
||
title: "准备液化环境",
|
||
content: "正在准备液化环境,请稍候...",
|
||
okButtonProps: { style: { display: "none" } },
|
||
centered: true,
|
||
closable: false,
|
||
maskClosable: false,
|
||
});
|
||
|
||
// 准备液化环境
|
||
liquifyManager.initialize({
|
||
canvas: this.canvas,
|
||
layerManager: this.layerManager,
|
||
});
|
||
|
||
// 准备液化操作,获取原始图像数据
|
||
const prepareResult = await liquifyManager.prepareForLiquify(
|
||
targetObject
|
||
);
|
||
|
||
// 创建和初始化命令
|
||
const initCommand = new InitLiquifyToolCommand({
|
||
canvas: this.canvas,
|
||
layerManager: this.layerManager,
|
||
liquifyManager,
|
||
toolManager: this,
|
||
});
|
||
|
||
// 执行初始化命令
|
||
await this.commandManager.execute(initCommand);
|
||
|
||
// 关闭准备Modal
|
||
preparingModal.destroy();
|
||
|
||
// 触发液化面板显示事件
|
||
document.dispatchEvent(
|
||
new CustomEvent("showLiquifyPanel", {
|
||
detail: {
|
||
targetObject,
|
||
targetLayerId: layerId,
|
||
originalImageData: prepareResult.originalImageData,
|
||
},
|
||
})
|
||
);
|
||
} catch (error) {
|
||
console.error("启动液化工具失败:", error);
|
||
Modal.error({
|
||
title: "液化工具启动失败",
|
||
content: `启动液化工具失败:${error.message}`,
|
||
okText: "确定",
|
||
centered: true,
|
||
});
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 触发文件上传操作
|
||
*/
|
||
openFile() {
|
||
this.onFileSelected?.();
|
||
}
|
||
|
||
/**
|
||
* 设置文件选择回调
|
||
* @param {Function} callback 回调函数
|
||
*/
|
||
setFileUploadHandler(callback) {
|
||
this.onFileSelected = callback;
|
||
}
|
||
|
||
/**
|
||
* 更新笔刷大小
|
||
* @param {Number} size 笔刷大小
|
||
*/
|
||
updateBrushSize(size) {
|
||
if (!this.canvas || !this.brushManager) return;
|
||
|
||
// 更新BrushStore
|
||
BrushStore.setBrushSize(size);
|
||
|
||
// 直接更新笔刷管理器
|
||
this.brushManager.setBrushSize(size);
|
||
|
||
// 更新应用到画布
|
||
this.brushManager.updateBrush();
|
||
}
|
||
|
||
/**
|
||
* 创建文字对象并添加到画布
|
||
* @param {Number} x 文本位置x坐标
|
||
* @param {Number} y 文本位置y坐标
|
||
* @param {Object} options 文本选项
|
||
*/
|
||
createText(x, y, options = {}) {
|
||
if (!this.canvas || !this.layerManager) return null;
|
||
|
||
// 默认文本属性
|
||
const defaultOptions = {
|
||
text: "双击编辑文本",
|
||
fontFamily: "Arial",
|
||
fontSize: 24,
|
||
fontWeight: "normal",
|
||
fontStyle: "normal",
|
||
textAlign: "left",
|
||
fill: "#000000",
|
||
opacity: 1,
|
||
underline: false,
|
||
overline: false,
|
||
linethrough: false,
|
||
textBackgroundColor: "transparent",
|
||
lineHeight: 1.16,
|
||
charSpacing: 0,
|
||
};
|
||
|
||
// 合并默认选项和用户选项
|
||
const textOptions = { ...defaultOptions, ...options, left: x, top: y };
|
||
|
||
// 创建文本对象
|
||
const textObj = new fabric.IText(textOptions.text, {
|
||
...textOptions,
|
||
originX: "center",
|
||
originY: "center",
|
||
id: options.id || this.generateId(), // 生成唯一ID
|
||
});
|
||
|
||
// 创建文本图层并通过LayerManager添加到画布
|
||
this.layerManager.createTextLayerWithObject(textObj, textOptions);
|
||
this.canvas.renderAll();
|
||
|
||
return textObj;
|
||
}
|
||
|
||
/**
|
||
* 生成唯一ID
|
||
* @returns {String} 唯一ID
|
||
*/
|
||
generateId() {
|
||
return "text_" + Date.now() + "_" + Math.floor(Math.random() * 1000);
|
||
}
|
||
|
||
/**
|
||
* 更新笔刷颜色
|
||
* @param {String} color 颜色值,如 "#ff0000"
|
||
*/
|
||
updateBrushColor(color) {
|
||
if (!this.canvas || !this.brushManager) return;
|
||
|
||
// 更新BrushStore
|
||
BrushStore.setBrushColor(color);
|
||
|
||
// 直接更新笔刷管理器
|
||
this.brushManager.setBrushColor(color);
|
||
|
||
// 更新应用到画布
|
||
this.brushManager.updateBrush();
|
||
}
|
||
|
||
/**
|
||
* 更新笔刷透明度
|
||
* @param {Number} opacity 透明度值,范围 0-1
|
||
*/
|
||
updateBrushOpacity(opacity) {
|
||
if (!this.canvas || !this.brushManager) return;
|
||
|
||
// 更新BrushStore
|
||
BrushStore.setBrushOpacity(opacity);
|
||
|
||
// 直接更新笔刷管理器
|
||
this.brushManager.setBrushOpacity(opacity);
|
||
|
||
// 更新应用到画布
|
||
this.brushManager.updateBrush();
|
||
}
|
||
|
||
/**
|
||
* 设置双击文本的事件监听
|
||
* 当用户双击文本对象时,显示文本编辑弹窗
|
||
*/
|
||
setupTextEditingEvents() {
|
||
if (!this.canvas) return;
|
||
|
||
// 如果已有监听器,先移除以避免重复
|
||
if (this._textEditHandler) {
|
||
this.canvas.off("mouse:dblclick", this._textEditHandler);
|
||
}
|
||
|
||
// 创建双击事件处理函数
|
||
this._textEditHandler = (e) => {
|
||
const target = e.target;
|
||
if (
|
||
target &&
|
||
(target.type === "text" ||
|
||
target.type === "i-text" ||
|
||
target.type === "textbox")
|
||
) {
|
||
// 获取对应的图层
|
||
const layer = this.layerManager.getLayerById(target.layerId);
|
||
if (layer) {
|
||
// 显示文本编辑面板
|
||
this.showTextEditor(target, layer);
|
||
}
|
||
}
|
||
};
|
||
|
||
// 添加双击事件监听
|
||
this.canvas.on("mouse:dblclick", this._textEditHandler);
|
||
}
|
||
|
||
/**
|
||
* 显示文本编辑面板
|
||
* @param {Object} textObject 文本对象
|
||
* @param {Object} layer 图层对象
|
||
*/
|
||
showTextEditor(textObject, layer) {
|
||
// 这个方法将在TextEditorPanel组件实现后调用
|
||
console.log("显示文本编辑面板", textObject, layer);
|
||
// 将发出一个事件,让Vue组件捕获并显示编辑面板
|
||
document.dispatchEvent(
|
||
new CustomEvent("showTextEditor", {
|
||
detail: {
|
||
textObject,
|
||
layer,
|
||
},
|
||
})
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 清理资源
|
||
*/
|
||
dispose() {
|
||
if (this.brushManager) {
|
||
this.brushManager.dispose();
|
||
}
|
||
|
||
// 移除文本编辑相关事件监听器
|
||
if (this.canvas) {
|
||
this.canvas.off("mouse:dblclick", this._textEditHandler);
|
||
}
|
||
|
||
this._textEditHandler = null;
|
||
|
||
this.observers = []; // 清空观察者
|
||
this.canvas = null;
|
||
this.commandManager = null;
|
||
this.onFileSelected = null;
|
||
}
|
||
|
||
/**
|
||
* 设置文本工具
|
||
*/
|
||
setupTextTool() {
|
||
if (!this.canvas) return;
|
||
|
||
this.canvas.isDrawingMode = false;
|
||
this.canvas.selection = false;
|
||
|
||
console.log("文本工具已激活");
|
||
}
|
||
|
||
/**
|
||
* 设置红色笔刷工具(红绿图模式专用)
|
||
*/
|
||
setupRedBrushTool() {
|
||
if (!this.canvas) return;
|
||
|
||
this.canvas.isDrawingMode = true;
|
||
this.canvas.selection = false;
|
||
|
||
// 确保有笔刷管理器
|
||
if (this.brushManager) {
|
||
// 设置红色笔刷
|
||
this.brushManager.setBrushColor("#FF0000"); // 纯红色
|
||
this.brushManager.setBrushOpacity(1.0); // 完全不透明
|
||
this.brushManager.setBrushType("pencil"); // 铅笔类型
|
||
|
||
// 更新笔刷大小(使用当前大小)
|
||
if (BrushStore && BrushStore.state.size) {
|
||
this.brushManager.setBrushSize(BrushStore.state.size);
|
||
}
|
||
|
||
// 更新应用到画布
|
||
this.brushManager.updateBrush();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 设置绿色笔刷工具(红绿图模式专用)
|
||
*/
|
||
setupGreenBrushTool() {
|
||
if (!this.canvas) return;
|
||
|
||
this.canvas.isDrawingMode = true;
|
||
this.canvas.selection = false;
|
||
|
||
// 确保有笔刷管理器
|
||
if (this.brushManager) {
|
||
// 设置绿色笔刷
|
||
this.brushManager.setBrushColor("#00FF00"); // 纯绿色
|
||
this.brushManager.setBrushOpacity(1.0); // 完全不透明
|
||
this.brushManager.setBrushType("pencil"); // 铅笔类型
|
||
|
||
// 更新笔刷大小(使用当前大小)
|
||
if (BrushStore && BrushStore.state.size) {
|
||
this.brushManager.setBrushSize(BrushStore.state.size);
|
||
}
|
||
|
||
// 更新应用到画布
|
||
this.brushManager.updateBrush();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 进入红绿图模式
|
||
* @param {Object} redGreenModeManager 红绿图模式管理器实例
|
||
*/
|
||
enterRedGreenMode(redGreenModeManager) {
|
||
this.isRedGreenMode = true;
|
||
this.redGreenModeManager = redGreenModeManager;
|
||
|
||
// 切换到红色笔刷工具作为默认工具
|
||
this.setTool(OperationType.RED_BRUSH);
|
||
|
||
console.log("工具管理器已进入红绿图模式");
|
||
}
|
||
|
||
/**
|
||
* 退出红绿图模式
|
||
*/
|
||
exitRedGreenMode() {
|
||
this.isRedGreenMode = false;
|
||
this.redGreenModeManager = null;
|
||
|
||
// 切换回选择工具
|
||
this.setTool(OperationType.SELECT);
|
||
|
||
console.log("工具管理器已退出红绿图模式");
|
||
}
|
||
|
||
/**
|
||
* 检查工具是否在红绿图模式下可用
|
||
* @param {String} toolId 工具ID
|
||
* @returns {Boolean} 是否可用
|
||
*/
|
||
isToolAvailableInRedGreenMode(toolId) {
|
||
if (!this.isRedGreenMode) return true;
|
||
|
||
const tool = this.tools[toolId];
|
||
return tool && tool.allowedInRedGreen === true;
|
||
}
|
||
|
||
/**
|
||
* 获取红绿图模式下可用的工具列表
|
||
* @returns {Array} 工具列表
|
||
*/
|
||
getRedGreenModeTools() {
|
||
return Object.keys(this.tools)
|
||
.filter((toolId) => this.tools[toolId].allowedInRedGreen === true)
|
||
.map((toolId) => ({
|
||
id: toolId,
|
||
...this.tools[toolId],
|
||
}));
|
||
}
|
||
|
||
/**
|
||
* 检查是否为红绿图模式专用工具
|
||
* @param {String} toolId 工具ID
|
||
* @returns {Boolean} 是否为红绿图模式专用工具
|
||
*/
|
||
isRedGreenOnlyTool(toolId) {
|
||
const tool = this.tools[toolId];
|
||
return tool && tool.redGreenOnly === true;
|
||
}
|
||
}
|