소스 검색

fix: 制作辅助线

bill 7 달 전
부모
커밋
5179d94622

+ 3 - 1
src/core/components/arrow/index.ts

@@ -52,9 +52,11 @@ export const dataToConfig = (data: ArrowData): ArrowConfig => ({
 });
 
 export const getSnapInfos = (data: ArrowData) => {
-  return generateSnapInfos(data.points, true, false);
+  return generateSnapInfos(getSnapPoints(data), true, false);
 };
 
+export const getSnapPoints = (data: ArrowData) => data.points
+
 export const interactiveToData = (
   info: AddMessage<'arrow'>,
   preset: Partial<ArrowData> = {}

+ 6 - 2
src/core/components/circle/index.ts

@@ -34,13 +34,17 @@ export const getMouseStyle = (data: CircleData) => {
 };
 
 export const getSnapInfos = (data: CircleData) => {
+  return generateSnapInfos(getSnapPoints(data), true, false);
+};
+
+export const getSnapPoints = (data: CircleData) => {
   const size = data.radius * 2;
   const points = getRectSnapPoints(size, size).map((v) => ({
     x: v.x + data.x,
     y: v.y + data.y,
   }));
-  return generateSnapInfos(points, true, false);
-};
+  return points
+}
 
 export type CircleData = Partial<typeof defaultStyle> &
   BaseItem & {

+ 9 - 5
src/core/components/icon/index.ts

@@ -19,17 +19,21 @@ export const defaultStyle = {
 export const addMode = "dot";
 
 export const getSnapInfos = (data: IconData) => {
-  const tf = new Transform(data.mat);
-  const w = data.width || defaultStyle.width;
-  const h = data.height || defaultStyle.height;
-  const points = getRectSnapPoints(w, h)
   return generateSnapInfos(
-    points.map((v) => tf.point(v)),
+    getSnapPoints(data),
     true,
     false
   );
 };
 
+export const getSnapPoints = (data: IconData) => {
+  const tf = new Transform(data.mat);
+  const w = data.width || defaultStyle.width;
+  const h = data.height || defaultStyle.height;
+  const points = getRectSnapPoints(w, h)
+  return points.map((v) => tf.point(v))
+}
+
 export const getMouseStyle = (data: IconData) => {
   const fillStatus = getMouseColors(data.coverFill || defaultStyle.coverFill);
   const hCoverOpcaoty = data.coverOpcatiy ? data.coverOpcatiy : 0.3

+ 10 - 8
src/core/components/image/index.ts

@@ -27,6 +27,14 @@ export const getMouseStyle = (data: ImageData) => {
 
 
 export const getSnapInfos = (data: ImageData) => {
+  return generateSnapInfos(
+    getSnapPoints(data),
+    true,
+    false
+  );
+};
+
+export const getSnapPoints = (data: ImageData) => {
   const tf = new Transform(data.mat);
   const useData = data.width && data.height
   if (!useData && !(data.url in imageInfo)) {
@@ -36,14 +44,8 @@ export const getSnapInfos = (data: ImageData) => {
   const w = useData ?  data.width : imageInfo[data.url].width
   const h = useData ?  data.height : imageInfo[data.url].height
   const points = getRectSnapPoints(w, h)
-  
-  return generateSnapInfos(
-    points.map((v) => tf.point(v)),
-    true,
-    false
-  );
-};
-
+  return points.map((v) => tf.point(v))
+}
 
 export type ImageData = Partial<typeof defaultStyle> & BaseItem & {
   fill?: string;

+ 7 - 1
src/core/components/line/index.ts

@@ -26,7 +26,13 @@ export const getMouseStyle = (data: LineData) => {
   };
 };
 
-export const getSnapInfos = (data: LineData) => generateSnapInfos(data.points, true, true, true)
+export const getSnapInfos = (data: LineData) => generateSnapInfos(getSnapPoints(data), true, true, true)
+
+
+export const getSnapPoints = (data: LineData) => {
+  return data.points
+}
+
 
 export type LineData = Partial<typeof defaultStyle> & BaseItem & {
   points: Pos[];

+ 6 - 1
src/core/components/polygon/index.ts

@@ -27,7 +27,12 @@ export const getMouseStyle = (data: PolygonData) => {
 
 export const addMode = "dots";
 
-export const getSnapInfos = (data: PolygonData) => generateSnapInfos(data.points, true, true)
+export const getSnapInfos = (data: PolygonData) => generateSnapInfos(getSnapPoints(data), true, true)
+
+
+export const getSnapPoints = (data: PolygonData) => {
+  return data.points
+}
 
 export type PolygonData = Partial<typeof defaultStyle> & BaseItem & {
   fill?: string

+ 5 - 1
src/core/components/rectangle/index.ts

@@ -58,7 +58,11 @@ export const interactiveToData = (
   }
 };
 
-export const getSnapInfos = (data: RectangleData) => generateSnapInfos(data.points, true, false)
+export const getSnapPoints = (data: RectangleData) => {
+  return data.points
+}
+
+export const getSnapInfos = (data: RectangleData) => generateSnapInfos(getSnapPoints(data), true, false)
 
 export const interactiveFixData = (
   data: RectangleData,

+ 0 - 1
src/core/components/rectangle/rectangle.vue

@@ -14,7 +14,6 @@ import { RectangleData, getMouseStyle, defaultStyle } from "./index.ts";
 import { PropertyUpdate, Operate } from "../../propertys";
 import TempLine from "./temp-rectangle.vue";
 import { useComponentStatus } from "@/core/hook/use-component.ts";
-import { watchEffect } from "vue";
 
 const props = defineProps<{ data: RectangleData }>();
 const emit = defineEmits<{

+ 7 - 3
src/core/components/text/index.ts

@@ -43,12 +43,16 @@ export const getMouseStyle = (data: TextData) => {
 
 export const textNodeMap: Record<BaseItem['id'], Text> = shallowReactive({})
 
-export const getSnapInfos = (data: TextData) => {
+
+export const getSnapPoints = (data: TextData) => {
   if (!textNodeMap[data.id]) return []
   const node = textNodeMap[data.id]
   const tf = new Transform(data.mat)
-  const points = getRectSnapPoints(data.width || node.width(), node.height(), 0, 0).map((v) => tf.point(v));
-  return generateSnapInfos(points, true, false);
+  return getRectSnapPoints(data.width || node.width(), node.height(), 0, 0).map((v) => tf.point(v));
+}
+
+export const getSnapInfos = (data: TextData) => {
+  return generateSnapInfos(getSnapPoints(data), true, false);
 };
 
 export const interactiveToData = (

+ 5 - 1
src/core/components/triangle/index.ts

@@ -34,8 +34,12 @@ export const getMouseStyle = (data: TriangleData) => {
 export type TriangleData = Partial<typeof defaultStyle> &
   BaseItem & { points: Pos[]; attitude: number[]; fill?: string };
 
+export const getSnapPoints = (data: TriangleData) => {
+  return data.points
+}
+
 export const getSnapInfos = (data: TriangleData) =>
-  generateSnapInfos(data.points, true, false);
+  generateSnapInfos(getSnapPoints(data), true, false);
 
 export const interactiveToData = (
   info: AddMessage<"triangle">,

+ 1 - 1
src/core/helper/back-grid.vue

@@ -1,5 +1,5 @@
 <template>
-  <v-group ref="grid" v-if="hLines && rect">
+  <v-group ref="grid" v-if="hLines && rect" :config="{ listening: false }">
     <template v-for="item in hLines">
       <v-line
         v-for="l in item.children"

+ 0 - 78
src/core/helper/mouse-line.vue

@@ -1,78 +0,0 @@
-<template>
-  <template v-if="pointer">
-    <v-line
-      :config="{ ...vConfig, x: pointer[0], y: 0 }"
-      v-if="pointer[0]"
-      :ref="(l: any) => lines[0] = l"
-    />
-    <v-line
-      :config="{ ...hConfig, x: 0, y: pointer[1] }"
-      v-if="pointer[1]"
-      :ref="(l: any) => lines[1] = l"
-    />
-  </template>
-</template>
-
-<script lang="ts" setup>
-import { computed, reactive, ref, watchEffect } from "vue";
-import { usePointerPos, useStage } from "../hook/use-global-vars";
-import { useSnapPoints } from "../hook/use-snap";
-import { useViewerInvertTransform, useViewerTransform } from "../hook/use-viewer";
-import { RectConfig } from "konva/lib/shapes/Rect";
-import { themeColor } from "@/constant/help-style";
-import { Pos } from "@/utils/math";
-import { DC } from "@/deconstruction";
-import { Line } from "konva/lib/shapes/Line";
-import { useDashAnimation } from "../hook/use-animation";
-
-const config: RectConfig = {
-  dash: [10, 10],
-  stroke: themeColor,
-  strokeWidth: 1,
-  listening: false,
-};
-const vConfig = reactive({
-  ...config,
-  points: [0, 0, 0, 0],
-});
-const hConfig = reactive({
-  ...config,
-  points: [0, 0, 0, 0],
-});
-
-const pointerPixel = usePointerPos();
-const stage = useStage();
-watchEffect(() => {
-  const $stage = stage.value?.getNode();
-  if (!$stage || !pointerPixel.value) return;
-  vConfig.points[3] = $stage.height();
-  hConfig.points[2] = $stage.width();
-});
-
-const snaps = useSnapPoints();
-const viewerInvertTransform = useViewerInvertTransform();
-const viewerTransform = useViewerTransform();
-const snapOffset = 10;
-const pointer = computed(() => {
-  if (!pointerPixel.value) return;
-  const current = viewerInvertTransform.value.point(pointerPixel.value);
-  const final: Partial<Pos> = {};
-  for (const snap of snaps.value) {
-    if (Math.abs(snap.x - current.x) < snapOffset) {
-      final.x = snap.x;
-      break;
-    }
-  }
-  for (const snap of snaps.value) {
-    if (Math.abs(snap.y - current.y) < snapOffset) {
-      final.y = snap.y;
-      break;
-    }
-  }
-  const finalPixel = viewerTransform.value.point({ x: 0, y: 0, ...final });
-  return ["x" in final ? finalPixel.x : null, "y" in final ? finalPixel.y : null];
-});
-
-const lines = ref<DC<Line>[]>([]);
-useDashAnimation(lines);
-</script>

+ 237 - 0
src/core/helper/split-line.vue

@@ -0,0 +1,237 @@
+<template>
+  <template v-if="axissInfo">
+    <v-group v-for="info in axissInfo" :config="{ listening: false }">
+      <v-line :config="{ points: info.points, ...style }" />
+      <v-text
+        :config="{ ...style, strokeWidth: 0, ...textConfig, fontSize, align: 'center' }"
+        v-for="textConfig in info.texts"
+      />
+    </v-group>
+  </template>
+</template>
+
+<script lang="ts" setup>
+import { computed, onUnmounted } from "vue";
+import { components } from "../components";
+import { useComponentsAttach } from "../hook/use-component";
+import { useViewerTransform } from "../hook/use-viewer";
+import { useResize } from "../hook/use-event";
+import { Pos } from "@/utils/math";
+import { TextConfig } from "konva/lib/shapes/Text";
+import { Transform } from "konva/lib/Util";
+
+const style = {
+  stroke: "#000",
+  strokeWidth: 1,
+  opacity: 1,
+  strokeScaleEnabled: false,
+  shadowColor: "#fff",
+  shadowBlur: 3,
+  shadowOffset: { x: 0, y: 0 },
+  shadowOpacity: 1,
+};
+
+const props = withDefaults(
+  defineProps<{
+    splitOffset?: number;
+    showOffset?: number;
+    splitWidth?: number;
+  }>(),
+  { splitOffset: 80, showOffset: 20, splitWidth: 10 }
+);
+
+const size = useResize();
+const center = computed(() => {
+  if (!size.value) return;
+  return {
+    x: size.value.width / 2,
+    y: size.value.height / 2,
+  };
+});
+const rang = computed(() => {
+  if (!size.value) return;
+  return {
+    lt: { x: props.showOffset, y: props.showOffset },
+    rb: {
+      x: size.value.width - props.showOffset,
+      y: size.value.height - props.showOffset,
+    },
+  };
+});
+
+const compsInfo = useComponentsAttach(
+  (type, item) => components[type].getSnapPoints(item as any)
+  // ["polygon"]
+);
+onUnmounted(compsInfo.cleanup);
+
+const viewerTransform = useViewerTransform();
+const points = computed(() => compsInfo.attachs.flatMap((p) => p));
+const rectAxisSteps = computed(() => {
+  const axis = {
+    top: [] as number[],
+    left: [] as number[],
+    right: [] as number[],
+    bottom: [] as number[],
+  };
+  if (!center.value || !rang.value) return axis;
+
+  const pushStep = (axis: number[], cur: number, pixel: Pos) => {
+    if (
+      !(
+        pixel.x >= rang.value!.lt.x &&
+        pixel.x <= rang.value!.rb.x &&
+        pixel.y >= rang.value!.lt.y &&
+        pixel.y <= rang.value!.rb.y
+      )
+    ) {
+      return;
+    }
+
+    const can = (data: number) => Math.abs(data - cur) > props.splitOffset;
+
+    let i = axis.length - 1;
+    for (i; i >= 0; i--) {
+      if (axis[i] < cur) {
+        break;
+      }
+    }
+
+    if (axis.length === 0) {
+      axis.push(cur);
+    } else if (can(axis[i]) && (i === axis.length - 1 || can(axis[i + 1]))) {
+      axis.splice(i + 1, 0, cur);
+    } else if (i === -1) {
+      axis[0] = cur;
+    } else if (i === axis.length - 1) {
+      axis[i] = cur;
+    }
+  };
+
+  for (const point of points.value) {
+    const pixel = viewerTransform.value.point(point);
+    pushStep(pixel.x > center.value.x ? axis.right : axis.left, pixel.y, pixel);
+    pushStep(pixel.y > center.value.y ? axis.bottom : axis.top, pixel.x, pixel);
+  }
+  return axis;
+});
+
+const fontSize = 12;
+const axissInfo = computed(() => {
+  if (!rang.value) return;
+  const infos: Record<string, { points: number[]; texts: TextConfig[] }> = {
+    left: { points: [], texts: [] },
+    right: { points: [], texts: [] },
+    top: { points: [], texts: [] },
+    bottom: { points: [], texts: [] },
+  };
+
+  if (rectAxisSteps.value.left.length > 1) {
+    for (let i = 0; i < rectAxisSteps.value.left.length; i++) {
+      const cur = rectAxisSteps.value.left[i];
+      infos.left.points.push(rang.value!.lt.x, cur);
+      infos.left.points.push(rang.value!.lt.x + props.splitWidth, cur);
+      infos.left.points.push(rang.value!.lt.x, cur);
+
+      if (i === 0) continue;
+      const prev = rectAxisSteps.value.left[i - 1];
+      const lt = {
+        x: rang.value!.lt.x + fontSize / 2,
+        y: prev,
+      };
+      const width = cur - prev;
+      const height = fontSize;
+      const center = { x: 0, y: height / 2 };
+
+      const tf = new Transform()
+        .translate(center.x + lt.x, center.y + lt.y)
+        .rotate(Math.PI / 2)
+        .translate(-center.x, -center.y);
+
+      infos.left.texts.push({
+        width: width,
+        text: Math.floor(width * 100).toString(),
+        ...tf.decompose(),
+      });
+    }
+  }
+
+  if (rectAxisSteps.value.right.length > 1) {
+    for (let i = 0; i < rectAxisSteps.value.right.length; i++) {
+      const cur = rectAxisSteps.value.right[i];
+      infos.right.points.push(rang.value!.rb.x, cur);
+      infos.right.points.push(rang.value!.rb.x - props.splitWidth, cur);
+      infos.right.points.push(rang.value!.rb.x, cur);
+
+      if (i === 0) continue;
+      const prev = rectAxisSteps.value.right[i - 1];
+      const lt = {
+        x: rang.value!.rb.x - fontSize / 1.5,
+        y: prev,
+      };
+      const width = cur - prev;
+      const height = fontSize;
+      const center = { x: 0, y: height / 2 };
+
+      const tf = new Transform()
+        .translate(center.x + lt.x, center.y + lt.y)
+        .rotate(Math.PI / 2)
+        .translate(-center.x, -center.y);
+
+      infos.right.texts.push({
+        width: width,
+        text: Math.floor(width * 100).toString(),
+        ...tf.decompose(),
+      });
+    }
+  }
+
+  if (rectAxisSteps.value.top.length > 1) {
+    for (let i = 0; i < rectAxisSteps.value.top.length; i++) {
+      const cur = rectAxisSteps.value.top[i];
+      infos.top.points.push(cur, rang.value!.lt.y);
+      infos.top.points.push(cur, rang.value!.lt.y + props.splitWidth);
+      infos.top.points.push(cur, rang.value!.lt.y);
+
+      if (i === 0) continue;
+      const prev = rectAxisSteps.value.top[i - 1];
+      const lt = {
+        x: prev,
+        y: rang.value!.lt.y + fontSize / 3,
+      };
+
+      const width = cur - prev;
+      infos.top.texts.push({
+        width: width,
+        text: Math.floor(width * 100).toString(),
+        ...lt,
+      });
+    }
+  }
+
+  if (rectAxisSteps.value.bottom.length > 1) {
+    for (let i = 0; i < rectAxisSteps.value.bottom.length; i++) {
+      const cur = rectAxisSteps.value.bottom[i];
+      infos.bottom.points.push(cur, rang.value!.rb.y);
+      infos.bottom.points.push(cur, rang.value!.rb.y - props.splitWidth);
+      infos.bottom.points.push(cur, rang.value!.rb.y);
+
+      if (i === 0) continue;
+      const prev = rectAxisSteps.value.bottom[i - 1];
+      const lt = {
+        x: prev,
+        y: rang.value!.rb.y - fontSize,
+      };
+
+      const width = cur - prev;
+      infos.bottom.texts.push({
+        width: width,
+        text: Math.floor(width * 100).toString(),
+        ...lt,
+      });
+    }
+  }
+
+  return infos;
+});
+</script>

+ 61 - 4
src/core/hook/use-component.ts

@@ -1,5 +1,15 @@
 import { DC, EntityShape } from "@/deconstruction";
-import { computed, EmitFn, isRef, Ref, ref, shallowReactive } from "vue";
+import {
+  computed,
+  EmitFn,
+  isRef,
+  reactive,
+  Ref,
+  ref,
+  shallowReactive,
+  watch,
+  watchEffect,
+} from "vue";
 import { useAutomaticData } from "./use-automatic-data";
 import { useMouseMigrateTempLayer, useZIndex } from "./use-layer";
 import { useAnimationMouseStyle } from "./use-mouse-status";
@@ -7,11 +17,12 @@ import { components, DrawItem, ShapeType } from "../components";
 import { useMatCompTransformer, useLineTransformer } from "./use-transformer";
 import { useGetShapeCopyTransform } from "./use-copy";
 import { Delete, DocumentCopy } from "@element-plus/icons-vue";
-import { onlyId } from "@/utils/shared";
+import { mergeFuns, onlyId } from "@/utils/shared";
 import { Shape } from "konva/lib/Shape";
 import { Transform } from "konva/lib/Util";
 import { mergeDescribes, PropertyKeys } from "../propertys";
 import { useStore } from "../store";
+import { globalWatch } from "./use-global-vars";
 
 type Emit<T> = EmitFn<{
   updateShape: (value: T) => void;
@@ -24,7 +35,7 @@ export type UseComponentStatusProps<
   S extends EntityShape
 > = {
   emit: Emit<T>;
-  type?: ShapeType,
+  type?: ShapeType;
   props: { data: T };
   getMouseStyle: any;
   defaultStyle: any;
@@ -79,7 +90,7 @@ export const useComponentStatus = <S extends EntityShape, T extends DrawItem>(
 
   useZIndex(shape, data);
   useMouseMigrateTempLayer(shape);
-  const name = args.type ? components[args.type].shapeName : ''
+  const name = args.type ? components[args.type].shapeName : "";
   const getCopyTransform = useGetShapeCopyTransform(shape);
   const operateMenus = shallowReactive([
     {
@@ -126,3 +137,49 @@ export const useGetComponentData = <D extends DrawItem>() => {
       return store.getItemById(shape.id()) as D;
     });
 };
+
+export const useComponentsAttach = <T>(
+  getter: <K extends ShapeType>(type: K, data: DrawItem<K>) => T,
+  types = Object.keys(components) as ShapeType[]
+) => {
+  const store = useStore();
+  const attachs = reactive([]) as T[];
+  const cleanups = [] as Array<() => void>;
+
+  for (const type of types) {
+    cleanups.push(
+      globalWatch(
+        () => store.data[type]?.map((item) => item),
+        (items, _, onCleanup) => {
+          if (!items) return;
+          for (const item of items) {
+            const attachWatchStop = watchEffect((onCleanup) => {
+              const attach = getter(type, item);
+              attachs.push(attach);
+              onCleanup(() => {
+                const ndx = attachs.indexOf(attach);
+                ~ndx && attachs.splice(ndx, 1);
+              });
+            });
+            const existsWatchStop = watchEffect(() => {
+              if (!items.includes(item)) {
+                attachWatchStop();
+                existsWatchStop();
+              }
+            });
+            onCleanup(() => {
+              attachWatchStop();
+              existsWatchStop();
+            });
+          }
+        },
+        { immediate: true }
+      )
+    );
+  }
+
+  return {
+    attachs,
+    cleanup: mergeFuns(cleanups),
+  };
+};

+ 2 - 1
src/core/hook/use-expose.ts

@@ -22,7 +22,8 @@ export const useExpose = () => {
 	const viewer = useViewer().viewer
 	const { updateSize } = useGlobalResize()
 	const config = reactive({
-		showGrid: true
+		showGrid: true,
+		showLabelLine: true
 	})
 
 	const exposeBlob = (config?: PickParams<'toBlob', 'callback'>) => {

+ 1 - 1
src/core/hook/use-layer.ts

@@ -158,7 +158,7 @@ const useZIndexsManage = installGlobalVar(() => {
     });
   };
 
-  watch(sortItems, setZIndexs);
+  watch(() => [sortItems.value, current.value], setZIndexs);
 
   return {
     set(shape: EntityShape, item: DrawItem) {

+ 2 - 0
src/core/renderer/renderer.vue

@@ -17,6 +17,7 @@
       <v-layer id="helper">
         <ActiveBoxs />
         <SnapLines />
+        <SplitLine v-if="expose.config.showLabelLine" />
       </v-layer>
     </v-stage>
   </div>
@@ -28,6 +29,7 @@ import TempShapeGroup from "./draw-group.vue";
 import ActiveBoxs from "../helper/active-boxs.vue";
 import SnapLines from "../helper/snap-lines.vue";
 import BackGrid from "../helper/back-grid.vue";
+import SplitLine from "../helper/split-line.vue";
 import { DrawData, ShapeType, components } from "../components";
 import { useCursor, useDownKeys, useMode, useStage } from "../hook/use-global-vars.ts";
 import { useViewerTransformConfig } from "../hook/use-viewer.ts";

+ 9 - 1
src/example/fuse/views/header/header.vue

@@ -22,7 +22,15 @@
       </div>
       <div>
         <span class="operate" @click="draw.config.showGrid = !draw.config.showGrid">
-          {{ draw.config.showGrid ? "隐藏" : "显示" }}辅助线<el-icon><Plus /></el-icon>
+          {{ draw.config.showGrid ? "隐藏" : "显示" }}栅格<el-icon><Plus /></el-icon>
+        </span>
+        <span
+          class="operate"
+          @click="draw.config.showLabelLine = !draw.config.showLabelLine"
+        >
+          {{ draw.config.showLabelLine ? "隐藏" : "显示" }}标注线<el-icon
+            ><Plus
+          /></el-icon>
         </span>
         <span class="operate" @click="draw.history.clearCurrent()">
           清除<el-icon><Plus /></el-icon>