123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640 |
- import { useMouseShapeStatus } from "./use-mouse-status.ts";
- import { Ref, ref, watch } from "vue";
- import { DC, EntityShape } from "../../deconstruction";
- import {
- installGlobalVar,
- useCan,
- useMode,
- useStage,
- useTransformIngShapes,
- } from "./use-global-vars.ts";
- import { Mode } from "../../constant/mode.ts";
- import { Transform, Util } from "konva/lib/Util";
- import { Pos, vector } from "@/utils/math.ts";
- import { useConversionPosition } from "./use-coversion-position.ts";
- import { getOffset, listener } from "@/utils/event.ts";
- import { flatPositions, mergeFuns, round } from "@/utils/shared.ts";
- import { Line } from "konva/lib/shapes/Line";
- import { setShapeTransform } from "@/utils/shape.ts";
- import { Transformer } from "../transformer.ts";
- import { TransformerConfig } from "konva/lib/shapes/Transformer";
- import { themeColor, themeMouseColors } from "@/constant/help-style.ts";
- import { useComponentSnap } from "./use-snap.ts";
- import { useViewerInvertTransform, useViewerTransform } from "./use-viewer.ts";
- import { Rect } from "konva/lib/shapes/Rect";
- import { Text } from "konva/lib/shapes/Text";
- import { Group } from "konva/lib/Group";
- import { BaseItem } from "../components/util.ts";
- import { useGetComponentData } from "./use-component.ts";
- export type TransformerExtends = Transformer & {
- queueShapes: Ref<EntityShape[]>;
- };
- export const useTransformer = installGlobalVar(() => {
- const anchorCornerRadius = 5;
- const transformer = new Transformer({
- borderStrokeWidth: 2,
- borderStroke: themeMouseColors.pub,
- anchorCornerRadius,
- anchorSize: anchorCornerRadius * 2,
- rotationSnaps: [0, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330, 360],
- rotationSnapTolerance: 3,
- anchorStrokeWidth: 2,
- anchorStroke: themeMouseColors.pub,
- anchorFill: themeMouseColors.press,
- flipEnabled: false,
- padding: 10,
- useSingleNodeRotation: true,
- }) as TransformerExtends;
- transformer.queueShapes = ref([]);
- transformer.on("transformstart.attachText", () => {
- const operateType = transformer.getActiveAnchor() as TransformerVectorType;
- if (operateType !== "rotater") return;
- const $node = transformer.findOne<Rect>(".rotater")!;
- const $g = new Group();
- const $text = new Text({
- listening: false,
- fill: themeColor,
- fontSize: 12,
- width: 100,
- align: "center",
- offset: { x: 50, y: 0 },
- });
- $g.add($text);
- $node.parent!.add($g);
- setShapeTransform($g, $node.getTransform());
- $g.y($g.y() - 2 * $text.fontSize());
- const updateText = () => {
- const rotation = transformer.rotation();
- $text.rotation(-rotation).text(` ${round(rotation, 1)}°`);
- };
- updateText();
- transformer.on("transform.attachText", updateText);
- transformer.on("transformend.attachText", () => {
- transformer.off("transform.attachText transformend.attachText");
- $g.destroy();
- });
- });
- return transformer;
- }, Symbol("transformer"));
- export const usePointerIsTransformerInner = () => {
- const transformer = useTransformer();
- const stage = useStage();
- return () => {
- const $stage = stage.value!.getStage();
- const tfRect = transformer.getClientRect();
- const padding = transformer.padding();
- tfRect.x -= padding;
- tfRect.y -= padding;
- tfRect.width += padding;
- tfRect.height += padding;
- const pointRect = { ...$stage.pointerPos!, width: 1, height: 1 };
- return Util.haveIntersection(tfRect, pointRect);
- };
- };
- export type ScaleVectorType =
- | "middle-left"
- | "middle-right"
- | "top-center"
- | "bottom-center"
- | "top-right"
- | "top-left"
- | "bottom-right"
- | "bottom-left";
- export type TransformerVectorType = ScaleVectorType | "rotater";
- export const useGetTransformerOperType = () => {
- const transformer = useTransformer();
- return () => {
- if (!transformer.nodes().length) return null;
- return transformer.getActiveAnchor() as TransformerVectorType;
- };
- };
- export const useGetTransformerVectors = () => {
- const viewerInvertTransform = useViewerInvertTransform();
- const transformer = useTransformer();
- return (type: TransformerVectorType): Pos | null => {
- if (!transformer.nodes().length) return null;
- const merTransform = viewerInvertTransform.value
- .copy()
- .multiply(transformer.getTransform());
- const getVector = (operateType: TransformerVectorType): Pos => {
- if (operateType === "rotater") {
- return vector(getVector("bottom-left"))
- .add(getVector("bottom-right"))
- .add(getVector("top-left"))
- .add(getVector("top-right"))
- .divideScalar(4);
- } else {
- const centerNode = transformer.findOne(`.${operateType}`)!;
- return {
- x: centerNode.x(),
- y: centerNode.y(),
- };
- }
- };
- return merTransform.point(getVector(type));
- };
- };
- export const useGetTransformerOperDirection = () => {
- const getTransformerOperType = useGetTransformerOperType();
- const getTransformerVectors = useGetTransformerVectors();
- const originTypeMap = {
- "middle-left": "middle-right",
- "middle-right": "middle-left",
- "top-center": "bottom-center",
- "bottom-center": "top-center",
- "top-right": "bottom-left",
- "top-left": "bottom-right",
- "bottom-right": "top-left",
- "bottom-left": "top-right",
- rotater: "rotater",
- } as const;
- return () => {
- const operateType = getTransformerOperType();
- if (!operateType || !originTypeMap[operateType]) return null;
- const origin = getTransformerVectors(originTypeMap[operateType]);
- const operTarget = getTransformerVectors(operateType);
- return origin && operTarget && ([origin, operTarget] as const);
- };
- };
- export const useShapeDrag = (shape: Ref<DC<EntityShape> | undefined>) => {
- const offset = ref<Pos>();
- const mode = useMode();
- const can = useCan();
- const conversion = useConversionPosition(true);
- const transformIngShapes = useTransformIngShapes();
- const status = useMouseShapeStatus(shape)
- const init = (shape: EntityShape) => {
- const dom = shape.getStage()!.container();
- let start: Pos | undefined;
- const enter = (position: Pos) => {
- mode.push(Mode.update);
- if (!start) {
- start = position;
- if (!can.dragMode) return;
- mode.add(Mode.draging);
- 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 if (can.dragMode) {
- 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-drag");
- start && leave();
- },
- listener(document.documentElement, "pointerup", () => {
- start && leave();
- }),
- ]);
- };
- watch(
- () =>
- (can.editMode || mode.include(Mode.update)) &&
- (status.value.active || status.value.hover),
- (canEdit, _, onCleanup) => {
- canEdit && onCleanup(init(shape.value!.getNode()));
- }
- );
- return offset;
- };
- type Rep<T> = {
- tempShape: T;
- init?: () => void;
- update?: () => void;
- destory: () => void;
- };
- const emptyFn = () => {};
- export const useShapeTransformer = <T extends EntityShape>(
- shape: Ref<DC<T> | undefined>,
- transformerConfig: TransformerConfig = {},
- replaceShape?: (shape: T) => Rep<T>,
- handlerTransform?: (transform: Transform) => Transform
- ) => {
- const offset = useShapeDrag(shape);
- const transform = ref<Transform>();
- const status = useMouseShapeStatus(shape);
- const mode = useMode();
- const transformer = useTransformer();
- const transformIngShapes = useTransformIngShapes();
- const viewTransform = useViewerTransform();
- const getComponentData = useGetComponentData();
- const can = useCan();
- const init = ($shape: T) => {
- let rep: Rep<T>;
- if (replaceShape) {
- rep = replaceShape($shape);
- } else {
- rep = {
- tempShape: $shape,
- destory: emptyFn,
- update: emptyFn,
- };
- }
- const updateTransform = () => {
- if (!can.dragMode) return;
- let appleTransform = rep.tempShape.getTransform().copy();
- if (handlerTransform) {
- appleTransform = handlerTransform(appleTransform);
- setShapeTransform(rep.tempShape, appleTransform);
- }
- transform.value = appleTransform;
- };
- rep.tempShape.on("transform.shapemer", updateTransform);
- const boundHandler = () => {
- rep.update && rep.update()
- };
- $shape.on("bound-change", boundHandler);
- // 拖拽时要更新矩阵
- let prevMoveTf: Transform | null = null;
- const stopDragWatch = watch(
- offset,
- (translate, oldTranslate) => {
- if (translate) {
- if (!oldTranslate) {
- rep.init && rep.init();
- rep.update && 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);
- rep.tempShape.fire("transform");
- } else {
- prevMoveTf = null;
- transform.value = void 0;
- }
- },
- { immediate: true }
- );
- const stopTransformerWatch = watch(
- () => status.value.active,
- (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]);
- }
- transformer.nodes([rep.tempShape]);
- transformer.queueShapes.value = [$shape];
- parent.add(transformer);
- rep.init && rep.init()
- let isEnter = false;
- const downHandler = () => {
- if (isEnter) {
- mode.pop();
- }
- mode.push(Mode.update);
- isEnter = true;
- if (!can.dragMode) return;
- rep.update && rep.update();
- mode.add(Mode.draging);
- transformIngShapes.value.push($shape);
- };
- transformer.on("pointerdown.shapemer", downHandler);
- const stopPointupListener = 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);
- }
- }
- );
- const stopTransformerForceUpdate = watch(
- viewTransform,
- () => transformer.forceUpdate(),
- { flush: "post" }
- );
- const stopLeaveUpdate = watch(
- () => getComponentData($shape).value,
- (val) => {
- rep.update && rep.update();
- },
- { flush: "post", deep: true }
- );
- onCleanup(() => {
- for (const key in oldConfig) {
- (transformer as any)[key](oldConfig[key]);
- }
- stopTransformerForceUpdate();
- stopPointupListener();
- stopLeaveUpdate();
- // TODO: 有可能transformer已经转移
- if (transformer.queueShapes.value.includes($shape)) {
- transformer.nodes([]);
- transformer.queueShapes.value = [];
- }
- transform.value = void 0;
- if (isEnter) {
- mode.pop();
- const ndx = transformIngShapes.value.indexOf($shape);
- ~ndx && transformIngShapes.value.splice(ndx, 1);
- }
- transformer.off("pointerdown.shapemer", downHandler);
- });
- },
- {immediate: true}
- );
- return () => {
- $shape.off("bound-change", boundHandler);
- rep.tempShape.off("transform.shapemer", updateTransform);
- stopDragWatch();
- stopTransformerWatch();
- rep.destory();
- };
- };
- watch(
- () => shape.value,
- (shape, _) => {
- if (!shape) return;
- watch(
- () =>
- (can.editMode || mode.include(Mode.update)) &&
- (status.value.active || status.value.hover),
- (canEdit, _, onCleanup) => {
- if (canEdit) {
- const stop = init(shape.getStage());
- onCleanup(stop);
- } else {
- onCleanup(() => {});
- }
- },
- { immediate: true }
- );
- }
- );
- return transform;
- };
- export const cloneRepShape = <T extends EntityShape>(
- shape: T
- ): ReturnType<GetRepShape<T, any>> => {
- shape =
- ((shape as Group)?.findOne &&
- ((shape as Group)?.findOne(".repShape") as T)) ||
- shape;
- return {
- shape: shape.clone({
- fill: "rgb(0, 255, 0)",
- visible: false,
- strokeWidth: 0,
- }),
- update: (_, rep) => {
- setShapeTransform(rep, shape.getTransform());
- },
- };
- };
- export const transformerRepShapeHandler = <T extends EntityShape>(
- shape: T,
- repShape: T
- ) => {
- if (import.meta.env.DEV) {
- repShape.visible(true);
- repShape.opacity(0.1);
- }
- shape.parent!.add(repShape);
- repShape.zIndex(shape.getZIndex());
- repShape.listening(false);
- shape.repShape = repShape;
- return [
- repShape,
- () => {
- shape.repShape = undefined;
- repShape.remove();
- },
- ] as const;
- };
- type GetRepShape<T extends EntityShape, K extends object> = (shape: T) => {
- shape: T;
- update?: (data: K, shape: T) => void;
- init?: (data: K, shape: T) => void;
- };
- export type CustomTransformerProps<
- T extends BaseItem,
- S extends EntityShape
- > = {
- openSnap?: boolean;
- getRepShape?: GetRepShape<S, T>;
- beforeHandler?: (data: T, mat: Transform) => T;
- handler?: (data: T, mat: Transform, raw?: Transform) => Transform | void | true;
- callback?: (data: T, mat: Transform) => void;
- transformerConfig?: TransformerConfig;
- };
- export const useCustomTransformer = <T extends BaseItem, S extends EntityShape>(
- shape: Ref<DC<S> | undefined>,
- data: Ref<T>,
- props: CustomTransformerProps<T, S>
- ) => {
- const { getRepShape, handler, callback, openSnap, transformerConfig } = props;
- const needSnap = openSnap && useComponentSnap(data.value.id);
- const transformer = useTransformer();
- let repResult: ReturnType<GetRepShape<S, T>>;
- const transform = useShapeTransformer(
- shape,
- transformerConfig,
- getRepShape &&
- ((shape) => {
- repResult = getRepShape(shape);
- const [_, destory] = transformerRepShapeHandler(shape, repResult.shape);
- return {
- tempShape: repResult.shape,
- update: () => {
- repResult.update && repResult.update(data.value, repResult.shape);
- },
- init: () => {
- repResult.init && repResult.init(data.value, repResult.shape);
- },
- destory,
- };
- })
- );
- let callMat: Transform;
- watch(transform, (current, oldTransform) => {
- if (current) {
- if (!handler) return;
- const snapData = props.beforeHandler
- ? props.beforeHandler(data.value, current)
- : data.value;
- let nTransform;
- const raw = current
- if (needSnap && (nTransform = needSnap[0](snapData))) {
- current = nTransform.multiply(current);
- }
- callMat = current;
- const mat = handler(data.value, current, raw);
- if (mat) {
- if (repResult.update) {
- repResult.update(data.value, repResult.shape);
- } else if (mat !== true) {
- setShapeTransform(repResult.shape, mat);
- callMat = mat;
- }
- transformer.forceUpdate();
- }
- } else if (oldTransform) {
- needSnap && needSnap[1]();
- callback && callback(data.value, callMat);
- }
- });
- return transform;
- };
- export type LineTransformerData = BaseItem & {
- points: Pos[];
- attitude: number[];
- };
- export const useLineTransformer = <T extends LineTransformerData>(
- shape: Ref<DC<Line> | undefined>,
- data: Ref<T>,
- callback: (data: T) => void,
- genRepShape?: ($shape: Line) => Line
- ) => {
- let tempShape: Line;
- let inverAttitude: Transform;
- let stableVs = data.value.points;
- let tempVs = data.value.points;
- useCustomTransformer(shape, data, {
- openSnap: true,
- beforeHandler(data, mat) {
- const transfrom = mat.copy().multiply(inverAttitude);
- return {
- ...data,
- points: stableVs.map((v) => transfrom.point(v)),
- };
- },
- handler(data, mat) {
- // 顶点更新
- const transfrom = mat.copy().multiply(inverAttitude);
- data.points = tempVs = stableVs.map((v) => transfrom.point(v));
- },
- callback(data, mat) {
- data.attitude = mat.m;
- data.points = stableVs = tempVs;
- callback(data);
- },
- getRepShape($shape) {
- let repShape: Line;
- if (genRepShape) {
- repShape = genRepShape($shape);
- } else {
- repShape = cloneRepShape($shape).shape;
- }
- repShape = (repShape as any).points
- ? repShape
- : (repShape as unknown as Group).findOne<Line>(".line")!;
- tempShape = repShape;
- const update = (data: T) => {
- const attitude = new Transform(data.attitude);
- const inverMat = attitude.copy().invert();
- setShapeTransform(repShape, attitude);
- const initVs = data.points.map((v) => inverMat.point(v));
- stableVs = tempVs = data.points;
- repShape.points(flatPositions(initVs));
- repShape.closed(true);
- inverAttitude = inverMat;
- };
- update(data.value);
- return {
- update,
- shape: repShape,
- };
- },
- });
- };
- export const useMatCompTransformer = <T extends BaseItem & { mat: number[] }>(
- shape: Ref<DC<EntityShape> | undefined>,
- data: Ref<T>,
- callback: (data: T) => void
- ) => {
- return useCustomTransformer(shape, data, {
- beforeHandler(data, mat) {
- return { ...data, mat: mat.m };
- },
- handler(data, mat) {
- data.mat = mat.m;
- },
- getRepShape: cloneRepShape,
- callback,
- openSnap: true,
- });
- };
|