import { Layer } from "konva/lib/Layer"; import { debounce, mergeFuns } from "../../shared"; import { Entity, EntityShape, EntityTransmit } from "./entity"; import { Root } from "./entity-root"; import { Stage } from "konva/lib/Stage"; import { contain } from "../helper/shape"; import { autoEmitDataChange, hasAutoEmitDataChange, } from "./entity-root-server"; import { canEntityReply } from "../event"; import { Pos } from "../type"; import { KonvaEventObject } from "konva/lib/Node"; import { getOffset } from "../helper/dom"; export const traversEntityTree = ( entity: Entity, call: (entity: Entity, inverse: boolean) => void | "interrupt", inverse: boolean | "all" = false ) => { if (!inverse || inverse === "all") { if (call(entity, false) === "interrupt") { return "interrupt"; } } const eqed: Entity[] = []; while (true) { const child = entity.children.find((child) => !eqed.includes(child)); if (child) { if (traversEntityTree(child, call, inverse) === "interrupt") { return "interrupt"; } eqed.push(child); } else { break; } } if (inverse || inverse === "all") { if (call(entity, true) === "interrupt") { return "interrupt"; } } }; export const bubbleTraversEntityTree = ( entity: Entity, call: (entity: Entity, inverse: boolean) => void | "interrupt", inverse: boolean | "all" = false ) => { if (!entity) return; if (!inverse || inverse === "all") { if (call(entity, false) === "interrupt") return "interrupt"; } if (bubbleTraversEntityTree(entity.parent, call, inverse) === "interrupt") { return "interrupt"; } if (inverse || inverse === "all") { if (call(entity, true) === "interrupt") return "interrupt"; } }; export const findEntity = ( entity: Entity, name: string ): T => { let find: T; traversEntityTree(entity, (t) => { if (t.name === name) { find = t as T; return "interrupt"; } }); return find; }; export const findEntityByShape = ( entity: Entity, shape: EntityShape ) => { let find: T; if (shape instanceof Stage) { if (entity instanceof Root && entity.stage === shape) { return entity; } else { return null; } } const checked: EntityShape[] = []; traversEntityTree( entity, (child) => { if (contain(child.shape, shape, checked)) { find = child as T; return "interrupt"; } else { checked.push(child.shape); } }, true ); return find; }; export const getEntityNdx = (entity: Entity) => { const parent = entity.parent; if (!parent) return null; const zIndex = entity.getZIndex(); const level = parent.children; for (let i = level.length - 1; i >= 0; i--) { if (level[i] !== entity && level[i].getZIndex() <= zIndex) { return i; } } return -1; }; export const summarizeEntity = (entity: Entity) => { if (!entity.isMounted) return; const packNdx = getEntityNdx(entity); if (packNdx === null) return; const packChild = entity.children; const oldNdx = packChild.indexOf(entity); if (oldNdx !== packNdx + 1) { let rep = entity; for (let i = packNdx + 1; i < packChild.length; i++) { const temp = packChild[i]; packChild[i] = rep; rep = temp; } } const parentShape = entity.getTeleport(); const levelShapes = parentShape.children; const shapeNdx = packNdx === -1 ? 0 : levelShapes.indexOf(packChild[packNdx].getShape()) + 1; const oldShapeNdx = levelShapes.indexOf(entity.shape); if (oldShapeNdx !== shapeNdx) { if (shapeNdx !== 0) { let repShape = entity.shape; for (let i = shapeNdx; i < levelShapes.length; i++) { const temp = levelShapes[i]; parentShape.add(repShape); repShape.zIndex(i); repShape = temp; } } else { entity.shape.zIndex(0); } } }; export const entityInit = (entity: T) => { entity.bus.emit("createBefore"); entity.shape = entity.initShape(); entity.shape.id(entity.name); entity.bus.emit("created"); let releases = entity.needReleases(); releases = Array.isArray(releases) ? releases : [releases]; if (releases.length) { entity.bus.on("destroyBefore", () => { mergeFuns(releases)(); }); } }; export const entityMount = (entity: T) => { traversEntityTree(entity, (entity) => { entity.bus.emit("mountBefore"); if (entity.parent) { const transmit = getEntityTransmitProps(entity.parent); for (const key in transmit) { entity[key] = transmit[key]; } } entity.diffRedraw(); }); traversEntityTree( entity, (entity) => { setEntityShapePosition(entity); entity.isMounted = true; entity.bus.emit("mounted"); }, true ); }; const setEntityShapePosition = (entity: Entity) => { const parentShape = (entity.getTeleport() || entity.parent?.shape) as Layer; if (parentShape) { parentShape.add(entity.shape); } summarizeEntity(entity); }; export const setEntityParent = ( parent: T["parent"] | null, entity: T ) => { if (entity.parent) { const ndx = entity.parent.children.indexOf(entity); ~ndx && entity.parent.children.splice(ndx, 1); } entity.parent = parent; parent && entity.parent.children.push(entity); entity.isMounted && setEntityShapePosition(entity); }; const getEntityTransmitProps = (parent: Entity): EntityTransmit => { return { root: parent.root, }; }; export const mountEntityTree = ( parent: T["parent"] | null, entity: T ) => { if (parent) { setEntityParent(parent, entity); } entityMount(entity); if (entity.root) { entity.root.bus.emit("addEntity", entity); if ( entity.root.history && !entity.root.history.hasRecovery && !hasAutoEmitDataChange(entity.root) ) { entity.root.bus.emit("entityChange", { addEntitys: [this] }); } } return entity; }; export type DragHandlers = (ev: KonvaEventObject) => { move: (move: Pos, ev: MouseEvent | TouchEvent) => void; end?: (ev: KonvaEventObject) => void; }; export const openEntityDrag = ( entity: T, getHandlers: DragHandlers, trasnform = true ) => { const shape = entity instanceof Root ? entity.stage : entity.shape; shape.draggable(true); let canReply = false; let moveHandler: ReturnType["move"]; let endHandler: ReturnType["end"]; let destoryAutoEmit: () => void; let start: Pos; shape.on("dragstart.drag", (ev) => { canReply = canEntityReply(entity); if (canReply) { const handlers = getHandlers(ev); moveHandler = handlers.move; endHandler = handlers.end; if (entity.root.history) { destoryAutoEmit = autoEmitDataChange(entity.root); } start = getOffset(ev.evt); } }); shape.dragBoundFunc((pos, ev) => { if (canReply) { const end = getOffset(ev); const move = { x: end.x - start.x, y: end.y - start.y, }; moveHandler(trasnform ? entity.root.invMat.point(pos) : move, ev); } return shape.absolutePosition(); }); shape.on("dragend.drag", (ev) => { moveHandler = null; canReply = false; start = null; endHandler && endHandler(ev); destoryAutoEmit && destoryAutoEmit(); }); return () => { shape.draggable(false); shape.off("dragstart.drag dragend.drag"); }; }; export type EntityPointerStatus = { drag: boolean; hover: boolean; focus: boolean; }; export const openOnEntityPointerStatus = (entity: Entity) => { const status: EntityPointerStatus = { drag: false, hover: false, focus: false, }; const emti = debounce(() => { entity.bus.emit("pointerStatus", status); }, 16); const handler = (partial: Partial) => { Object.assign(status, partial); emti(); }; const focusHandler = () => handler({ focus: true }); const blurHandler = () => handler({ focus: false }); const hoverHandler = () => handler({ hover: true }); const leaveHandler = () => handler({ hover: false }); const dragHandler = () => handler({ drag: true }); const dropHandler = () => handler({ drag: false }); entity.bus.on("focus", focusHandler); entity.bus.on("blur", blurHandler); entity.bus.on("hover", hoverHandler); entity.bus.on("leave", leaveHandler); entity.bus.on("drag", dragHandler); entity.bus.on("drop", dropHandler); const destory = () => { entity.bus.off("focus", focusHandler); entity.bus.off("blur", blurHandler); entity.bus.off("hover", hoverHandler); entity.bus.off("leave", leaveHandler); entity.bus.off("drag", dragHandler); entity.bus.off("drop", dropHandler); }; entity.bus.on("destroyBefore", destory); return destory; };