Pārlūkot izejas kodu

feat: 初始化工程结构

bill 9 mēneši atpakaļ
vecāks
revīzija
ede4dab35d
77 mainītis faili ar 1150 papildinājumiem un 870 dzēšanām
  1. 2 2
      src/draw/constant/help-style.ts
  2. 0 0
      src/constant/mode.ts
  3. 26 0
      src/core/components/arrow/arrow.vue
  4. 18 10
      src/draw/core/components/arrow/index.ts
  5. 50 0
      src/core/components/circle/circle.vue
  6. 10 3
      src/draw/core/components/circle/index.ts
  7. 30 0
      src/core/components/icon/icon.vue
  8. 69 0
      src/core/components/icon/index.ts
  9. 33 0
      src/core/components/image/image.vue
  10. 20 10
      src/draw/core/components/image/index.ts
  11. 0 0
      src/core/components/index.ts
  12. 26 10
      src/draw/core/components/line/index.ts
  13. 26 0
      src/core/components/line/line.vue
  14. 17 11
      src/draw/core/components/polygon/index.ts
  15. 26 0
      src/core/components/polygon/polygon.vue
  16. 4 10
      src/draw/core/components/rectangle/index.ts
  17. 35 0
      src/core/components/rectangle/rectangle.vue
  18. 28 0
      src/core/components/rectangle/temp-rectangle.vue
  19. 6 6
      src/draw/core/components/share/point.vue
  20. 18 11
      src/draw/core/components/text/index.ts
  21. 60 0
      src/core/components/text/text.vue
  22. 6 5
      src/draw/core/components/triangle/index.ts
  23. 1 1
      src/draw/core/components/triangle/temp-triangle.vue
  24. 26 0
      src/core/components/triangle/triangle.vue
  25. 1 1
      src/draw/core/history.ts
  26. 15 12
      src/draw/core/hook/use-animation.ts
  27. 0 0
      src/core/hook/use-automatic-data.ts
  28. 0 0
      src/core/hook/use-coversion-position.ts
  29. 0 0
      src/core/hook/use-event.ts
  30. 10 2
      src/draw/core/hook/use-expose.ts
  31. 27 7
      src/draw/core/hook/use-global-vars.ts
  32. 0 0
      src/core/hook/use-interactive.ts
  33. 64 59
      src/draw/core/hook/use-mouse-status.ts
  34. 336 0
      src/core/hook/use-transformer.ts
  35. 1 1
      src/draw/core/hook/use-viewer.ts
  36. 1 1
      src/draw/core/renderer/draw-group.vue
  37. 0 0
      src/core/renderer/group.vue
  38. 1 1
      src/draw/core/renderer/renderer.vue
  39. 45 19
      src/draw/core/store/index.ts
  40. 2 2
      src/draw/core/viewer.ts
  41. 0 11
      src/draw/core/components/arrow/arrow.vue
  42. 0 11
      src/draw/core/components/circle/circle.vue
  43. 0 69
      src/draw/core/components/icon/icon.vue
  44. 0 57
      src/draw/core/components/icon/index.ts
  45. 0 48
      src/draw/core/components/image/image.vue
  46. 0 11
      src/draw/core/components/line/line.vue
  47. 0 11
      src/draw/core/components/polygon/polygon.vue
  48. 0 26
      src/draw/core/components/rectangle/rectangle.vue
  49. 0 8
      src/draw/core/components/rectangle/temp-rectangle.vue
  50. 0 50
      src/draw/core/components/text/text.vue
  51. 0 52
      src/draw/core/components/triangle/triangle.vue
  52. 0 227
      src/draw/core/hook/use-transformer.ts
  53. 0 0
      src/example/fuse/App.vue
  54. 0 0
      src/example/fuse/main.ts
  55. 0 0
      src/example/fuse/store/index.ts
  56. 0 0
      src/example/fuse/styles/element.scss
  57. 0 0
      src/example/fuse/styles/global.scss
  58. 12 4
      src/views/header/funds.ts
  59. 3 0
      src/views/header/header.vue
  60. 0 0
      src/example/fuse/views/header/index.ts
  61. 61 0
      src/example/fuse/views/home.vue
  62. 0 0
      src/example/fuse/views/slide/index.ts
  63. 2 2
      src/views/slide/menu.ts
  64. 0 0
      src/example/fuse/views/slide/slide-item.vue
  65. 4 4
      src/views/slide/slide.vue
  66. 9 0
      src/example/fuse/views/use-draw.ts
  67. 0 0
      src/helper/deconstruction.d.ts
  68. 1 1
      src/draw/index.ts
  69. 1 1
      src/draw/utils/align-port.ts
  70. 5 0
      src/draw/utils/colors.ts
  71. 3 2
      src/draw/utils/event.ts
  72. 1 1
      src/draw/utils/math.ts
  73. 0 0
      src/utils/resource.ts
  74. 7 2
      src/draw/utils/shape.ts
  75. 1 1
      src/draw/utils/shared.ts
  76. 0 61
      src/views/home.vue
  77. 0 26
      src/views/use-draw.ts

+ 2 - 2
src/draw/constant/help-style.ts

@@ -1,4 +1,4 @@
-import { getMouseColors } from "../utils/colors"
+import { getMouseColors } from "../utils/colors.ts"
 
 export const themeColor = '#D8000A'
 export const themeMouseColors = getMouseColors(themeColor)
