import { Entity } from "../base/entity"; import { Root } from "./entity-root"; import { Pos } from "../type"; import { Transform } from "konva/lib/Util"; import { canEntityReply, onEntity, openOnEntityTree, TreeEvent, } from "../event"; import { findEntityByShape } from "./entity-server"; import { debounce, getChangePart, mergeFuns } from "../../shared"; // 指定某些entity可编辑 export const openOnlyMode = ( root: Root, _entitys: Entity[], includeNews = true ) => { const entitys: (Entity | null)[] = []; const prevEntitysReply: Entity["replyEvents"][] = []; const setEntityReply = (entity: Entity, reply: Entity["replyEvents"]) => { const ndx = entitys.length; entitys[ndx] = entity; prevEntitysReply[ndx] = entity.replyEvents; entity.replyEvents = reply; }; root.children.forEach((item) => setEntityReply(item, "none")); _entitys.forEach((item) => setEntityReply(item, "all")); const delHandler = (entity: Entity) => { const ndx = entitys.indexOf(entity); prevEntitysReply[ndx] = null; entitys[ndx] = null; }; const addHandler = (entity: Entity) => { if (includeNews) { setEntityReply(entity, "all"); } else if (root.children.includes(entity)) { setEntityReply(entity, "none"); } }; root.bus.on("delEntity", delHandler); root.bus.on("addEntity", addHandler); return () => { root.bus.off("delEntity", delHandler); root.bus.off("addEntity", addHandler); entitys.forEach((entity, ndx) => { if (entity) { entity.replyEvents = prevEntitysReply[ndx]; } }); }; }; export type EditModeProps = { entitys?: Entity[]; only?: boolean; includeNews?: boolean; }; export type EditModeChange = { addEntitys?: Entity[]; setEntitys?: Entity[]; delEntitys?: Entity[]; }; export const openEditMode = ( root: Root, main: () => any, props: EditModeProps = { entitys: [], only: false, includeNews: false, } ) => { if (!props.entitys) props.entitys = []; if (!props.only) props.only = false; if (!props.includeNews) props.includeNews = false; root.bus.emit("entityChangeBefore"); const quitOnlyMode = props.only && openOnlyMode(root, props.entitys, props.includeNews); let state = "normal"; let resolve: () => void; const interrupt = () => { state = "interrupt"; resolve(); }; const destoryAutoEmit = root.history && autoEmitDataChange(root); const interruptPromise = new Promise((r) => (resolve = r)); const draw = Promise.any([interruptPromise, Promise.resolve(main())]).then( () => { quitOnlyMode && quitOnlyMode(); destoryAutoEmit && destoryAutoEmit(); setTimeout(() => { root.bus.emit("entityChangeAfter"); }); return { state }; } ); return { draw, interrupt, }; }; export const openEditModePacking = (root: Root, props?: EditModeProps) => { let complete: () => void; const { interrupt } = openEditMode( root, () => { return new Promise((r) => (complete = r)); }, props ); return { interrupt, complete, }; }; const autoedRoots = new WeakMap< Root, { quete: number; destory: () => void; pause: () => void; continue: () => void; } >(); export const hasAutoEmitDataChange = (root: Root) => { return autoedRoots.has(root); }; export const pauseAutoEmitDataChange = (root: Root) => { if (autoedRoots.has(root)) { autoedRoots.get(root).pause(); } }; export const continueAutoEmitDataChange = (root: Root) => { if (autoedRoots.has(root)) { autoedRoots.get(root).continue(); } }; export const autoEmitDataChange = (root: Root) => { if (autoedRoots.has(root)) { const old = autoedRoots.get(root); old.quete++; return old.destory; } let pause = false; const addHandler = (entity: Entity) => pause || (!root.history?.hasRecovery && root.bus.emit("entityChange", { addEntitys: [entity] })); const delHandler = (entity: Entity) => pause || (!root.history?.hasRecovery && root.bus.emit("entityChange", { delEntitys: [entity] })); const changeEntitys = new Set(); const setHandler = (entity: Entity) => { if (!pause) { if (!entity.root.dragEntity) { !root.history?.hasRecovery && root.bus.emit("entityChange", { setEntitys: [entity] }); } else { changeEntitys.add(entity); } } }; const triggerDragHandler = (entity: Entity) => { if (!entity) { changeEntitys.forEach(setHandler); } }; root.bus.on("addEntity", addHandler); root.bus.on("delEntity", delHandler); root.bus.on("setEntity", setHandler); root.bus.on("triggerDrag", triggerDragHandler); const destory = () => { if (--autoedRoots.get(root).quete === 0) { root.bus.off("setEntity", setHandler); root.bus.off("delEntity", delHandler); root.bus.off("addEntity", addHandler); root.bus.off("triggerDrag", triggerDragHandler); autoedRoots.delete(root); } }; autoedRoots.set(root, { quete: 1, destory, pause: () => (pause = true), continue: () => (pause = false), }); return destory; }; const cursorResources = { pic_pen_a: "/cursors/pic_pen_a.ico", pic_pen_r: "/cursors/pic_pen_r.ico", pic_pen: "/cursors/pic_pen.ico", }; export const addCursorResource = (key: string, url: string) => { cursorResources[key] = url; }; export const injectSetCursor = (root: Root) => { const cursorStack = []; const setCursorStyle = (ico: string) => { const url = ico in cursorResources ? cursorResources[ico] : null; root.container.style.cursor = url ? `url("${ico}"), auto` : ico; }; return (ico: string) => { const ndx = cursorStack.length; cursorStack[ndx] = ico; setCursorStyle(ico); return () => { cursorStack[ndx] = null; let last = cursorStack.length - 1; for (; last >= 0; last--) { if (cursorStack[last] !== null) { break; } } if (last === -1) { setCursorStyle("inherit"); cursorStack.length = 0; } else if (last < ndx) { setCursorStyle(cursorStack[last]); } }; }; }; export const injectConstant = (root: Root) => { const origin: { [key in string]: number | Pos } = {}; const current: { [key in string]: number | Pos } = {}; let mat: Transform; let scale: Pos; let position: Pos; let rCos: number, rSin: number; root.bus.on("mounted", function handler() { mat = root.stage.getTransform().invert(); scale = root.stage.scale(); position = root.stage.position(); let radians = root.stage.rotation() * (Math.PI / 180); rCos = Math.cos(radians); rSin = Math.sin(radians); root.bus.off("mounted", handler); }); const invView = (key: string) => { if (key.startsWith("fix:")) { if (typeof origin[key] === "number") { current[key] = mat.point({ x: origin[key], y: 0 }).x; } else { current[key] = mat.point(origin[key]); } } }; root.bus.on("changeView", () => { mat = root.stage.getTransform().invert(); Object.keys(origin).forEach(invView); }); const invScale = (key: string) => { if (key.startsWith("fixScale:")) { if (typeof origin[key] === "number") { current[key] = origin[key] / scale.x; } else { current[key] = { x: origin[key].x / scale.x, y: origin[key].y / scale.y, }; } } }; root.bus.on("changeViewScale", (nscale) => { scale = nscale; Object.keys(origin).forEach(invScale); }); const invPosition = (key: string) => { if (key.startsWith("fixPosition:")) { if (typeof origin[key] === "number") { current[key] = origin[key] - position.x; } else { current[key] = { x: origin[key].x - position.x, y: origin[key].y - position.y, }; } } }; root.bus.on("changeViewPosition", (nposition) => { position = nposition; Object.keys(origin).forEach(invPosition); }); const invRotation = (key: string) => { if (key.startsWith("fixRotation:")) { const p = origin[key]; if (typeof p !== "number") { current[key] = { x: p.x * rCos - p.y * rSin, y: p.x * rSin + p.y * rCos, }; } } }; root.bus.on("changeViewRotation", (rotation) => { let radians = rotation * (Math.PI / 180); rCos = Math.cos(radians); rSin = Math.sin(radians); Object.keys(origin).forEach(invRotation); }); return { set(key: string, val: number) { origin[key] = val; invView(key); invPosition(key); invRotation(key); invScale(key); }, get(key: string): T { return (current[key] || origin[key]) as T; }, }; }; const rootStack: Root[] = []; export const pushRoot = (root: Root) => rootStack.push(root); export const popRoot = () => rootStack.pop(); export const currentRoot = () => rootStack[rootStack.length - 1]; export const currentConstant = new Proxy( {}, { get(_, p) { return currentRoot().constant.get(p as string); }, } ); export const injectPointerEvents = (root: Root) => { const store = { hovers: new Set(), focus: new Set(), drag: null as Entity, }; const oldStore = { hovers: [] as Entity[], focus: [] as Entity[], drag: null as Entity, }; const emit = debounce(() => { const hovers = [...store.hovers]; const focus = [...store.focus]; const hoverChange = getChangePart(hovers, oldStore.hovers); const focusChange = getChangePart(focus, oldStore.focus); hoverChange.addPort.forEach((entity) => entity.bus.emit("hover")); hoverChange.delPort.forEach((entity) => entity.bus.emit("leave")); focusChange.addPort.forEach((entity) => entity.bus.emit("focus")); focusChange.delPort.forEach((entity) => entity.bus.emit("blur")); if (oldStore.drag !== store.drag) { oldStore.drag && oldStore.drag.bus.emit("drop"); store.drag && store.drag.bus.emit("drag"); } oldStore.drag = store.drag; oldStore.hovers = hovers; oldStore.focus = focus; }, 16); const needReleases = [ openOnEntityTree(root, "mouseover"), openOnEntityTree(root, "mouseout"), openOnEntityTree(root, "click"), openOnEntityTree(root, "touchend"), onEntity(root, "dragstart", (ev) => { const hit = findEntityByShape(root, ev.target); if (canEntityReply(hit)) { store.drag = hit; root.dragEntity = hit; root.bus.emit("triggerDrag", hit); emit(); } }), onEntity(root, "dragend", () => { if (store.drag && canEntityReply(store.drag)) { store.drag = null; root.dragEntity = null; root.bus.emit("triggerDrag", null); emit(); } }), ]; const enterHandler = ({ paths }: TreeEvent) => { paths.forEach((entity) => { if (canEntityReply(entity)) { store.hovers.add(entity); } }); emit(); }; const leaveHandler = ({ paths }: TreeEvent) => { paths.forEach((entity) => { if (canEntityReply(entity)) { store.hovers.delete(entity); } }); emit(); }; const clickHandler = ({ paths }: TreeEvent) => { store.focus.clear(); if (canEntityReply(paths[0])) { root.bus.emit("triggerFocus", paths[0]); } paths.forEach((entity) => { if (canEntityReply(entity)) { store.focus.add(entity); } }); emit(); }; root.bus.on("mouseover" as any, enterHandler); root.bus.on("mouseout" as any, leaveHandler); root.bus.on("click", clickHandler); root.bus.on("touchend", clickHandler); const destory = () => { mergeFuns(needReleases)(); root.bus.off("mouseover" as any, enterHandler); root.bus.off("mouseout" as any, leaveHandler); root.bus.off("click", clickHandler); root.bus.off("touchend", clickHandler); }; root.bus.on("destroyBefore", destory); return { focus(...entitys: Entity[]) { store.focus.clear(); entitys.forEach((entity) => store.focus.add(entity)); emit(); }, blur(...entitys: Entity[]) { entitys.forEach((entity) => store.focus.delete(entity)); emit(); }, hover(...entitys: Entity[]) { store.hovers.clear(); entitys.forEach((entity) => store.hovers.add(entity)); emit(); }, leave(...entitys: Entity[]) { entitys.forEach((entity) => store.hovers.delete(entity)); emit(); }, drag(entity: Entity) { store.drag = entity; emit(); }, drop(entity: Entity) { if (store.drag === entity) { store.drag = entity; emit(); } }, destory, }; }; export type PointerEvents = ReturnType;