/** * 键盘管理器 * 负责处理编辑器中的键盘事件和快捷键 * 支持PC、Mac和iPad三端适配 */ export class KeyboardManager { /** * 创建键盘管理器 * @param {Object} options 配置选项 * @param {Object} options.toolManager 工具管理器实例 * @param {Object} options.commandManager 命令管理器实例 * @param {Object} options.layerManager 图层管理器实例 * @param {Function} options.pasteText 粘贴文本回调函数 * @param {Function} options.pasteImage 粘贴图片回调函数 * @param {HTMLElement} options.container 容器元素,用于添加事件监听 */ constructor(options = {}) { this.toolManager = options.toolManager; this.commandManager = options.commandManager; this.layerManager = options.layerManager; this.container = options.container || document; this.pasteText = options.pasteText || (() => {}); this.pasteImage = options.pasteImage || (() => {}); // 检测平台类型 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: "删除" }, // 选择 [`${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(); // 阻止默认粘贴行为 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("复制当前选中图层"); this.layerManager.copyLayer(this.layerManager.activeLayerId.value); break; case "paste": // 粘贴逻辑 console.log("粘贴"); this.layerManager.pasteLayer(); break; case "cut": // 剪切逻辑 console.log("剪切"); this.layerManager.cutLayer(this.layerManager.activeLayerId.value); break; case "delete": // 删除逻辑 console.log("删除"); this.layerManager.removeLayer(this.layerManager.activeLayerId.value); break; case "selectAll": // 全选逻辑 console.log("全选"); // 这里需要实现全选逻辑 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 "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.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); } // 清除引用 this.toolManager = null; this.commandManager = null; this.layerManager = null; this.container = null; this.customHandlers = {}; this.tempToolState = { active: false, originalTool: null }; this.touchState = {}; } }