import { Entity } from "../base/entity"; import { KonvaEventObject } from "konva/lib/Node"; import { bubbleTraversEntityTree, findEntityByShape, } from "../base/entity-server"; import { Root } from "../base/entity-root"; const canEntityReply = (entity: Entity) => { let canReply = true; bubbleTraversEntityTree(entity, (entity) => { if (entity.replyEvents === "none") { canReply = false; return "interrupt"; } else if (entity.replyEvents === "all") { return "interrupt"; } }); return canReply; }; // 仅限内部使用 export const onEntity = ( entity: Entity, event: string, cb: (ev: KonvaEventObject) => void ) => { const handler = (ev: KonvaEventObject) => canEntityReply(entity) && cb(ev); const shape = entity === entity.root ? entity.root.shape : entity.shape; shape.on(event, handler); const destory = () => shape.off(event, handler); entity.bus.on("destroyBefore", destory); return destory; }; export type TreeEvent = { paths: Entity[]; target: Entity; cancelBubble: boolean; }; export type TreeEventName = "mouseenter" | "mouseleave" | "click" | "touchend"; export const emitEntityTree = ( entity: Entity, name: TreeEventName, one = false ) => { const ev: TreeEvent = { cancelBubble: false, target: entity, paths: [entity], }; if (one) { entity.bus.emit(name as any, ev); entity.bus.emit((name + ".capture") as any, ev); return; } let paths: Entity[] = []; bubbleTraversEntityTree(entity, (t) => { paths.push(t); }); let prevStatus = "all"; for (let i = paths.length - 1; i >= 0; i++) { const currStatus = paths[i].replyEvents; if ( currStatus === "none" || (prevStatus === "none" && currStatus === "auto") ) { paths.splice(i--, 1); prevStatus = "none"; } else { prevStatus = "all"; } } ev.paths = paths; for (const entity of paths) { entity.bus.emit(name as any, ev); if (ev.cancelBubble) break; } ev.cancelBubble = false; paths = paths.reverse(); ev.paths = paths; for (const entity of paths) { entity.bus.emit((name + ".capture") as any, ev); if (ev.cancelBubble) break; } }; // 将konva的时间转发到entity const entityTreeDistributor = (root: Root) => { const shape = root.stage; const listenEvents: TreeEventName[] = []; const handlerMap: { [key in TreeEventName]?: Entity[] } = {}; const addEvent = (name: TreeEventName) => { if (listenEvents.includes(name)) return; listenEvents.push(name); shape.on(name, (ev: KonvaEventObject) => { const self = findEntityByShape(root, ev.target); emitEntityTree(self, name); }); }; const delEvent = (name: TreeEventName) => { const ndx = listenEvents.indexOf(name); if (!~ndx) return; listenEvents.splice(ndx, 1); shape.off(name); }; return { on: (entity: Entity, name: TreeEventName) => { if (!handlerMap[name]) { handlerMap[name] = []; } handlerMap[name].push(entity); addEvent(name); }, off: (entity: Entity, key?: TreeEventName) => { const keys = (key ? [key] : Object.keys(handlerMap)) as TreeEventName[]; const vals = key ? entity[key] ? [entity[key]] : [] : Object.values(handlerMap); for (let i = 0; i < vals.length; i++) { const ckey = keys[i]; const items = vals[i]; let j = 0; for (; j < items.length; j++) { const centity = items[j]; if (entity === centity) { items.splice(j--, 1); } } if (j === 0) { delete handlerMap[ckey]; delEvent(ckey); } } }, handlerMap: handlerMap, destory() { while (listenEvents.length) { delEvent(listenEvents[0]); } }, }; }; const distributors = new WeakMap< Entity, ReturnType >(); const getDistributor = (entity: Entity) => { let distributor = distributors.get(entity.root); if (!distributor) { distributor = entityTreeDistributor(entity.root); distributors.set(entity.root, distributor); } return distributor; }; const getEventArgs = (key: string) => { const [name, ...args] = key.split("."); const useCapture = args.includes("capture"); return [name as TreeEventName, useCapture] as const; }; // 外部使用 export const openOnEntityTree = (entity: Entity, key: string) => { const distributor = getDistributor(entity); const [name] = getEventArgs(key); const destory = () => closeOnEntityTree(entity, key); if ( !distributor.handlerMap[name] || !distributor.handlerMap[name].includes(entity) ) { distributor.on(entity, name); entity.bus.on("destroyBefore", destory); } return destory; }; export const closeOnEntityTree = (entity: Entity, key?: string) => { const distributor = getDistributor(entity); const [name] = getEventArgs(key); distributor && distributor.off(entity, name); };