Files
aida_front/src/component/Canvas/CanvasEditor/managers/events/KeyboardManager.js
2026-01-02 11:24:11 +08:00

763 lines
23 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 键盘管理器
* 负责处理编辑器中的键盘事件和快捷键
* 支持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<Boolean>} 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 = {};
}
}