import { computed, reactive, ref, Ref, toRaw, watch, watchEffect } from "vue"; import { DC, EntityShape } from "../../deconstruction"; import { Shape } from "konva/lib/Shape"; import { globalWatch, installGlobalVar, usePointerIntersections, usePointerPos, useStage, useTransformIngShapes, } from "./use-global-vars.ts"; import { useCan, useOperMode } from "./use-status"; import { Stage } from "konva/lib/Stage"; import { listener } from "../../utils/event.ts"; import { asyncTimeout, inRevise, mergeFuns } from "../../utils/shared.ts"; import { ComponentValue, DrawItem, ShapeType } from "../components"; import { shapeTreeContain, shapeTreeContains } from "../../utils/shape.ts"; import { usePointerIsTransformerInner, useTransformer, } from "./use-transformer.ts"; import { KonvaEventObject } from "konva/lib/Node"; import { useStore } from "../store/index.ts"; import { Group } from "konva/lib/Group"; import { usePause } from "./use-pause.ts"; import { lineLen, Pos } from "@/utils/math.ts"; import { useFormalLayer } from "./use-layer.ts"; const stageHoverMap = new WeakMap< Stage, { result: Ref; count: number; des: () => void } >(); export const getHoverShape = (stage: Stage) => { let isStop = false; const stop = () => { if (isStop || !stageHoverMap.has(stage)) return; isStop = true; const data = stageHoverMap.get(stage)!; if (--data.count <= 0) { data.des(); } }; if (stageHoverMap.has(stage)) { const data = stageHoverMap.get(stage)!; ++data.count; return [data.result, stop] as const; } const hover = ref(); const enterHandler = (ev: KonvaEventObject) => { const target = ev.target; hover.value = target; target.off(`pointerleave`, leaveHandler); target.on(`pointerleave`, leaveHandler as any); }; const leaveHandler = (ev?: KonvaEventObject) => { if (hover.value) { hover.value.off(`pointerleave`, leaveHandler); hover.value = undefined; } }; stage.on(`pointerenter`, enterHandler); stageHoverMap.set(stage, { result: hover, count: 1, des: () => { stage.off(`pointerenter`, enterHandler); leaveHandler(); stageHoverMap.delete(stage); }, }); return [hover, stop] as const; }; export const useShapeClick = ( shape: Ref | undefined>, fn: () => void ) => { const init = (shape: Shape) => { let downTime: number; let move = false; const downHandler = (ev: KonvaEventObject) => { if (ev.evt.button === 0) { ev.cancelBubble = true downTime = Date.now(); move = false; } }; const moveHandler = (ev: KonvaEventObject) => { ev.cancelBubble = true downTime = Date.now(); move = true; }; const upHandler = (ev: KonvaEventObject) => { ev.cancelBubble = true if (Date.now() - downTime < 500 && !move) { fn(); } }; shape.on("pointerdown.click", downHandler); shape.on("pointermove.click", moveHandler); shape.on("pointerup.click", upHandler); return () => { shape.off("pointerdown.click", downHandler); shape.off("pointermove.click", moveHandler); shape.off("pointerup.click", upHandler); }; }; watch(shape, (shape, _, onCleanup) => { const $shape = shape?.getNode(); $shape && onCleanup(init($shape)); }); }; export const useShapeIsHover = (shape: Ref | undefined>) => { const stage = useStage(); const store = useStore(); const format = useFormalLayer(); const pos = usePointerPos(); const isHover = ref(false); const stop = watch( () => ({ stage: stage.value?.getNode(), shape: shape.value?.getNode() }), ({ stage, shape }, _, onCleanup) => { if (!stage || !shape || result.isPause) { isHover.value = false; return; } const forciblyCheck = async () => { await asyncTimeout(6) if (!pos.value || !format.value) { isHover.value = false; return; } isHover.value = format.value.getIntersection(pos.value) === toRaw(shape); }; store.bus.on("addItemAfter", forciblyCheck); store.bus.on("setItemAfter", forciblyCheck); store.bus.on("delItemAfter", forciblyCheck); const [hoverShape, stopHoverListener] = getHoverShape(stage); watchEffect(() => { isHover.value = !!( hoverShape.value && shapeTreeContain([shape], toRaw(hoverShape.value)) ); }); onCleanup( mergeFuns([ stopHoverListener, () => { isHover.value = false; store.bus.off("addItemAfter", forciblyCheck); store.bus.off("setItemAfter", forciblyCheck); store.bus.off("delItemAfter", forciblyCheck); }, ]) ); }, { immediate: true } ); const result = usePause([isHover, stop] as const); return result; }; export const useMouseShapeIsHover = ( shape: Ref | undefined> ) => { const stage = useStage(); const hitShapes = usePointerIntersections(); const isHover = ref(false); const stop = watch( () => ({ stage: stage.value?.getNode(), shape: shape.value?.getNode() }), ({ stage, shape }, _, onCleanup) => { if (!stage || !shape || result.isPause) { isHover.value = false; return; } const stopHoverListener = watch(hitShapes, () => { isHover.value = hitShapes.value.includes(shape as any); }); onCleanup(mergeFuns([stopHoverListener, () => (isHover.value = false)])); }, { immediate: true } ); const result = usePause([isHover, stop] as const); return result; }; export const useShapeIsTransformerInner = () => { const transformer = useTransformer(); const pointerIsTransformerInner = usePointerIsTransformerInner(); const stage = useStage(); return (shape: EntityShape) => { const inner = ref(true); const $stage = stage.value!.getNode(); const updateInner = () => { inner.value = transformer.isTransforming() || (transformer.queueShapes.value.includes(shape) && pointerIsTransformerInner()); }; const stop = watch( transformer.queueShapes, (_a, _, onCleanup) => { updateInner(); if (inner.value) { $stage.on("pointermove", updateInner); onCleanup(() => { $stage.off("pointermove", updateInner); }); } }, { immediate: true, flush: "sync" } ); return [inner, stop] as const; }; }; export const useMouseShapesStatus = installGlobalVar(() => { const can = useCan(); const stage = useStage(); const listeners = ref([]) as Ref; const hovers = ref([]) as Ref; const press = ref([]) as Ref; const selects = ref([]) as Ref; const actives = ref([]) as Ref; const operMode = useOperMode(); const pointerIsTransformerInner = usePointerIsTransformerInner(); const init = (stage: Stage) => { const prevent = computed(() => operMode.value.freeView); const [hover, hoverDestory] = getHoverShape(stage); const hoverChange = () => { if (prevent.value) { return; } hovers.value = hover.value ? shapeTreeContains(listeners.value, hover.value) : []; }; const stopHoverCheck = watch( () => [hover.value, prevent.value], hoverChange ); let downTime: number; let downPos: Pos | null = null; let downTarget: EntityShape | null; stage.on("pointerdown.mouse-status", (ev) => { if (ev.evt.button !== 0) return; downPos = { x: ev.evt.pageX, y: ev.evt.pageY }; downTime = Date.now(); if (prevent.value) return; const target = shapeTreeContain(listeners.value, ev.target); if (target && !press.value.includes(target)) { press.value.push(target); } downTarget = target; }); return mergeFuns( stopHoverCheck, hoverDestory, listener( stage.container().parentElement as HTMLDivElement, "pointerup", async (ev) => { if (ev.button !== 0) return; const target = downTarget; const moveDis = downPos ? lineLen(downPos!, { x: ev.pageX, y: ev.pageY }) : 3; downPos = null; downTarget = null; const isMove = moveDis > 2; if (prevent.value) return; press.value = []; if (Date.now() - downTime > 300 || isMove) return; if (pointerIsTransformerInner()) return; if (ev.button !== 0) { // actives.value = []; // if (!operMode.value.mulSelection) { // selects.value = []; // } return; } if (operMode.value.mulSelection) { if (!target) return; actives.value = []; const ndx = selects.value.findIndex( (item) => item.id() === target?.id() ); if (~ndx) { selects.value.splice(ndx, 1); } else { selects.value.push(target!); } return; } else { selects.value = []; actives.value = target ? [target] : []; } } ), // listener( // stage.container().parentElement as HTMLDivElement, // "pointermove", // async () => { // if (prevent.value) return; // if (downTarget && !operMode.value.mulSelection) { // selects.value = []; // } // } // ), () => { listeners.value.forEach((shape) => { shape.off("pointerleave.mouse-status"); }); stage.off("pointerdown.mouse-status"); hovers.value = []; actives.value = []; press.value = []; selects.value = []; } ); }; let cleanup: () => void; const stopStatusWatch = globalWatch( () => can.mouseReact, (current, prev) => { if (inRevise(prev, current)) { cleanup! && cleanup(); if (current) { cleanup = init(stage.value!.getNode()); } } }, { immediate: true } ); const pauseShapes = ref(new Set()); const getShapes = (shapes: Ref) => computed({ get: () => { return shapes.value .filter((shape) => !pauseShapes.value.has(shape)) .map(toRaw); }, set: (val: EntityShape[]) => { shapes.value = val; }, }); const status = reactive({ hovers: getShapes(hovers), actives: getShapes(actives), selects: getShapes(selects), press: getShapes(press), listeners, pause(shape: EntityShape) { pauseShapes.value.add(shape); }, resume(shape: EntityShape) { pauseShapes.value.delete(shape); }, }); return { var: status, onDestroy: () => { stopStatusWatch(); cleanup && cleanup(); }, }; }, Symbol("mouseStatus")); export const useMouseShapeStatus = ( shape: Ref | undefined> ) => { const status = useMouseShapesStatus(); const shapeStatus = computed(() => { const $shape = shape.value?.getStage() as Shape; return { hover: status.hovers.includes($shape), active: status.actives.includes($shape), press: status.press.includes($shape), select: status.selects.includes($shape), pause: () => shape.value?.getNode() && status.pause(shape.value?.getNode()), resume: () => shape.value?.getNode() && status.resume(shape.value?.getNode()), }; }); watch( () => shape.value?.getStage(), (shape, _, onCleanup) => { if (shape) { if (status.listeners.includes(shape)) return; status.listeners.push(shape); onCleanup(() => { for (const key in status) { const k = key as keyof typeof status; if (Array.isArray(status[k])) { const ndx = status[k].indexOf(shape); ~ndx && status[k].splice(ndx, 1); } } }); } }, { immediate: true } ); return shapeStatus; }; export const useActiveItem = () => { const status = useMouseShapesStatus(); const store = useStore(); return computed(() => { if (!status.actives[0]) return; let shape = status.actives[0] as T; shape = ((shape as Group)?.findOne && ((shape as Group)?.findOne(".repShape") as T)) || shape; return { shape, item: store.getItemById(status.actives[0].id()), }; }); }; type MouseStyleProps = { shape?: Ref | undefined>; getMouseStyle: (data: any) => Record; data: Ref>; }; export const useMouseStyle = ( props: MouseStyleProps ) => { const shape = props.shape || ref(); const status = useMouseShapeStatus(shape); const transformIngShapes = useTransformIngShapes(); const mouseStyle = computed(() => { return props.getMouseStyle(props.data.value as any) as any; }); const getStyle = () => { const styleMap = new Map([[mouseStyle.value.default, true]]); if ("hover" in mouseStyle.value) { styleMap.set(mouseStyle.value.hover, status.value.hover); } if ("press" in mouseStyle.value) { styleMap.set(mouseStyle.value.press, status.value.press); } if ("focus" in mouseStyle.value) { styleMap.set(mouseStyle.value.focus, status.value.active); } if ( "drag" in mouseStyle.value && transformIngShapes.value.includes(shape.value?.getNode()!) ) { styleMap.set(mouseStyle.value.drag, true); } if ("select" in mouseStyle.value) { styleMap.set(mouseStyle.value.select, status.value.select); } const finalStyle = {}; for (const [style, use] of styleMap.entries()) { use && Object.assign(finalStyle as any, style); } return finalStyle; }; const style = ref(); watchEffect(() => { style.value = getStyle(); }); return { currentStyle: style, status, shape }; }; export const useAnimationMouseStyle = ( props: MouseStyleProps ) => { const { currentStyle, status } = useMouseStyle(props); // const [data, pauseAnimation, resumeAnimation] = useAniamtion( // currentStyle as any // ); return [ currentStyle, () => { // pauseAnimation(); status.value.pause(); }, () => { status.value.resume(); // resumeAnimation(); }, ] as const; };