深度画布键盘事件-撤回功能
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -7,16 +7,57 @@
|
||||
<svg-icon name="dc-down_arrow2" size="10" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="content" v-show="show">
|
||||
这是一些设置参数...
|
||||
<div class="content" v-if="activeObject" v-show="show">
|
||||
<div>
|
||||
<span class="label">X:</span>
|
||||
<span class="value">{{ activeObject.left }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="label">Y:</span>
|
||||
<span class="value">{{ activeObject.top }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="label">Width:</span>
|
||||
<span class="value">{{ activeObject.width }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="label">Height:</span>
|
||||
<span class="value">{{ activeObject.height }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="label">缩放X:</span>
|
||||
<span class="value">{{ activeObject.scaleX }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="label">缩放Y:</span>
|
||||
<span class="value">{{ activeObject.scaleY }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="label">Angle:</span>
|
||||
<span class="value">{{ activeObject.angle }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, inject, computed } from 'vue'
|
||||
import { ref, inject, computed, watch, onMounted } from 'vue'
|
||||
const props = defineProps({})
|
||||
const layerManager = inject('layerManager') as any
|
||||
const canvasManager = inject('canvasManager') as any
|
||||
const show = ref(true)
|
||||
const activeID = computed(() => layerManager.activeID.value)
|
||||
const layers = computed(() => layerManager.layers.value)
|
||||
const activeObject = ref(null)
|
||||
const updateActiveObject = () => {
|
||||
const obj = layers.value.find((v: any) => v.info.id === activeID.value)
|
||||
activeObject.value = obj?.toJSON('info') || null
|
||||
}
|
||||
watch(layers, () => updateActiveObject())
|
||||
watch(activeID, () => updateActiveObject())
|
||||
onMounted(() => {
|
||||
updateActiveObject()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@@ -53,6 +94,23 @@
|
||||
}
|
||||
> .content {
|
||||
padding: 1.6rem;
|
||||
> div {
|
||||
padding: 0.8rem 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
> .label {
|
||||
font-size: 1.6rem;
|
||||
color: #000;
|
||||
font-weight: bold;
|
||||
min-width: 7rem;
|
||||
text-align: right;
|
||||
margin-right: 0.8rem;
|
||||
}
|
||||
> .value {
|
||||
font-size: 1.6rem;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { fabric } from 'fabric-with-all'
|
||||
import { computed, ref, markRaw, onMounted, nextTick, provide, onBeforeMount } from 'vue'
|
||||
import { computed, ref, markRaw, onMounted, nextTick, provide, onBeforeUnmount } from 'vue'
|
||||
import { OperationType } from './tools/layerHelper'
|
||||
// 组件
|
||||
import layerPanel from './components/layer-panel/index.vue'
|
||||
@@ -34,6 +34,7 @@
|
||||
import { LayerManager } from './manager/LayerManager'
|
||||
import { CanvasManager } from './manager/CanvasManager'
|
||||
import { ToolManager } from './manager/ToolManager'
|
||||
import { KeyEventManager } from './manager/events/KeyEventManager'
|
||||
|
||||
// 准备就绪
|
||||
const isReady = ref(false)
|
||||
@@ -66,6 +67,11 @@
|
||||
stateManager.setManager({ toolManager })
|
||||
provide('toolManager', toolManager)
|
||||
|
||||
//键盘事件管理器
|
||||
const keyEventManager = new KeyEventManager({ stateManager })
|
||||
stateManager.setManager({ keyEventManager })
|
||||
provide('keyEventManager', keyEventManager)
|
||||
|
||||
const observer = ref(null)
|
||||
onMounted(() => {
|
||||
canvasManager.initCanvas({
|
||||
@@ -79,6 +85,7 @@
|
||||
canvasManager?.onMounted?.()
|
||||
layerManager?.onMounted?.()
|
||||
toolManager?.onMounted?.()
|
||||
keyEventManager?.onMounted?.()
|
||||
|
||||
const trailingTimeout = ref(null)
|
||||
observer.value = new ResizeObserver((entries) => {
|
||||
@@ -91,7 +98,13 @@
|
||||
|
||||
isReady.value = true // 准备就绪
|
||||
})
|
||||
onBeforeMount(() => {})
|
||||
onBeforeUnmount(() => {
|
||||
canvasManager.dispose()
|
||||
stateManager.dispose()
|
||||
layerManager.dispose()
|
||||
toolManager.dispose()
|
||||
keyEventManager.dispose()
|
||||
})
|
||||
async function handleWindowResize() {
|
||||
console.log('==========画布窗口大小变化==========')
|
||||
canvasManager.setCanvasViewSize({
|
||||
@@ -135,6 +148,7 @@
|
||||
} else {
|
||||
console.log('导入失败')
|
||||
}
|
||||
stateManager.clearState(true)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,4 +96,5 @@ export class EraserStateManager {
|
||||
this.currentSnapshot = null;
|
||||
this.pendingCommand = null;
|
||||
}
|
||||
dispose() {}
|
||||
}
|
||||
|
||||
@@ -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() {}
|
||||
}
|
||||
@@ -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() { }
|
||||
}
|
||||
|
||||
@@ -170,5 +170,8 @@ export class ToolManager {
|
||||
|
||||
this.brushIndicator.disable();
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.brushIndicator?.dispose()
|
||||
this.brushManager?.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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 = {};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user