From 86f1efbf432f7fdc8d1fc97af526e4f66d28ed5a Mon Sep 17 00:00:00 2001 From: lzp Date: Thu, 12 Mar 2026 11:40:48 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=B1=E5=BA=A6=E7=94=BB=E5=B8=83=E9=94=AE?= =?UTF-8?q?=E7=9B=98=E4=BA=8B=E4=BB=B6-=E6=92=A4=E5=9B=9E=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.development | 2 +- .../components/details-panel/index.vue | 64 +- .../Canvas/DepthCanvas/depth-canvas.vue | 18 +- .../DepthCanvas/manager/CanvasManager.ts | 32 +- .../DepthCanvas/manager/EraserStateManager.js | 1 + .../DepthCanvas/manager/LayerManager.ts | 12 + .../DepthCanvas/manager/StateManager.ts | 64 +- .../Canvas/DepthCanvas/manager/ToolManager.ts | 5 +- .../manager/events/CanvasEventManager.js | 184 +---- .../manager/events/KeyEventManager.ts | 35 + .../manager/events/KeyboardManager.js | 762 ------------------ 11 files changed, 228 insertions(+), 951 deletions(-) create mode 100644 src/components/Canvas/DepthCanvas/manager/events/KeyEventManager.ts delete mode 100644 src/components/Canvas/DepthCanvas/manager/events/KeyboardManager.js diff --git a/.env.development b/.env.development index 8eca712..f844c2d 100644 --- a/.env.development +++ b/.env.development @@ -1,5 +1,5 @@ # VITE_APP_URL = http://192.168.31.82:8771 VITE_APP_URL = http://18.167.251.121:10015 # VITE_APP_URL = http://192.168.31.118:8080 -VITE_APP_URL = http://192.168.31.82:8755 +# VITE_APP_URL = http://192.168.31.82:8755 VITE_GOOGLE_CLIENT_ID = 216037134725-7q8vqp0ohtmohlosltkfg7bd2v29rm5a.apps.googleusercontent.com diff --git a/src/components/Canvas/DepthCanvas/components/details-panel/index.vue b/src/components/Canvas/DepthCanvas/components/details-panel/index.vue index 289ec28..3439d21 100644 --- a/src/components/Canvas/DepthCanvas/components/details-panel/index.vue +++ b/src/components/Canvas/DepthCanvas/components/details-panel/index.vue @@ -7,16 +7,57 @@ -
- 这是一些设置参数... +
+
+ X: + {{ activeObject.left }} +
+
+ Y: + {{ activeObject.top }} +
+
+ Width: + {{ activeObject.width }} +
+
+ Height: + {{ activeObject.height }} +
+
+ 缩放X: + {{ activeObject.scaleX }} +
+
+ 缩放Y: + {{ activeObject.scaleY }} +
+
+ Angle: + {{ activeObject.angle }} +
diff --git a/src/components/Canvas/DepthCanvas/depth-canvas.vue b/src/components/Canvas/DepthCanvas/depth-canvas.vue index 1f56296..41fae1f 100644 --- a/src/components/Canvas/DepthCanvas/depth-canvas.vue +++ b/src/components/Canvas/DepthCanvas/depth-canvas.vue @@ -20,7 +20,7 @@ diff --git a/src/components/Canvas/DepthCanvas/manager/CanvasManager.ts b/src/components/Canvas/DepthCanvas/manager/CanvasManager.ts index bb9c713..f23f79b 100644 --- a/src/components/Canvas/DepthCanvas/manager/CanvasManager.ts +++ b/src/components/Canvas/DepthCanvas/manager/CanvasManager.ts @@ -59,6 +59,18 @@ export class CanvasManager { const canvasY = this.canvasViewHeight / 2 - this.canvasHeight / 2 this.canvas.viewportTransform = [1, 0, 0, 1, canvasX, canvasY] + // 动画管理器 + this.animationManager = new AnimationManager(this.canvas, { + currentZoom: this.currentZoom, + canvasManager: this, + wheelThrottleTime: 15, // 降低滚轮事件节流时间,提高响应性 + defaultEase: "power2.lin", + defaultDuration: 0.3, // 缩短默认动画时间 + }); + this.setupCanvasEvents() + this.setupBrushEvents() + + this.stateManager.toolManager.setTool(OperationType.SELECT) // 创建矩形 const rect = this.layerManager.createRectLayer({ left: 400, @@ -71,21 +83,9 @@ export class CanvasManager { }) // 文字 const text = this.layerManager.createTextLayer('Hello World'); - - this.animationManager = new AnimationManager(this.canvas, { - currentZoom: this.currentZoom, - canvasManager: this, - wheelThrottleTime: 15, // 降低滚轮事件节流时间,提高响应性 - defaultEase: "power2.lin", - defaultDuration: 0.3, // 缩短默认动画时间 - }); - this.setupCanvasEvents() - this.stateManager.toolManager.setTool(OperationType.SELECT) this.layerManager.updateLayers() this.layerManager.setActiveID(text.info.id) - - - this.setupBrushEvents() + this.stateManager.recordState() } /** 画布添加对象 */ add(obj: any, isUpdate = true) { @@ -104,6 +104,7 @@ export class CanvasManager { animationManager: this.animationManager, toolManager: this.stateManager.toolManager, layerManager: this.stateManager.layerManager, + stateManager: this.stateManager, }); // 设置动画交互效果 this.animationManager.setupInteractionAnimations(); @@ -176,7 +177,6 @@ export class CanvasManager { getCanvasJSON() { const keys = ["top", "left", "width", "height", "scaleX", "scaleY", "info",] const json = this.canvas.toJSON(keys) - console.log(json, this.getObjects()) return JSON.stringify(json) } /** 加载画布JSON */ @@ -195,4 +195,8 @@ export class CanvasManager { }) } + dispose() { + this.animationManager?.dispose() + this.eventManager?.dispose() + } } diff --git a/src/components/Canvas/DepthCanvas/manager/EraserStateManager.js b/src/components/Canvas/DepthCanvas/manager/EraserStateManager.js index 49e0476..4fe6d39 100644 --- a/src/components/Canvas/DepthCanvas/manager/EraserStateManager.js +++ b/src/components/Canvas/DepthCanvas/manager/EraserStateManager.js @@ -96,4 +96,5 @@ export class EraserStateManager { this.currentSnapshot = null; this.pendingCommand = null; } + dispose() {} } diff --git a/src/components/Canvas/DepthCanvas/manager/LayerManager.ts b/src/components/Canvas/DepthCanvas/manager/LayerManager.ts index 53957db..07f2fe1 100644 --- a/src/components/Canvas/DepthCanvas/manager/LayerManager.ts +++ b/src/components/Canvas/DepthCanvas/manager/LayerManager.ts @@ -53,6 +53,17 @@ export class LayerManager { // 更新图层列表 updateLayers() { this.layers.value = this.canvasManager.getObjects().filter((v: any) => !!v?.info?.id).reverse() + window["layers"] = this.layers + } + // 更新图层参数 + updateLayerParams(layer, keys = []) { + this.layers.value.forEach((item: any) => { + if (item.info.id === layer.info.id) { + keys.forEach((key: string) => { + item.set(key, layer[key]) + }) + } + }) } /** 设置图层位置-不设置默认居中 */ @@ -204,4 +215,5 @@ export class LayerManager { }) }) } + dispose() {} } \ No newline at end of file diff --git a/src/components/Canvas/DepthCanvas/manager/StateManager.ts b/src/components/Canvas/DepthCanvas/manager/StateManager.ts index cb05bde..d033828 100644 --- a/src/components/Canvas/DepthCanvas/manager/StateManager.ts +++ b/src/components/Canvas/DepthCanvas/manager/StateManager.ts @@ -9,6 +9,7 @@ export class StateManager { mxHistory: any historyList: any historyIndex: any + running: any // 管理器 canvasManager: any @@ -16,6 +17,7 @@ export class StateManager { eventManager: any toolManager: any brushManager: any + keyEventManager: any // 设置管理器 setManager(options) { options.eventManager && (this.eventManager = options.eventManager) @@ -23,45 +25,65 @@ export class StateManager { options.layerManager && (this.layerManager = options.layerManager) options.toolManager && (this.toolManager = options.toolManager) options.brushManager && (this.brushManager = options.brushManager) + options.keyEventManager && (this.keyEventManager = options.keyEventManager) } constructor(options) { this.mxHistory = ref(50) this.historyList = ref([]) this.historyIndex = ref(0) + this.running = ref(false) } + /** 清空状态 */ + clearState(isRecordCurrentState?: boolean) { + this.historyList.value = [] + this.historyIndex.value = 0 + this.running.value = false + if (isRecordCurrentState) this.recordState() + } /** 记录状态 */ recordState() { - // if (this.historyIndex.value < this.historyList.value.length - 1) { - // this.historyList.value.splice(this.historyIndex.value + 1) - // } - // const state = { - // nodes: JSON.stringify(this.nodes.value) - // } - // this.historyList.value.push(state) - // const size = this.historyList.value.length - this.mxHistory.value - // if (size > 0) this.historyList.value.splice(0, size) - // this.historyIndex.value = this.historyList.value.length - 1 + if (this.running.value) return + this.running.value = true + if (this.historyIndex.value < this.historyList.value.length - 1) { + this.historyList.value.splice(this.historyIndex.value + 1) + } + const state = { + canvas: this.canvasManager.getCanvasJSON(), + } + this.historyList.value.push(state) + const size = this.historyList.value.length - this.mxHistory.value + if (size > 0) this.historyList.value.splice(0, size) + this.historyIndex.value = this.historyList.value.length - 1 + this.running.value = false } /** 撤回状态 */ undoState() { - // var index = this.historyIndex.value - 1 - // const state = this.historyList.value[index] - // if (!state) return - // this.historyIndex.value = index - // this.nodes.value = JSON.parse(state.nodes) + if (this.running.value) return + var index = this.historyIndex.value - 1 + const state = this.historyList.value[index] + if (!state) return + this.running.value = true + this.historyIndex.value = index + this.canvasManager.loadJSON(state.canvas, () => { + this.running.value = false + }) } /** 重做状态 */ redoState() { - // var index = this.historyIndex.value + 1 - // const state = this.historyList.value[index] - // if (!state) return - // this.historyIndex.value = index - // this.nodes.value = JSON.parse(state.nodes) + if (this.running.value) return + var index = this.historyIndex.value + 1 + const state = this.historyList.value[index] + if (!state) return + this.running.value = true + this.historyIndex.value = index + this.canvasManager.loadJSON(state.canvas, () => { + this.running.value = false + }) } - + dispose() { } } diff --git a/src/components/Canvas/DepthCanvas/manager/ToolManager.ts b/src/components/Canvas/DepthCanvas/manager/ToolManager.ts index 51d1f23..8b17c25 100644 --- a/src/components/Canvas/DepthCanvas/manager/ToolManager.ts +++ b/src/components/Canvas/DepthCanvas/manager/ToolManager.ts @@ -170,5 +170,8 @@ export class ToolManager { this.brushIndicator.disable(); } - + dispose() { + this.brushIndicator?.dispose() + this.brushManager?.dispose() + } } diff --git a/src/components/Canvas/DepthCanvas/manager/events/CanvasEventManager.js b/src/components/Canvas/DepthCanvas/manager/events/CanvasEventManager.js index b073193..8571fb7 100644 --- a/src/components/Canvas/DepthCanvas/manager/events/CanvasEventManager.js +++ b/src/components/Canvas/DepthCanvas/manager/events/CanvasEventManager.js @@ -6,6 +6,7 @@ export class CanvasEventManager { this.canvas = canvas; this.canvasManager = options.canvasManager; this.toolManager = options.toolManager || null; + this.stateManager = options.stateManager || null; this.animationManager = options.animationManager; this.thumbnailManager = options.thumbnailManager; this.activeElementId = options.activeElementId || { value: null }; @@ -44,7 +45,7 @@ export class CanvasEventManager { // 共享事件 this.setupSelectionEvents(); - // this.setupObjectEvents(); + this.setupObjectEvents(); // this.setupDoubleClickEvents(); // this.setupHandlePathCreated(); @@ -632,129 +633,47 @@ export class CanvasEventManager { setupObjectEvents() { // 监听对象变化事件,用于更新缩略图 - this.canvas.on("object:added", (e) => { - if (this.thumbnailManager && e.target && e.target.id) { - // 延迟更新以确保对象完全添加 - setTimeout(() => { - // 现在图层就是元素本身,直接更新元素的缩略图 - this.thumbnailManager.generateLayerThumbnail(e.target.layerId); - }, 300); - } - }); + // this.canvas.on("object:added", (e) => { + // if (this.thumbnailManager && e.target && e.target.id) { + // // 延迟更新以确保对象完全添加 + // setTimeout(() => { + // // 现在图层就是元素本身,直接更新元素的缩略图 + // this.thumbnailManager.generateLayerThumbnail(e.target.layerId); + // }, 300); + // } + // }); + + const updateLayers = (e) => { + if (e.target._objects) return; + this.layerManager.updateLayers(); + }; // 添加对象开始变换时的状态捕获 - this.canvas.on( - "object:moving", - this._captureInitialTransformState.bind(this) - ); - this.canvas.on( - "object:scaling", - this._captureInitialTransformState.bind(this) - ); - this.canvas.on( - "object:rotating", - this._captureInitialTransformState.bind(this) - ); - this.canvas.on( - "object:skewing", - this._captureInitialTransformState.bind(this) - ); - + this.canvas.on("object:moving", (e) => { + // console.log("object:moving", e); + updateLayers(e); + }); + this.canvas.on("object:scaling", (e) => { + // console.log("object:scaling", e); + updateLayers(e); + }); + this.canvas.on("object:rotating", (e) => { + // console.log("object:rotating", e); + updateLayers(e); + }); + this.canvas.on("object:skewing", (e) => { + // console.log("object:skewing", e); + updateLayers(e); + }); this.canvas.on("object:modified", (e) => { - // 移除调试日志 - // console.log("object:modified", e); - - const activeObj = e.target || this.canvas.getActiveObject(); - - if (activeObj && this.layerManager?.commandManager) { - // 使用新的轻量级 TransformCommand 替代完整状态保存 - // 检查对象是否有初始变换状态记录 - if (activeObj._initialTransformState) { - // 创建并执行 TransformCommand,只记录变换属性的变化 - const transformCmd = new TransformCommand({ - canvas: this.canvas, - objectId: activeObj.id, - initialState: activeObj._initialTransformState, - finalState: TransformCommand.captureTransformState(activeObj), - objectType: activeObj.type, - name: `变换 ${activeObj.type || "对象"}`, - layerManager: this.layerManager, - layers: this.layers, - lastSelectLayerId: this.lastSelectLayerId, - }); - - // 执行并将命令添加到历史栈 - this.layerManager.commandManager.execute(transformCmd, { - name: "对象修改", - }); - // 清除临时状态记录 - delete activeObj._initialTransformState; - } - } else { - this.canvasManager.changeCanvas(); - } - - if (this.thumbnailManager && e.target) { - if (e.target.id) { - this.updateLayerThumbnail(e.target.id, e.target); - - // 如果该元素是分组图层的一部分,也更新分组图层的缩略图 - if (e.target.parentId) { - this.updateLayerThumbnail(e.target.parentId); - } - } - } + updateLayers(e); + this.stateManager.recordState(); }); - this.canvas.on("object:removed", (e) => { - if (this.thumbnailManager && e.target) { - if (e.target.id) { - // 如果该元素是分组图层的一部分,也更新分组图层的缩略图 - if (e.target.parentId) { - // setTimeout(() => this.updateLayerThumbnail(e.target.parentId), 50); - this.thumbnailManager.generateLayerThumbnail(e.target.parentId); - } - } - } + updateLayers(e); + this.stateManager.recordState(); }); - - // // 鼠标抬起时,检查是否需要保存状态 - // this.canvas.on("mouse:up", (e) => { - // // 只在选择模式下处理对象变换的状态保存 - // if (this.toolManager.currentTool.value !== OperationType.SELECT) { - // // 绘画、擦除等模式通过各自的命令管理状态,不需要在这里保存 - // return; - // } - - // const activeObj = this.canvas.getActiveObject(); - // if ( - // activeObj && - // activeObj._stateRecord && - // activeObj._stateRecord.isModifying - // ) { - // const original = activeObj._stateRecord.originalState; - - // // 检查是否是真正的变换操作(移动、缩放、旋转) - // const hasTransformChanged = - // original.left !== activeObj.left || - // original.top !== activeObj.top || - // original.scaleX !== activeObj.scaleX || - // original.scaleY !== activeObj.scaleY || - // original.angle !== activeObj.angle; - - // // 只有在对象发生变换且不是命令执行过程中时才保存状态 - // if (hasTransformChanged && this.layerManager) { - // // 立即保存状态,而不是延迟执行 - // this.layerManager.saveCanvasState(); - // delete activeObj._stateRecord; - // } else { - // // 清理状态记录,即使没有保存状态 - // delete activeObj._stateRecord; - // } - // } - // }); } - setupDoubleClickEvents() { // 双击处理 this.canvas.on("mouse:dblclick", (opt) => { @@ -878,15 +797,11 @@ export class CanvasEventManager { updateSelectedLayer(opt) { const selected = opt.selected[0]; - if (selected) { + if (selected && opt.selected.length === 1) { this.layerManager.setActiveID(selected?.info?.id, false) } } - // clearSelectedElements() { - // this.activeElementId.value = null; - // } - // 更新图层缩略图 updateLayerThumbnail(layerId) { if (!this.thumbnailManager || !layerId || !this.layers) return; @@ -937,31 +852,6 @@ export class CanvasEventManager { this.stopInertiaAnimation(); } - /** - * 捕获对象开始变换时的初始状态 - * @private - * @param {Object} e 事件对象 - */ - _captureInitialTransformState(e) { - const obj = e.target; - - // 只在首次触发变换事件时记录初始状态 - if (obj && !obj._initialTransformState && obj.id) { - // 捕获对象的初始变换状态 - obj._initialTransformState = TransformCommand.captureTransformState(obj); - - // 添加调试日志(可选) - // console.log(`捕获对象 ${obj.id} (${obj.type}) 的初始变换状态`); - } - const arrs = []; - if (e.target._objects) { - e.target._objects.forEach((v) => arrs.push(v)); - } else { - arrs.push(e.target); - } - this.canvasManager.beforeChangeCanvas(arrs); - } - /** * 精确检测设备类型,区分 PC、Mac、平板和移动设备 * @private diff --git a/src/components/Canvas/DepthCanvas/manager/events/KeyEventManager.ts b/src/components/Canvas/DepthCanvas/manager/events/KeyEventManager.ts new file mode 100644 index 0000000..e863bab --- /dev/null +++ b/src/components/Canvas/DepthCanvas/manager/events/KeyEventManager.ts @@ -0,0 +1,35 @@ +export class KeyEventManager { + stateManager: any + constructor(options) { + this.stateManager = options.stateManager; + this.registerEvents() + } + + /** 处理键盘事件 */ + handleKeyDown(event: any) { + const ctrl = event.ctrlKey ? 'ctrl-' : ""; + const shift = event.shiftKey ? 'shift-' : ""; + const key = event.key; + const reg = new RegExp(`^${ctrl}${shift}${key}$`, 'i') + const list = [ + // { key: "ctrl-c", handler: () => this.handleCopy(event) }, + // { key: "delete", handler: () => this.handleDelete(event) }, + { key: "ctrl-z", handler: () => this.stateManager.undoState() }, + { key: "ctrl-shift-z", handler: () => this.stateManager.redoState() }, + ] + list.forEach((v: any) => { + if (reg.test(v.key)) v.handler(event) + }) + } + /** 注册事件 */ + registerEvents() { + document.addEventListener('keydown', this.handleKeyDown.bind(this)) + } + /** 删除事件 */ + removeEvents() { + document.removeEventListener('keydown', this.handleKeyDown.bind(this)) + } + dispose() { + this.removeEvents() + } +} diff --git a/src/components/Canvas/DepthCanvas/manager/events/KeyboardManager.js b/src/components/Canvas/DepthCanvas/manager/events/KeyboardManager.js deleted file mode 100644 index 3e711f3..0000000 --- a/src/components/Canvas/DepthCanvas/manager/events/KeyboardManager.js +++ /dev/null @@ -1,762 +0,0 @@ -/** - * 键盘管理器 - * 负责处理编辑器中的键盘事件和快捷键 - * 支持PC、Mac和iPad三端适配 - */ -export class KeyboardManager { - /** - * 创建键盘管理器 - * @param {Object} options 配置选项 - * @param {Object} options.toolManager 工具管理器实例 - * @param {Object} options.commandManager 命令管理器实例 - * @param {Object} options.layerManager 图层管理器实例 - * @param {Object} options.canvasManager 画布管理器实例 - * @param {Function} options.pasteText 粘贴文本回调函数 - * @param {Function} options.pasteImage 粘贴图片回调函数 - * @param {Ref} options.isRedGreenMode 是否为红绿模式 - * @param {HTMLElement} options.container 容器元素,用于添加事件监听 - */ - constructor(options = {}) { - this.toolManager = options.toolManager; - this.commandManager = options.commandManager; - this.layerManager = options.layerManager; - this.canvasManager = options.canvasManager; - this.container = options.container || document; - this.pasteText = options.pasteText || (() => { }); - this.pasteImage = options.pasteImage || (() => { }); - this.isRedGreenMode = options.isRedGreenMode; - - // 检测平台类型 - this.platform = this.detectPlatform(); - this.isTouchDevice = this.detectTouchDevice(); - - // 快捷键的平台特定键名 - this.modifierKeys = { - ctrl: this.platform === "mac" ? "meta" : "ctrl", - cmdOrCtrl: this.platform === "mac" ? "meta" : "ctrl", - alt: "alt", - shift: "shift", - option: "alt", // Mac 特有,等同于 alt - cmd: "meta", // Mac 特有,等同于 Command - }; - - // 快捷键显示的平台特定符号 - this.keySymbols = { - ctrl: this.platform === "mac" ? "⌃" : "Ctrl", - meta: this.platform === "mac" ? "⌘" : "Win", - alt: this.platform === "mac" ? "⌥" : "Alt", - shift: this.platform === "mac" ? "⇧" : "Shift", - escape: "Esc", - space: "空格", - }; - - // 快捷键映射表 - 可通过配置进行扩展 - this.shortcuts = this.initShortcuts(); - - // 触摸相关状态 - this.touchState = { - pinchStartDistance: 0, - pinchStartBrushSize: 0, - touchStartX: 0, - touchStartY: 0, - isTwoFingerTouch: false, - }; - - // 临时工具状态 - this.tempToolState = { - active: false, - originalTool: null, - }; - - // 事件绑定 - this._handleKeyDown = this.handleKeyDown.bind(this); - this._handleKeyUp = this.handleKeyUp.bind(this); - this._handlePaste = this.handlePaste.bind(this); - this._handleTouchStart = this.handleTouchStart.bind(this); - this._handleTouchMove = this.handleTouchMove.bind(this); - this._handleTouchEnd = this.handleTouchEnd.bind(this); - - // 已注册的自定义事件处理程序 - this.customHandlers = {}; - } - - /** - * 检测当前平台 - * @returns {'mac'|'windows'|'ios'|'android'|'other'} 平台类型 - */ - detectPlatform() { - const userAgent = navigator.userAgent.toLowerCase(); - - if (userAgent.indexOf("mac") !== -1) return "mac"; - if (userAgent.indexOf("win") !== -1) return "windows"; - if (/(iphone|ipad|ipod)/.test(userAgent)) return "ios"; - if (userAgent.indexOf("android") !== -1) return "android"; - - return "other"; - } - - /** - * 检测是否为触摸设备 - * @returns {boolean} 是否为触摸设备 - */ - detectTouchDevice() { - return ( - "ontouchstart" in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0 - ); - } - - /** - * 初始化快捷键配置 - * @returns {Object} 快捷键配置 - */ - initShortcuts() { - const cmdOrCtrl = this.modifierKeys.cmdOrCtrl; - - // 基本快捷键映射,将在构建时根据平台类型自动调整 - return { - // 撤销/重做 - [`${cmdOrCtrl}+z`]: { action: "undo", description: "撤销" }, - [`${cmdOrCtrl}+shift+z`]: { action: "redo", description: "重做" }, - [`${cmdOrCtrl}+y`]: { action: "redo", description: "重做" }, - - // 复制/粘贴 - [`${cmdOrCtrl}+c`]: { action: "copy", description: "复制" }, - [`${cmdOrCtrl}+v`]: { action: "paste", description: "粘贴", noStop: true }, - [`${cmdOrCtrl}+x`]: { action: "cut", description: "剪切" }, - - // 删除 - delete: { action: "delete", description: "删除" }, - backspace: { action: "delete", description: "删除" }, - up: { action: "up", description: "上" }, - down: { action: "down", description: "下" }, - left: { action: "left", description: "左" }, - right: { action: "right", description: "右" }, - - // 选择 - [`${cmdOrCtrl}+a`]: { action: "selectAll", description: "全选" }, - escape: { action: "clearSelection", description: "取消选择" }, - - // 保存 - [`${cmdOrCtrl}+s`]: { action: "save", description: "保存" }, - - // 工具切换 (这些会由工具管理器处理) - v: { action: "selectTool", param: "select", description: "选择工具" }, - b: { action: "selectTool", param: "draw", description: "画笔工具" }, - e: { action: "selectTool", param: "eraser", description: "橡皮擦" }, - i: { action: "selectTool", param: "eyedropper", description: "吸色工具" }, - h: { action: "selectTool", param: "pan", description: "移动画布" }, - l: { action: "selectTool", param: "lasso", description: "套索工具" }, - m: { - action: "selectTool", - param: "area_custom", - description: "自由选区工具", - }, - w: { action: "selectTool", param: "wave", description: "波浪工具" }, - j: { action: "selectTool", param: "liquify", description: "液化工具" }, - - // 数值调整 - "shift+[": { - action: "decreaseTextureScale", - description: "减小材质图片大小", - }, - "shift+]": { - action: "increaseTextureScale", - description: "增大材质图片大小", - }, - "[": { action: "decreaseBrushSize", param: 1, description: "减小画笔" }, - "]": { action: "increaseBrushSize", param: 1, description: "增大画笔" }, - - ",": { - action: "decreaseBrushOpacity", - param: 0.01, - description: "减小透明度", - }, - ".": { - action: "increaseBrushOpacity", - param: 0.01, - description: "增大透明度", - }, - - // 空格 - 临时切换到手型工具 - space: { - action: "toggleTempTool", - param: "pan", - description: "临时切换到手形工具", - }, - - // 图层操作 - [`${cmdOrCtrl}+shift+n`]: { action: "newLayer", description: "新建图层" }, - [`${cmdOrCtrl}+g`]: { action: "groupLayers", description: "组合图层" }, - [`${cmdOrCtrl}+o`]: { - action: "addImageToNewLayer", - description: "上传图片到新图层", - }, - [`${cmdOrCtrl}+shift+g`]: { - action: "ungroupLayers", - description: "取消组合", - }, - [`${cmdOrCtrl}+j`]: { action: "mergeLayers", description: "合并图层" }, - - // iPad特有的快捷键(当无法使用键盘时) - ...(this.platform === "ios" && { - two_finger_tap: { - action: "contextMenu", - description: "显示上下文菜单", - }, - three_finger_swipe_left: { action: "undo", description: "撤销" }, - three_finger_swipe_right: { action: "redo", description: "重做" }, - }), - }; - } - - /** - * 处理粘贴事件 - * @param {ClipboardEvent} event 粘贴事件 - */ - handlePaste(event) { - event.preventDefault(); // 阻止默认粘贴行为 - if (this.isRedGreenMode.value) return; - const text = event.clipboardData?.getData("text/plain") || ""; - if (/^aida_copy_canvas_layer/.test(text)) return; - const items = event.clipboardData?.items || []; - // console.log(this); - for (const item of items) { - if (item.type.indexOf("text/plain") !== -1) { - item.getAsString((text) => { - this.pasteText(text); - }); - } else if (item.type.indexOf("image") !== -1) { - const blob = item.getAsFile(); - this.pasteImage(blob); - } - } - } - - /** - * 初始化并开始监听键盘事件 - */ - init() { - // 添加键盘事件监听 - this.container.addEventListener("keydown", this._handleKeyDown); - this.container.addEventListener("keyup", this._handleKeyUp); - this.container.addEventListener("paste", this._handlePaste); - - // 如果是触摸设备,添加触摸事件监听 - if (this.isTouchDevice) { - this.container.addEventListener("touchstart", this._handleTouchStart); - this.container.addEventListener("touchmove", this._handleTouchMove); - this.container.addEventListener("touchend", this._handleTouchEnd); - this.container.addEventListener("touchcancel", this._handleTouchEnd); - } - - // console.log(`键盘管理器已初始化,平台: ${this.platform}, 触摸设备: ${this.isTouchDevice}`); - } - - /** - * hide模式下,关闭快捷键 - */ - removeEvents() { - // 移除键盘事件监听 - this.container.removeEventListener("keydown", this._handleKeyDown); - this.container.removeEventListener("keyup", this._handleKeyUp); - this.container.removeEventListener("paste", this._handlePaste); - - // 如果是触摸设备,移除触摸事件监听 - if (this.isTouchDevice) { - this.container.removeEventListener("touchstart", this._handleTouchStart); - this.container.removeEventListener("touchmove", this._handleTouchMove); - this.container.removeEventListener("touchend", this._handleTouchEnd); - this.container.removeEventListener("touchcancel", this._handleTouchEnd); - } - } - - - - /** - * 处理键盘按下事件 - * @param {KeyboardEvent} event 键盘事件 - */ - handleKeyDown(event) { - // 如果当前焦点在输入框内,不处理大部分快捷键 - if (this.isInputActive() && !["Escape", "Tab"].includes(event.key)) { - return; - } - - // 构建快捷键标识符 - const shortcutKey = this.buildShortcutKey(event); - - // 查找并执行快捷键动作 - const shortcut = this.shortcuts[shortcutKey]; - if (shortcut) { - // 阻止默认行为,例如浏览器的保存对话框等 - if (shortcutKey.includes(`${this.modifierKeys.cmdOrCtrl}+`) && !shortcut.noStop) { - event.preventDefault(); - } - - this.executeAction(shortcut.action, shortcut.param, event); - return; - } - - // 工具快捷键处理 - if (this.toolManager && !event.ctrlKey && !event.metaKey && !event.altKey) { - this.toolManager.handleKeyboardShortcut(event); - } - } - - /** - * 处理键盘释放事件 - * @param {KeyboardEvent} event 键盘事件 - */ - handleKeyUp(event) { - // 当空格键释放时,如果是临时工具,切回原始工具 - if (event.key === " " && this.tempToolState.active) { - this.restoreTempTool(); - } - - // 调用自定义处理程序 - const key = event.key.toLowerCase(); - if (this.customHandlers[key] && typeof this.customHandlers[key].onKeyUp === "function") { - this.customHandlers[key].onKeyUp(event); - } - } - - /** - * 处理触摸开始事件 - * @param {TouchEvent} event 触摸事件 - */ - handleTouchStart(event) { - const touches = event.touches; - - // 存储初始状态以便后续计算 - if (touches.length === 2) { - // 双指触摸 - 可用于缩放或调整画笔大小 - this.touchState.isTwoFingerTouch = true; - this.touchState.pinchStartDistance = this.getDistanceBetweenTouches(touches[0], touches[1]); - - // 如果有画笔管理器,记录起始画笔大小 - if (this.toolManager && this.toolManager.brushManager) { - this.touchState.pinchStartBrushSize = this.toolManager.brushManager.brushSize.value; - } - } else if (touches.length === 3) { - // 三指触摸 - 可用于撤销/重做 - this.touchState.touchStartX = touches[0].clientX; - } - } - - /** - * 处理触摸移动事件 - * @param {TouchEvent} event 触摸事件 - */ - handleTouchMove(event) { - const touches = event.touches; - - // 阻止默认行为(例如滚动) - if (touches.length >= 2) { - event.preventDefault(); - } - - // 双指缩放处理 - 调整画笔大小 - if (touches.length === 2 && this.touchState.isTwoFingerTouch) { - const currentDistance = this.getDistanceBetweenTouches(touches[0], touches[1]); - const scale = currentDistance / this.touchState.pinchStartDistance; - - // 调整画笔大小 - if (this.toolManager && this.toolManager.brushManager && scale !== 1) { - const newSize = this.touchState.pinchStartBrushSize * scale; - this.toolManager.brushManager.setBrushSize(newSize); - } - } - // 三指滑动处理 - 撤销/重做 - else if (touches.length === 3) { - const deltaX = touches[0].clientX - this.touchState.touchStartX; - - // 滑动超过50px认为是有效的手势 - if (Math.abs(deltaX) > 50) { - if (deltaX < 0) { - // 向左滑动 - 撤销 - this.executeAction("undo"); - } else { - // 向右滑动 - 重做 - this.executeAction("redo"); - } - - // 更新起始位置,防止连续触发 - this.touchState.touchStartX = touches[0].clientX; - } - } - } - - /** - * 处理触摸结束事件 - * @param {TouchEvent} event 触摸事件 - */ - handleTouchEnd(event) { - // 检测双指轻拍 (两个手指几乎同时按下,又几乎同时抬起) - if (this.touchState.isTwoFingerTouch && event.touches.length === 0) { - if (new Date().getTime() - this.touchState.touchStartTime < 300) { - // 双指轻拍 - 可以触发上下文菜单 - this.executeAction("contextMenu"); - } - } - - // 重置触摸状态 - this.touchState.isTwoFingerTouch = false; - } - - /** - * 计算两个触摸点之间的距离 - * @param {Touch} touch1 第一个触摸点 - * @param {Touch} touch2 第二个触摸点 - * @returns {number} 两点间距离 - */ - getDistanceBetweenTouches(touch1, touch2) { - const dx = touch1.clientX - touch2.clientX; - const dy = touch1.clientY - touch2.clientY; - return Math.sqrt(dx * dx + dy * dy); - } - - /** - * 执行快捷键对应的动作 - * @param {string} action 动作名称 - * @param {*} param 动作参数 - * @param {Event} event 原始事件 - */ - executeAction(action, param, event) { - switch (action) { - case "undo": - if (this.commandManager) { - this.commandManager.undo(); - } - break; - - case "redo": - if (this.commandManager) { - this.commandManager.redo(); - } - break; - - case "copy": - // 复制逻辑 - // console.log("复制当前选中图层"); - if (this.isRedGreenMode.value) return; - this.layerManager.copyLayer(this.layerManager.activeLayerId.value); - break; - - case "paste": - // 粘贴逻辑 - // console.log("粘贴"); - if (this.isRedGreenMode.value) return; - this.layerManager.pasteLayer(); - break; - - case "cut": - // 剪切逻辑 - // console.log("剪切"); - if (this.isRedGreenMode.value) return; - this.layerManager.cutLayer(this.layerManager.activeLayerId.value); - break; - - case "delete": - // 删除逻辑 - // console.log("删除"); - if (this.isRedGreenMode.value) return; - this.layerManager.removeLayer(this.layerManager.activeLayerId.value); - break; - - case "selectAll": - // 全选逻辑 - // console.log("全选"); - if (this.isRedGreenMode.value) return; - // 这里需要实现全选逻辑 TODO: 是否在选择模式下才可以全选? - if (this.layerManager) { - this.layerManager.selectAll(); - } - break; - - case "clearSelection": - // 清除选择逻辑 - // console.log("清除选择"); - // 这里需要实现清除选择逻辑 - if (this.layerManager) { - this.layerManager.clearSelection(); - } - break; - - case "save": - // 保存逻辑 - // console.log("保存"); - break; - - case "selectTool": - // 选择工具 - if (this.toolManager && param) { - this.toolManager.setToolWithCommand(param); - } - break; - - case "up": - case "down": - case "left": - case "right": - // 方向键逻辑 - this.canvasManager.moveActiveObject(action); - break; - - case "increaseBrushSize": - // 增大画笔尺寸 - if (this.toolManager && this.toolManager.brushManager) { - const amount = param || 5; - this.toolManager.brushManager.increaseBrushSize(amount); - } - break; - - case "decreaseBrushSize": - // 减小画笔尺寸 - if (this.toolManager && this.toolManager.brushManager) { - const amount = param || 5; - this.toolManager.brushManager.decreaseBrushSize(amount); - } - break; - - case "increaseBrushOpacity": - // 增大画笔透明度 - if (this.toolManager && this.toolManager.brushManager) { - const amount = param || 0.01; - this.toolManager.brushManager.increaseBrushOpacity(amount); - } - break; - - case "decreaseTextureScale": - // 减小画笔材质图片大小 - if (this.toolManager && this.toolManager.brushManager) { - const amount = param || 5; - this.toolManager.brushManager.decreaseBrushSize(amount); - } - break; - - case "increaseTextureScale": - // 增大画笔材质图片大小 - if (this.toolManager && this.toolManager.brushManager) { - const amount = param || 0.01; - this.toolManager.brushManager.increaseTextureScale(amount); - } - break; - - case "decreaseBrushOpacity": - // 减小画笔透明度 - if (this.toolManager && this.toolManager.brushManager) { - const amount = param || 0.01; - this.toolManager.brushManager.decreaseBrushOpacity(amount); - } - break; - - case "toggleTempTool": - // 临时切换工具 - if (param && this.toolManager) { - this.setTempTool(param); - } - break; - - case "newLayer": - // 创建新图层 - if (this.layerManager) { - this.layerManager.createNewLayer(); - } - break; - - case "addImageToNewLayer": - this.toolManager?.openFile?.(); - break; - - case "groupLayers": - // 组合图层 - if (this.layerManager) { - this.layerManager.groupSelectedLayers(); - } - break; - - case "ungroupLayers": - // 解组图层 - if (this.layerManager) { - this.layerManager.ungroupSelectedLayer(); - } - break; - - case "mergeLayers": - // 合并图层 - if (this.layerManager) { - this.layerManager.mergeSelectedLayers(); - } - break; - - case "contextMenu": - // 上下文菜单(通常由右击或触控设备上的特定手势触发) - // console.log("显示上下文菜单"); - // 这里需要实现显示上下文菜单的逻辑 - break; - - default: - // 调用自定义注册的动作处理 - if (this.customHandlers[action]) { - this.customHandlers[action].execute(param, event); - } - } - } - - /** - * 设置临时工具 - * @param {string} toolId 临时工具ID - */ - setTempTool(toolId) { - if (!this.toolManager || this.tempToolState.active) return; - - // 保存当前工具 - this.tempToolState.originalTool = this.toolManager.getCurrentTool(); - this.tempToolState.active = true; - - // 切换到临时工具 - this.toolManager.setTool(toolId); - } - - /** - * 恢复临时工具切换前的工具 - */ - restoreTempTool() { - if (!this.toolManager || !this.tempToolState.active) return; - - // 恢复到原始工具 - if (this.tempToolState.originalTool) { - this.toolManager.setTool(this.tempToolState.originalTool); - } - - // 重置状态 - this.tempToolState.active = false; - this.tempToolState.originalTool = null; - } - - /** - * 构建快捷键标识符 - * @param {KeyboardEvent} event 键盘事件 - * @returns {string} 快捷键标识符 - */ - buildShortcutKey(event) { - let shortcutKey = ""; - - // 统一处理Mac和PC的修饰键 - if ((this.platform === "mac" && event.metaKey) || (this.platform !== "mac" && event.ctrlKey)) { - shortcutKey += `${this.modifierKeys.cmdOrCtrl}+`; - } else if (event.ctrlKey) { - shortcutKey += "ctrl+"; - } - - if (event.shiftKey) shortcutKey += "shift+"; - if (event.altKey) shortcutKey += "alt+"; - - const key = event.key.toLowerCase(); - // 特殊键处理 - switch (key) { - case " ": - shortcutKey += "space"; - break; - case "arrowup": - shortcutKey += "up"; - break; - case "arrowdown": - shortcutKey += "down"; - break; - case "arrowleft": - shortcutKey += "left"; - break; - case "arrowright": - shortcutKey += "right"; - break; - default: - shortcutKey += key; - } - - return shortcutKey; - } - - /** - * 检查当前是否有输入框处于活动状态 - * @returns {boolean} 是否有输入框处于活动状态 - */ - isInputActive() { - const activeElement = document.activeElement; - const tagName = activeElement.tagName.toLowerCase(); - return ( - tagName === "input" || - tagName === "textarea" || - activeElement.getAttribute("contenteditable") === "true" - ); - } - - /** - * 获取所有可用的快捷键 - * @returns {Array} 快捷键列表 - */ - getShortcuts() { - return Object.entries(this.shortcuts).map(([key, value]) => ({ - key, - displayKey: this.formatShortcutForDisplay(key), - ...value, - })); - } - - /** - * 格式化快捷键以便显示 - * @param {string} shortcut 快捷键标识符 - * @returns {string} 格式化后的快捷键显示 - */ - formatShortcutForDisplay(shortcut) { - // 将快捷键格式化为适合当前平台显示的形式 - return shortcut - .split("+") - .map((key) => { - // 将键名转换为显示符号 - return this.keySymbols[key.toLowerCase()] || key.toUpperCase(); - }) - .join("+"); - } - - /** - * 注册自定义快捷键处理程序 - * @param {string} action 动作名称 - * @param {Object} handler 处理程序对象 - * @param {Function} handler.execute 执行函数 - * @param {Function} handler.onKeyUp 键释放处理函数(可选) - * @param {string} description 描述 - */ - registerCustomHandler(action, handler, description = "") { - if (!action || typeof handler.execute !== "function") { - console.error("无效的自定义处理程序"); - return; - } - - this.customHandlers[action] = handler; - - // 如果提供了快捷键,添加到快捷键映射 - if (handler.shortcut) { - this.shortcuts[handler.shortcut] = { - action, - description: description || handler.description || action, - }; - } - } - - /** - * 清理资源 - */ - dispose() { - // 移除事件监听 - this.removeEvents(); - // 清除引用 - this.toolManager = null; - this.commandManager = null; - this.layerManager = null; - this.container = null; - this.customHandlers = {}; - this.tempToolState = { active: false, originalTool: null }; - this.touchState = {}; - } -}