深度画布键盘事件-撤回功能

This commit is contained in:
lzp
2026-03-12 11:40:48 +08:00
parent adf562bbe4
commit 86f1efbf43
11 changed files with 228 additions and 951 deletions

View File

@@ -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()
}
}

View File

@@ -96,4 +96,5 @@ export class EraserStateManager {
this.currentSnapshot = null;
this.pendingCommand = null;
}
dispose() {}
}

View File

@@ -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() {}
}

View File

@@ -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() { }
}

View File

@@ -170,5 +170,8 @@ export class ToolManager {
this.brushIndicator.disable();
}
dispose() {
this.brushIndicator?.dispose()
this.brushManager?.dispose()
}
}

View File

@@ -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

View File

@@ -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()
}
}

View File

@@ -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<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 = {};
}
}