@@ -6,7 +6,7 @@ export const themeMouseColors = getMouseColors(themeColor)
 
 
 export const HelpPointStyle = {
-	radius: 10,
+	radius: 1,
 	fill: 'red',
 	stroke: 'black',
 	strokeWidth: 4,

src/draw/constant/mode.ts → src/constant/mode.ts


+ 26 - 0
src/core/components/arrow/arrow.vue

@@ -0,0 +1,26 @@
+<template>
+  <v-arrow :config="dataToConfig(data)" ref="shape" />
+</template>
+
+<script lang="ts" setup>
+import { ArrowData, dataToConfig, style } from "./index.ts";
+import { useMouseStyle } from "../../hook/use-mouse-status.ts";
+import { ref } from "vue";
+import { useLineTransformer } from "../../hook/use-transformer.ts";
+import { DC } from "@/helper/deconstruction";
+import { useAniamtion } from "../../hook/use-animation.ts";
+import { Arrow } from "konva/lib/shapes/Arrow";
+
+const props = defineProps<{ data: ArrowData }>();
+const emit = defineEmits<{ (e: "update", value: ArrowData): void }>();
+const shape = ref<DC<Arrow>>();
+useLineTransformer(
+  shape,
+  () => props.data,
+  (newData) => {
+    emit("update", { ...props.data, ...newData });
+  }
+);
+const { currentStyle } = useMouseStyle({ style, shape });
+useAniamtion(currentStyle, shape);
+</script>

+ 18 - 10
src/draw/core/components/arrow/index.ts

@@ -1,32 +1,40 @@
-import { Pos } from "@/draw/utils/math.ts";
-import { flatPositions } from "@/draw/utils/shared.ts";
+import { lineVerticalVector, Pos } from "@/utils/math.ts";
+import { flatPositions } from "@/utils/shared.ts";
 import { InteractiveMessage } from "../../hook/use-interactive.ts";
 import { ArrowConfig } from "konva/lib/shapes/Arrow";
+import { themeMouseColors } from "@/constant/help-style.ts";
 
 export { default as Component } from "./arrow.vue";
+export { default as TempComponent } from "./temp-arrow.vue";
 
 export const shapeName = "箭头";
 export const defaultStyle = {
-  stroke: "#000",
-  strokeWidth: 1,
-  rotation: 0,
-  fill: "#000",
+  stroke: themeMouseColors.theme,
+  strokeWidth: 2,
+  fill: themeMouseColors.theme,
 };
 
 export const addMode = 'area'
 
 export const style = {
   default: defaultStyle,
-  focus: defaultStyle,
-  hover: defaultStyle,
+  focus: {
+    stroke: themeMouseColors.hover,
+    fill: themeMouseColors.hover,
+  },
+  hover: {
+    stroke: themeMouseColors.theme,
+    fill: themeMouseColors.hover,
+  }
 };
 
-export type ArrowData = Partial<typeof defaultStyle> & { points: Pos[] };
+export type ArrowData = Partial<typeof defaultStyle> & { points: Pos[], attitude: number[] };
 
 export const dataToConfig = (data: ArrowData): ArrowConfig => ({
   ...defaultStyle,
   ...data,
   points: flatPositions(data.points),
+  hitStrokeWidth: 20
 });
 
 export const interactiveToData = (
@@ -34,7 +42,7 @@ export const interactiveToData = (
   preset: Partial<ArrowData> = {}
 ): ArrowData | undefined => {
   if (info.area) {
-    return interactiveFixData({ ...preset, points: [info.area[0]] }, info);
+    return interactiveFixData({ ...preset, points: [info.area[0]], attitude: [1, 0, 0, 1, 0, 0] }, info);
   }
 };
 

+ 50 - 0
src/core/components/circle/circle.vue

@@ -0,0 +1,50 @@
+<template>
+  <v-circle :config="dataToConfig(atData)" ref="shape"> </v-circle>
+</template>
+
+<script lang="ts" setup>
+import { Circle } from "konva/lib/shapes/Circle";
+import { useShapeTransformer, useTransformer } from "../../hook/use-transformer.ts";
+import { CircleData, dataToConfig, style } from "./index.ts";
+import { DC } from "@/helper/deconstruction";
+import { ref, watch } from "vue";
+import { useMouseStyle } from "../../hook/use-mouse-status.ts";
+import { useAniamtion } from "../../hook/use-animation.ts";
+import { useAutomaticData } from "../../hook/use-automatic-data.ts";
+import { setShapeTransform } from "@/utils/shape.ts";
+import { Transform } from "konva/lib/Util";
+
+const props = defineProps<{ data: CircleData }>();
+const emit = defineEmits<{ (e: "update", value: CircleData): void }>();
+const shape = ref<DC<Circle>>();
+const transform = useShapeTransformer(shape, {
+  rotateEnabled: false,
+  keepRatio: true,
+  enabledAnchors: ["top-left", "top-right", "bottom-left", "bottom-right"],
+});
+const transformer = useTransformer();
+
+const atData = useAutomaticData(() => props.data);
+
+watch(transform, (transform, oldTransform) => {
+  if (transform) {
+    const { x, y, scaleX } = transform.decompose();
+    if (scaleX !== 1) {
+      atData.value.radius *= scaleX;
+      setShapeTransform(
+        shape.value!.getNode(),
+        new Transform().translate(atData.value.x, atData.value.y)
+      );
+      transformer.forceUpdate();
+    } else {
+      atData.value.x = x;
+      atData.value.y = y;
+    }
+  } else if (oldTransform) {
+    emit("update", atData.value);
+  }
+});
+
+const { currentStyle } = useMouseStyle({ style, shape });
+useAniamtion(currentStyle, shape);
+</script>

+ 10 - 3
src/draw/core/components/circle/index.ts

@@ -1,11 +1,13 @@
 import { InteractiveMessage } from "../../hook/use-interactive.ts";
 import { CircleConfig } from "konva/lib/shapes/Circle";
+import { themeMouseColors } from "@/constant/help-style.ts";
 
 export { default as Component } from "./circle.vue";
+export { default as TempComponent } from "./temp-circle.vue";
 
 export const shapeName = "圆形";
 export const defaultStyle = {
-  stroke: "#000",
+  stroke: themeMouseColors.theme,
   fill: "#fff",
   strokeWidth: 1,
 };
@@ -14,8 +16,13 @@ export const addMode = 'area'
 
 export const style = {
   default: defaultStyle,
-  focus: defaultStyle,
-  hover: defaultStyle,
+  focus: {
+    fill: themeMouseColors.theme,
+    stroke: themeMouseColors.hover,
+  },
+  hover: {
+    stroke: themeMouseColors.theme,
+  }
 };
 
 export type CircleData = Partial<typeof defaultStyle> & {

+ 30 - 0
src/core/components/icon/icon.vue

@@ -0,0 +1,30 @@
+<template>
+  <TempIcon :data="{ ...styleData.data, ...data }" :ref="(e: any) => shape = e.shape" />
+</template>
+
+<script lang="ts" setup>
+import TempIcon from "./temp-icon.vue";
+import { IconData, style } from "./index.ts";
+import { ref, watch } from "vue";
+import { Group } from "konva/lib/Group";
+import { DC } from "@/helper/deconstruction";
+import { useShapeTransformer } from "../../hook/use-transformer.ts";
+import { useAutomaticData } from "../../hook/use-automatic-data.ts";
+import { useMouseStyle } from "../../hook/use-mouse-status.ts";
+import { useAniamtion } from "../../hook/use-animation.ts";
+
+const props = defineProps<{ data: IconData; addMode?: boolean }>();
+const emit = defineEmits<{ (e: "update", value: IconData): void }>();
+const shape = ref<DC<Group>>();
+const transform = useShapeTransformer(shape);
+const atData = useAutomaticData(() => props.data);
+
+watch(transform, (transform, oldTransform) => {
+  if (!transform && oldTransform) {
+    emit("update", { ...atData.value, mat: oldTransform.m });
+  }
+});
+
+const { currentStyle } = useMouseStyle({ style, shape });
+const styleData = useAniamtion(currentStyle);
+</script>

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

@@ -0,0 +1,69 @@
+import { InteractiveMessage } from "../../hook/use-interactive.ts";
+import { themeMouseColors } from "@/constant/help-style.ts";
+import { Transform } from "konva/lib/Util";
+
+export { default as Component } from "./icon.vue";
+export { default as TempComponent } from "./temp-icon.vue";
+
+export const shapeName = "图例";
+export const defaultStyle = {
+  coverFill: '#fff',
+  coverOpcatiy: 0,
+  // strokeScaleEnabled: true,
+  width: 80,
+  height: 80,
+};
+
+export const addMode = 'dot'
+
+export const style = {
+  default: defaultStyle,
+  focus: {
+    coverOpcatiy: 0.3,
+    coverFill: themeMouseColors.hover,
+  },
+  hover: {
+    coverOpcatiy: 0.3,
+    coverFill: themeMouseColors.theme,
+  },
+  press: {
+    coverOpcatiy: 0.3,
+    coverFill: themeMouseColors.press,
+  },
+};
+
+export type IconData = Partial<typeof defaultStyle> & {
+  coverFill?: string;
+  coverStroke?: string,
+  coverStrokeWidth?: number,
+  width: number;
+  height: number;
+  mat: number[];
+  url: string
+};
+
+
+export const dataToConfig = (data: IconData) => {
+  return {
+    ...defaultStyle,
+    ...data,
+  }
+}
+
+export const interactiveToData = (
+  info: InteractiveMessage,
+  preset: Partial<IconData> = {}
+): IconData | undefined => {
+  if (info.dot) {
+    return interactiveFixData({ ...preset } as unknown as IconData, info);
+  }
+};
+
+export const interactiveFixData = (
+  data: IconData,
+  info: InteractiveMessage
+) => {
+  const mat = new Transform().translate(info.dot!.x, info.dot!.y)
+  data.mat = mat.m
+  return data;
+};

+ 33 - 0
src/core/components/image/image.vue

@@ -0,0 +1,33 @@
+<template>
+  <TempImage
+    :data="{ ...props.data, ...animation.data }"
+    :ref="(e: any) => shape = e.shape"
+  />
+</template>
+
+<script lang="ts" setup>
+import TempImage from "./temp-image.vue";
+import { ImageData, style } from "./index.ts";
+import { ref, watch } from "vue";
+import { Group } from "konva/lib/Group";
+import { DC } from "@/helper/deconstruction";
+import { useShapeTransformer } from "../../hook/use-transformer.ts";
+import { useAutomaticData } from "../../hook/use-automatic-data.ts";
+import { useMouseStyle } from "../../hook/use-mouse-status.ts";
+import { useAniamtion } from "../../hook/use-animation.ts";
+
+const props = defineProps<{ data: ImageData; addMode?: boolean }>();
+const emit = defineEmits<{ (e: "update", value: ImageData): void }>();
+const shape = ref<DC<Group>>();
+const transform = useShapeTransformer(shape);
+const atData = useAutomaticData(() => props.data);
+
+watch(transform, (transform, oldTransform) => {
+  if (!transform && oldTransform) {
+    emit("update", { ...atData.value, mat: oldTransform.m });
+  }
+});
+
+const { currentStyle } = useMouseStyle({ style, shape });
+const animation = useAniamtion(currentStyle);
+</script>

+ 20 - 10
src/draw/core/components/image/index.ts

@@ -1,17 +1,28 @@
+import { themeMouseColors } from "@/constant/help-style.ts";
 import { InteractiveMessage } from "../../hook/use-interactive.ts";
-import { ImageConfig } from "konva/lib/shapes/Image";
+import { Transform } from "konva/lib/Util";
 
 export { default as Component } from "./image.vue";
+export { default as TempComponent } from "./temp-image.vue";
 
 export const shapeName = "图片";
-export const defaultStyle = {};
+export const defaultStyle = {
+  strokeWidth: 0,
+  stroke: '#000000',
+};
 
 export const addMode = 'dot'
 
 export const style = {
   default: defaultStyle,
-  focus: defaultStyle,
-  hover: defaultStyle,
+  focus: {
+    strokeWidth: 4,
+    stroke: themeMouseColors.hover,
+  },
+  hover: {
+    strokeWidth: 4,
+    stroke: themeMouseColors.theme,
+  }
 };
 
 export type ImageData = Partial<typeof defaultStyle> & {
@@ -21,13 +32,12 @@ export type ImageData = Partial<typeof defaultStyle> & {
   cornerRadius: number
   width: number;
   height: number;
-  x: number;
-  y: number;
-  url: string
+  url: string;
+  mat: number[]
 };
 
 
-export const dataToConfig = (data: ImageData): Omit<ImageConfig, 'image'> => ({
+export const dataToConfig = (data: ImageData) => ({
   ...defaultStyle,
   ...data
 })
@@ -45,7 +55,7 @@ export const interactiveFixData = (
   data: ImageData,
   info: InteractiveMessage
 ) => {
-  data.x = info.dot!.x
-  data.y = info.dot!.y
+  const mat = new Transform().translate(info.dot!.x, info.dot!.y)
+  data.mat = mat.m
   return data;
 };

src/draw/core/components/index.ts → src/core/components/index.ts


+ 26 - 10
src/draw/core/components/line/index.ts

@@ -1,30 +1,46 @@
-import { Pos } from "@/draw/utils/math.ts";
-import { flatPositions } from "@/draw/utils/shared.ts";
+import { Pos } from "@/utils/math.ts";
+import { flatPositions } from "@/utils/shared.ts";
 import { InteractiveAction, InteractiveMessage } from "../../hook/use-interactive.ts";
 import { LineConfig } from "konva/lib/shapes/Line";
+import { themeMouseColors } from "@/constant/help-style.ts";
 
 export { default as Component } from "./line.vue";
+export { default as TempComponent } from "./temp-line.vue";
 
 export const shapeName = "线段";
 export const defaultStyle = {
-  stroke: "#000",
-  strokeWidth: 10,
+  stroke: themeMouseColors.theme,
+  strokeWidth: 5
 };
 
-export const addMode = 'dots'
 
+export const addMode = 'dots'
 export const style = {
   default: defaultStyle,
-  focus: defaultStyle,
-  hover: defaultStyle,
-};
+  focus: {
+    stroke: themeMouseColors.hover,
+  },
+  hover: {
+    stroke: themeMouseColors.theme,
+  }
+}
+
 
-export type LineData = Partial<typeof defaultStyle> & { points: Pos[] };
+export type LineData = Partial<typeof defaultStyle> & { points: Pos[], attitude: number[] };
 
 export const dataToConfig = (data: LineData): LineConfig => ({
   ...defaultStyle,
   ...data,
   points: flatPositions(data.points),
+  hitFunc(con, shape) {
+    con.beginPath()
+    con.moveTo(data.points[0].x, data.points[0].y)
+    for (let i = 1; i < data.points.length; i++) {
+      con.lineTo(data.points[i].x, data.points[i].y)
+    }
+    con.closePath()
+    con.fillStrokeShape(shape);
+  },
 });
 
 export const interactiveToData = (
@@ -32,7 +48,7 @@ export const interactiveToData = (
   preset: Partial<LineData> = {}
 ): LineData | undefined => {
   if (info.dot) {
-    return interactiveFixData({ ...preset, points: [] }, info);
+    return interactiveFixData({ ...preset, points: [], attitude: [1, 0, 0, 1, 0, 0] }, info);
   }
 };
 

+ 26 - 0
src/core/components/line/line.vue

@@ -0,0 +1,26 @@
+<template>
+  <v-line :config="dataToConfig(data)" ref="shape"> </v-line>
+</template>
+
+<script lang="ts" setup>
+import { LineData, dataToConfig, style } from "./index.ts";
+import { useMouseStyle } from "../../hook/use-mouse-status.ts";
+import { ref } from "vue";
+import { useLineTransformer } from "../../hook/use-transformer.ts";
+import { DC } from "@/helper/deconstruction";
+import { useAniamtion } from "../../hook/use-animation.ts";
+import { Line } from "konva/lib/shapes/Line";
+
+const props = defineProps<{ data: LineData }>();
+const emit = defineEmits<{ (e: "update", value: LineData): void }>();
+const shape = ref<DC<Line>>();
+useLineTransformer(
+  shape,
+  () => props.data,
+  (newData) => {
+    emit("update", { ...props.data, ...newData });
+  }
+);
+const { currentStyle } = useMouseStyle({ style, shape });
+useAniamtion(currentStyle, shape);
+</script>

+ 17 - 11
src/draw/core/components/polygon/index.ts

@@ -1,27 +1,33 @@
-import { Pos } from "@/draw/utils/math.ts";
-import { flatPositions } from "@/draw/utils/shared.ts";
+import { Pos } from "@/utils/math.ts";
+import { flatPositions } from "@/utils/shared.ts";
 import { InteractiveAction, InteractiveMessage } from "../../hook/use-interactive.ts";
 import { LineConfig } from "konva/lib/shapes/Line";
+import { themeMouseColors } from "@/constant/help-style.ts";
 
 export { default as Component } from "./polygon.vue";
+export { default as TempComponent } from "./temp-polygon.vue";
 
 export const shapeName = "多边形";
 export const defaultStyle = {
-  stroke: "#000",
-  strokeWidth: 10,
-  rotation: 0,
-  fill: "#fff",
+  stroke: themeMouseColors.theme,
+  strokeWidth: 5,
+  fill: '#fff',
 };
 
 export const addMode = 'dots'
 
 export const style = {
   default: defaultStyle,
-  focus: defaultStyle,
-  hover: defaultStyle,
-};
+  focus: {
+    fill: themeMouseColors.theme,
+    stroke: themeMouseColors.hover,
+  },
+  hover: {
+    stroke: themeMouseColors.theme,
+  }
+}
 
-export type PolygonData = Partial<typeof defaultStyle> & { points: Pos[] };
+export type PolygonData = Partial<typeof defaultStyle> & { points: Pos[], attitude: number[] };
 
 export const dataToConfig = (data: PolygonData): LineConfig => ({
   ...defaultStyle,
@@ -35,7 +41,7 @@ export const interactiveToData = (
   preset: Partial<PolygonData> = {}
 ): PolygonData | undefined => {
   if (info.dot) {
-    return interactiveFixData({ ...preset, points: [] }, info);
+    return interactiveFixData({ ...preset, points: [], attitude: [1, 0, 0, 1, 0, 0] }, info);
   }
 };
 

+ 26 - 0
src/core/components/polygon/polygon.vue

@@ -0,0 +1,26 @@
+<template>
+  <v-line :config="dataToConfig(data)" ref="shape" />
+</template>
+
+<script lang="ts" setup>
+import { PolygonData, dataToConfig, style } from "./index.ts";
+import { useMouseStyle } from "../../hook/use-mouse-status.ts";
+import { ref } from "vue";
+import { useLineTransformer } from "../../hook/use-transformer.ts";
+import { DC } from "@/helper/deconstruction";
+import { useAniamtion } from "../../hook/use-animation.ts";
+import { Line } from "konva/lib/shapes/Line";
+
+const props = defineProps<{ data: PolygonData }>();
+const emit = defineEmits<{ (e: "update", value: PolygonData): void }>();
+const shape = ref<DC<Line>>();
+useLineTransformer(
+  shape,
+  () => props.data,
+  (newData) => {
+    emit("update", { ...props.data, ...newData });
+  }
+);
+const { currentStyle } = useMouseStyle({ style, shape });
+useAniamtion(currentStyle, shape);
+</script>

+ 4 - 10
src/draw/core/components/rectangle/index.ts

@@ -1,7 +1,7 @@
-import { Pos } from "@/draw/utils/math.ts";
+import { Pos } from "@/utils/math.ts";
 import { InteractiveMessage } from "../../hook/use-interactive.ts";
-import { themeMouseColors } from "@/draw/constant/help-style.ts";
-import { flatPositions } from "@/draw/utils/shared";
+import { themeMouseColors } from "@/constant/help-style.ts";
+import { flatPositions } from "@/utils/shared.ts";
 import { LineConfig } from "konva/lib/shapes/Line";
 
 export { default as Component } from "./rectangle.vue";
@@ -48,13 +48,6 @@ export type RectangleData = Partial<typeof defaultStyle> & {
   points: Pos[]
 };
 
-export const dataToConfig = (data: RectangleData): LineConfig => {
-  return {
-    ...data,
-    closed: true,
-    points: flatPositions(data.points),
-  };
-};
 
 export const interactiveToData = (
   info: InteractiveMessage,
@@ -81,6 +74,7 @@ export const interactiveFixData = (
       {x: info.area[0].x + width, y: info.area[0].y + height},
       {x: info.area[0].x, y: info.area[0].y + height},
     ]
+    data.attitude = [1, 0, 0, 1, 0, 0]
   }
   return data;
 };

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

@@ -0,0 +1,35 @@
+<template>
+  <TempLine :data="data" :ref="(v: any) => shape = v.shape" />
+  <Propertys :describes="propertyDescribes" :data="data" :target="shape" />
+</template>
+
+<script lang="ts" setup>
+import { RectangleData, style } from "./index.ts";
+import { PropertyDescribes, Propertys } from "../../propertys/controller.ts";
+import { useMouseStyle } from "../../hook/use-mouse-status.ts";
+import { ref } from "vue";
+import { useLineTransformer } from "../../hook/use-transformer.ts";
+import { DC } from "@/helper/deconstruction";
+import { useAniamtion } from "../../hook/use-animation.ts";
+import { Line } from "konva/lib/shapes/Line";
+import TempLine from "./temp-rectangle.vue";
+
+const props = defineProps<{ data: RectangleData; addMode?: boolean }>();
+const emit = defineEmits<{ (e: "update", value: RectangleData): void }>();
+const shape = ref<DC<Line>>();
+
+const [_, data] = useLineTransformer(
+  shape,
+  () => ({ ...props.data, ...style.default }),
+  (newData) => {
+    emit("update", { ...props.data, ...newData });
+  }
+);
+const { currentStyle } = useMouseStyle({ style, shape });
+useAniamtion(currentStyle, shape);
+
+const propertyDescribes: PropertyDescribes = {
+  stroke: { type: "color", label: "边框" },
+  fill: { type: "color", label: "填充" },
+};
+</script>

+ 28 - 0
src/core/components/rectangle/temp-rectangle.vue

@@ -0,0 +1,28 @@
+<template>
+  <v-line
+    :config="{
+      ...style.default,
+      ...data,
+      closed: true,
+      points: flatPositions(data.points),
+      opacity: addMode ? 0.3 : 1,
+    }"
+    ref="shape"
+  />
+</template>
+
+<script lang="ts" setup>
+import { flatPositions } from "@/utils/shared.ts";
+import { RectangleData, style } from "./index.ts";
+import { ref } from "vue";
+import { DC } from "@/helper/deconstruction";
+import { Line } from "konva/lib/shapes/Line";
+defineProps<{ data: RectangleData; addMode?: boolean }>();
+
+const shape = ref<DC<Line>>();
+defineExpose({
+  get shape() {
+    return shape.value;
+  },
+});
+</script>

+ 6 - 6
src/draw/core/components/share/point.vue

@@ -6,20 +6,20 @@
 </template>
 
 <script lang="ts" setup>
-import { Pos } from "@/draw/utils/math.ts";
-import { HelpPointStyle } from "@/draw/constant/help-style.ts";
+import { Pos } from "@/utils/math.ts";
+import { HelpPointStyle } from "@/constant/help-style.ts";
 import { ref, watch, watchEffect } from 'vue'
-import { DC } from "@/draw/helper/deconstruction";
+import { DC } from "@/helper/deconstruction";
 import { Circle } from "konva/lib/shapes/Circle";
-import { useMouseDrag } from "@/draw/core/hook/use-mouse-status.ts";
+import { useShapeDrag } from "@/core/hook/use-transformer.ts";
 
 const props = defineProps<{ position: Pos }>()
 const emit = defineEmits<{ (e: 'update:position', position: Pos): void, (e: 'dragend'): void }>()
 
 const circle = ref<DC<Circle>>()
-const { absPosition } = useMouseDrag(circle)
+const offset = useShapeDrag(circle)
 
-watch(absPosition, (position) => {
+watch(offset, (position) => {
 	if (position) {
 		emit('update:position', position)
 	} else {

+ 18 - 11
src/draw/core/components/text/index.ts

@@ -1,29 +1,36 @@
+import { Transform } from "konva/lib/Util";
 import { InteractiveMessage } from "../../hook/use-interactive.ts";
 import { TextConfig } from "konva/lib/shapes/Text";
+import { themeMouseColors } from "@/constant/help-style.ts";
 
 export { default as Component } from "./text.vue";
 
 export const shapeName = "文字";
 export const defaultStyle = {
-  fill: "#000",
-  stroke: 'red',
-  strokeWidth: 3,
+  stroke: themeMouseColors.theme,
+  fill: themeMouseColors.theme,
+  strokeWidth: 1,
   fontFamily: 'Calibri',
-  fontSize: 30,
-  maxWidth: 300
+  fontSize: 16,
+  width: 300
 };
 
 export const addMode = 'dot'
 
 export const style = {
   default: defaultStyle,
-  focus: defaultStyle,
-  hover: defaultStyle,
+  hover: {
+    stroke: themeMouseColors.hover,
+    fill: themeMouseColors.hover,
+  },
+  press: {
+    stroke: themeMouseColors.press,
+    fill: themeMouseColors.press,
+  },
 };
 
 export type TextData = Partial<typeof defaultStyle> & {
-  x: number;
-  y: number;
+  mat: number[]
   content: string
 };
 
@@ -53,7 +60,7 @@ export const interactiveFixData = (
   data: TextData,
   info: InteractiveMessage
 ) => {
-  data.x = info.dot!.x
-  data.y = info.dot!.y
+  const mat = new Transform().translate(info.dot!.x, info.dot!.y)
+  data.mat = mat.m
   return data;
 };

+ 60 - 0
src/core/components/text/text.vue

@@ -0,0 +1,60 @@
+<template>
+  <TempText :data="{ ...atData, ...animation.data }" :ref="(e: any) => shape = e.shape" />
+</template>
+
+<script lang="ts" setup>
+import TempText from "./temp-text.vue";
+import { TextData, style } from "./index.ts";
+import { computed, ref, watch, watchEffect } from "vue";
+
+import { DC } from "@/helper/deconstruction";
+import { useShapeTransformer } from "../../hook/use-transformer.ts";
+import { useAutomaticData } from "../../hook/use-automatic-data.ts";
+import { useMouseStyle } from "../../hook/use-mouse-status.ts";
+import { useAniamtion } from "../../hook/use-animation.ts";
+import { Text } from "konva/lib/shapes/Text";
+
+const props = defineProps<{ data: TextData; addMode?: boolean }>();
+const emit = defineEmits<{ (e: "update", value: TextData): void }>();
+const shape = ref<DC<Text>>();
+
+const minWidth = computed(() => (props.data.fontSize || 12) * 2);
+const transform = useShapeTransformer(shape, {
+  enabledAnchors: ["middle-left", "middle-right"],
+  flipEnabled: false,
+  boundBoxFunc: (oldBox, newBox) => {
+    if (Math.abs(newBox.width) < minWidth.value) {
+      return oldBox;
+    }
+    return newBox;
+  },
+});
+const atData = useAutomaticData(() => props.data);
+
+watchEffect(() => {
+  const $text = shape.value?.getNode();
+  if (!$text) return;
+  $text.on("transform", () => {
+    const newWidth = Math.max($text.width() * $text.scaleX(), minWidth.value);
+    $text.setAttrs({
+      width: newWidth,
+      scaleX: 1,
+      scaleY: 1,
+    });
+  });
+});
+
+watch(transform, (transform, oldTransform) => {
+  if (!transform && oldTransform) {
+    const $text = shape.value!.getNode();
+    emit("update", {
+      ...atData.value,
+      width: $text.width(),
+      mat: $text.getTransform().m,
+    });
+  }
+});
+
+const { currentStyle } = useMouseStyle({ style, shape });
+const animation = useAniamtion(currentStyle);
+</script>

+ 6 - 5
src/draw/core/components/triangle/index.ts

@@ -1,8 +1,8 @@
-import { InteractiveMessage } from "../../hook/use-interactive";
+import { InteractiveMessage } from "../../hook/use-interactive.ts";
 import { LineConfig } from "konva/lib/shapes/Line";
-import { Pos } from "@/draw/utils/math";
-import { flatPositions } from "@/draw/utils/shared";
-import { themeMouseColors } from "@/draw/constant/help-style";
+import { Pos } from "@/utils/math.ts";
+import { flatPositions } from "@/utils/shared.ts";
+import { themeMouseColors } from "@/constant/help-style.ts";
 
 export { default as Component } from "./triangle.vue";
 export { default as TempComponent } from "./temp-triangle.vue";
@@ -27,13 +27,14 @@ export const style = {
   }
 }
 
-export type TriangleData = Partial<typeof defaultStyle> & { points: Pos[], recordRotation: number };
+export type TriangleData = Partial<typeof defaultStyle> & { points: Pos[], attitude: number[] };
 
 export const dataToConfig = (data: TriangleData): LineConfig => {
   return {
     ...data,
     closed: true,
     points: flatPositions(data.points),
+    attitude: [1, 0, 0, 1, 0, 0],
   };
 };
 

+ 1 - 1
src/draw/core/components/triangle/temp-triangle.vue

@@ -3,6 +3,6 @@
 </template>
 
 <script lang="ts" setup>
-import { TriangleData, dataToConfig } from "./index";
+import { TriangleData, dataToConfig } from "./index.ts";
 defineProps<{ data: TriangleData }>()
 </script>

+ 26 - 0
src/core/components/triangle/triangle.vue

@@ -0,0 +1,26 @@
+<template>
+  <v-line :config="dataToConfig(data)" ref="shape"> </v-line>
+</template>
+
+<script lang="ts" setup>
+import { TriangleData, style, dataToConfig } from "./index.ts";
+import { useMouseStyle } from "../../hook/use-mouse-status.ts";
+import { ref } from "vue";
+import { useLineTransformer } from "../../hook/use-transformer.ts";
+import { DC } from "@/helper/deconstruction";
+import { useAniamtion } from "../../hook/use-animation.ts";
+import { Line } from "konva/lib/shapes/Line";
+
+const props = defineProps<{ data: TriangleData }>();
+const emit = defineEmits<{ (e: "update", value: TriangleData): void }>();
+const shape = ref<DC<Line>>();
+useLineTransformer(
+  shape,
+  () => props.data,
+  (newData) => {
+    emit("update", { ...props.data, ...newData });
+  }
+);
+const { currentStyle } = useMouseStyle({ style, shape });
+useAniamtion(currentStyle, shape);
+</script>

+ 1 - 1
src/draw/core/history.ts

@@ -1,5 +1,5 @@
 import { History } from "stateshot";
-import { inRevise } from "../utils/shared";
+import { inRevise } from "../utils/shared.ts";
 
 export type HistoryState = {
 	hasUndo: boolean;

+ 15 - 12
src/draw/core/hook/use-animation.ts

@@ -1,15 +1,16 @@
-import { onUnmounted, ref, Ref, watch, watchEffect } from "vue"
+import { onUnmounted, reactive, ref, Ref, watch, watchEffect } from "vue"
 import { Tween, Easing } from '@tweenjs/tween.js'
-import { inRevise } from "@/draw/utils/shared"
+import { inRevise } from "@/utils/shared.ts"
 import { Color, RGB } from "three"
-import { DC } from "@/draw/helper/deconstruction"
-import { Shape } from "konva/lib/Shape"
+import { DC, EntityShape } from "@/helper/deconstruction"
 
 const pickColors = <T extends object>(origin: T): Record<string, RGB>  => {
   const originColors = {} as any
   for (const [key, val] of Object.entries(origin)) {
     if (typeof val === 'string') {
-      originColors[key] = new Color(val).getRGB({} as any)
+      if (val.startsWith('#') || val.startsWith('rgb')) {
+        originColors[key] = new Color(val).getRGB({} as any)
+      } 
     }
   }
   return originColors
@@ -30,17 +31,16 @@ const animation = <T extends object>(origin: T, target: T) => {
   }
   const oColors = pickColors(origin);
   const tColors = pickColors(target)
-  const colorKeys = new Set([...Object.keys(oColors), ...Object.keys(tColors)])
   const tOrigin = {...origin}
   const tTarget = {...target}
 
   for (const key in tOrigin) {
-    if (colorKeys.has(key)) {
+    if (typeof tOrigin[key] === 'string') {
       delete tOrigin[key]
     }
   }
   for (const key in tTarget) {
-    if (colorKeys.has(key)) {
+    if (typeof tTarget[key] === 'string') {
       delete tTarget[key]
     }
   }
@@ -78,9 +78,8 @@ const animation = <T extends object>(origin: T, target: T) => {
   return stop
 }
 
-
-export const useAniamtion = <T extends object>(data: Ref<T>, shape?: Ref<DC<Shape>| undefined>) => {
-  const atData = ref<T>({...data.value})
+export const useAniamtion = <T extends object>(data: Ref<T>, shape?: Ref<DC<EntityShape>| undefined>) => {
+  const atData = ref({...data.value}) as Ref<T>
   let animationStop: () => void
 
   const { pause, resume } = watch(data, (newData) => {
@@ -101,9 +100,13 @@ export const useAniamtion = <T extends object>(data: Ref<T>, shape?: Ref<DC<Shap
     animationStop && animationStop()
     stopShapeSet()
   })
-  return {
+  return reactive({
     data: atData,
     pause,
     resume
+  }) as {
+    data: T,
+    pause: () => void,
+    resume: () => void,
   }
 }

src/draw/core/hook/use-automatic-data.ts → src/core/hook/use-automatic-data.ts


src/draw/core/hook/use-coversion-position.ts → src/core/hook/use-coversion-position.ts


src/draw/core/hook/use-event.ts → src/core/hook/use-event.ts


+ 10 - 2
src/draw/core/hook/use-expose.ts

@@ -1,4 +1,4 @@
-import { useMode, useStage } from "./use-global-vars.ts";
+import { useLayers, useMode, useStage } from "./use-global-vars.ts";
 import { Stage } from "konva/lib/Stage";
 import { reactive } from "vue";
 import { useInteractiveProps, useInteractiveShapeAPI } from "./use-interactive.ts";
@@ -11,7 +11,7 @@ export const useExpose = () => {
 	const mode = useMode()
 	const interactiveProps = useInteractiveProps()
 	const stage = useStage()
-
+	const layers = useLayers()
 
 	const exposeBlob = (config?: PickParams<'toBlob', 'callback'>) => {
 		const $stage = stage.value!.getStage()
@@ -20,9 +20,17 @@ export const useExpose = () => {
 		})
 	}
 
+	const toggleHit = () => {
+		if (!layers.value) return;
+		layers.value.forEach(layer => {
+			layer.toggleHitCanvas()
+		})
+	}
+
 	return reactive({
 		...useInteractiveShapeAPI(),
 		exposeBlob,
+		toggleHit,
 		mode,
 		presetAdd: interactiveProps,
 	})

+ 27 - 7
src/draw/core/hook/use-global-vars.ts

@@ -1,10 +1,12 @@
-import { DC } from "../../helper/deconstruction";
+import { DC, EntityShape } from "../../helper/deconstruction";
 import { Stage } from "konva/lib/Stage";
 import {
+	computed,
 	getCurrentInstance,
 	nextTick,
 	onUnmounted,
 	reactive,
+	ref,
 	shallowRef,
 	watch,
 	WatchCallback,
@@ -12,23 +14,24 @@ import {
 	WatchSource,
 } from "vue";
 import { Mode } from "../../constant/mode.ts";
+import { Layer } from "konva/lib/Layer";
 
 export const installGlobalVar = <T>(
-	create: () => { var: T, onDestory: () => void } | T,
+	create: () => { var: T, onDestroy: () => void } | T,
 	key = Symbol('globalVar'),
 	noRefDel = true,
 ) => {
 	let initialed = false
 	let refCount = 0;
-	let onDestory: (() => void) | null = null
+	let onDestroy: (() => void) | null = null
 
 	const useGlobalVar = (): T => {
 		const instance = getCurrentInstance() as any;
 		const ctx = instance.appContext
 		if (!initialed) {
 			let val = create() as any
-			if (typeof val === 'object' && 'var' in val && 'onDestory' in val) {
-				onDestory = val.onDestory
+			if (typeof val === 'object' && 'var' in val && 'onDestroy' in val) {
+				onDestroy = val.onDestory
 				val = val.var;
 			}
 			ctx[key] = val
@@ -47,8 +50,8 @@ export const installGlobalVar = <T>(
 						initialed = false;
 						delete ctx[key]
 						console.log('销毁', key)
-						onDestory && onDestory()
-						onDestory = null
+						onDestroy && onDestroy()
+						onDestroy = null
 					}
 				})
 				return useGlobalVar();
@@ -102,3 +105,20 @@ export const globalWatch = <T>(
 export const useStage = installGlobalVar(() => shallowRef<DC<Stage> | undefined>(), Symbol("stage"));
 export const useMode = installGlobalVar(() => stackVar(Mode.viewer), Symbol("mode"));
 
+export const useLayers = () => {
+	const stage = useStage()
+	return computed(() => stage.value?.getNode().children as Layer[]) 
+}
+
+export const useTempLayer = () => {
+	const stage = useStage()
+	return computed(() => stage.value?.getNode().find<Layer>('#temp')[0]) 
+}
+
+export const useFormalLayer = () => {
+	const stage = useStage()
+	return computed(() => stage.value?.getNode().find<Layer>('#formal')[0]) 
+}
+
+export const useTransformIngShapes = installGlobalVar(() => ref<EntityShape[]>([]))
+export const useSetAttrisIngShapes = installGlobalVar(() => ref<EntityShape[]>([]))

src/draw/core/hook/use-interactive.ts → src/core/hook/use-interactive.ts


+ 64 - 59
src/draw/core/hook/use-mouse-status.ts

@@ -1,17 +1,18 @@
-import { computed, nextTick, reactive, ref, Ref, watch,  } from "vue";
+import { computed, reactive, ref, Ref, watch, watchEffect } from "vue";
 import { DC, EntityShape } from "../../helper/deconstruction";
 import { Shape } from "konva/lib/Shape";
-import { globalWatch, installGlobalVar, useStage } from "./use-global-vars.ts";
+import { globalWatch, installGlobalVar, useMode, useStage, useTransformIngShapes } from "./use-global-vars.ts";
 import { Stage } from "konva/lib/Stage";
 import { listener } from "../../utils/event.ts";
 import { mergeFuns } from "../../utils/shared.ts";
 import { ComponentValue, ShapeType } from "../components";
 import { shapeTreeContain } from "../../utils/shape.ts";
-import { transformRectPadding, useShapeTransformer, useTransformer } from "./use-transformer.ts";
-import { Util } from "konva/lib/Util";
+import { usePointerIsTransformerInner, useShapeTransformer, useTransformer } from "./use-transformer.ts";
+import { Mode } from "@/constant/mode.ts";
 
 export const useMouseShapesStatus = installGlobalVar(
 	() => {
+		const mode = useMode()
 		const stage = useStage()
 		const listeners = ref([]) as Ref<EntityShape[]>
 		const hovers = ref([]) as Ref<EntityShape[]>
@@ -19,58 +20,55 @@ export const useMouseShapesStatus = installGlobalVar(
 		const selects = ref([]) as Ref<EntityShape[]>
 		const actives = ref([]) as Ref<EntityShape[]>
 		const transformer = useTransformer()
+		const pointerIsTransformerInner = usePointerIsTransformerInner()
 
 	
 		const init = (stage: Stage) => {
 			let downTime: number
 			let downTarget: EntityShape
+			const inner = new WeakMap<EntityShape, boolean>()
+			
 
 			stage.on('pointerenter.mouse-status', async (ev) => {
 				const target = shapeTreeContain(listeners.value, ev.target)
-				if (!target || hovers.value.includes(target)) return;
+				if (!target) return;
+				inner.set(target, true)
+				if (hovers.value.includes(target)) return;
 
-				let timeout: number
+				hovers.value.push(target)
 				const targetLeave = () => {
-					clearTimeout(timeout)
-					timeout = setTimeout(() => {
-						target.off('pointerleave.mouse-status')
-						stage.off('pointermove.mouse-status')
-						const ndx = hovers.value.indexOf(target)
-						if (~ndx) {
-							hovers.value.splice(ndx, 1)
-						}	
-					})
-				}
-				const targetEnter = () => {
-					clearTimeout(timeout)
-					if (!hovers.value.includes(target)) {
-						hovers.value.push(target)
-					}
+					target.off('pointerleave.mouse-status')
+					stage.off('pointermove.mouse-status')
+					stopIncludeWatch! && stopIncludeWatch()
+					const ndx = hovers.value.indexOf(target)
+					if (~ndx) {
+						hovers.value.splice(ndx, 1)
+					}	
 				}
-				targetEnter()
 
+				let stopIncludeWatch: () => void
 				target.on('pointerleave.mouse-status', ev => {
-					target === ev.target && targetLeave()
+					const target = shapeTreeContain(listeners.value, ev.target)
+					if (!target) return;
+					inner.set(target, false)
+
+					// TODO: 有可能在transformer上,需要额外检测
+					stopIncludeWatch! && stopIncludeWatch()
+					stopIncludeWatch = watch(transformer.queueShapes, (queueShapes, _, onCleanup) => {
+						if (inner.get(target)) return;
+
+						if (!queueShapes.includes(target) || !pointerIsTransformerInner()) {
+							targetLeave()
+						} else {
+							stage.on('pointermove.mouse-status', () => {
+								if (!inner.get(target) && !pointerIsTransformerInner()) {
+									targetLeave()
+								}
+							})
+							onCleanup(() => stage.off('pointermove.mouse-status'))
+						}
+					}, {immediate: true})
 				})
-				// 有可能外面套了transformer要监听是否真正离开
-				await nextTick()
-				console.log(transformer.queueShapes, target)
-				if (!transformer.queueShapes.includes(target)) return;
-				const moveHandler = () => {
-					const tfRect = transformer.getClientRect()
-					tfRect.x -= transformRectPadding
-					tfRect.y -= transformRectPadding
-					tfRect.width += transformRectPadding
-					tfRect.height += transformRectPadding
-					const pointRect = { ...stage.pointerPos!, width: 1, height: 1 }
-					
-					if (Util.haveIntersection(tfRect, pointRect)) {
-						targetEnter()
-					} else {
-						targetLeave()
-					}
-				}
-				stage.on('pointermove.mouse-status', moveHandler)
 			})
 			stage.on('pointerdown.mouse-status', (ev) => {
 				downTime = Date.now()
@@ -103,29 +101,37 @@ export const useMouseShapesStatus = installGlobalVar(
 					actives.value = []
 					press.value = []
 					selects.value = []
-					listeners.value = []
 				}
 			)
 		}
 
+		let cleanup: () => void
+		const stopStatusWatch = globalWatch(
+			() => [stage.value?.getStage(), [Mode.update, Mode.viewer].includes(mode.value)] as const, 
+			([stage, canUse], prev) => {
+				if (prev && (stage !== prev[0] || canUse !== prev[1])) {
+					cleanup! && cleanup()
+				}
+				if (stage && canUse) {
+					cleanup = init(stage)
+				}
+			},
+			{ immediate: true }
+		)
+
 		return {
 			var: reactive({hovers, actives, selects, press, listeners}),
-			onDestory: globalWatch(
-				() => stage.value?.getStage(), 
-				(stage, _, onCleanup) => {
-					if (stage) {
-						onCleanup(init(stage))
-					}
-				},
-				{ immediate: true }
-			)
+			onDestroy: () => {
+				stopStatusWatch()
+				cleanup && cleanup()
+			}
 		}
 	},
 	Symbol('mouseStatus')
 )
 
 
-export const useMouseShapeStatus = (shape: Ref<DC<Shape> | undefined>) => {
+export const useMouseShapeStatus = (shape: Ref<DC<EntityShape> | undefined>) => {
 	const status = useMouseShapesStatus()
 	watch(
 		() => shape.value?.getStage(), 
@@ -142,7 +148,6 @@ export const useMouseShapeStatus = (shape: Ref<DC<Shape> | undefined>) => {
 		}
 	})
 
-
 	return computed(() => {
 		const $shape = shape.value?.getStage() as Shape
 		return {
@@ -156,19 +161,19 @@ export const useMouseShapeStatus = (shape: Ref<DC<Shape> | undefined>) => {
 
 
 type MouseStyleProps<T extends ShapeType> = {
-	shape?: Ref<DC<Shape> | undefined>
+	shape?: Ref<DC<EntityShape> | undefined>
 	style: ComponentValue<T, 'style'>
 }
 type ValueOf<T> = T[keyof T]
 export const useMouseStyle = <T extends ShapeType>(
 	props: MouseStyleProps<T>, 
-	tf?: ReturnType<typeof useShapeTransformer>
 ) => {
 	const shape = props.shape || ref()
 	const status = useMouseShapeStatus(shape)
+	const transformIngShapes = useTransformIngShapes()
 
 	const style = computed(() => {
-		const styleMap = new Map([[props.style.default, true]])
+		const styleMap = new Map([[props.style.default as any, true]])
 		if ('hover' in props.style) {
 			styleMap.set(props.style.hover, status.value.hover)
 		}
@@ -178,8 +183,8 @@ export const useMouseStyle = <T extends ShapeType>(
 		if ('focus' in props.style) {
 			styleMap.set(props.style.focus, status.value.active)
 		}
-		if ( 'drag' in props.style && tf) {
-			styleMap.set(props.style.drag, !!tf.value)
+		if ( 'drag' in props.style && transformIngShapes.value.includes(shape.value?.getNode()!)) {
+			styleMap.set(props.style.drag, true)
 		}
 		// if ('select' in props.style) {
 		// 	styleMap.set(props.style.select, status.value.select)

+ 336 - 0
src/core/hook/use-transformer.ts

@@ -0,0 +1,336 @@
+import { useMouseShapeStatus } from "./use-mouse-status.ts";
+import { Ref, ref, watch } from "vue";
+import { DC, EntityShape } from "../../helper/deconstruction";
+import { Shape } from "konva/lib/Shape";
+import { installGlobalVar, useMode, useStage, useTransformIngShapes } from "./use-global-vars.ts";
+import { Mode } from "../../constant/mode.ts";
+import { Transform, Util } from "konva/lib/Util";
+import { Pos } from "@/utils/math.ts";
+import { useConversionPosition } from "./use-coversion-position.ts";
+import { getOffset, listener } from "@/utils/event.ts";
+import { flatPositions, mergeFuns } from "@/utils/shared.ts";
+import { Line } from "konva/lib/shapes/Line";
+import { setShapeTransform } from "@/utils/shape.ts";
+import { TransformerConfig, Transformer } from "konva/lib/shapes/Transformer";
+import { themeMouseColors } from "@/constant/help-style.ts";
+import { useAutomaticData } from "./use-automatic-data.ts";
+
+export type TransformerExtends = Transformer &  { queueShapes: Ref<EntityShape[]> }
+export const transformRectPadding = 10;
+export const useTransformer = installGlobalVar(() => {
+  const anchorCornerRadius = 5
+  const transformer = new Transformer({ 
+    borderStrokeWidth: 2, 
+    borderStroke: themeMouseColors.pub,
+    anchorCornerRadius,
+    anchorSize: anchorCornerRadius * 2,
+    anchorStrokeWidth: 2,
+    anchorStroke: themeMouseColors.pub,
+    anchorFill: themeMouseColors.press,
+    padding: 10,
+    useSingleNodeRotation: true 
+  }) as TransformerExtends;
+  transformer.queueShapes = ref([])
+  return transformer;
+}, Symbol("transformer"));
+
+export const usePointerIsTransformerInner = () => {
+  const transformer = useTransformer()
+  const stage = useStage()
+  return () => {
+    const $stage = stage.value!.getStage()
+    const tfRect = transformer.getClientRect()
+    tfRect.x -= transformRectPadding
+    tfRect.y -= transformRectPadding
+    tfRect.width += transformRectPadding
+    tfRect.height += transformRectPadding
+    const pointRect = { ...$stage.pointerPos!, width: 1, height: 1 }
+    
+    return Util.haveIntersection(tfRect, pointRect);
+  }
+}
+
+
+export const useShapeDrag = (
+  shape: Ref<DC<EntityShape> | undefined>) => {
+  const offset = ref<Pos>()
+  const mode = useMode();
+  const conversion = useConversionPosition(true);
+  const transformIngShapes = useTransformIngShapes()
+
+  const init = (shape: EntityShape) => {
+    const dom = shape.getStage()!.container();
+
+    let start: Pos | undefined;
+    const enter = (position: Pos) => {
+      if (!start) {
+        start = position
+        mode.push(Mode.update);
+        transformIngShapes.value.push(shape)
+      }
+    }
+    const leave = () => {
+      if (start) {
+        offset.value = void 0
+        mode.pop();
+        start = void 0;
+        const ndx = transformIngShapes.value.indexOf(shape)
+        ~ndx && transformIngShapes.value.splice(ndx, 1)
+      }
+    }
+
+    shape.draggable(true);
+    shape.dragBoundFunc((_, ev) => {
+      if (!start) {
+        enter(ev)
+      } else {
+        const end = conversion(getOffset(ev, dom));
+        offset.value = {
+          x: end.x - start.x,
+          y: end.y - start.y
+        }
+      }
+      return shape.absolutePosition()
+    });
+
+    shape.on("pointerdown.mouse-drag", (ev) => {
+      enter(conversion(getOffset(ev.evt)))
+    });
+
+    return mergeFuns([
+      () => {
+        shape.draggable(false);
+        shape.off("pointerdown.mouse-status");
+        start && leave()
+      },
+      listener(document.documentElement, "pointerup", () => {
+        start && leave()
+      }),
+    ]);
+  };
+
+  watch(
+    () => !!shape.value?.getStage(),
+    (loaded, _, onCleanup) => {
+      loaded && onCleanup(init(shape.value!.getNode()));
+    }
+  );
+  return offset;
+};
+
+type Rep<T> = { tempShape: T, update: () => void, destory: () => void }
+const emptyFn = () => {}
+export const useShapeTransformer = <T extends EntityShape>(
+  shape: Ref<DC<T> | undefined>, 
+  transformerConfig: TransformerConfig = {},
+  replaceShape?: (transformer: TransformerExtends, shape: T) => Rep<T>
+) => {
+  const offset = useShapeDrag(shape)
+  const transform = ref<Transform>()
+  const status = useMouseShapeStatus(shape);
+  const mode = useMode();
+  const transformer = useTransformer();
+  const transformIngShapes = useTransformIngShapes()
+
+  const init = ($shape: T) => mergeFuns(
+    watch(
+      () => status.value.hover,
+      (active, _, onCleanup) => {
+        const parent = $shape.parent;
+        if (!(active && parent)) return;
+        const oldConfig: TransformerConfig = {}
+
+        for (const key in transformerConfig) {
+          oldConfig[key] = (transformer as any)[key]();
+          (transformer as any)[key](transformerConfig[key]);
+        }
+
+        let rep: Rep<T>
+        if (replaceShape) {
+          rep = replaceShape(transformer, $shape)
+        } else {
+          rep = {
+            tempShape: $shape,
+            destory: emptyFn,
+            update: emptyFn
+          }
+          transformer.nodes([$shape])
+          transformer.queueShapes.value = [$shape]
+        }
+        parent.add(transformer);
+
+        const updateTransform = () => {
+          transform.value = rep.tempShape.getTransform().copy()
+        }
+
+        const downHandler = () => {
+          isEnter && mode.pop()
+          isEnter = true
+          rep.update()
+          mode.push(Mode.update);
+          transformIngShapes.value.push($shape)
+        }
+
+        let isEnter = false
+        transformer.on("pointerdown.shapemer", downHandler);
+        transformer.on("transform.shapemer", updateTransform);
+        const stop = listener($shape.getStage()!.container(), "pointerup", () => {
+          if (isEnter) {
+            mode.pop()
+            transform.value = void 0
+            isEnter = false
+            const ndx = transformIngShapes.value.indexOf($shape)
+            ~ndx && transformIngShapes.value.splice(ndx, 1)
+          }
+        })
+
+        // 拖拽时要更新矩阵
+        let prevMoveTf: Transform | null = null
+        const stopDragWatch = watch(offset, (translate, oldTranslate) => {
+          if (translate) {
+            if (!oldTranslate) {
+              rep.update()
+            }
+            const moveTf = new Transform().translate(translate.x, translate.y)
+            const finalTf = moveTf.copy()
+            prevMoveTf && finalTf.multiply(prevMoveTf.invert())
+            finalTf.multiply(rep.tempShape.getTransform())
+            prevMoveTf = moveTf
+
+            setShapeTransform(rep.tempShape, finalTf);
+            updateTransform()
+          } else {
+            prevMoveTf = null
+            transform.value = void 0
+          }
+        }, { immediate: true })
+
+        onCleanup(() => {
+          for (const key in oldConfig) {
+            ;(transformer as any)[key](oldConfig[key]);
+          }
+          stop()
+          stopDragWatch()
+          parent.add($shape);
+          // TODO: 有可能transformer已经转移
+          if (transformer.queueShapes.value.includes($shape)) {
+            transformer.nodes([]);
+            transformer.queueShapes.value = []
+          }
+          transform.value = void 0;
+          rep.destory()
+          if (isEnter) {
+            mode.pop()
+            const ndx = transformIngShapes.value.indexOf($shape)
+            ~ndx && transformIngShapes.value.splice(ndx, 1)
+          }
+          transformer.off("pointerdown.shapemer", downHandler);
+          transformer.off("transform.shapemer", updateTransform);
+        });
+      },
+      { immediate: true }
+    )
+  )
+  watch(shape, (shape, _, onCleanup) => {
+    if (shape) {
+      onCleanup(init(shape.getStage()));
+    }
+  });
+  return transform;
+};
+
+
+export const genTransformerRepShape = <T extends Shape>(
+  shape: T, 
+  transformer: TransformerExtends,
+  getAttitudeMat: () => number[],
+  resumeData: (repShape: T, inverAttitude: Transform) => void
+) => {
+  const repShape = shape.clone({ 
+    fill: 'rgb(0, 255, 0)', 
+    visible: false, 
+    strokeWidth: 0
+  })
+
+  const update = () => {
+    const attitude = new Transform(getAttitudeMat());
+    const inverAttitude = attitude.copy().invert();
+    setShapeTransform(repShape, attitude)
+    resumeData(repShape, inverAttitude)
+  }
+
+
+  if (import.meta.env.DEV) {
+    repShape.visible(true)
+    shape.opacity(0.9)
+    repShape.opacity(0.1)
+  }
+
+  update()
+  shape.parent!.add(repShape)
+  repShape.zIndex(shape.getZIndex() - 1)
+  transformer.nodes([repShape]);
+  transformer.queueShapes.value = [shape]
+
+  return {
+    tempShape: repShape,
+    update,
+    destory: () => {
+      repShape.remove()
+      shape.opacity(1)
+    }
+  };
+}
+
+
+export type LineTransformerData = {
+  points: Pos[],
+  attitude: number[]
+}
+export const useLineTransformer = (
+  shape: Ref<DC<Line> | undefined>,
+  init: () => LineTransformerData,
+  callback: (data: LineTransformerData) => void,
+) => {
+  const data = useAutomaticData(() => init())
+  let tempShape: Line
+  let inverAttitude: Transform
+  let stableVs = data.value.points
+  let tempVs = data.value.points
+
+  const transform = useShapeTransformer(shape, undefined, (transformer, $shape) => {
+    const result = genTransformerRepShape(
+      $shape, 
+      transformer, 
+      () => data.value.attitude, (repShape, inverMat) => {
+        const initVs = stableVs.map(v => inverMat.point(v))
+        repShape.points(flatPositions(initVs) )
+        repShape.closed(true)
+        inverAttitude = inverMat
+      }
+    )
+    tempShape = result.tempShape
+    return result
+  })
+
+  watch(() => shape.value?.getNode(), $shape => {
+    if ($shape) {
+      $shape.points(flatPositions(tempVs))
+    }
+  })
+
+  watch(transform, (current, prev) => {
+    if (current) {
+      // 顶点更新
+      const $shape = shape.value!.getNode()
+      const transfrom = current.copy().multiply(inverAttitude)
+      tempVs = stableVs.map(v => transfrom.point(v))
+      $shape.points(flatPositions(tempVs))
+    } else if (prev && tempShape) {
+      data.value.attitude = tempShape.getTransform().m;
+      data.value.points = stableVs = tempVs
+      callback(data.value)
+    }
+  })
+  return [transform, data] as const
+}

+ 1 - 1
src/draw/core/hook/use-viewer.ts

@@ -32,7 +32,7 @@ export const useViewer = installGlobalVar(
 				transform: transform,
 				viewer,
 			},
-			onDestory: globalWatch(
+			onDestroy: globalWatch(
 				() => stage.value && interactive.value === Mode.viewer,
 				(can, _, onCleanup) => {
 					if (can) {

+ 1 - 1
src/draw/core/renderer/draw-group.vue

@@ -6,7 +6,7 @@
 import { ShapeType, DrawData, components } from "../components";
 import { ref, reactive, watch } from "vue";
 import { Interactive, InteractiveAction, useInteractiveAreas, useInteractiveDots } from "../hook/use-interactive.ts";
-import { mergeFuns } from "@/draw/utils/shared.ts";
+import { mergeFuns } from "@/utils/shared.ts";
 
 const props = defineProps<{ type: ShapeType }>()
 const emit = defineEmits<{ (e: 'addItems', v: DrawData[ShapeType]): void }>()

src/draw/core/renderer/group.vue → src/core/renderer/group.vue


+ 1 - 1
src/draw/core/renderer/renderer.vue

@@ -31,7 +31,7 @@ import {
 import { useListener, useResize } from "../hook/use-event.ts";
 import { useExpose } from "../hook/use-expose.ts";
 import { useInteractiveShapeAPI } from '../hook/use-interactive.ts'
-import { data } from '../store/index'
+import { data } from '../store'
 
 const stage = useStage();
 const size = useResize()

+ 45 - 19
src/draw/core/store/index.ts

@@ -35,6 +35,7 @@ export const data = reactive({
 	],
 	"triangle": [
 		{
+			"attitude": [1, 0, 0, 1, 0, 0],
 			"points": [
 				{
 					"x": 115.58984375,
@@ -53,6 +54,7 @@ export const data = reactive({
 	],
 	"polygon": [
 		{
+			"attitude": [1, 0, 0, 1, 0, 0],
 			"points": [
 				{
 					"x": 416.5390625,
@@ -75,6 +77,7 @@ export const data = reactive({
 	],
 	"line": [
 		{
+			attitude: [1, 0, 0, 1, 0, 0],
 			"points": [
 				{
 					"x": 327.5546875,
@@ -97,6 +100,7 @@ export const data = reactive({
 	],
 	"arrow": [
 		{
+			attitude: [1, 0, 0, 1, 0, 0],
 			"points": [
 				{
 					"x": 962.1484375,
@@ -109,6 +113,7 @@ export const data = reactive({
 			]
 		},
 		{
+			attitude: [1, 0, 0, 1, 0, 0],
 			"points": [
 				{
 					"x": 756.35546875,
@@ -131,29 +136,52 @@ export const data = reactive({
 	"icon": [
 		{
 			"url": "/src/assets/icons/vue.svg",
-			"x": 908.390625,
-			"y": 454.0390625
+			"mat": [
+				1,
+				0,
+				0,
+				1,
+				791.984375,
+				464.45703125
+			]
 		},
 		{
 			"url": "/src/assets/icons/vue.svg",
-			"x": 1049.234375,
-			"y": 442.33984375
+			"mat": [
+				1,
+				0,
+				0,
+				1,
+				1072.265625,
+				467.41796875
+			]
 		},
 		{
-			"url": "/src/assets/icons/BedsideCupboard.svg",
-			"stroke": "red",
-			"strokeWidth": 1,
-			"strokeScaleEnabled": false,
-			"x": 1209.515625,
-			"y": 446.01171875
+			"url": "/src/assets/icons/vue.svg",
+			"mat": [
+				1,
+				0,
+				0,
+				1,
+				1281.1015625,
+				448.33984375
+			]
 		},
 		{
 			"url": "/src/assets/icons/BedsideCupboard.svg",
+			"width": 300,
+			"height": 300,
 			"stroke": "red",
 			"strokeWidth": 1,
 			"strokeScaleEnabled": false,
-			"x": 1417.73046875,
-			"y": 449.8046875
+			"mat": [
+				1,
+				0,
+				0,
+				1,
+				954.53515625,
+				519.8671875
+			]
 		}
 	],
 	"text": [
@@ -163,10 +191,9 @@ export const data = reactive({
 			"strokeWidth": 3,
 			"fontFamily": "Calibri",
 			"fontSize": 30,
-			"maxWidth": 300,
-			"content": "文字",
-			"x": 484.13671875,
-			"y": 663.13671875
+			"width": 100,
+			"content": "Hello from the Konva framework. Try to resize me.",
+			"mat": [1, 0, 0, 1, 484.13671875, 663.13671875],
 		},
 		{
 			"fill": "#000",
@@ -174,10 +201,9 @@ export const data = reactive({
 			"strokeWidth": 3,
 			"fontFamily": "Calibri",
 			"fontSize": 30,
-			"maxWidth": 300,
+			"width": 300,
 			"content": "文字",
-			"x": 873.94921875,
-			"y": 659.453125
+			"mat": [1, 0, 0, 1, 873.94921875, 659.453125],
 		}
 	],
 })

+ 2 - 2
src/draw/core/viewer.ts

@@ -1,6 +1,6 @@
 import { Transform } from "konva/lib/Util";
-import { Pos } from "../utils/math";
-import { alignPortMat } from "@/draw/utils/align-port.ts";
+import { Pos } from "../utils/math.ts";
+import { alignPortMat } from "@/utils/align-port.ts";
 import mitt from 'mitt'
 
 export type ViewerProps = {

+ 0 - 11
src/draw/core/components/arrow/arrow.vue

@@ -1,11 +0,0 @@
-<template>
-	<v-arrow :config="dataToConfig(data)" v-if="!addMode">
-	</v-arrow>
-	<v-arrow :config="{...dataToConfig(data), opacity: 0.3}" v-else>
-	</v-arrow>
-</template>
-
-<script lang="ts" setup>
-import { ArrowData, dataToConfig } from "./";
-defineProps<{ data: ArrowData, addMode?: boolean }>()
-</script>

+ 0 - 11
src/draw/core/components/circle/circle.vue

@@ -1,11 +0,0 @@
-<template>
-	<v-circle :config="dataToConfig(data)" v-if="!addMode">
-	</v-circle>
-	<v-circle :config="{...dataToConfig(data), opacity: 0.3}" v-else>
-	</v-circle>
-</template>
-
-<script lang="ts" setup>
-import { CircleData, dataToConfig } from "./index";
-defineProps<{ data: CircleData, addMode?: boolean }>()
-</script>

+ 0 - 69
src/draw/core/components/icon/icon.vue

@@ -1,69 +0,0 @@
-<template>
-	<v-group :config="groupConfig" v-if="groupConfig">
-		<v-rect :config="rectConfig"/>
-		<v-path v-for="config in pathConfigs" :config="config"/>
-	</v-group>
-</template>
-
-<script lang="ts" setup>
-import { IconData, dataToConfig } from "./index";
-import { computed, ref, watch } from "vue";
-import { getSvgContent, parseSvgContent, SVGParseResult } from "@/draw/utils/resource";
-
-const props = defineProps<{ data: IconData, addMode?: boolean }>()
-const svg = ref<SVGParseResult | null>(null)
-const data = computed(() => dataToConfig(props.data))
-
-watch(() => data.value.url, async url => {
-	svg.value = null;
-	const svgContent = await getSvgContent(url)
-	svg.value = parseSvgContent(svgContent)
-}, {immediate: true})
-
-const pathConfigs = computed(() => {
-	if (!svg.value) return [];
-	return svg.value.paths.map(path => ({
-		...path,
-		offset: {x: svg.value!.x, y: svg.value!.y},
-		strokeScaleEnabled: data.value.strokeScaleEnabled,
-		fill: data.value.fill || path.fill,
-		stroke: data.value.stroke || path.stroke,
-		strokeWidth: data.value.strokeWidth || path.strokeWidth,
-	}))
-})
-
-
-const groupConfig = computed(() => {
-	if (!svg.value) return null;
-	let w = data.value.width
-	let h = data.value.height
-	w = w || svg.value.width || 0
-	h = h || svg.value.height || 0
-
-	const scale = {
-		x: w / svg.value.width,
-		y: h / svg.value.height,
-	}
-	return {
-		x: data.value.x,
-		y: data.value.y,
-		opacity: props.addMode ? 0.3 : 1,
-		offset: {
-			x: svg.value.width / 2,
-			y: svg.value.height / 2,
-		},
-		scale,
-	}
-})
-
-const rectConfig = computed(() => {
-	if (!svg.value) return null;
-	return {
-		fill: data.value.coverFill,
-		stroke: data.value.coverStroke,
-		strokeWidth: data.value.coverStrokeWidth,
-		width: svg.value.width ,
-		height: svg.value.height,
-	}
-})
-</script>

+ 0 - 57
src/draw/core/components/icon/index.ts

@@ -1,57 +0,0 @@
-import { InteractiveMessage } from "../../hook/use-interactive.ts";
-import { ImageConfig } from "konva/lib/shapes/Image";
-
-export { default as Component } from "./icon.vue";
-
-export const shapeName = "图例";
-export const defaultStyle = {
-  strokeScaleEnabled: true,
-  width: 80,
-  height: 80
-};
-
-export const addMode = 'dot'
-
-export const style = {
-  default: defaultStyle,
-  focus: defaultStyle,
-  hover: defaultStyle,
-};
-
-export type IconData = Partial<typeof defaultStyle> & {
-  fill?: string;
-  stroke?: string;
-  strokeWidth?: number;
-  coverFill?: string;
-  coverStroke?: string,
-  coverStrokeWidth?: number,
-  width: number;
-  height: number;
-  x: number;
-  y: number;
-  url: string
-};
-
-
-export const dataToConfig = (data: IconData): Omit<ImageConfig, 'image'> => ({
-  ...defaultStyle,
-  ...data
-})
-
-export const interactiveToData = (
-  info: InteractiveMessage,
-  preset: Partial<IconData> = {}
-): IconData | undefined => {
-  if (info.dot) {
-    return interactiveFixData({ ...preset, } as unknown as IconData, info);
-  }
-};
-
-export const interactiveFixData = (
-  data: IconData,
-  info: InteractiveMessage
-) => {
-  data.x = info.dot!.x
-  data.y = info.dot!.y
-  return data;
-};

+ 0 - 48
src/draw/core/components/image/image.vue

@@ -1,48 +0,0 @@
-<template>
-	<v-image :config="config" v-if="image"/>
-</template>
-
-<script lang="ts" setup>
-import { ImageData, dataToConfig } from "./index";
-import { computed, ref, watch } from "vue";
-import { getImage } from "@/draw/utils/resource";
-import { useResize } from "@/draw/core/hook/use-event.ts";
-
-const props = defineProps<{ data: ImageData, addMode?: boolean }>()
-const image = ref<HTMLImageElement | null>(null)
-
-console.log(props.data)
-watch(() => props.data.url, async url => {
-	image.value = null;
-	image.value = await getImage(url)
-
-}, {immediate: true})
-
-const size = useResize()
-const config = computed(() => {
-	let w = props.data.width
-	let h = props.data.height
-
-	// 认为是百分比
-	if (image.value && size.value && (w <= 1 || h <= 1)) {
-		w = w <= 1 ? size.value.width * w : w
-		h = h <= 1 ? size.value.height * h : h
-		w = w || (image.value.width / image.value.height) * h
-		h = h || (image.value.height / image.value.width) * w
-	}
-	w = w || image.value?.width || 0
-	h = h || image.value?.height || 0
-	return {
-		...dataToConfig(props.data),
-		image: image.value,
-		opacity: props.addMode ? 0.3 : 1,
-		stroke: 'red',
-		width: w,
-		height: h,
-		offset: {
-			x: w / 2,
-			y: h / 2,
-		},
-	}
-})
-</script>

+ 0 - 11
src/draw/core/components/line/line.vue

@@ -1,11 +0,0 @@
-<template>
-	<v-line :config="dataToConfig(data)" v-if="!addMode">
-	</v-line>
-	<v-line :config="{...dataToConfig(data), opacity: 0.3}" v-else>
-	</v-line>
-</template>
-
-<script lang="ts" setup>
-import { PolygonData, dataToConfig } from ".";
-defineProps<{ data: PolygonData, addMode?: boolean }>()
-</script>

+ 0 - 11
src/draw/core/components/polygon/polygon.vue

@@ -1,11 +0,0 @@
-<template>
-	<v-line :config="dataToConfig(data)" v-if="!addMode">
-	</v-line>
-	<v-line :config="{...dataToConfig(data), opacity: 0.3}" v-else>
-	</v-line>
-</template>
-
-<script lang="ts" setup>
-import { PolygonData, dataToConfig } from ".";
-defineProps<{ data: PolygonData, addMode?: boolean }>()
-</script>

+ 0 - 26
src/draw/core/components/rectangle/rectangle.vue

@@ -1,26 +0,0 @@
-<template>
-	<v-line :config="dataToConfig(data)" ref="shape"/>
-</template>
-
-<script lang="ts" setup>
-import { RectangleData, style, dataToConfig } from "./index";
-import { useMouseStyle } from "../../hook/use-mouse-status.ts";
-import { ref } from "vue";
-import { useLineTransformer } from "../../hook/use-transformer.ts";
-import { DC } from "@/draw/helper/deconstruction.js";
-import { useAniamtion } from "../../hook/use-animation.ts";
-import { Line } from "konva/lib/shapes/Line";
-
-const props = defineProps<{ data: RectangleData, addMode?: boolean }>()
-const emit = defineEmits<{ (e: 'update', value: RectangleData): void }>()
-const shape = ref<DC<Line>>()
-const transform = useLineTransformer(
-	shape, 
-	props.data,
-	(newData) => {
-		emit('update', { ...props.data, ...newData })
-	}
-)
-const { currentStyle } = useMouseStyle({ style, shape }, transform);
-useAniamtion(currentStyle, shape)
-</script>

+ 0 - 8
src/draw/core/components/rectangle/temp-rectangle.vue

@@ -1,8 +0,0 @@
-<template>
-	<v-line :config="{ ...style.default, ...dataToConfig(data), opacity: 0.3 }" />
-</template>
-
-<script lang="ts" setup>
-import { RectangleData, style, dataToConfig } from "./index";
-defineProps<{ data: RectangleData }>()
-</script>

+ 0 - 50
src/draw/core/components/text/text.vue

@@ -1,50 +0,0 @@
-<template>
-	<v-text :config="config" ref="text">
-	</v-text>
-</template>
-
-<script lang="ts" setup>
-import { TextData, dataToConfig } from "./index";
-import { computed, nextTick, ref, watch } from "vue";
-import { DC } from "@/draw/helper/deconstruction";
-import { Text } from 'konva/lib/shapes/Text'
-import { Pos } from "@/draw/utils/math.ts";
-
-const props = defineProps<{ data: TextData, addMode?: boolean }>()
-const text = ref<DC<Text>>()
-
-const width = ref<number>()
-const offset = ref<Pos>()
-const refreshOffset = async () => {
-	const $text = text.value!.getStage()
-	const oldWidth = width.value
-	const oldHeight = $text.height()
-	width.value = void 0;
-	if (!props.data.maxWidth) {
-		return;
-	}
-	await nextTick()
-	width.value = Math.min($text.width(), props.data.maxWidth!)
-	if (oldWidth !== width.value || oldHeight !==  $text.height()) {
-		await nextTick()
-		offset.value = {
-			x: $text.width() / 2,
-			y: $text.height() / 2,
-		}
-	}
-}
-
-const config = computed(() => ({
-	...dataToConfig(props.data),
-	offset: offset.value,
-	width: width.value,
-	opacity: props.addMode ? 0.3 : 1
-}))
-
-watch(
-		() => [text.value, props.data.content, props.data.maxWidth, props.data.fontSize],
-		() => {
-			text.value && refreshOffset()
-		}
-)
-</script>

+ 0 - 52
src/draw/core/components/triangle/triangle.vue

@@ -1,52 +0,0 @@
-<template>
-	<v-line :config="dataToConfig(atData)" ref="shape">
-	</v-line>
-</template>
-
-<script lang="ts" setup>
-import { TriangleData, style, dataToConfig } from "./index";
-import { useMouseStyle } from "../../hook/use-mouse-status.ts";
-import { ref, watch } from "vue";
-import { useAutomaticData } from "@/draw/core/hook/use-automatic-data.ts";
-import { useShapeTransformer } from "../../hook/use-transformer.ts";
-import { DC } from "@/draw/helper/deconstruction.js";
-import { Rect } from "konva/lib/shapes/Rect";
-import { useAniamtion } from "../../hook/use-animation.ts";
-
-const props = defineProps<{ data: TriangleData, }>()
-const emit = defineEmits<{ (e: 'update', value: TriangleData): void }>()
-const atData = useAutomaticData(() => props.data)
-const shape = ref<DC<Rect>>()
-const transformer = useShapeTransformer(shape)
-const { currentStyle } = useMouseStyle({ style, shape }, transformer);
-useAniamtion(currentStyle, shape)
-
-watch(() => transformer.offset, (offset) => {
-	if (offset) {
-		for (let i = 0; i < props.data.points.length; i++) {
-			atData.value.points[i].x = props.data.points[i].x + offset.x
-			atData.value.points[i].y = props.data.points[i].y + offset.y
-		}
-	} else {
-		emit('update', atData.value)
-	}
-})
-
-
-watch(() => transformer.transform, (transform, oldTransform) => {
-	if (!transform && oldTransform) {
-		const $shape = shape.value!.getNode()
-		for (let i = 0; i < atData.value.points.length; i++) {
-			const position = oldTransform.point(atData.value.points[i])
-			atData.value.points[i].x = position.x
-			atData.value.points[i].y = position.y
-		}
-		atData.value.recordRotation = oldTransform.decompose().rotation
-		$shape.rotation(0)
-		$shape.scale({x: 1, y: 1})
-		$shape.position({x: 0, y: 0})
-		emit('update', atData.value)
-	}
-})
-
-</script>

+ 0 - 227
src/draw/core/hook/use-transformer.ts

@@ -1,227 +0,0 @@
-import { useMouseShapeStatus } from "./use-mouse-status.ts";
-import { Ref, ref, watch } from "vue";
-import { DC, EntityShape } from "../../helper/deconstruction";
-import { Shape } from "konva/lib/Shape";
-import { Transformer } from "../Transformer";
-import { installGlobalVar, useMode } from "./use-global-vars.ts";
-import { Mode } from "../../constant/mode.ts";
-import { Transform } from "konva/lib/Util";
-import { Pos } from "@/draw/utils/math.ts";
-import { useConversionPosition } from "./use-coversion-position.ts";
-import { getOffset, listener } from "@/draw/utils/event.ts";
-import { flatPositions, mergeFuns } from "@/draw/utils/shared.ts";
-import { Line } from "konva/lib/shapes/Line";
-import { setShapeTransform } from "@/draw/utils/shape.ts";
-
-export type TransformerExtends = Transformer &  { queueShapes: EntityShape[] }
-export const transformRectPadding = 10;
-export const useTransformer = installGlobalVar(() => {
-  const transformer = new Transformer({ 
-    borderStrokeWidth: 1, 
-    useSingleNodeRotation: true 
-  }) as TransformerExtends;
-  transformer.queueShapes = []
-  return transformer;
-}, Symbol("transformer"));
-
-
-export const useShapeDrag = (
-  shape: Ref<DC<Shape> | undefined>, 
-  transform = ref<Transform>()
-) => {
-  const mode = useMode();
-  const conversion = useConversionPosition(true);
-
-  const init = (shape: Shape) => {
-    const dom = shape.getStage()!.container();
-    const moveTransform = new Transform()
-    let start: Pos | undefined;
-
-    const enter = (position: Pos) => {
-      start = position
-      mode.push(Mode.update);
-    }
-    const leave = () => {
-      transform.value = void 0
-      mode.pop();
-      start = void 0;
-    }
-
-    shape.draggable(true);
-    shape.dragBoundFunc((cur, ev) => {
-      const end = conversion(getOffset(ev, dom));
-      if (!start) {
-        enter(end)
-      } else {
-        moveTransform.reset()
-        moveTransform.translate(end.x - start.x, end.y - start.y)
-        transform.value = shape.getTransform().copy().multiply(moveTransform);
-      }
-      return cur
-    });
-
-    shape.on("pointerdown.mouse-drag", (ev) => {
-      enter(conversion(getOffset(ev.evt)))
-    });
-
-    return mergeFuns([
-      () => {
-        shape.draggable(false);
-        shape.off("pointerdown.mouse-status");
-        start && leave()
-      },
-      listener(document.documentElement, "pointerup", () => {
-        start && leave()
-      }),
-    ]);
-  };
-
-  watch(
-    () => shape.value?.getStage(),
-    (shape, _, onCleanup) => {
-      shape && onCleanup(init(shape));
-    }
-  );
-  return transform;
-};
-
-const emptyFn = () => {}
-export const useShapeTransformer = <T extends Shape>(
-  shape: Ref<DC<T> | undefined>, 
-  replaceShape?: (transformer: TransformerExtends, shape: T) => ({ tempShape: T, destory: () => void })
-) => {
-  const dragShape = ref<DC<T>>()
-  const dragTransform = useShapeDrag(dragShape)
-  const transform = ref<Transform>()
-  const status = useMouseShapeStatus(shape);
-  const mode = useMode();
-  const transformer = useTransformer();
-
-  const init = ($shape: T) => mergeFuns(
-    watch(
-      () => status.value.hover,
-      (active, _, onCleanup) => {
-        const parent = $shape.parent;
-        if (!(active && parent)) return;
-        let repShape: T
-
-        const resetTransformer = () => {
-          if (replaceShape) {
-            const rep = replaceShape(transformer, $shape)
-            repShape = rep.tempShape
-            return rep.destory
-          } else if (!repShape) {
-            repShape = $shape
-            transformer.nodes([repShape])
-            transformer.queueShapes = [repShape]
-          }
-          return emptyFn
-        }
-        let resetDestory = resetTransformer()
-        dragShape.value = shape.value
-        parent.add(transformer);
-
-        const enter = () => {
-          resetDestory()
-          resetDestory = resetTransformer()
-          mode.push(Mode.update);
-        }
-        const leave = () => {
-          mode.pop()
-          transform.value = void 0;
-        }
-        
-        transformer.on("pointerdown.shapemer", enter);
-        transformer.on("transformend.shapemer", leave);
-        transformer.on("transform.shapemer", () => {
-          transform.value = repShape.getTransform().copy()
-        });
-
-        onCleanup(() => {
-          parent.add($shape);
-          resetDestory()
-          transformer.nodes([]);
-          transformer.queueShapes = []
-          transform.value && leave()
-          transformer.off('pointerdown.shapemer transformend.shapemer transform.shapemer')
-        });
-      },
-      { immediate: true }
-    )
-  )
-  watch(shape, (shape, _, onCleanup) => {
-    if (shape) {
-      onCleanup(init(shape.getStage()));
-    }
-  });
-  return transform;
-};
-
-export type LineTransformerData = {
-  points: Pos[],
-  attitude: number[]
-}
-export const useLineTransformer = (
-  shape: Ref<DC<Line> | undefined>,
-  init: LineTransformerData,
-  callback: (data: LineTransformerData) => void
-) => {
-  const data = { ...init }
-  let tempShape: Line
-  let inverAttitude: Transform
-  let attitude: Transform
-  let stableVs = data.points
-  let tempVs = data.points
-
-  const transform = useShapeTransformer(shape, (transformer, $shape) => {
-    attitude = new Transform(data.attitude);
-    inverAttitude = attitude.copy().invert()
-
-    // 将数据转回初始状态
-    const initVs = stableVs.map(v => inverAttitude.point(v))
-    tempShape = $shape.clone({ 
-      fill: 'rgb(0, 255, 0)', 
-      visible: false, 
-      points: flatPositions(initVs) 
-    }) as Line
-    // 恢复姿态
-    setShapeTransform(tempShape, attitude)
-
-    tempShape.visible(true)
-    $shape.opacity(0.3)
-
-    $shape.parent!.add(tempShape)
-    tempShape.zIndex($shape.getZIndex() - 1)
-    transformer.nodes([tempShape]);
-    transformer.queueShapes = [$shape]
-    return {
-      tempShape,
-      destory: () => {
-        tempShape.remove()
-        $shape.opacity(1)
-        console.log('destory?')
-      }
-    };
-  })
-
-  watch(() => shape.value?.getNode(), $shape => {
-    if ($shape) {
-      $shape.points(flatPositions(tempVs))
-    }
-  })
-
-  watch(transform, (current, prev) => {
-    if (current) {
-      // 顶点更新
-      const $shape = shape.value!.getNode()
-      const transfrom = current.copy().multiply(inverAttitude)
-      tempVs = stableVs.map(v => transfrom.point(v))
-      $shape.points(flatPositions(tempVs))
-    } else if (prev && tempShape) {
-      data.attitude = tempShape.getTransform().m;
-      data.points = stableVs = tempVs
-      callback(data)
-    }
-  })
-  return transform
-}

src/App.vue → src/example/fuse/App.vue


src/main.ts → src/example/fuse/main.ts


src/store/index.ts → src/example/fuse/store/index.ts


src/styles/element.scss → src/example/fuse/styles/element.scss


src/styles/global.scss → src/example/fuse/styles/global.scss


+ 12 - 4
src/views/header/funds.ts

@@ -1,4 +1,4 @@
-import { useAction } from "@/views/use-draw.ts";
+import { useDraw } from "@/views/use-draw.ts";
 
 export const revoke = () => {
 
@@ -43,9 +43,12 @@ export const gotoDrawing = () => {
 
 import bgImage from '@/assets/WX20241031-111850.png'
 export const useHeaderFunds = () => {
-	const action = useAction()
+	const draw = useDraw()
 	const setBGImage = () => {
-		action.value.setBackgroundImage(bgImage)
+		draw.value!.addShape('image', { url: bgImage, width: 1, height: 1 }, {x: window.innerWidth / 2, y: window.innerHeight / 2}, true );
+	}
+	const toggleHit = () => {
+		draw.value?.toggleHit()
 	}
 
 
@@ -66,6 +69,10 @@ export const useHeaderFunds = () => {
 		[gotoDrawing, {name: '图纸', icon: ''}],
 	])
 
+	if (import.meta.env.DEV) {
+		describes.set(toggleHit, {name: '显示hit', icon: ''})
+	}
+
 	return {
 		revoke,
 		recover,
@@ -78,6 +85,7 @@ export const useHeaderFunds = () => {
 		saveData,
 		exportData,
 		gotoDrawing,
-		describes
+		toggleHit,
+		describes,
 	}
 }

+ 3 - 0
src/views/header/header.vue

@@ -26,6 +26,9 @@ const groups = [
 	[funds.clear, funds.rotate, funds.full],
 	[funds.aiImport, funds.setBGImage, funds.gotoVR],
 ]
+if (import.meta.env.DEV) {
+	groups.push([funds.toggleHit])
+}
 </script>
 
 <style lang="scss" scoped>

src/views/header/index.ts → src/example/fuse/views/header/index.ts


+ 61 - 0
src/example/fuse/views/home.vue

@@ -0,0 +1,61 @@
+<template>
+  <div class="layout">
+    <Header class="header" />
+    <div class="container">
+      <Slide class="slide" />
+      <div class="content" ref="drawEle">
+        <Renderer v-if="drawEle" ref="draw" />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import Header from "@/views/header/header.vue";
+import { ref } from "vue";
+import { Slide } from "./slide";
+import { DrawExpose, Renderer } from "@/draw";
+import { installDraw } from "@/views/use-draw.ts";
+
+const drawEle = ref<HTMLDivElement | null>(null);
+const draw = ref<DrawExpose>();
+installDraw(draw);
+</script>
+
+<style lang="scss" scoped>
+@use '@/styles/global';
+
+.layout {
+  display: flex;
+  flex-direction: column;
+  align-items: stretch;
+  height: 100vh;
+  background: #f0f2f5;
+
+  .header {
+    height: global.$headerSize;
+  }
+
+  .container {
+    position: relative;
+    width: 100%;
+    height: calc(100% - #{global.$headerSize});
+  }
+
+  .slide {
+    position: absolute;
+    left: 0;
+    width: global.$slideSize;
+    overflow-y: auto;
+    height: 100%;
+    top: 0;
+    background: #fff;
+    z-index: 1;
+  }
+
+  .content {
+    position: absolute;
+    inset: 0;
+  }
+}
+</style>

src/views/slide/index.ts → src/example/fuse/views/slide/index.ts


+ 2 - 2
src/views/slide/menu.ts

@@ -84,12 +84,12 @@ export const menus: MenuItem[] = [
 		children: [
 			{
 				icon: '',
-				...genItem('icon', { url: svg1 }),
+				...genItem('icon', { url: svg1, width: 500, height: 500 }),
 				name: 'vue'
 			},
 			{
 				icon: '',
-				...genItem('icon', { url: svg2, stroke: 'red', strokeWidth: 1, strokeScaleEnabled: false }),
+				...genItem('icon', { url: svg2, width: 300, height: 300, stroke: 'red', strokeWidth: 1, strokeScaleEnabled: false }),
 				name: '自定义'
 			}
 		]

src/views/slide/slide-item.vue → src/example/fuse/views/slide/slide-item.vue


+ 4 - 4
src/views/slide/slide.vue

@@ -1,7 +1,7 @@
 <template>
 	<div class="slide">
 		<el-menu
-				:default-active="action.presetAdd && getValue(action.presetAdd)"
+				:default-active="draw?.presetAdd && getValue(draw?.presetAdd)"
 				class="slide-menu"
 				@select="selectHandler"
 				collapse
@@ -15,13 +15,13 @@
 <script lang="ts" setup>
 import { getItem, getValue, menus } from './menu.ts'
 import SlideItem from "@/views/slide/slide-item.vue";
-import { useAction } from "@/views/use-draw.ts";
+import { useDraw } from "@/views/use-draw.ts";
 
-const action  = useAction()
+const draw  = useDraw()
 const selectHandler = (value: string) => {
 	const item = getItem(value)
 	if (!item || !item.payload) throw '无效菜单'
-	action.value.enterMouseAddShape(item.payload.type, item.payload.preset)
+	draw.value!.enterMouseAddShape(item.payload.type, item.payload.preset)
 }
 </script>/
 

+ 9 - 0
src/example/fuse/views/use-draw.ts

@@ -0,0 +1,9 @@
+import { inject, provide, Ref } from 'vue'
+import { DrawExpose } from "@/draw";
+
+const actionKey = Symbol('drawAction');
+export const installDraw = (drawRef: Ref<DrawExpose | undefined>) => {
+	provide(actionKey, drawRef)
+}
+
+export const useDraw = () => inject<Ref<DrawExpose | undefined>>(actionKey)!

src/draw/helper/deconstruction.d.ts → src/helper/deconstruction.d.ts


+ 1 - 1
src/draw/index.ts

@@ -10,5 +10,5 @@ for (const key in components) {
 	shapeNames[key as ShapeType] = components[key as ShapeType].shapeName
 }
 
-export type { DrawExpose } from './core/hook/use-expose'
+export type { DrawExpose } from './core/hook/use-expose.ts'
 export type { PresetAdd } from './core/hook/use-global-vars.ts'

+ 1 - 1
src/draw/utils/align-port.ts

@@ -1,5 +1,5 @@
 import { Transform } from "konva/lib/Util";
-import { vector, Pos } from "./math";
+import { vector, Pos } from "./math.ts";
 
 /**
  * 创建对齐端口矩阵

+ 5 - 0
src/draw/utils/colors.ts

@@ -1,6 +1,11 @@
 import { Color, HSL } from "three";
 
 const offset = {
+  pub: {
+    h: 0,
+    s: 0,
+    l: 0,
+  },
   hover: {
     h: -0.012,
     s: 0,

+ 3 - 2
src/draw/utils/event.ts

@@ -1,5 +1,5 @@
-import { lineLen, Pos } from "@/draw/utils/math.ts";
-import { mergeFuns } from "@/draw/utils/shared.ts";
+import { lineLen, Pos } from "@/utils/math.ts";
+import { mergeFuns } from "@/utils/shared.ts";
 
 export const listener = <T extends HTMLElement | Window, K extends keyof HTMLElementEventMap>(
 	target: T,
@@ -49,6 +49,7 @@ export const dragListener = (dom: HTMLElement, props: DragProps | DragProps['mov
 
 	let moveHandler: any, endHandler: any
 	const downHandler = (ev: PointerEvent) => {
+		console.log(ev.target)
 		const start = getOffset(ev, dom)
 		let prev = start
 		down && down(start, ev)

+ 1 - 1
src/draw/utils/math.ts

@@ -1,6 +1,6 @@
 import { Vector2, ShapeUtils, Box2 } from "three";
 import { Transform } from "konva/lib/Util";
-import { round } from "./shared";
+import { round } from "./shared.ts";
 
 export type Pos = { x: number; y: number };
 

src/draw/utils/resource.ts → src/utils/resource.ts


+ 7 - 2
src/draw/utils/shape.ts

@@ -1,5 +1,5 @@
 import { Transform } from "konva/lib/Util";
-import { EntityShape } from "../helper/deconstruction";
+import { DC, EntityShape } from "../helper/deconstruction";
 
 
 export const shapeTreeEq = (
@@ -56,4 +56,9 @@ export const setShapeTransform = (shape: EntityShape, transform: Transform) => {
 	for (const key in config) {
 		(shape as any)[key]((config as any)[key])
 	}
-}
+}
+
+export const packShape = <T extends EntityShape>(shape: T): DC<T> => ({
+	getNode: () => shape,
+	getStage: () => shape
+})

+ 1 - 1
src/draw/utils/shared.ts

@@ -1,4 +1,4 @@
-import { Pos } from "@/draw/utils/math.ts";
+import { Pos } from "@/utils/math.ts";
 import { v4 as uuid } from 'uuid'
 
 /**

+ 0 - 61
src/views/home.vue

@@ -1,61 +0,0 @@
-<template>
-	<div class="layout">
-		<Header class="header" />
-		<div class="container">
-			<Slide class="slide" />
-			<div class="content" ref="drawEle">
-				<Renderer v-if="drawEle"  ref="draw" />
-			</div>
-		</div>
-	</div>
-</template>
-
-<script lang="ts" setup>
-import Header from "@/views/header/header.vue";
-import { ref } from 'vue'
-import { Slide } from "./slide";
-import { DrawExpose, Renderer } from "@/draw";
-import { installAction } from "@/views/use-draw.ts";
-
-const drawEle = ref<HTMLDivElement | null>(null);
-const draw = ref<DrawExpose>();
-installAction(draw)
-</script>
-
-<style lang="scss" scoped>
-@use '@/styles/global';
-
-.layout {
-	display: flex;
-	flex-direction: column;
-	align-items: stretch;
-	height: 100vh;
-	background: #f0f2f5;
-
-	.header {
-		height: global.$headerSize;
-	}
-
-	.container {
-		position: relative;
-		width: 100%;
-		height: calc(100% - #{global.$headerSize});
-	}
-
-	.slide {
-		position: absolute;
-		left: 0;
-		width: global.$slideSize;
-		overflow-y: auto;
-		height: 100%;
-		top: 0;
-		background: #fff;
-		z-index: 1;
-	}
-
-	.content {
-		position: absolute;
-		inset: 0;
-	}
-}
-</style>

+ 0 - 26
src/views/use-draw.ts

@@ -1,26 +0,0 @@
-import { inject, provide, Ref, ref, watchEffect } from 'vue'
-import { DrawExpose } from "@/draw";
-
-const attachAction = (draw: DrawExpose) => ({
-	setBackgroundImage(url: string) {
-		return draw.addShape('image', { url, width: 1, height: 1 }, {x: window.innerWidth / 2, y: window.innerHeight / 2}, true );
-	}
-})
-
-const actionKey = Symbol('drawAction');
-export const installAction = (drawRef: Ref<DrawExpose | undefined>) => {
-	const actions = ref({} as DrawExpose & ReturnType<typeof attachAction>)
-
-	watchEffect(() => {
-		if (drawRef.value) {
-			actions.value = drawRef.value as any
-			Object.assign(actions.value, attachAction(drawRef.value))
-		}
-	})
-	provide(actionKey, actions)
-	return actions
-}
-
-export const useAction = () => {
-	return inject<ReturnType<typeof installAction>>(actionKey)!
-}