Bladeren bron

feat: 添加多子级模式,修改多选模式

bill 2 maanden geleden
bovenliggende
commit
17edd538fe

+ 26 - 4
src/core/components/group/group.vue

@@ -12,8 +12,14 @@
 <script lang="ts" setup>
 import TempGroup from "./temp-group.vue";
 import { Operate } from "../../html-mount/propertys/index.ts";
-import { GroupData, getMouseStyle, defaultStyle, matResponse } from "./index.ts";
-import { useComponentStatus } from "@/core/hook/use-component.ts";
+import {
+  GroupData,
+  getMouseStyle,
+  defaultStyle,
+  matResponse,
+  GroupMatCtxItem,
+} from "./index.ts";
+import { useComponentStatus, useGetShapeBelong } from "@/core/hook/use-component.ts";
 import { useMouseShapeStatus } from "@/core/hook/use-mouse-status.ts";
 import { useCustomTransformer } from "@/core/hook/use-transformer.ts";
 import { Rect } from "konva/lib/shapes/Rect";
@@ -24,7 +30,7 @@ import { useHistory } from "@/core/hook/use-history.ts";
 import { computed, nextTick, onUnmounted, ref, shallowRef } from "vue";
 import { useOperMode } from "@/core/hook/use-status.ts";
 import { EntityShape } from "@/deconstruction.js";
-import { useForciblyShowItemIds } from "@/core/hook/use-global-vars.ts";
+import { useForciblyShowItemIds, useStage } from "@/core/hook/use-global-vars.ts";
 import { themeColor } from "@/constant";
 import { debounce } from "@/utils/shared.ts";
 
