123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514 |
- 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<EntityShape | undefined>; 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<EntityShape>();
- const enterHandler = (ev: KonvaEventObject<any, Stage>) => {
- const target = ev.target;
- hover.value = target;
- target.off(`pointerleave`, leaveHandler);
- target.on(`pointerleave`, leaveHandler as any);
- };
- const leaveHandler = (ev?: KonvaEventObject<any, Stage>) => {
- 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<DC<Shape> | undefined>,
- fn: () => void
- ) => {
- const init = (shape: Shape) => {
- let downTime: number;
- let move = false;
- const downHandler = (ev: KonvaEventObject<any>) => {
- if (ev.evt.button === 0) {
- ev.cancelBubble = true
- downTime = Date.now();
- move = false;
- }
- };
- const moveHandler = (ev: KonvaEventObject<any>) => {
- ev.cancelBubble = true
- downTime = Date.now();
- move = true;
- };
- const upHandler = (ev: KonvaEventObject<any>) => {
- 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<DC<EntityShape> | 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<DC<EntityShape> | 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<EntityShape[]>;
- const hovers = ref([]) as Ref<EntityShape[]>;
- const press = ref([]) as Ref<EntityShape[]>;
- const selects = ref([]) as Ref<EntityShape[]>;
- const actives = ref([]) as Ref<EntityShape[]>;
- 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<EntityShape>());
- const getShapes = (shapes: Ref<EntityShape[]>) =>
- 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<DC<EntityShape> | 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 = <T extends EntityShape>() => {
- 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<T extends ShapeType> = {
- shape?: Ref<DC<EntityShape> | undefined>;
- getMouseStyle: (data: any) => Record<string, any>;
- data: Ref<DrawItem<T>>;
- };
- export const useMouseStyle = <T extends ShapeType>(
- props: MouseStyleProps<T>
- ) => {
- 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 = <T extends ShapeType>(
- props: MouseStyleProps<T>
- ) => {
- 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;
- };
|