123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542 |
- import { h, nextTick, reactive, ref, watch, watchEffect } from "vue";
- import {
- installGlobalVar,
- useCan,
- useCursor,
- useDownKeys,
- useMode,
- useStage,
- } from "./use-global-vars";
- import {
- Area,
- InteractiveHook,
- useInteractiveAreas,
- useInteractiveDots,
- useInteractiveProps,
- } from "./use-interactive";
- import { Mode } from "@/constant/mode";
- import { mergeFuns } from "@/utils/shared";
- import {
- Components,
- components,
- ComponentSnapInfo,
- ComponentValue,
- DrawItem,
- ShapeType,
- SnapPoint,
- } from "../components";
- import { useConversionPosition } from "./use-coversion-position";
- import { eqPoint, lineInner, linePointLen, Pos, zeroEq } from "@/utils/math";
- import { useCustomSnapInfos, useSnap } from "./use-snap";
- import { generateSnapInfos } from "../components/util";
- import { useStore, useStoreRenderProcessors } from "../store";
- import DrawShape from "../renderer/draw-shape.vue";
- import { useHistory, useHistoryAttach } from "./use-history";
- import penA from "../assert/cursor/pic_pen_a.ico";
- import penR from "../assert/cursor/pic_pen_r.ico";
- type PayData<T extends ShapeType> = ComponentValue<T, "addMode"> extends "area"
- ? Area
- : Pos;
- export enum MessageAction {
- add,
- delete,
- update,
- replace,
- }
- export type AddMessage<T extends ShapeType> = {
- consumed: PayData<T>[];
- cur?: PayData<T>;
- ndx?: number;
- action: MessageAction;
- };
- export const useInteractiveAddShapeAPI = installGlobalVar(() => {
- const mode = useMode();
- const can = useCan();
- const interactiveProps = useInteractiveProps();
- const conversion = useConversionPosition(true);
- let isEnter = false;
- const enter = () => {
- if (!isEnter) {
- isEnter = true;
- mode.push(Mode.add);
- }
- };
- const leave = () => {
- if (isEnter) {
- isEnter = false;
- mode.pop();
- }
- };
- return {
- addShape: <T extends ShapeType>(
- shapeType: T,
- preset: Partial<DrawItem<T>> = {},
- data: PayData<T>,
- pixel = false
- ) => {
- if (!can.addMode) {
- throw "当前状态不允许添加";
- }
- enter();
- if (pixel) {
- data = (
- Array.isArray(data) ? data.map(conversion) : conversion(data)
- ) as PayData<T>;
- }
- interactiveProps.value = {
- type: shapeType,
- preset,
- callback: leave,
- operate: { single: true, immediate: true, data },
- };
- },
- enterMouseAddShape: async <T extends ShapeType>(
- shapeType: T,
- preset: Partial<DrawItem<T>> = {},
- single = false
- ) => {
- if (isEnter) {
- leave();
- await new Promise((resolve) => setTimeout(resolve, 16));
- }
- if (!can.addMode || mode.include(Mode.add)) {
- throw "当前状态不允许添加";
- }
- enter();
- interactiveProps.value = {
- type: shapeType,
- preset,
- operate: { single },
- callback: leave,
- };
- },
- quitMouseAddShape: () => {
- leave();
- interactiveProps.value = void 0;
- },
- };
- });
- export const useIsAddRunning = (shapeType?: ShapeType) => {
- const stage = useStage();
- const mode = useMode();
- const interactiveProps = useInteractiveProps();
- const isRunning = ref<boolean>(false);
- let currentPreset: any;
- const updateIsRunning = () => {
- const isRun = !!(
- stage.value &&
- mode.include(Mode.add) &&
- (!shapeType || shapeType === interactiveProps.value?.type)
- );
- if (isRunning.value !== isRun) {
- isRunning.value = isRun;
- } else if (currentPreset !== interactiveProps.value?.preset) {
- isRunning.value = false;
- nextTick(() => {
- isRunning.value = isRun;
- });
- }
- currentPreset = interactiveProps.value?.preset;
- };
- watchEffect(updateIsRunning);
- return isRunning;
- };
- const usePointBeforeHandler = (enableTransform = false, enableSnap = false) => {
- const conversionPosition = useConversionPosition(enableTransform);
- const snap = enableSnap && useSnap();
- const infos = useCustomSnapInfos();
- const addedInfos: ComponentSnapInfo[] = [];
- return {
- transform: (
- point: SnapPoint,
- prevPoint?: SnapPoint,
- nextPoint?: SnapPoint
- ) => {
- snap && snap.clear();
- let p = conversionPosition(point);
- const geo = [p];
- prevPoint && geo.unshift({ ...prevPoint, view: true });
- nextPoint && geo.push({ ...nextPoint, view: true });
- const selfInfos = generateSnapInfos(geo, true, true);
- const transform = snap && snap.move(selfInfos);
- p = transform ? transform.point(p) : p;
- return p;
- },
- addRef(p: Pos | Pos[]) {
- const geo = Array.isArray(p) ? p : [p];
- const snapInfos = generateSnapInfos(geo, true, true);
- snapInfos.forEach((info) => {
- infos.add(info);
- addedInfos.push(info);
- });
- },
- clear: () => {
- snap && snap.clear();
- },
- clearRef: () => {
- addedInfos.forEach((info) => {
- infos.remove(info);
- });
- addedInfos.length = 0;
- },
- };
- };
- const useInteractiveAddTemp = <T extends ShapeType>({
- type,
- useIA,
- refSelf,
- enter,
- quit,
- }: {
- type: T;
- useIA: InteractiveHook;
- refSelf?: boolean;
- enter?: () => void;
- quit?: () => void;
- }) => {
- const { quitMouseAddShape } = useInteractiveAddShapeAPI();
- const isRuning = useIsAddRunning(type);
- const items = reactive([]) as DrawItem<T>[];
- const obj = components[type] as Components[T];
- const beforeHandler = usePointBeforeHandler(true, true);
- const processors = useStoreRenderProcessors();
- const store = useStore();
- const processorIds = processors.register(() => DrawShape);
- const clear = () => {
- beforeHandler.clear();
- beforeHandler.clearRef();
- };
- const ia = useIA({
- shapeType: type,
- isRuning,
- quit: () => {
- items.length = 0;
- processorIds.length = 0;
- quitMouseAddShape();
- clear();
- quit && quit();
- },
- enter,
- beforeHandler: (p) => {
- beforeHandler.clear();
- return beforeHandler.transform(p);
- },
- });
- const addItem = (cur: PayData<T>) => {
- let item: any = obj.interactiveToData(
- {
- consumed: ia.consumedMessage,
- cur,
- action: MessageAction.add,
- } as any,
- ia.preset
- );
- if (!item) return;
- item = reactive(item);
- if (ia.singleDone.value) {
- store.addItem(type, item);
- return;
- }
- items.push(item);
- // 箭头参考自身位置
- if (refSelf && Array.isArray(cur)) {
- beforeHandler.addRef(cur[0]);
- }
- const stop = mergeFuns(
- // 监听位置变化
- watch(
- cur,
- () =>
- obj.interactiveFixData(item, {
- consumed: ia.consumedMessage,
- cur,
- action: MessageAction.update,
- } as any),
- { deep: true }
- ),
- // 监听是否消费完毕
- watch(ia.singleDone, () => {
- processorIds.push(item.id);
- store.addItem(type, item);
- const ndx = items.indexOf(item);
- items.splice(ndx, 1);
- clear();
- stop();
- })
- );
- };
- // 每次拽结束都加组件
- watch(
- () => ia.messages,
- (datas: any) => {
- datas.forEach(addItem);
- ia.consume(datas);
- },
- { immediate: true }
- );
- return items;
- };
- // 拖拽面积确定组件
- export const useInteractiveAddAreas = <T extends ShapeType>(type: T) => {
- const cursor = useCursor();
- return useInteractiveAddTemp({
- type,
- useIA: useInteractiveAreas,
- refSelf: type === "arrow",
- enter() {
- cursor.push("crosshair");
- },
- quit() {
- cursor.pop();
- },
- });
- };
- export const useInteractiveAddDots = <T extends ShapeType>(type: T) => {
- const cursor = useCursor();
- return useInteractiveAddTemp({
- type,
- useIA: useInteractiveDots,
- enter() {
- cursor.push(penA);
- },
- quit() {
- cursor.pop();
- },
- });
- };
- export const penUpdatePoints = <T extends Pos>(
- transfromPoints: T[],
- cur: T
- ) => {
- const points = [...transfromPoints];
- let oper: "del" | "add" | "set" | "no" = "add";
- const resetCur = () => {
- if (points.length) {
- return (cur = points.pop()!);
- } else {
- return cur;
- }
- };
- let repeatStart = false;
- for (let i = 0; i < points.length; i++) {
- if (eqPoint(points[i], cur)) {
- const isLast = i === points.length - 1;
- const isStart = i === 0;
- if (!isStart && !isLast) {
- points.splice(i--, 1);
- oper = "del";
- } else if ((oper !== "del" && isLast) || isStart) {
- oper = "no";
- if (isStart) {
- repeatStart = true;
- }
- }
- }
- }
- if (oper === "del" || oper === "no") {
- if (repeatStart) {
- const change = points.length > 2
- return {
- points,
- oper,
- cur: change ? cur : resetCur(),
- unchanged: !change
- };
- }
- return { points, oper, cur: repeatStart ? cur : resetCur() };
- }
- for (let i = 0, ndx = 0; i < transfromPoints.length - 1; i++, ndx++) {
- const line = [transfromPoints[i], transfromPoints[i + 1]];
- if (lineInner(line, cur)) {
- oper = "set";
- points.splice(++ndx, 0, cur);
- resetCur();
- }
- }
- return { points, oper, cur };
- };
- // 钢笔添加
- export const useInteractiveAddPen = <T extends ShapeType>(type: T) => {
- const { quitMouseAddShape } = useInteractiveAddShapeAPI();
- const isRuning = useIsAddRunning(type);
- const items = reactive([]) as DrawItem<T>[];
- const obj = components[type] as Components[T];
- const beforeHandler = usePointBeforeHandler(true, true);
- const history = useHistory();
- const processors = useStoreRenderProcessors();
- const store = useStore();
- const downKeys = useDownKeys();
- const processorIds = processors.register(() => {
- return (props: any) => h(DrawShape, { ...props, show: false });
- });
- let prev: Pos;
- let firstEntry = true;
- // 可能历史空间会撤销 重做更改到正在绘制的组件
- const messages = useHistoryAttach<Pos[]>(`${type}-pen`, isRuning, []);
- const currentCursor = ref(penA);
- const cursor = useCursor();
- const ia = useInteractiveDots({
- shapeType: type,
- isRuning,
- enter() {
- cursor.push(currentCursor.value);
- watch(currentCursor, () => {
- cursor.value = currentCursor.value;
- });
- },
- quit: () => {
- items.length = 0;
- processorIds.length = 0;
- quitMouseAddShape();
- beforeHandler.clear();
- cursor.pop();
- },
- beforeHandler: (p) => {
- beforeHandler.clear();
- return beforeHandler.transform(p, prev);
- },
- });
- const getAddMessage = (cur: Pos) => {
- let consumed = messages.value;
- currentCursor.value = penA;
- let pen: null | ReturnType<typeof penUpdatePoints> = null;
- if (!downKeys.has("Control")) {
- pen = penUpdatePoints(messages.value, cur);
- if (pen.oper === "del") {
- currentCursor.value = penR;
- }
- consumed = pen.points;
- cur = pen.cur;
- }
- return {
- pen,
- consumed,
- cur,
- action: firstEntry ? MessageAction.add : MessageAction.replace,
- } as any;
- };
- const pushMessages = (cur: Pos) => {
- const { pen } = getAddMessage(cur);
- if (pen) {
- if (!pen.unchanged) {
- messages.value = pen.points;
- cur = pen.cur;
- messages.value.push(cur);
- }
- } else {
- messages.value.push(cur);
- }
-
- return !pen?.unchanged;
- };
- const addItem = (cur: PayData<T>) => {
- const dot = cur as Pos;
- if (messages.value.length === 0) {
- firstEntry = true;
- items.length = 0;
- }
- let item: any = items.length === 0 ? null : items[0];
- if (!item) {
- item = obj.interactiveToData(getAddMessage(dot), ia.preset);
- if (!item) return;
- items[0] = item = reactive(item);
- }
- if (ia.singleDone.value) {
- store.addItem(type, item);
- return;
- }
- const update = () => {
- obj.interactiveFixData(item, getAddMessage(dot));
- };
- const stop = mergeFuns(
- watch(dot, update, { immediate: true, deep: true }),
- watch(
- messages,
- () => {
- if (!messages.value) return;
- if (messages.value.length === 0) {
- quitMouseAddShape();
- } else {
- update();
- }
- },
- { deep: true }
- ),
- // 监听是否消费完毕
- watch(ia.singleDone, () => {
- prev = dot;
- const cItem = JSON.parse(JSON.stringify(item));
- const isChange = pushMessages(dot);
- if (isChange) {
- if (firstEntry) {
- processorIds.push(item.id);
- history.preventTrack(() => store.addItem(type, cItem));
- } else {
- store.setItem(type, { id: item.id, value: cItem });
- }
- }
- beforeHandler.clear();
- stop();
- firstEntry = false;
- })
- );
- };
- // 每次拽结束都加组件
- watch(
- () => ia.messages,
- (datas: any) => {
- datas.forEach(addItem);
- ia.consume(datas);
- },
- { immediate: true }
- );
- return items;
- };
- export const useInteractiveAdd = <T extends ShapeType>(type: T) => {
- const obj = components[type];
- if (obj.addMode === "dots") {
- return useInteractiveAddPen(type);
- } else if (obj.addMode === "area") {
- return useInteractiveAddAreas(type);
- } else {
- return useInteractiveAddDots(type);
- }
- };
|