Jelajahi Sumber

fix: 添加固定模式画板

bill 4 bulan lalu
induk
melakukan
bd2f3c0508

TEMPAT SAMPAH
public/icons/m_zoom_h.png


TEMPAT SAMPAH
public/icons/m_zoom_r.png


TEMPAT SAMPAH
public/icons/m_zoom_v.png


+ 2 - 3
src/core/components/icon/temp-icon.vue

@@ -14,9 +14,8 @@ import { getSvgContent, parseSvgContent, SVGParseResult } from "@/utils/resource
 import { Group } from "konva/lib/Group";
 import { DC } from "@/deconstruction.js";
 import { Transform } from "konva/lib/Util";
-import { useViewerInvertTransform } from "@/core/hook/use-viewer.ts";
+import { useViewerInvertTransform, useViewSize } from "@/core/hook/use-viewer.ts";
 import { getFixPosition } from "@/utils/bound.ts";
-import { useResize } from "@/core/hook/use-event.ts";
 
 const props = defineProps<{ data: IconData; addMode?: boolean }>();
 const svg = ref<SVGParseResult | null>(null);
@@ -69,7 +68,7 @@ const initDecMat = computed(() => {
 });
 
 const viewInvTransform = useViewerInvertTransform();
-const size = useResize();
+const size = useViewSize();
 const groupConfig = computed(() => {
   let mat = new Transform(data.value.mat);
 

+ 2 - 2
src/core/components/image/temp-image.vue

@@ -16,10 +16,10 @@
 import { defaultStyle, ImageData } from "./index.ts";
 import { computed, ref, watch } from "vue";
 import { getImage } from "@/utils/resource.ts";
-import { useResize } from "@/core/hook/use-event.ts";
 import { Transform } from "konva/lib/Util";
 import { Group } from "konva/lib/Group";
 import { DC } from "@/deconstruction.js";
+import { useViewSize } from "@/core/hook/use-viewer.ts";
 
 const props = defineProps<{ data: ImageData; addMode?: boolean }>();
 const data = computed(() => ({ ...defaultStyle, ...props.data }));
@@ -42,7 +42,7 @@ watch(
   { immediate: true }
 );
 
-const size = useResize();
+const size = useViewSize();
 const config = computed(() => {
   let w = data.value.width;
   let h = data.value.height;

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

@@ -5,6 +5,8 @@
     :id="data.id"
     @update:position="updatePosition"
     @update="emit('updateShape', { ...data })"
+    @deletePoint="deletePoint"
+    @addPoint="addPoint"
     canEdit
   />
   <PropertyUpdate
@@ -25,6 +27,7 @@ import { EditPen } from "@element-plus/icons-vue";
 import { useInteractiveDrawShapeAPI } from "@/core/hook/use-draw.ts";
 import { useStore } from "@/core/store/index.ts";
 import { Pos } from "@/utils/math.ts";
+import { de } from "element-plus/es/locale/index.mjs";
 
 const props = defineProps<{ data: LineData }>();
 const emit = defineEmits<{
@@ -60,6 +63,17 @@ const updatePosition = ({ ndx, val }: { ndx: number; val: Pos }) => {
   shape.value?.getNode().fire("bound-change");
 };
 
+const deletePoint = (ndx: number) => {
+  data.value.points.splice(ndx, 1);
+  shape.value?.getNode().fire("bound-change");
+};
+
+const addPoint = ({ ndx, val }: { ndx: number; val: Pos }) => {
+  console.log(ndx, val);
+  data.value.points.splice(ndx + 1, 0, val);
+  shape.value?.getNode().fire("bound-change");
+};
+
 const draw = useInteractiveDrawShapeAPI();
 const store = useStore();
 operateMenus.push({

+ 23 - 15
src/core/components/line/temp-line.vue

@@ -10,21 +10,27 @@
         hitFunc,
       }"
     />
-    <EditPolygon
-      :data="data"
-      :shape="shape"
-      :addMode="addMode"
-      :canEdit="canEdit"
-      @update:position="(data) => emit('update:position', data)"
-      @update="emit('update')"
-      v-if="shape"
-    />
-    <SizeLine
-      v-if="config.showComponentSize"
-      :points="data.points"
-      :strokeWidth="data.strokeWidth"
-      :stroke="data.stroke"
-    />
+    <v-group>
+      <SizeLine
+        v-if="config.showComponentSize"
+        :points="data.points"
+        :strokeWidth="data.strokeWidth"
+        :stroke="data.stroke"
+      />
+    </v-group>
+    <v-group>
+      <EditPolygon
+        :data="data"
+        :shape="shape"
+        :addMode="addMode"
+        :canEdit="canEdit"
+        @update:position="(data) => emit('update:position', data)"
+        @update="emit('update')"
+        @deletePoint="(ndx) => emit('deletePoint', ndx)"
+        @addPoint="(data) => emit('addPoint', data)"
+        v-if="shape"
+      />
+    </v-group>
   </v-group>
 </template>
 
@@ -46,7 +52,9 @@ const props = defineProps<{
 }>();
 const emit = defineEmits<{
   (e: "update:position", data: { ndx: number; val: Pos }): void;
+  (e: "addPoint", data: { ndx: number; val: Pos }): void;
   (e: "update"): void;
+  (e: "deletePoint", ndx: number): void;
 }>();
 
 const data = computed(() => ({ ...defaultStyle, ...props.data }));

+ 34 - 1
src/core/components/share/edit-line.vue

@@ -8,6 +8,11 @@
       hitStrokeWidth: data.strokeWidth + 20,
     }"
   />
+
+  <v-circle
+    :config="{ ...pointStyle, ...center, opacity: isHover || isPointHover ? 1 : 0 }"
+    ref="point"
+  />
 </template>
 
 <script lang="ts" setup>
@@ -17,7 +22,7 @@ import { computed, ref, watch } from "vue";
 import { DC } from "@/deconstruction";
 import { Line } from "konva/lib/shapes/Line";
 import { useShapeDrag } from "@/core/hook/use-transformer";
-import { useShapeIsHover } from "@/core/hook/use-mouse-status";
+import { useShapeIsHover, useShapeClick } from "@/core/hook/use-mouse-status";
 import { useCursor } from "@/core/hook/use-global-vars";
 import { lineCenter, Pos } from "@/utils/math";
 import { useCustomSnapInfos, useGlobalSnapInfos, useSnap } from "@/core/hook/use-snap";
@@ -39,6 +44,7 @@ const emit = defineEmits<{
   (e: "update:line", data: Pos[]): void;
   (e: "dragend"): void;
   (e: "dragstart"): void;
+  (e: "addPoint", pos: Pos): void;
 }>();
 
 const line = ref<DC<Line>>();
@@ -108,4 +114,31 @@ watch(offset, (offset, oldOffsert) => {
     emit("dragend");
   }
 });
+
+const point = ref<DC<Circle>>();
+const [isPointHover] = useShapeIsHover(point);
+let addCursorPop: () => void;
+watch(isPointHover, (hover, _, onCleanup) => {
+  if (hover) {
+    let pop: (() => void) | null = cursor.push("/icons/m_add.png");
+    addCursorPop = () => {
+      pop && pop();
+      pop = null;
+    };
+    onCleanup(addCursorPop);
+  }
+});
+useShapeClick(point, () => {
+  emit("addPoint", { ...center.value });
+  addCursorPop();
+});
+const center = computed(() => lineCenter(points.value));
+const pointStyle = computed(() => {
+  const color = getMouseColors(props.data.stroke || themeColor);
+  const size = props.data.strokeWidth + 6 || 5;
+  return {
+    radius: size / 2,
+    fill: color.pub,
+  };
+});
 </script>

+ 17 - 5
src/core/components/share/edit-point.vue

@@ -16,7 +16,7 @@ import { getMouseColors } from "@/utils/colors";
 import { useCustomSnapInfos, useGlobalSnapInfos, useSnap } from "@/core/hook/use-snap";
 import { generateSnapInfos } from "../util";
 import { ComponentSnapInfo } from "..";
-import { useShapeIsHover } from "@/core/hook/use-mouse-status";
+import { useShapeClick, useShapeIsHover } from "@/core/hook/use-mouse-status";
 import { useCursor } from "@/core/hook/use-global-vars";
 import { rangMod } from "@/utils/shared";
 
@@ -34,6 +34,7 @@ const emit = defineEmits<{
   (e: "update:position", position: Pos): void;
   (e: "dragend"): void;
   (e: "dragstart"): void;
+  (e: "delete"): void;
 }>();
 
 const position = computed(() => props.points[props.ndx]);
@@ -91,10 +92,19 @@ const offset = useShapeDrag(circle);
 const hResult = useShapeIsHover(circle);
 const isHover = hResult[0];
 const cursor = useCursor();
-watch(isHover, (hover, _, onCleanup) => {
-  if (hover) {
-    onCleanup(cursor.push("/icons/m_move.png"));
-  }
+watch(
+  isHover,
+  (hover, _, onCleanup) => {
+    if (hover) {
+      onCleanup(cursor.push("/icons/m_reduce.png"));
+    }
+  },
+  { immediate: true }
+);
+
+useShapeClick(circle, () => {
+  emit("delete");
+  isHover.value = false;
 });
 
 let init: Pos;
@@ -104,6 +114,7 @@ watch(offset, (offset, oldOffsert) => {
     init = { ...position.value };
     startHandler();
     emit("dragstart");
+    cursor.push("/icons/m_move.png");
   }
   if (offset) {
     const point = {
@@ -117,6 +128,7 @@ watch(offset, (offset, oldOffsert) => {
     emit("update:position", transform ? transform.point(point) : point);
   } else {
     emit("dragend");
+    cursor.pop();
     clearInfos();
   }
 });

+ 14 - 9
src/core/components/share/edit-polygon.vue

@@ -17,14 +17,12 @@
           }
         "
         @dragend="emit('update')"
-      />
-      <EditSlitPoint
-        :data="data"
-        :points="data.points"
-        :id="data.id"
-        :ndx="ndx"
-        v-for="(_, ndx) in closed ? data.points.length : data.points.length - 1"
-        @dragend="emit('update')"
+        @add-point="
+          (pos) => {
+            emit('addPoint', { ndx, val: pos });
+            emit('update');
+          }
+        "
       />
     </template>
   </v-group>
@@ -41,6 +39,12 @@
         :color="data.stroke"
         @update:position="(p) => emit('update:position', { ndx, val: p })"
         @dragend="emit('update')"
+        @delete="
+          () => {
+            emit('deletePoint', ndx);
+            emit('update');
+          }
+        "
       />
     </template>
   </v-group>
@@ -49,7 +53,6 @@
 <script lang="ts" setup>
 import Point from "../share/edit-point.vue";
 import EditLine from "../share/edit-line.vue";
-import EditSlitPoint from "../share/edit-slit-point.vue";
 import { LineData } from "../line";
 import { DC, EntityShape } from "@/deconstruction.js";
 import { Pos } from "@/utils/math.ts";
@@ -67,6 +70,8 @@ const props = defineProps<{
 const emit = defineEmits<{
   (e: "update:position", data: { ndx: number; val: Pos }): void;
   (e: "update"): void;
+  (e: "deletePoint", ndx: number): void;
+  (e: "addPoint", data: { ndx: number; val: Pos }): void;
 }>();
 
 const status = useMouseShapeStatus(computed(() => props.shape));

+ 0 - 113
src/core/components/share/edit-slit-point.vue

@@ -1,113 +0,0 @@
-<template>
-  <v-circle :config="{ ...pointStyle, ...lineCenter(points) }" ref="point" />
-</template>
-
-<script lang="ts" setup>
-import { copy } from "@/utils/shared";
-import { LineData } from "../line";
-import { computed, ref, watch } from "vue";
-import { DC } from "@/deconstruction";
-import { useShapeDrag } from "@/core/hook/use-transformer";
-import { useShapeIsHover } from "@/core/hook/use-mouse-status";
-import { useCursor } from "@/core/hook/use-global-vars";
-import { lineCenter, Pos } from "@/utils/math";
-import { useCustomSnapInfos, useGlobalSnapInfos, useSnap } from "@/core/hook/use-snap";
-import { ComponentSnapInfo } from "..";
-import { generateSnapInfos } from "../util";
-import { getMouseColors } from "@/utils/colors";
-import { themeColor } from "@/constant/help-style";
-import { Circle } from "konva/lib/shapes/Circle";
-
-type LData = Required<Pick<LineData, "strokeWidth" | "stroke">>;
-
-const props = defineProps<{
-  data: LData;
-  points: Pos[];
-  id: string;
-  ndx: number;
-}>();
-
-const pointStyle = computed(() => {
-  const color = getMouseColors(props.data.stroke || themeColor);
-  const size = props.data.strokeWidth + 6 || 5;
-  return {
-    radius: size / 2,
-    fill: color.pub,
-  };
-});
-
-const emit = defineEmits<{
-  (e: "dragend"): void;
-  (e: "dragstart"): void;
-}>();
-
-const point = ref<DC<Circle>>();
-const offset = useShapeDrag(point);
-const [isHover] = useShapeIsHover(point);
-const cursor = useCursor();
-watch(isHover, (hover, _, onCleanup) => {
-  if (hover) {
-    onCleanup(cursor.push("/icons/m_add.png"));
-  }
-});
-const points = computed(() => {
-  return [props.points[props.ndx], props.points[(props.ndx + 1) % props.points.length]];
-});
-
-const infos = useCustomSnapInfos();
-const addedInfos = [] as ComponentSnapInfo[];
-const clearInfos = () => {
-  addedInfos.forEach(infos.remove);
-};
-const dragStartHandler = () => {
-  clearInfos();
-  const ndx = props.ndx;
-  const geos = [
-    props.points.slice(Number(ndx === props.points.length - 1), ndx),
-    props.points.slice(ndx + 2, props.points.length),
-  ];
-  if (ndx > 0 && ndx < props.points.length - 2) {
-    geos.push([props.points[ndx - 1], props.points[ndx + 2]]);
-  }
-  geos.forEach((geo) => {
-    const snapInfos = generateSnapInfos(geo, true, true, true);
-    snapInfos.forEach((item) => {
-      infos.add(item);
-      addedInfos.push(item);
-    });
-  });
-};
-const snapInfos = useGlobalSnapInfos();
-const refSnapInfos = computed(() => {
-  if (!props.id) {
-    return snapInfos.value;
-  } else {
-    return snapInfos.value.filter((p) => !("id" in p) || p.id !== props.id);
-  }
-});
-const snap = useSnap(refSnapInfos);
-
-let init: Pos[];
-watch(offset, (offset, oldOffsert) => {
-  snap.clear();
-  if (!oldOffsert) {
-    emit("dragstart");
-    init = copy(points.value);
-    dragStartHandler();
-  }
-  if (offset) {
-    const current = init.map((p) => ({
-      x: p.x + offset.x,
-      y: p.y + offset.y,
-    }));
-    const refSnapInfos = generateSnapInfos(current, true, true);
-    const transform = snap.move(refSnapInfos);
-    // emit("update:line", transform ? current.map((p) => transform.point(p)) : current);
-  } else {
-    clearInfos();
-    emit("dragend");
-  }
-});
-
-const slitPoint = ref<DC<Circle>>();
-</script>

+ 29 - 14
src/core/helper/back.vue

@@ -2,22 +2,37 @@
   <v-rect :config="config" />
 </template>
 <script lang="ts" setup>
-import { computed, ref } from "vue";
-import { useViewerInvertTransformConfig } from "../hook/use-viewer";
+import { computed } from "vue";
+import { useViewerInvertTransformConfig, useViewSize } from "../hook/use-viewer";
 import { useExpose } from "../hook/use-expose";
 import { useResize } from "../hook/use-event";
 
-const invConfig = useViewerInvertTransformConfig();
-const size = useResize();
 const econfig = useExpose().config;
-const config = computed(
-  () =>
-    econfig.back && {
-      ...invConfig.value,
-      listening: false,
-      ...size.value,
-      ...econfig.back,
-      fill: econfig.back.color,
-    }
-);
+const size = useViewSize();
+const canvasSize = useResize();
+const viewConfig = useViewerInvertTransformConfig();
+
+const config = computed(() => {
+  const backConfig = econfig.back
+    ? {
+        ...econfig.back,
+        fill: econfig.back.color,
+      }
+    : {
+        color: "#ffffff",
+        fill: "#ffffff",
+        opacity: 0,
+      };
+  const config = {
+    listening: false,
+    ...size.value,
+    ...backConfig,
+    x: 0,
+    y: 0,
+  };
+  if (size.value === canvasSize.value) {
+    Object.assign(config, viewConfig.value);
+  }
+  return config;
+});
 </script>

+ 16 - 16
src/core/helper/compass.vue

@@ -15,16 +15,17 @@
 import TempIcon from "../components/icon/temp-icon.vue";
 import { PropertyUpdate, mergeDescribes } from "../html-mount/propertys/index.ts";
 import { computed, ref, watch } from "vue";
-import { themeMouseColors } from "@/constant/help-style.ts";
 import { useAnimationMouseStyle } from "../hook/use-mouse-status.ts";
 import { Group } from "konva/lib/Group";
 import { DC } from "@/deconstruction.js";
 import { useConfig } from "../hook/use-config.ts";
 import { Transform } from "konva/lib/Util";
-import { getFixPosition } from "@/utils/bound.ts";
-import { useResize } from "../hook/use-event.ts";
 import { MathUtils } from "three";
-import { useViewerTransformConfig } from "../hook/use-viewer.ts";
+import {
+  useGetViewBoxPositionPixel,
+  useViewer,
+  useViewerTransformConfig,
+} from "../hook/use-viewer.ts";
 
 const config = useConfig();
 const data = ref({
@@ -37,7 +38,6 @@ const data = ref({
   rotation: 0,
   url: config.compass?.url || "icons/edit_compass.svg",
 });
-console.log(themeMouseColors.theme);
 
 watch(
   () => config.compass!.url,
@@ -82,8 +82,9 @@ describes.rotate = {
   },
 };
 
-const size = useResize();
+const getPixel = useGetViewBoxPositionPixel();
 const viewerConfig = useViewerTransformConfig();
+const { sizeMat } = useViewer();
 const margin = computed(() => {
   let margin = config.margin || 0;
   if (!Array.isArray(margin)) {
@@ -93,17 +94,16 @@ const margin = computed(() => {
 });
 const mat = computed(() => {
   const tf = new Transform();
-  if (!size.value) return tf.m;
-
-  const pos = getFixPosition(
+  const pos = getPixel(
     { right: 20 + margin.value[1], top: 20 + margin.value[0] },
-    data.value,
-    size.value
+    data.value
   );
-  pos.x += data.value.width / 2;
-  pos.y += data.value.height / 2;
-  return tf
-    .translate(pos.x, pos.y)
-    .rotate(MathUtils.degToRad(other.value.rotate + viewerConfig.value.rotation)).m;
+  tf.translate(pos.x, pos.y);
+  if (sizeMat.value) {
+    tf.scale(viewerConfig.value.scaleX, viewerConfig.value.scaleY);
+  }
+  tf.rotate(MathUtils.degToRad(other.value.rotate + viewerConfig.value.rotation));
+  tf.translate(data.value.width / 2, data.value.height / 2);
+  return tf.m;
 });
 </script>

+ 10 - 29
src/core/helper/facade.vue

@@ -2,30 +2,29 @@
   <template v-for="config in borderConfigs">
     <v-rect :config="config" />
   </template>
-  <template v-for="config in lineConfigs">
-    <v-line :config="config" />
-  </template>
 </template>
 <script lang="ts" setup>
-import { computed, watchEffect } from "vue";
+import { computed } from "vue";
 import { useExpose } from "../hook/use-expose";
-import { useResize } from "../hook/use-event";
 import { normalPadding } from "@/utils/bound";
+import { useGetViewBoxPositionPixel } from "../hook/use-viewer";
 
-const size = useResize();
 const econfig = useExpose().config;
+const getPixel = useGetViewBoxPositionPixel();
 
 const borderConfigs = computed(() => {
-  if (!econfig.border || !size.value) return [];
+  if (!econfig.border) return [];
   const bds = Array.isArray(econfig.border) ? econfig.border : [econfig.border];
 
   return bds.map((border) => {
     const margin = normalPadding(border.margin);
+    const lt = getPixel({ left: margin[3], top: margin[0] });
+    const rb = getPixel({ right: margin[1], bottom: margin[2] });
     return {
-      x: margin[3],
-      y: margin[0],
-      width: size.value!.width - margin[1] - margin[3],
-      height: size.value!.height - margin[0] - margin[2],
+      x: lt.x,
+      y: lt.y,
+      width: rb.x - lt.x,
+      height: rb.y - lt.y,
       strokeWidth: border.lineWidth,
       stroke: border.color,
       opacity: border.opacity,
@@ -33,22 +32,4 @@ const borderConfigs = computed(() => {
     };
   });
 });
-
-const lineConfigs = computed(() => {
-  if (!econfig.margin || !size.value) return [];
-  const mrs = normalPadding(econfig.margin);
-  const color = econfig.back?.color || "#fff";
-  const rect = [
-    [0, 0],
-    [size.value.width, 0],
-    [size.value.width, size.value.height],
-    [0, size.value.height],
-  ];
-  return rect.map((point, ndx) => ({
-    listening: false,
-    stroke: color,
-    points: [...point, ...rect[(ndx + 1) % rect.length]],
-    strokeWidth: mrs[ndx] * 2,
-  }));
-});
 </script>

+ 14 - 12
src/core/helper/split-line.vue

@@ -14,7 +14,12 @@
 import { computed, onUnmounted } from "vue";
 import { components } from "../components";
 import { useComponentsAttach } from "../hook/use-component";
-import { useViewerInvertTransformConfig, useViewerTransform } from "../hook/use-viewer";
+import {
+  useGetViewBoxPositionPixel,
+  useViewerInvertTransformConfig,
+  useViewerTransform,
+  useViewSize,
+} from "../hook/use-viewer";
 import { useResize } from "../hook/use-event";
 import { Pos } from "@/utils/math";
 import { TextConfig } from "konva/lib/shapes/Text";
@@ -43,26 +48,23 @@ const props = withDefaults(
   { splitOffset: 80, showOffset: 20, splitWidth: 10 }
 );
 
-const size = useResize();
+const getPixel = useGetViewBoxPositionPixel();
+const size = useViewSize();
 const center = computed(() => {
   if (!size.value) return;
-  return {
-    x: size.value.width / 2,
-    y: size.value.height / 2,
-  };
+  return getPixel({
+    left: size.value.width / 2,
+    top: size.value.height / 2,
+  });
 });
-
 const config = useConfig();
 const rang = computed(() => {
   if (!size.value) return;
   const mrs = normalPadding(config.margin || 0);
 
   return {
-    lt: { x: props.showOffset + mrs[3], y: props.showOffset + mrs[0] },
-    rb: {
-      x: size.value.width - props.showOffset - mrs[1],
-      y: size.value.height - props.showOffset - mrs[2],
-    },
+    lt: getPixel({ left: props.showOffset + mrs[3], top: props.showOffset + mrs[0] }),
+    rb: getPixel({ right: props.showOffset + mrs[1], bottom: props.showOffset + mrs[2] }),
   };
 });
 

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

@@ -134,7 +134,7 @@ export const useAutoService = () => {
     if (operMode.value.freeView) {
       style = "pointer";
     } else if (mode.include(Mode.update)) {
-      style = "move";
+      style = "/icons/m_move.png";
     } else if (status.hovers.length) {
       style = "pointer";
     } else {

+ 22 - 0
src/core/hook/use-global-vars.ts

@@ -233,6 +233,28 @@ export const usePointerIntersections = installGlobalVar(() => {
   }
 })
 
+
+export const usePointerIntersection = installGlobalVar(() => {
+  const shape = ref<Shape<ShapeConfig> | null>(null)
+  const pos = usePointerPos()
+  const stage = useStage()
+  const updateShape = debounce(() => {
+    if (!pos.value || !stage.value) {
+      return;
+    }
+    shape.value = stage.value.getNode().getIntersection(pos.value)
+  }, 16)
+
+  const stopWatch = watch(pos, updateShape)
+
+  return {
+    var: shape,
+    onDestroy: () => {
+      stopWatch()
+      shape.value = null
+    }
+  }
+})
 export const useDownKeys = installGlobalVar(() => {
   const keyKeys = reactive(new Set<string>());
   const mouseKeys = reactive(new Set<string>());

+ 69 - 5
src/core/hook/use-mouse-status.ts

@@ -4,6 +4,7 @@ import { Shape } from "konva/lib/Shape";
 import {
   globalWatch,
   installGlobalVar,
+  usePointerIntersection,
   usePointerIntersections,
   usePointerPos,
   useStage,
@@ -12,20 +13,19 @@ import {
 import { useCan, useOperMode } from "./use-status";
 import { Stage } from "konva/lib/Stage";
 import { listener } from "../../utils/event.ts";
-import { inRevise, mergeFuns } from "../../utils/shared.ts";
+import { asyncTimeout, inRevise, mergeFuns } from "../../utils/shared.ts";
 import { ComponentValue, DrawItem, ShapeType } from "../components";
 import { shapeTreeContain, shapeTreeContains } from "../../utils/shape.ts";
 import {
   usePointerIsTransformerInner,
   useTransformer,
 } from "./use-transformer.ts";
-import { useAniamtion } from "./use-animation.ts";
 import { KonvaEventObject } from "konva/lib/Node";
 import { useStore } from "../store/index.ts";
 import { Group } from "konva/lib/Group";
 import { usePause } from "./use-pause.ts";
 import { lineLen, Pos } from "@/utils/math.ts";
-import { Util } from "konva/lib/Util";
+import { useFormalLayer } from "./use-layer.ts";
 
 const stageHoverMap = new WeakMap<
   Stage,
@@ -76,8 +76,51 @@ export const getHoverShape = (stage: Stage) => {
   return [hover, stop] as const;
 };
 
+export const useShapeClick = (
+  shape: Ref<DC<Shape> | undefined>,
+  fn: () => void
+) => {
+  const init = (shape: Shape) => {
+    let downTime: number;
+    let move = false;
+    const downHandler = (ev: KonvaEventObject<any>) => {
+      ev.cancelBubble = true
+      downTime = Date.now();
+      move = false;
+    };
+    const moveHandler = (ev: KonvaEventObject<any>) => {
+      ev.cancelBubble = true
+      downTime = Date.now();
+      move = true;
+    };
+    const upHandler = (ev: KonvaEventObject<any>) => {
+      ev.cancelBubble = true
+      if (Date.now() - downTime < 500 && !move) {
+        fn();
+      }
+    };
+
+    shape.on("pointerdown.click", downHandler);
+    shape.on("pointermove.click", moveHandler);
+    shape.on("pointerup.click", upHandler);
+    return () => {
+      shape.off("pointerdown.click", downHandler);
+      shape.off("pointermove.click", moveHandler);
+      shape.off("pointerup.click", upHandler);
+    };
+  };
+  watch(shape, (shape, _, onCleanup) => {
+    const $shape = shape?.getNode();
+    $shape && onCleanup(init($shape));
+  });
+};
+
 export const useShapeIsHover = (shape: Ref<DC<EntityShape> | undefined>) => {
   const stage = useStage();
+  const store = useStore();
+  const format = useFormalLayer();
+  const pos = usePointerPos();
+
   const isHover = ref(false);
   const stop = watch(
     () => ({ stage: stage.value?.getNode(), shape: shape.value?.getNode() }),
@@ -86,15 +129,36 @@ export const useShapeIsHover = (shape: Ref<DC<EntityShape> | undefined>) => {
         isHover.value = false;
         return;
       }
+      const forciblyCheck = async () => {
+        await asyncTimeout(6)
+        if (!pos.value || !format.value) {
+          isHover.value = false;
+          return;
+        }
+        isHover.value =
+          format.value.getIntersection(pos.value) === toRaw(shape);
+      };
+      store.bus.on("addItemAfter", forciblyCheck);
+      store.bus.on("setItemAfter", forciblyCheck);
+      store.bus.on("delItemAfter", forciblyCheck);
 
       const [hoverShape, stopHoverListener] = getHoverShape(stage);
-
       watchEffect(() => {
         isHover.value = !!(
           hoverShape.value && shapeTreeContain([shape], toRaw(hoverShape.value))
         );
       });
-      onCleanup(stopHoverListener);
+      onCleanup(
+        mergeFuns([
+          stopHoverListener,
+          () => {
+            isHover.value = false;
+            store.bus.off("addItemAfter", forciblyCheck);
+            store.bus.off("setItemAfter", forciblyCheck);
+            store.bus.off("delItemAfter", forciblyCheck);
+          },
+        ])
+      );
     },
     { immediate: true }
   );

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

@@ -213,7 +213,8 @@ export const useShapeDrag = (shape: Ref<DC<EntityShape> | undefined>) => {
     shape.draggable(true);
     shape.dragBoundFunc((_, ev) => {
       if (!start) {
-        enter(ev);
+        // enter(conversion(getOffset(ev, dom)));
+        return shape.absolutePosition();
       } else if (can.dragMode) {
         const end = conversion(getOffset(ev, dom));
         offset.value = {

+ 79 - 13
src/core/hook/use-viewer.ts

@@ -1,30 +1,28 @@
 import { Viewer } from "../viewer.ts";
-import { computed, ref, watch, watchEffect } from "vue";
+import { computed, Ref, ref, watch, watchEffect } from "vue";
 import { dragListener, scaleListener } from "../../utils/event.ts";
-import {
-  globalWatch,
-  installGlobalVar,
-  useStage,
-} from "./use-global-vars.ts";
-import { useCan } from './use-status'
+import { globalWatch, installGlobalVar, useStage } from "./use-global-vars.ts";
+import { useCan } from "./use-status";
 import { mergeFuns } from "../../utils/shared.ts";
 import { Transform } from "konva/lib/Util";
 import { lineLen } from "@/utils/math.ts";
 import { useResize } from "./use-event.ts";
+import { FixScreen, getFixPosition } from "@/utils/bound.ts";
 
 export const useViewer = installGlobalVar(() => {
   const stage = useStage();
   const viewer = new Viewer();
-  const can = useCan()
-  const size = useResize()
+  const can = useCan();
+  const size = useResize();
   const transform = ref(new Transform());
+  const sizeMat = ref<Transform | null>(null);
 
   const init = (dom: HTMLDivElement) => {
     const onDestroy = mergeFuns(
       dragListener(dom, {
         move: ({ end, prev }) => {
           if (can.viewMode) {
-            viewer.movePixel({ x: end.x - prev.x, y: end.y - prev.y }); 
+            viewer.movePixel({ x: end.x - prev.x, y: end.y - prev.y });
           }
         },
         notPrevent: true,
@@ -35,14 +33,18 @@ export const useViewer = installGlobalVar(() => {
         }
       }),
       watchEffect(() => {
-        size.value && viewer.setSize(size.value)
+        size.value && viewer.setSize(size.value);
       })
-    )
+    );
 
     viewer.bus.on("transformChange", (newTransform) => {
       transform.value = newTransform;
     });
+    viewer.bus.on("viewSizeChange", () => {
+      sizeMat.value = viewer.sizeMat;
+    });
     transform.value = viewer.transform;
+    sizeMat.value = viewer.sizeMat;
     return onDestroy;
   };
 
@@ -50,9 +52,10 @@ export const useViewer = installGlobalVar(() => {
     var: {
       transform: transform,
       viewer,
+      sizeMat,
     },
     onDestroy: globalWatch(
-      () =>  can.viewMouseReact,
+      () => can.viewMouseReact,
       (can, _, onCleanup) => {
         if (can) {
           const dom = stage.value!.getNode().container();
@@ -134,3 +137,66 @@ export const useCacheUnitTransform = installGlobalVar(() => {
     },
   };
 }, Symbol("cacheUnitTransform"));
+
+export const useViewSize = installGlobalVar(() => {
+  const size = useResize();
+  const { sizeMat, viewer } = useViewer();
+
+  return computed(() => {
+    if (sizeMat.value) {
+      return viewer.viewSize!;
+    } else {
+      return size.value;
+    }
+  });
+});
+
+export const useViewBoxPixelRect = installGlobalVar(() => {
+  const size = useResize();
+  const { sizeMat, viewer, transform } = useViewer();
+
+  return computed(() => {
+    if (sizeMat.value) {
+      const size = viewer.viewSize!;
+      const p1 = transform.value.point({ x: 0, y: 0 });
+      const p2 = transform.value.point({ x: size.width, y: size.height });
+      return {
+        ...p1,
+        width: p2.x - p1.x,
+        height: p2.y - p1.y,
+      };
+    } else {
+      return {
+        x: 0,
+        y: 0,
+        width: 100,
+        height: 100,
+        ...size.value,
+      };
+    }
+  });
+});
+
+export const useGetViewBoxPositionPixel = () => {
+  const size = useResize();
+  const { sizeMat, viewer, transform } = useViewer();
+
+
+  return (fixPosition: FixScreen, selfSize = { width: 1, height: 1 }) => {
+    if (sizeMat.value) {
+      const size = viewer.viewSize!;
+      const pos = getFixPosition(
+        fixPosition,
+        selfSize,
+        size
+      );
+      return transform.value.point(pos)
+    } else {
+      return getFixPosition(
+        fixPosition,
+        selfSize,
+        size.value || { width: 100, height: 100 }
+      );
+    }
+  }
+};

+ 6 - 4
src/core/renderer/renderer.vue

@@ -15,9 +15,12 @@
       <v-stage ref="stage" :config="size" v-if="layout">
         <v-layer :config="viewerConfig" id="formal">
           <v-group>
-            <Back />
-            <!--	不可去除,去除后移动端拖拽会有溢出	-->
-            <BackGrid v-if="expose.config.showGrid" />
+            <v-group>
+              <Back />
+            </v-group>
+            <v-group>
+              <BackGrid v-if="expose.config.showGrid" />
+            </v-group>
           </v-group>
           <v-group :id="DataGroupId">
             <component
@@ -50,7 +53,6 @@
           <layers v-if="store.layers.length > 1" />
           <Debugger v-if="isDev" />
           <Border />
-          <!-- <MultipleSelection /> -->
         </v-layer>
       </v-stage>
     </div>

+ 27 - 3
src/core/viewer.ts

@@ -7,15 +7,38 @@ import { MathUtils } from "three";
 
 export class Viewer {
   size?: Size;
+  sizeMat: Transform | null;
+  viewSize?: Size
   viewMat: Transform;
-  bus = mitt<{ transformChange: Transform }>();
+  bus = mitt<{ transformChange: Transform, viewSizeChange: void }>();
 
   constructor() {
     this.viewMat = new Transform();
+    this.sizeMat = null
   }
 
   setSize(size: Size) {
     this.size = size;
+    this.updateSizeMat()
+  }
+
+  setViewSize(size: Size) {
+    this.viewSize = size
+    this.updateSizeMat()
+  }
+
+  private updateSizeMat() {
+    if (!this.size || !this.viewSize) {
+      this.sizeMat = null
+    } else {
+      const diffw = this.size.width - this.viewSize.width
+      const diffh = this.size.height - this.viewSize.height
+      this.sizeMat = new Transform().translate(diffw / 2, diffh / 2)
+    }
+
+    this.bus.emit("transformChange", this.transform);
+    this.bus.emit('viewSizeChange')
+    console.log(this.transform.decompose())
   }
 
   setBound({
@@ -126,13 +149,14 @@ export class Viewer {
     } else {
       this.viewMat = new Transform(mat);
     }
+
     this.bus.emit("transformChange", this.transform);
   }
 
   get transform() {
-    return this.viewMat.copy();
+    return this.sizeMat ? this.sizeMat.copy().multiply(this.viewMat) : this.viewMat.copy();
   }
   get current() {
-    return this.viewMat.decompose();
+    return this.transform.decompose();
   }
 }

+ 0 - 1
src/example/components/container/container.vue

@@ -75,7 +75,6 @@ defineExpose({
   --left: 0px;
   overflow: hidden;
 
-
   .container {
     flex: 1;
     display: flex;

+ 6 - 2
src/example/components/header/actions.ts

@@ -1,4 +1,4 @@
-import { computed, reactive } from "vue";
+import { computed, nextTick, reactive } from "vue";
 import { Draw } from "../container/use-draw";
 import { animation } from "@/core/hook/use-animation";
 import saveAs from "@/utils/file-serve";
@@ -80,7 +80,11 @@ export const getHeaderActions = (draw: Draw) => {
         },
         {
           handler: async () => {
-            saveAs(await getImage(draw, 'image/png'), "canvas.png");
+            const oldBack = draw.config.back && {...draw.config.back}
+            draw.config.back = undefined
+            await nextTick()
+            await saveAs(await getImage(draw, 'image/png'), "canvas.png");
+            draw.config.back = oldBack
           },
           text: "png",
           icon: "a-visible",

+ 2 - 1
src/example/components/slide/actions.ts

@@ -99,7 +99,8 @@ const setPaper = (draw: Draw, p: number[], scale: number) => {
   const size = { width: p[0] * scale, height: p[1] * scale };
   const margin = [pad, pad, pad, pad * 5];
 
-  draw.config.size = size;
+  // draw.config.size = size;
+  draw.viewer.setViewSize(size)
   draw.config.back = { color: "#fff", opacity: 1 };
   draw.config.border = {
     margin: margin,

+ 8 - 0
src/example/fuse/views/overview/index.vue

@@ -38,6 +38,14 @@ const init = async (draw: Draw) => {
   draw.config.showLabelLine = true;
   draw.config.showComponentSize = true;
 
+  draw.config.back = { color: "#f0f2f5", opacity: 1 };
+  // draw.config.border = {
+  //   margin: 10,
+  //   lineWidth: 1,
+  //   color: "#000",
+  //   opacity: 1,
+  // };
+
   const data = await getOverviewData();
   draw.store.setStore(data.store);
   data.viewport && draw.viewer.setViewMat(data.viewport);

+ 12 - 10
src/utils/shared.ts

@@ -233,31 +233,33 @@ export const asyncTimeout = (time: number) => {
 
 export const getResizeCorsur = (level = true, r = 0) => {
   r = rangMod(r, 360);
+
+  // 上下  "/icons/m_zoom_v.png"
   if (level) {
     if ((r > 0 && r < 20) || (r > 160 && r <= 200)) {
-      return "ew-resize";
+      return "/icons/m_zoom_h.png";
     }
     if ((r >= 20 && r <= 70) || (r >= 200 && r <= 250)) {
-      return "nwse-resize";
+      return "/icons/m_zoom.png";
     } else if ((r > 70 && r < 110) || (r > 250 && r < 290)) {
-      return "ns-resize";
+      return "/icons/m_zoom_v.png";
     } else if ((r >= 110 && r <= 160) || (r >= 290 && r <= 340)) {
-      return "nesw-resize";
+      return "/icons/m_zoom_r.png";
     } else {
-      return "ew-resize";
+      return "/icons/m_zoom_h.png";
     }
   } else {
     if ((r > 0 && r < 20) || (r > 160 && r <= 200)) {
-      return "ns-resize";
+      return "/icons/m_zoom_v.png";
     }
     if ((r >= 20 && r <= 70) || (r >= 200 && r <= 250)) {
-      return "nesw-resize";
+      return "/icons/m_zoom_r.png";
     } else if ((r > 70 && r < 110) || (r > 250 && r < 290)) {
-      return "ew-resize";
+      return "/icons/m_zoom_h.png";
     } else if ((r >= 110 && r <= 160) || (r >= 290 && r <= 340)) {
-      return "nwse-resize";
+      return "/icons/m_zoom.png";
     } else {
-      return "ns-resize";
+      return "/icons/m_zoom_v.png";
     }
   }
 };