@@ -38,6 +44,21 @@ const emit = defineEmits<{
 const store = useStore();
 const history = useHistory();
 const autoUpdate = ref(true);
+const stage = useStage();
+const _getShapeBelong = useGetShapeBelong();
+const getShapeBelong = (id: string) => {
+  const shape = stage.value?.getNode().findOne(`#${id}`);
+  if (!shape) return;
+  const belong = _getShapeBelong(shape as EntityShape);
+  if (belong) {
+    return {
+      type: belong.type,
+      item: belong.item,
+      childrenId: belong.isSelf ? undefined : belong.curId,
+    } as GroupMatCtxItem;
+  }
+};
+
 const { shape, tData, data } = useComponentStatus<Rect, GroupData>({
   emit,
   props,
@@ -75,7 +96,8 @@ const { shape, tData, data } = useComponentStatus<Rect, GroupData>({
       },
       handler: debounce((data, mat) => {
         setShapeTransform(shape.value!.getNode(), mat);
-        matResponse({ data, mat, store }, prevMat);
+        const ctxs = data.ids.map(getShapeBelong).filter((item: any) => !!item);
+        matResponse({ data, mat, store }, prevMat, ctxs);
         prevMat = mat;
         getGroupShapes!().forEach((shape) => nextTick(() => shape.fire("bound-change")));
         return true;

+ 54 - 18
src/core/components/group/index.ts

@@ -1,6 +1,12 @@
 import { BaseItem, getBaseItem } from "../util.ts";
 import { getMouseColors } from "@/utils/colors.ts";
-import { DrawItem, InteractiveFix, InteractiveTo, MatResponseProps } from "../index.ts";
+import {
+  DrawItem,
+  InteractiveFix,
+  InteractiveTo,
+  MatResponseProps,
+  ShapeType,
+} from "../index.ts";
 import { Transform } from "konva/lib/Util";
 import { components } from "../index";
 
@@ -9,7 +15,7 @@ export { default as TempComponent } from "./temp-group.vue";
 
 export const shapeName = "分组";
 export const defaultStyle = {
-  stroke: '#cccccc',
+  stroke: "#cccccc",
   strokeWidth: 2,
   opacity: 0.8,
   dash: [10, 10],
@@ -22,10 +28,10 @@ export const getMouseStyle = (data: GroupData) => {
   return {
     default: {
       stroke: strokeStatus.pub,
-      dash: data.dash || defaultStyle.dash
+      dash: data.dash || defaultStyle.dash,
     },
     hover: { stroke: strokeStatus.hover },
-    select: { stroke: strokeStatus.select, dash: [10, 0], },
+    select: { stroke: strokeStatus.select, dash: [10, 0] },
     press: { stroke: strokeStatus.press },
   };
 };
@@ -62,25 +68,55 @@ export const interactiveFixData: InteractiveFix<"group"> = ({ data }) => {
   return data;
 };
 
-
-
-export const matResponse = ({data, mat, store}: MatResponseProps<'group'>, prevMat?: Transform) => {
+export type GroupMatCtxItem<T extends ShapeType = ShapeType> = {
+  type: T,
+  item: DrawItem<T>,
+  childrenId?: string,
+};
+export const matResponse = (
+  { data, mat, store }: MatResponseProps<"group">,
+  prevMat?: Transform,
+  ctxs?: GroupMatCtxItem[]
+) => {
   if (!store) {
     return;
   }
   const incMat = prevMat ? mat.copy().multiply(prevMat.invert()) : mat;
   prevMat = mat;
 
-  const items: DrawItem[] = []
-  for (const id of data.ids) {
-    const item = store.getItemById(id);
-    if (!item) continue;
-    items.push(item)
+  if (!ctxs) {
+    ctxs = [];
+    for (const id of data.ids) {
+      const item = store.getItemById(id);
+      if (!item) continue;
+      const type = store.getType(item.id)!;
+      ctxs.push({ type, item })
+    }
+  }
+
+  const startTypes: ShapeType[] = []
+  for (let i = 0; i < ctxs.length; i++) {
+    const type = ctxs[i].type
+    if (!startTypes.includes(type)) {
+      components[type].startMatResponse && components[type].startMatResponse()  
+      startTypes.push(type)
+    }
+    components[type].matResponse({
+      data: ctxs[i].item as any,
+      mat: incMat,
+      increment: true,
+      store,
+      operId: ctxs[i].childrenId
+    });
+  }
+  for (const type of startTypes) {
+    components[type].endMatResponse && components[type].endMatResponse!()  
   }
-  for (let i = 0; i < items.length; i++) {
-    const type = store.getType(items[i].id)!
-    const item = items[i]
-    components[type].matResponse({ data: item as any, mat: incMat, increment: true, store });
-    store.setItem(type, { value: item, id: item.id });
+  for (let i = 0; i < ctxs.length; i++) {
+    store.setItem(ctxs[i].type, { value: ctxs[i].item, id: ctxs[i].item.id });
   }
-}
+};
+
+export const useGetSelectionManage = () => {
+  return { canSelect: () => false };
+};

+ 0 - 2
src/core/components/icon/index.ts

@@ -139,7 +139,6 @@ export const interactiveToData: InteractiveTo<"icon"> = ({
   ...args
 }) => {
   if (info.cur) {
-  console.error(preset)
     return interactiveFixData({
       ...args,
       viewTransform,
@@ -168,7 +167,6 @@ export const interactiveFixData: InteractiveFix<"icon"> = ({
     const mat = new Transform().translate(info.cur!.x, info.cur!.y);
     data.mat = mat.m;
   }
-  console.error(data)
   return data;
 };
 

+ 4 - 0
src/core/components/index.ts

@@ -61,6 +61,9 @@ type CompAttach<key extends ShapeType> = {
   GroupComponent: (props: { data: DrawItem[] }) => any;
   getPredefine?: (attrKey: keyof DrawItem<key>) => any;
   useGetSelectionManage?: UseGetSelectionManage
+  startMatResponse?: () => void
+  endMatResponse?: () => void
+  childrenDataGetter?: (data: DrawItem<key>, id: string) => any
 };
 type _Components = {
   [key in keyof typeof _components]: (typeof _components)[key];
@@ -134,6 +137,7 @@ export type MatResponseProps<T extends ShapeType> = {
   increment?: boolean;
   operType?: TransformerVectorType;
   store?: DrawStore;
+  operId?: string
 };
 
 export const shapeTypes = Object.keys(components) as ShapeType[]

+ 72 - 16
src/core/components/line/index.ts

@@ -2,11 +2,14 @@ import { lineVector, Pos, vectorAngle, verticalVector } from "@/utils/math.ts";
 import { BaseItem, generateSnapInfos, getBaseItem } from "../util.ts";
 import { getMouseColors } from "@/utils/colors.ts";
 import { InteractiveFix, InteractiveTo, MatResponseProps } from "../index.ts";
-import { Transform } from "konva/lib/Util";
 import { inRevise, onlyId, rangMod } from "@/utils/shared.ts";
 import { MathUtils } from "three";
-import { DrawStore } from "@/core/store/index.ts";
-import { getInitCtx, normalLineData } from "./use-draw.ts";
+import { DrawStore, useStore } from "@/core/store/index.ts";
+import { getInitCtx, NLineDataCtx, normalLineData } from "./use-draw.ts";
+import { SelectionManageBus, UseGetSelectionManage } from "@/core/hook/use-selection.ts";
+import { EntityShape } from "@/deconstruction.js";
+import mitt from "mitt";
+import { watch } from "vue";
 
 export { default as Component } from "./line.vue";
 export { default as TempComponent } from "./temp-line.vue";
@@ -67,7 +70,6 @@ export type LineData = Partial<typeof defaultStyle> &
       dash: number[];
     }[];
     polygon: { points: string[]; id: string }[];
-    attitude: number[];
     updateTime?: number;
     calcTime?: number;
   };
@@ -89,7 +91,6 @@ export const interactiveToData: InteractiveTo<"line"> = ({
         lines: [],
         points: [],
         polygon: [],
-        attitude: [1, 0, 0, 1, 0, 0],
       },
     });
   }
@@ -130,31 +131,61 @@ export const interactiveFixData: InteractiveFix<"line"> = ({ data, info }) => {
   return data;
 };
 
+const matResPoints = new Set<string>()
+let matCtx: NLineDataCtx | null
+let matData: LineData
+export const startMatResponse = () => {
+  matCtx = getInitCtx()
+}
+
 export const matResponse = ({
   data,
   mat,
-  increment,
+  operId
 }: MatResponseProps<"line">) => {
-  let transfrom: Transform;
-  const attitude = new Transform(data.attitude);
-  if (!increment) {
-    const inverMat = attitude.copy().invert();
-    transfrom = mat.copy().multiply(inverMat);
-  } else {
-    transfrom = mat;
+  matData = data
+  const line =  data.lines.find(item => item.id === operId)
+  if (!line) return;
+  const ids = [line.a, line.b]
+  for (const id of ids) {
+    if (matResPoints.has(id)) {
+      continue;
+    }
+    const ndx = data.points.findIndex(item => item.id === id)
+    if (~ndx) {
+      const point = data.points[ndx]
+      data.points[ndx] = {
+        ...point,
+        ...mat.point(point)
+      }
+      matCtx!.update.points[point.id] = data.points[ndx]
+      matResPoints.add(id)
+    }
   }
-
-  data.points = data.points.map((v) => ({ ...v, ...transfrom.point(v) }));
-  data.attitude = transfrom.copy().multiply(attitude).m;
   return data;
 };
 
+export const endMatResponse = () => {
+  matResPoints.clear()
+  // matCtx && normalLineData(matData, matCtx)
+  // console.log(matData, matCtx)
+  matCtx = null
+}
+
 export const getPredefine = (key: keyof LineData) => {
   if (key === "strokeWidth") {
     return { proportion: true };
   }
 };
 
+export const childrenDataGetter = (data: LineData, id: string) => {
+  const line = data.lines.find(item => item.id === id)
+  if (!line) return;
+
+  const ids = [line.a, line.b]
+  return data.points.filter(p => ids.includes(p.id))
+}
+
 export const delItem = (store: DrawStore, data: LineData, childId: string) => {
   if (!childId) {
     store.delItem("line", data.id);
@@ -216,3 +247,28 @@ export const delPoint = (data: LineData, id: string, ctx = getInitCtx()) => {
   ~ndx && data.points.splice(ndx, 1);
   return { data, ctx };
 };
+
+export const useGetSelectionManage: UseGetSelectionManage = () => {
+  const store = useStore();
+
+  const canSelect = (shape: EntityShape) => {
+    const id = shape.id();
+    const line = store.getTypeItems('line')[0]
+    return !!(id && line.lines.some(item => item.id === id));
+  };
+  const listener = (shape: EntityShape) => {
+    const bus: SelectionManageBus = mitt();
+    const stop = watch(
+      () => canSelect(shape),
+      (exixts, _) => {
+        if (!exixts) {
+          bus.emit("del", shape);
+        }
+      },
+      { immediate: true }
+    );
+    return { stop, bus };
+  };
+
+  return { canSelect, listener };
+};

+ 2 - 5
src/core/components/line/line.vue

@@ -1,15 +1,12 @@
 <template>
-  <TempLine
-    :data="data"
-    @updateShape="emit('updateShape', { ...data })"
-    :canEdit="true"
-  />
+  <TempLine :data="data" @updateShape="emit('updateShape', { ...data })" />
 </template>
 
 <script lang="ts" setup>
 import { useAutomaticData } from "@/core/hook/use-automatic-data.ts";
 import { LineData } from "./index.ts";
 import TempLine from "./temp-line.vue";
+import { useOperMode } from "@/core/hook/use-status.ts";
 
 const props = defineProps<{ data: LineData }>();
 const data = useAutomaticData(() => props.data);

+ 1 - 1
src/core/components/line/temp-line.vue

@@ -7,7 +7,7 @@
         :line="item"
         :data="data"
         :add-mode="addMode"
-        :can-edit="!initData"
+        :can-edit="!initData && !operMode.mulSelection"
         :dragPointIds="dragPointIds"
         @add-point="(p) => addPointHandler(p, item)"
         @del-point="delPointHandler"

+ 16 - 3
src/core/hook/use-component.ts

@@ -278,14 +278,27 @@ export const useGetShapeBelong = () => {
   };
 };
 
-export const useGetComponentData = <D extends DrawItem>() => {
+export const useGetComponentData = () => {
   const store = useStore();
+  const getShapeBelong = useGetShapeBelong();
 
   return (shape: Ref<EntityShape | undefined> | EntityShape | undefined) =>
     computed(() => {
       shape = isRef(shape) ? shape.value : shape;
       if (!shape?.id()) return;
-      return store.getItemById(shape.id()) as D;
+      let item: any = store.getItemById(shape.id());
+      if (!item) {
+        const belong = getShapeBelong(shape);
+        if (belong && !belong.isSelf) {
+          const getter = components[belong.type].childrenDataGetter;
+          let parent: any;
+          if (getter && (parent = store.getItemById(belong.id))) {
+            item = getter(parent, belong.curId);
+          }
+        }
+      }
+
+      return item;
     });
 };
 
@@ -379,7 +392,7 @@ export const useOnComponentBoundChange = () => {
         $shapes,
         ($shapes, _, onCleanup) => {
           const cleanups = $shapes.flatMap(($shape) => {
-            const item = getComponentData($shape);
+            let item = getComponentData($shape);
             return [
               watch(item, () => nextTick(() => update($shape, "data")), {
                 deep: true,

+ 70 - 41
src/core/hook/use-selection.ts

@@ -7,9 +7,9 @@ import {
   useStage,
 } from "./use-global-vars";
 import {
-  useGetFormalChildren,
   useFormalLayer,
   useHelperLayer,
+  useGetFormalChildren,
 } from "./use-layer";
 import { themeColor } from "@/constant";
 import { dragListener } from "@/utils/event";
@@ -38,7 +38,7 @@ import { useMouseShapesStatus } from "./use-mouse-status";
 import Icon from "../components/icon/temp-icon.vue";
 import { Group } from "konva/lib/Group";
 import { Component as GroupComp, GroupData } from "../components/group";
-import { DrawStore, useStore } from "../store";
+import { useStore } from "../store";
 import { useGetShapeBelong, useOnComponentBoundChange } from "./use-component";
 import { useHistory } from "./use-history";
 import { isRectContained } from "@/utils/math";
@@ -47,6 +47,7 @@ import { IconData } from "../components/icon";
 import { usePause } from "./use-pause";
 import mitt, { Emitter } from "mitt";
 import { components, ShapeType, shapeTypes } from "../components";
+import { getFlatChildren } from "@/utils/shape";
 
 // 多选不包含分组, 只包含选中者
 export const useSelection = installGlobalVar(() => {
@@ -63,22 +64,27 @@ export const useSelection = installGlobalVar(() => {
   const operMode = useOperMode();
   const selections = ref<EntityShape[]>();
   const transformer = useTransformer();
+  const getShapeSelectionManage = useGetShapeSelectionManage();
 
-  let shapeBoxs: IRect[] = [];
-  let shapes: EntityShape[] = [];
+  let itemShapeBoxs: IRect[][] = [];
+  let itemShapes: EntityShape[][] = [];
 
   const updateSelections = () => {
     const boxRect = box.getClientRect();
     selections.value = [];
-
-    for (let i = 0; i < shapeBoxs.length; i++) {
-      if (
-        Util.haveIntersection(boxRect, shapeBoxs[i]) &&
-        !isRectContained(shapeBoxs[i], boxRect) &&
-        shapes[i] !== toRaw(transformer)
-      ) {
-        if (!selections.value.includes(shapes[i])) {
-          selections.value.push(shapes[i]);
+    for (let i = 0; i < itemShapeBoxs.length; i++) {
+      for (let j = 0; j < itemShapeBoxs[i].length; j++) {
+        const shape = itemShapes[i][j];
+        const box = itemShapeBoxs[i][j];
+        const itemSelects: EntityShape[] = [];
+        if (
+          Util.haveIntersection(boxRect, box) &&
+          !isRectContained(box, boxRect)
+        ) {
+          if (!selections.value.includes(shape)) {
+            selections.value.push(shape);
+            itemSelects.push(shape);
+          }
         }
       }
     }
@@ -110,8 +116,16 @@ export const useSelection = installGlobalVar(() => {
   };
 
   const updateInitData = () => {
-    shapes = getChildren();
-    shapeBoxs = shapes.map((shape) => shape.getClientRect());
+    itemShapes = getChildren().map((item) =>
+      getFlatChildren(item).filter(
+        (shape) =>
+          shape !== toRaw(transformer) &&
+          getShapeSelectionManage(shape)?.canSelect(shape)
+      )
+    );
+    itemShapeBoxs = itemShapes.map((shapes) =>
+      shapes.map((shape) => shape.getClientRect())
+    );
   };
 
   const stopWatch = globalWatch(
@@ -195,15 +209,15 @@ export const useShapesIcon = (
   return [stop, pause];
 };
 
-export type SelectionManageBus = Emitter<Record<"del" | "update", EntityShape>>
+export type SelectionManageBus = Emitter<Record<"del" | "update", EntityShape>>;
 export type SelectionManage = {
-  canSelect: (shape: EntityShape) => boolean;
+  canSelect: (shape: EntityShape, selects?: EntityShape[]) => boolean;
   listener: (shape: EntityShape) => {
     stop: () => void;
     bus: SelectionManageBus;
   };
 };
-export type UseGetSelectionManage = () => SelectionManage
+export type UseGetSelectionManage = () => SelectionManage;
 
 export const useStoreSelectionManage = installGlobalVar((): SelectionManage => {
   const store = useStore();
@@ -226,29 +240,31 @@ export const useStoreSelectionManage = installGlobalVar((): SelectionManage => {
       },
       { immediate: true }
     );
-    return { stop, bus }
+    return { stop, bus };
   };
 
   return { canSelect, listener };
 });
 
 export const useGetShapeSelectionManage = installGlobalVar(() => {
-  const compManages: Partial<Record<ShapeType, SelectionManage>> = {}
+  const compManages: Partial<Record<ShapeType, SelectionManage>> = {};
   for (const type of shapeTypes) {
-    compManages[type] = components[type].useGetSelectionManage && components[type].useGetSelectionManage()
+    compManages[type] =
+      components[type].useGetSelectionManage &&
+      components[type].useGetSelectionManage();
   }
   const storeManage = useStoreSelectionManage();
   const getShapeBelong = useGetShapeBelong();
   return (shape: EntityShape) => {
-      const bl = getShapeBelong(shape);
-      if (!bl) return;
-      const manage = bl.isSelf ? storeManage : compManages[bl.type]
-      if (!manage) {
-        console.log('找不到多选管理器', bl)
-      }
-      return manage;
-  }
-})
+    const bl = getShapeBelong(shape);
+    if (!bl) return;
+    if (compManages[bl.type]) {
+      return compManages[bl.type];
+    } else if (bl.isSelf) {
+      return storeManage;
+    }
+  };
+});
 
 export const useSelectionRevise = () => {
   const getShapeSelectionManage = useGetShapeSelectionManage();
@@ -256,6 +272,12 @@ export const useSelectionRevise = () => {
   const status = useMouseShapesStatus();
   const store = useStore();
   const { selections: rectSelects } = useSelection();
+  let selfSet = false;
+  const setSelectShapes = (shapes: EntityShape[]) => {
+    selfSet = true;
+    status.selects = shapes;
+    selfSet = false;
+  };
 
   let initSelections: EntityShape[] = [];
   watch(
@@ -266,27 +288,34 @@ export const useSelectionRevise = () => {
       } else if (!rectSelects) {
         initSelections = [];
       } else {
-        status.selects = initSelections.concat(rectSelects);
-        filterSelect();
+        setSelectShapes(initSelections.concat(rectSelects));
       }
     }
   );
   useShapesIcon(computed(() => status.selects.concat(rectSelects.value || [])));
-  
+
   const filterSelect = debounce(() => {
-    const selects: EntityShape[] = []
+    const selects: EntityShape[] = [];
     for (const shape of status.selects) {
-      const manage = getShapeSelectionManage(shape)
-      if (manage?.canSelect(shape)) {
-        selects.push(shape)
-      }
+      const children = getFlatChildren(shape)
+      children.forEach(childShape => {
+        const manage = getShapeSelectionManage(childShape);
+        if (manage?.canSelect(childShape)) {
+          selects.push(childShape);
+        }
+      })
     }
-    status.selects = selects;
+    setSelectShapes(selects);
   }, 16);
   store.bus.on("delItemAfter", filterSelect);
   store.bus.on("clearAfter", filterSelect);
   store.bus.on("dataChangeAfter", filterSelect);
   store.bus.on("setCurrentLayerAfter", filterSelect);
+  watch(
+    () => status.selects,
+    () => selfSet || filterSelect(),
+    { flush: "sync" }
+  );
 
   const ids = computed(() => [
     ...new Set(status.selects.map((item) => item.id())),
@@ -328,7 +357,7 @@ export const useSelectionRevise = () => {
         // data.ids;
       },
       onDelShape() {
-        status.selects = [];
+        setSelectShapes([]);
       },
       onAddShape(data: GroupData) {
         history.onceTrack(() => {
@@ -346,7 +375,7 @@ export const useSelectionRevise = () => {
             await nextTick();
             const $stage = stage.value!.getNode();
             const addShape = $stage.findOne("#" + data.id) as EntityShape;
-            status.selects = [addShape];
+            setSelectShapes([addShape]);
           });
         });
       },

+ 3 - 2
src/utils/shape.ts

@@ -98,10 +98,11 @@ export const getImageSize = (url: string | Blob): Promise<Size> => {
 export const getFlatChildren = (shape: EntityShape, children: EntityShape[] = []) => {
 	if ('children' in shape) {
 		for (const item of shape.children) {
-			getFlatChildren(item)
-			children.push(item)
+			getFlatChildren(item, children)
 		}
+		children.push(shape)
 	} else {
 		children.push(shape)
 	}
+	return children
 }