import { Entity } from "../base/entity"; import { onEntity, openOnEntityTree, TreeEvent } from "../event/spread"; import { findEntityByShape } from "../base/entity-server"; import { debounce, getChangePart, mergeFuns } from "../../shared"; import { Root } from "./entity-root"; 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.hovers); 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, "mouseenter"), openOnEntityTree(root, "mouseleave"), openOnEntityTree(root, "click"), openOnEntityTree(root, "touchend"), onEntity(root, "dragstart", (ev) => { const hit = findEntityByShape(root, ev.target); hit.contenteditable && (store.drag = hit); emit(); }), onEntity(root, "dragend", () => { store.drag = null; emit(); }), ]; const enterHandler = ({ paths }: TreeEvent) => { paths.forEach((entity) => { store.hovers.add(entity); }); emit(); }; const leaveHandler = ({ paths }: TreeEvent) => { paths.forEach((entity) => { store.hovers.delete(entity); }); emit(); }; const clickHandler = ({ paths }: TreeEvent) => { store.focus.clear(); paths.forEach((entity) => { store.focus.add(entity); }); emit(); }; root.bus.on("mouseenter", enterHandler); root.bus.on("mouseleave", leaveHandler); root.bus.on("click", clickHandler); root.bus.on("touchend", clickHandler); const destory = () => { mergeFuns(needReleases)(); root.bus.off("mouseenter", enterHandler); root.bus.off("mouseleave", 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; // 指定某些entity可编辑 export const openOnlyMode = ( root: Root, _entitys: Entity[], includeNews = true ) => { const prevRootReply = root.replyEvents; const entitys: (Entity | null)[] = []; const prevEntitysReply: Entity["replyEvents"][] = []; const pushEntity = (entity: Entity) => { const ndx = entitys.length; entitys[ndx] = entity; prevEntitysReply[ndx] = entity.replyEvents; entity.replyEvents = "all"; }; const delEntity = (entity: Entity) => { const ndx = entitys.indexOf(entity); prevEntitysReply[ndx] = null; entitys[ndx] = null; }; _entitys.forEach(pushEntity); // 新增的entity可以使用事件 includeNews && root.bus.on("addEntity", pushEntity); root.bus.on("delEntity", delEntity); return () => { root.bus.off("addEntity", pushEntity); root.bus.off("delEntity", delEntity); root.replyEvents = prevRootReply; entitys.forEach((entity, ndx) => { if (entity) { entity.replyEvents = prevEntitysReply[ndx]; } }); }; }; export type EditModeProps = { entitys: Entity[]; only: boolean; includeNews: boolean; }; export const openEditMode = async ( root: Root, main: () => any, props: EditModeProps = { entitys: [], only: false, includeNews: false } ) => { root.bus.emit("dataChangeBefore"); const quitOnlyMode = props.only && openOnlyMode(root, props.entitys, props.includeNews); const addEntitys = []; const setEntitys = []; const delEntitys = []; const addHandler = (entity: Entity) => { addEntitys.push(entity); }; const setHandler = (entity: Entity) => { if ( !addEntitys.includes(entity) && !delEntitys.includes(entity) && !setEntitys.includes(entity) ) { setEntitys.push(entity); } }; const delHandler = (entity: Entity) => { const delNdx = delEntitys.indexOf(entity); if (~delNdx) return; const addNdx = addEntitys.indexOf(entity); if (~addNdx) addEntitys.splice(addNdx, 1); const setNdx = setEntitys.indexOf(entity); if (~setNdx) setEntitys.splice(setNdx, 1); delEntitys.push(entity); }; root.bus.on("addEntity", addHandler); root.bus.on("updateEntity", setHandler); root.bus.on("delEntity", delHandler); let state = "normal"; const interrupt = new Promise((resolve) => { state = "interrupt"; resolve(); }); const draw = Promise.any([interrupt, Promise.resolve(main())]).then(() => { root.bus.off("addEntity", addHandler); root.bus.off("updateEntity", setHandler); root.bus.off("delEntity", addHandler); quitOnlyMode && quitOnlyMode(); const change = { addEntitys, delEntitys, setEntitys, }; root.bus.emit("dataChangeAfter", change); return { state, change }; }); return { draw, interrupt, }; }; const cursorResources = import.meta.glob("../resource/cursor?url"); const cursorDefs = ["move", "inherit", "pointer"]; const cursorMap = new WeakMap(); const setCursorStyle = (root: Root, ico: string) => { const style = cursorDefs.includes(ico) ? ico : ico in cursorResources ? `url("${ico}"), auto` : null; if (!style) throw "ico 不存在!"; root.container.style.cursor = ico; }; export const setCursor = (root: Root, ico: string) => { if (!cursorMap.get(root)) { cursorMap.set(root, []); } const stack = cursorMap.get(root); const ndx = stack.length; stack[ndx] = ico; setCursorStyle(root, ico); return () => { stack[ndx] = null; let last = stack.length - 1; for (; last >= 0; last--) { if (stack[last] !== null) { break; } } if (last === -1) { setCursor(root, "inherit"); stack.length = 0; } else if (last < ndx) { setCursor(root, stack[last]); } }; };