123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661 |
- import { computed, h, nextTick, reactive, ref, watch, watchEffect } from "vue";
- import { installGlobalVar, useCursor, useStage } from "./use-global-vars";
- import { useCan, useMode, useOperMode } from "./use-status";
- import {
- Area,
- InteractiveHook,
- InteractivePreset,
- useInteractiveAreas,
- useInteractiveDots,
- useInteractiveProps,
- } from "./use-interactive";
- import { Mode } from "@/constant/mode";
- import { copy, mergeFuns } from "@/utils/shared";
- import {
- Components,
- components,
- ComponentSnapInfo,
- ComponentValue,
- DrawItem,
- ShapeType,
- SnapPoint,
- } from "../components";
- import { useConversionPosition } from "./use-coversion-position";
- import { eqPoint, lineInner, Pos } 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 { useCurrentZIndex } from "./use-layer";
- import { useViewerTransform } from "./use-viewer";
- 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 useInteractiveDrawShapeAPI = installGlobalVar(() => {
- const mode = useMode();
- const can = useCan();
- const interactiveProps = useInteractiveProps();
- const conversion = useConversionPosition(true);
- const currentZIndex = useCurrentZIndex();
- const store = useStore();
- let addCount = 0;
- let isEnter = false;
- let modePop: (() => void) | undefined = void 0;
- const enter = () => {
- if (!isEnter) {
- isEnter = true;
- addCount = 0;
- modePop = mode.push(Mode.draw);
- }
- };
- const leave = () => {
- if (isEnter) {
- isEnter = false;
- modePop && modePop();
- addCount = 0;
- }
- };
- store.bus.on("addItemBefore", () => addCount++);
- store.bus.on("setItemBefore", () => addCount++);
- return {
- delShape(id: string) {
- const type = store.getType(id);
- type && store.delItem(type, id);
- },
- addShape: <T extends ShapeType>(
- shapeType: T,
- preset: Partial<DrawItem<T>> = {},
- data?: PayData<T>,
- pixel = false
- ) => {
- if (!can.drawMode) {
- throw "当前状态不允许添加";
- }
- enter();
- data = (data || {}) as PayData<T>;
- if (pixel) {
- data = (
- Array.isArray(data) ? data.map(conversion) : conversion(data)
- ) as PayData<T>;
- }
- if (!preset.zIndex) {
- preset.zIndex = currentZIndex.max + 1;
- }
- interactiveProps.value = {
- type: shapeType,
- preset,
- callback: leave,
- operate: { single: true, immediate: true, data },
- };
- },
- enterDrawShape: async <T extends ShapeType>(
- shapeType: T,
- preset: InteractivePreset<T>["preset"] = {},
- single = false
- ) => {
- if (isEnter) {
- leave();
- await new Promise((resolve) => setTimeout(resolve, 16));
- }
- if (!can.drawMode || mode.include(Mode.draw)) {
- throw "当前状态不允许添加";
- }
- if (!preset.zIndex) {
- preset.zIndex = currentZIndex.max + 1;
- }
- interactiveProps.value = {
- type: shapeType,
- preset,
- operate: { single },
- callback: leave,
- };
- enter();
- },
- quitDrawShape: () => {
- const currentAddCount = addCount;
- leave();
- interactiveProps.value = void 0;
- return currentAddCount;
- },
- drawing: computed(() => mode.include(Mode.draw)),
- drawType: computed(() => {
- return interactiveProps.value?.type && components[interactiveProps.value?.type].addMode
- })
- };
- });
- export const useDrawRunning = (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.draw) &&
- 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;
- };
- export const usePointBeforeHandler = (enableTransform = false, enableSnap = false) => {
- const operMode = useOperMode();
- const conversionPosition = useConversionPosition(enableTransform);
- const snap = enableSnap && useSnap();
- const infos = useCustomSnapInfos();
- const addedInfos: ComponentSnapInfo[] = [];
- return {
- transform: (p: SnapPoint, geo = [p], geoNeedConversion = true) => {
- p = conversionPosition(p);
- if (operMode.value.freeDraw) {
- return p;
- }
- snap && snap.clear();
- if (geoNeedConversion) {
- geo = geo.map(conversionPosition);
- }
- 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 useInteractiveDrawTemp = <T extends ShapeType>({
- type,
- useIA,
- refSelf,
- enter,
- quit,
- getSnapGeo,
- }: {
- type: T;
- useIA: InteractiveHook;
- refSelf?: boolean;
- enter?: () => void;
- quit?: () => void;
- getSnapGeo?: (data: DrawItem<T>) => SnapPoint[];
- }) => {
- const { quitDrawShape } = useInteractiveDrawShapeAPI();
- const isRuning = useDrawRunning(type);
- const items = reactive([]) as DrawItem<T>[];
- const obj = components[type] as Components[T];
- const beforeHandler = usePointBeforeHandler(true, true);
- const processors = useStoreRenderProcessors();
- const viewTransform = useViewerTransform();
- const conversionPosition = useConversionPosition(true);
- const history = useHistory();
- 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;
- quitDrawShape();
- clear();
- quit && quit();
- },
- enter,
- beforeHandler: (p) => {
- beforeHandler.clear();
- let geo: SnapPoint[] | undefined;
- if (items.length && getSnapGeo) {
- const item = obj.interactiveFixData({
- info: {
- cur: conversionPosition(p),
- consumed: ia.consumedMessage,
- action: MessageAction.update,
- },
- data: copy(items[0]),
- history,
- viewTransform: viewTransform.value,
- store,
- } as any);
- geo = getSnapGeo(item as any);
- }
- return beforeHandler.transform(p, geo, !geo);
- },
- });
- const addItem = (cur: PayData<T>) => {
- let item: any = obj.interactiveToData({
- info: { cur, consumed: ia.consumedMessage, action: MessageAction.add },
- preset: ia.preset,
- history,
- viewTransform: viewTransform.value,
- store,
- } as any);
- if (!item) return;
- item = reactive(item);
- const storeAddItem = (cItem: any) => {
- const items = store.getTypeItems(type);
- if (items.some((item) => item.id === cItem.id)) {
- store.setItem(type, { id: cItem.id, value: cItem });
- } else {
- store.addItem(type, cItem);
- }
- };
- if (ia.singleDone.value) {
- storeAddItem(item);
- return;
- }
- items.push(item);
- // 箭头参考自身位置
- if (refSelf && Array.isArray(cur)) {
- beforeHandler.addRef(cur[0]);
- }
- const stop = mergeFuns(
- // 监听位置变化
- watch(
- cur,
- () => {
- obj.interactiveFixData({
- info: {
- cur,
- consumed: ia.consumedMessage,
- action: MessageAction.update,
- },
- data: item,
- history,
- viewTransform: viewTransform.value,
- store,
- } as any);
- },
- { deep: true }
- ),
- // 监听是否消费完毕
- watch(ia.singleDone, () => {
- processorIds.push(item.id);
- storeAddItem(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 useInteractiveDrawAreas = <T extends ShapeType>(type: T) => {
- const cursor = useCursor();
- let cursorPop: () => void;
- return useInteractiveDrawTemp({
- type,
- useIA: useInteractiveAreas,
- refSelf: type === "arrow",
- enter() {
- cursorPop = cursor.push("./icons/m_draw.png");
- },
- quit() {
- cursorPop && cursorPop();
- },
- });
- };
- export const useInteractiveDrawDots = <T extends ShapeType>(type: T) => {
- const cursor = useCursor();
- let cursorPop: () => void;
- return useInteractiveDrawTemp({
- type,
- useIA: useInteractiveDots,
- enter() {
- cursorPop = cursor.push("./icons/m_add.png");
- },
- quit() {
- cursorPop && cursorPop();
- },
- getSnapGeo(item) {
- return components[type].getSnapPoints(item as any);
- },
- });
- };
- export const penUpdatePoints = <T extends Pos>(
- transfromPoints: T[],
- cur: T,
- needClose = false
- ) => {
- 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 = needClose && i === 0;
- if (!isStart && !isLast) {
- points.splice(i--, 1);
- oper = "del";
- repeatStart = false;
- } 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 useInteractiveDrawPen = <T extends ShapeType>(type: T) => {
- const { quitDrawShape } = useInteractiveDrawShapeAPI();
- const isRuning = useDrawRunning(type);
- const obj = components[type] as Components[T];
- const beforeHandler = usePointBeforeHandler(true, true);
- const history = useHistory();
- const processors = useStoreRenderProcessors();
- const store = useStore();
- const viewTransform = useViewerTransform();
- const operMode = useOperMode();
- const processorIds = processors.register(() => {
- return (props: any) => h(DrawShape, { ...props, show: false });
- });
- // 可能历史空间会撤销 重做更改到正在绘制的组件
- const currentCursor = ref("./icons/m_add.png");
- const cursor = useCursor();
- let cursorPop: ReturnType<typeof cursor.push> | null = null;
- let stopWatch: (() => void) | null = null;
- const ia = useInteractiveDots({
- shapeType: type,
- isRuning,
- enter() {
- cursorPop = cursor.push(currentCursor.value);
- watch(currentCursor, () => {
- cursorPop?.set(currentCursor.value);
- });
- },
- quit: () => {
- items.length = 0;
- processorIds.length = 0;
- quitDrawShape();
- beforeHandler.clear();
- cursorPop && cursorPop();
- stopWatch && stopWatch();
- },
- beforeHandler: (p) => {
- beforeHandler.clear();
- const pa = beforeHandler.transform(p, prev && [prev, p]);
- currentIsDel && beforeHandler.clear();
- return pa;
- },
- });
- const shape = computed(
- () =>
- ia.isRunning.value &&
- typeof ia.preset?.id === "string" &&
- ia.preset?.id &&
- ia.preset.getMessages &&
- store.getItemById(ia.preset.id)
- );
- const items = reactive([]) as DrawItem<T>[];
- const messages = useHistoryAttach<Pos[]>(
- `${type}-pen`,
- isRuning,
- shape.value ? (ia.preset!.getMessages! as any) : () => [],
- true
- );
- let prev: SnapPoint;
- let firstEntry = true;
- let currentIsDel = false;
- if (shape.value) {
- processorIds.push(shape.value.id);
- items[0] = copy(shape.value) as DrawItem<T>;
- firstEntry = false;
- }
- const getAddMessage = (cur: Pos) => {
- let consumed = messages.value;
- currentCursor.value = "./icons/m_add.png";
- let pen: null | ReturnType<typeof penUpdatePoints> = null;
- if (!operMode.value.freeDraw) {
- // pen = penUpdatePoints(messages.value, cur, type !== "polygon");
- // consumed = pen.points;
- // cur = pen.cur;
- }
- return {
- pen,
- consumed,
- cur,
- action: firstEntry ? MessageAction.add : MessageAction.replace,
- } as any;
- };
- const setMessage = (cur: Pos) => {
- const { pen, ...msg } = getAddMessage(cur);
- if ((currentIsDel = pen?.oper === "del")) {
- currentCursor.value = "./icons/m_reduce.png";
- beforeHandler.clear();
- }
- return msg;
- };
- 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({
- preset: ia.preset as any,
- info: setMessage(dot),
- viewTransform: viewTransform.value,
- history,
- store,
- });
- if (!item) return;
- items[0] = item = reactive(item);
- }
- const storeAddItem = (cItem: any) => {
- const items = store.getTypeItems(type);
- if (items.some((item) => item.id === cItem.id)) {
- store.setItem(type, { id: cItem.id, value: cItem });
- } else {
- store.addItem(type, cItem);
- }
- };
- if (ia.singleDone.value) {
- storeAddItem(item);
- return;
- }
- const update = () => {
- obj.interactiveFixData({
- data: item,
- info: setMessage(dot),
- viewTransform: viewTransform.value,
- history,
- store,
- });
- };
- stopWatch = mergeFuns(
- watch(() => operMode.value.freeDraw, update),
- watch(dot, update, { immediate: true, deep: true }),
- watch(
- messages,
- () => {
- if (!messages.value) return;
- if (messages.value.length === 0) {
- quitDrawShape();
- } else {
- update();
- }
- },
- { deep: true }
- ),
- // 监听是否消费完毕
- watch(ia.singleDone, () => {
- prev = { ...dot, view: true };
- const cItem = JSON.parse(JSON.stringify(item));
- const isChange = pushMessages(dot);
- if (isChange) {
- if (firstEntry) {
- processorIds.push(item.id);
- history.preventTrack(() => {
- storeAddItem(cItem);
- });
- } else {
- store.setItem(type, { id: item.id, value: cItem });
- }
- }
- beforeHandler.clear();
- stopWatch && stopWatch();
- stopWatch = null;
- 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 useInteractiveDrawPen(type);
- } else if (obj.addMode === "area") {
- return useInteractiveDrawAreas(type);
- } else {
- return useInteractiveDrawDots(type);
- }
- };
|