import { installGlobalVar, useCan, useMode, useStage, } from "./use-global-vars.ts"; import { DrawItem, ShapeType } from "../components"; import { reactive, Ref, ref, watch, watchEffect } from "vue"; import { Pos } from "../../utils/math.ts"; import { clickListener, getOffset, listener } from "../../utils/event.ts"; import { mergeFuns } from "../../utils/shared.ts"; import { Mode } from "@/constant/mode.ts"; export type InteractivePreset = { type: T; callback?: () => void; preset?: Partial>; operate?: { immediate?: boolean; single?: boolean; data?: any; }; }; export const useInteractiveProps = installGlobalVar( () => ref(), Symbol("interactiveProps") ); export type Area = [Pos, Pos]; export enum InteractiveAction { delete, } export type InteractiveMessage = { area?: Area; dot?: Pos; ndx?: number; action?: InteractiveAction; }; export type InteractiveAreas = ReturnType; export type InteractiveDots = ReturnType; export type Interactive = InteractiveAreas | InteractiveDots; const useInteractiveExpose = ( messages: Ref, init: (dom: HTMLDivElement) => () => void, singleDone: Ref, isRunning: Ref, quit: () => void, autoConsumed?: boolean ) => { const consumedMessages = reactive(new WeakSet()) as WeakSet; const stage = useStage(); const interactiveProps = useInteractiveProps(); watch(isRunning, (can, _, onCleanup) => { if (can) { const props = interactiveProps.value!; const cleanups = [] as Array<() => void>; if (props.operate?.single) { // 如果指定单次则消息中有信息,并且确定完成则马上退出 cleanups.push( watchEffect( () => { if (messages.value.length > 0 && singleDone.value) { quit(); props.callback && props.callback(); } }, { flush: "post" } ) ); } // 单纯添加 if (props.operate?.immediate) { messages.value.push(props.operate.data as T); singleDone.value = true; } else { const $stage = stage.value!.getStage(); const dom = $stage.container(); cleanups.push(init(dom)); cleanups.push(() => { quit(); props.callback && props.callback(); }); } onCleanup(mergeFuns(cleanups)); } else { messages.value = []; } }); return { isRunning, get preset() { return interactiveProps.value?.preset; }, get messages() { const items = messages.value; const result = items.filter((item) => !consumedMessages.has(item)); autoConsumed && result.forEach((item) => consumedMessages.add(item)); return result as T[]; }, getNdx(item: T) { return messages.value.indexOf(item); }, get consumedMessage() { const items = messages.value; return items.filter((item) => consumedMessages.has(item)) as T[]; }, consume(items: T[]) { items.forEach((item) => consumedMessages.add(item)); }, singleDone, }; }; type UseInteractiveProps = { isRuning: Ref; quit: () => void; beforeHandler?: (p: Pos) => Pos; shapeType?: ShapeType; autoConsumed?: boolean; }; export const useInteractiveAreas = ({ isRuning, autoConsumed, beforeHandler, quit, }: UseInteractiveProps) => { const mode = useMode(); const can = useCan(); const singleDone = ref(true); const messages = ref([]); const init = (dom: HTMLDivElement) => { let pushed = false; let pushNdx = -1; let downed = false; let tempArea: Area; let dragging = false; return mergeFuns( listener(dom, "pointerdown", (ev) => { if (!can.dragMode) return; const position = getOffset(ev, dom); if (ev.button === 0) { tempArea = [ beforeHandler ? beforeHandler(position) : position, ] as unknown as Area; downed = true; singleDone.value = false; dragging = false; mode.add(Mode.draging); } }), listener(document.documentElement, "pointermove", (ev) => { if (!can.dragMode) return; const end = getOffset(ev, dom); const point = beforeHandler ? beforeHandler(end) : end; if (downed) { if (pushed) { messages.value[pushNdx]![1] = point; } else { tempArea[1] = point; pushed = true; pushNdx = messages.value.length; messages.value[pushNdx] = tempArea; } dragging = true; } else { tempArea = [point] as unknown as Area; } }), listener(dom, "pointerup", (ev) => { if (downed) { mode.del(Mode.draging); } else if (!dragging) return; if (can.dragMode) { const position = getOffset(ev, dom); messages.value[pushNdx]![1] = beforeHandler ? beforeHandler(position) : position; } pushNdx = -1; pushed = false; downed = false; dragging = false; singleDone.value = true; }) ); }; return useInteractiveExpose( messages, init, singleDone, isRuning, quit, autoConsumed ); }; export const useInteractiveDots = ({ autoConsumed, isRuning, quit, beforeHandler, }: UseInteractiveProps) => { if (autoConsumed === void 0) autoConsumed = false; const mode = useMode(); const can = useCan(); const singleDone = ref(true); const messages = ref([]); const init = (dom: HTMLDivElement) => { if (!can.dragMode) return () => {}; let moveIng = false; let pushed = false; const empty = { x: -9999, y: -9999 }; const pointer = ref(empty); mode.add(Mode.draging); return mergeFuns( () => { mode.del(Mode.draging); }, clickListener(dom, (_, ev) => { if (!moveIng || !can.dragMode) return; pointer.value = { ...empty }; singleDone.value = true; moveIng = false; pushed = false; }), listener(dom, "pointermove", (ev) => { if (!can.dragMode) return; if (!pushed) { messages.value.push(pointer.value); singleDone.value = false; pushed = true; } moveIng = true; const position = getOffset(ev); const current = beforeHandler ? beforeHandler(position) : position; pointer.value.x = current.x; pointer.value.y = current.y; }) ); }; return useInteractiveExpose( messages, init, singleDone, isRuning, quit, autoConsumed ); };