use-transformer.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601
  1. import { useMouseShapeStatus } from "./use-mouse-status.ts";
  2. import { Ref, ref, watch, watchEffect } from "vue";
  3. import { DC, EntityShape } from "../../deconstruction";
  4. import {
  5. installGlobalVar,
  6. useCan,
  7. useMode,
  8. useStage,
  9. useTransformIngShapes,
  10. } from "./use-global-vars.ts";
  11. import { Mode } from "../../constant/mode.ts";
  12. import { Transform, Util } from "konva/lib/Util";
  13. import { Pos, vector } from "@/utils/math.ts";
  14. import { useConversionPosition } from "./use-coversion-position.ts";
  15. import { getOffset, listener } from "@/utils/event.ts";
  16. import { flatPositions, mergeFuns, round } from "@/utils/shared.ts";
  17. import { Line } from "konva/lib/shapes/Line";
  18. import { setShapeTransform } from "@/utils/shape.ts";
  19. import { Transformer } from "../transformer.ts";
  20. import { TransformerConfig } from "konva/lib/shapes/Transformer";
  21. import { themeColor, themeMouseColors } from "@/constant/help-style.ts";
  22. import { useComponentSnap } from "./use-snap.ts";
  23. import { useViewerInvertTransform, useViewerTransform } from "./use-viewer.ts";
  24. import { Rect } from "konva/lib/shapes/Rect";
  25. import { Text } from "konva/lib/shapes/Text";
  26. import { Group } from "konva/lib/Group";
  27. import { BaseItem } from "../components/util.ts";
  28. export type TransformerExtends = Transformer & {
  29. queueShapes: Ref<EntityShape[]>;
  30. };
  31. export const useTransformer = installGlobalVar(() => {
  32. const anchorCornerRadius = 5;
  33. const transformer = new Transformer({
  34. borderStrokeWidth: 2,
  35. borderStroke: themeMouseColors.pub,
  36. anchorCornerRadius,
  37. anchorSize: anchorCornerRadius * 2,
  38. rotationSnaps: [0, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330, 360],
  39. rotationSnapTolerance: 3,
  40. anchorStrokeWidth: 2,
  41. anchorStroke: themeMouseColors.pub,
  42. anchorFill: themeMouseColors.press,
  43. padding: 10,
  44. useSingleNodeRotation: true,
  45. }) as TransformerExtends;
  46. transformer.queueShapes = ref([]);
  47. transformer.on("transformstart.attachText", () => {
  48. const operateType = transformer.getActiveAnchor() as TransformerVectorType;
  49. if (operateType !== "rotater") return;
  50. const $node = transformer.findOne<Rect>(".rotater")!;
  51. const $g = new Group();
  52. const $text = new Text({
  53. listening: false,
  54. fill: themeColor,
  55. fontSize: 12,
  56. width: 100,
  57. align: "center",
  58. offset: { x: 50, y: 0 },
  59. });
  60. $g.add($text);
  61. $node.parent!.add($g);
  62. setShapeTransform($g, $node.getTransform());
  63. $g.y($g.y() - 2 * $text.fontSize());
  64. const updateText = () => {
  65. const rotation = transformer.rotation();
  66. $text.rotation(-rotation).text(` ${round(rotation, 1)}°`);
  67. };
  68. updateText();
  69. transformer.on("transform.attachText", updateText);
  70. transformer.on("transformend.attachText", () => {
  71. transformer.off("transform.attachText transformend.attachText");
  72. $g.destroy();
  73. });
  74. });
  75. return transformer;
  76. }, Symbol("transformer"));
  77. export const usePointerIsTransformerInner = () => {
  78. const transformer = useTransformer();
  79. const stage = useStage();
  80. return () => {
  81. const $stage = stage.value!.getStage();
  82. const tfRect = transformer.getClientRect();
  83. const padding = transformer.padding();
  84. tfRect.x -= padding;
  85. tfRect.y -= padding;
  86. tfRect.width += padding;
  87. tfRect.height += padding;
  88. const pointRect = { ...$stage.pointerPos!, width: 1, height: 1 };
  89. return Util.haveIntersection(tfRect, pointRect);
  90. };
  91. };
  92. export type ScaleVectorType =
  93. | "middle-left"
  94. | "middle-right"
  95. | "top-center"
  96. | "bottom-center"
  97. | "top-right"
  98. | "top-left"
  99. | "bottom-right"
  100. | "bottom-left";
  101. export type TransformerVectorType = ScaleVectorType | "rotater";
  102. export const useGetTransformerOperType = () => {
  103. const transformer = useTransformer();
  104. return () => {
  105. if (!transformer.nodes().length) return null;
  106. return transformer.getActiveAnchor() as TransformerVectorType;
  107. };
  108. };
  109. export const useGetTransformerVectors = () => {
  110. const viewerInvertTransform = useViewerInvertTransform();
  111. const transformer = useTransformer();
  112. return (type: TransformerVectorType): Pos | null => {
  113. if (!transformer.nodes().length) return null;
  114. const merTransform = viewerInvertTransform.value
  115. .copy()
  116. .multiply(transformer.getTransform());
  117. const getVector = (operateType: TransformerVectorType): Pos => {
  118. if (operateType === "rotater") {
  119. return vector(getVector("bottom-left"))
  120. .add(getVector("bottom-right"))
  121. .add(getVector("top-left"))
  122. .add(getVector("top-right"))
  123. .divideScalar(4);
  124. } else {
  125. const centerNode = transformer.findOne(`.${operateType}`)!;
  126. return {
  127. x: centerNode.x(),
  128. y: centerNode.y(),
  129. };
  130. }
  131. };
  132. return merTransform.point(getVector(type));
  133. };
  134. };
  135. export const useGetTransformerOperDirection = () => {
  136. const getTransformerOperType = useGetTransformerOperType();
  137. const getTransformerVectors = useGetTransformerVectors();
  138. const originTypeMap = {
  139. "middle-left": "middle-right",
  140. "middle-right": "middle-left",
  141. "top-center": "bottom-center",
  142. "bottom-center": "top-center",
  143. "top-right": "bottom-left",
  144. "top-left": "bottom-right",
  145. "bottom-right": "top-left",
  146. "bottom-left": "top-right",
  147. rotater: "rotater",
  148. } as const;
  149. return () => {
  150. const operateType = getTransformerOperType();
  151. if (!operateType || !originTypeMap[operateType]) return null;
  152. const origin = getTransformerVectors(originTypeMap[operateType]);
  153. const operTarget = getTransformerVectors(operateType);
  154. return origin && operTarget && ([origin, operTarget] as const);
  155. };
  156. };
  157. export const useShapeDrag = (shape: Ref<DC<EntityShape> | undefined>) => {
  158. const offset = ref<Pos>();
  159. const mode = useMode();
  160. const can = useCan();
  161. const conversion = useConversionPosition(true);
  162. const transformIngShapes = useTransformIngShapes();
  163. const init = (shape: EntityShape) => {
  164. const dom = shape.getStage()!.container();
  165. let start: Pos | undefined;
  166. const enter = (position: Pos) => {
  167. mode.push(Mode.update);
  168. if (!start) {
  169. start = position;
  170. if (!can.dragMode) return;
  171. mode.add(Mode.draging);
  172. transformIngShapes.value.push(shape);
  173. }
  174. };
  175. const leave = () => {
  176. if (start) {
  177. offset.value = void 0;
  178. mode.pop();
  179. start = void 0;
  180. const ndx = transformIngShapes.value.indexOf(shape);
  181. ~ndx && transformIngShapes.value.splice(ndx, 1);
  182. }
  183. };
  184. shape.draggable(true);
  185. shape.dragBoundFunc((_, ev) => {
  186. if (!start) {
  187. enter(ev);
  188. } else if (can.dragMode) {
  189. const end = conversion(getOffset(ev, dom));
  190. offset.value = {
  191. x: end.x - start.x,
  192. y: end.y - start.y,
  193. };
  194. }
  195. return shape.absolutePosition();
  196. });
  197. shape.on("pointerdown.mouse-drag", (ev) => {
  198. enter(conversion(getOffset(ev.evt)));
  199. });
  200. return mergeFuns([
  201. () => {
  202. shape.draggable(false);
  203. shape.off("pointerdown.mouse-drag");
  204. start && leave();
  205. },
  206. listener(document.documentElement, "pointerup", () => {
  207. start && leave();
  208. }),
  209. ]);
  210. };
  211. watch(
  212. () => (can.editMode || mode.include(Mode.update)) && shape.value?.getNode(),
  213. (canEdit, _, onCleanup) => {
  214. canEdit && onCleanup(init(shape.value!.getNode()));
  215. }
  216. );
  217. return offset;
  218. };
  219. type Rep<T> = {
  220. tempShape: T;
  221. init?: () => void;
  222. update?: () => void;
  223. destory: () => void;
  224. };
  225. const emptyFn = () => {};
  226. export const useShapeTransformer = <T extends EntityShape>(
  227. shape: Ref<DC<T> | undefined>,
  228. transformerConfig: TransformerConfig = {},
  229. replaceShape?: (transformer: TransformerExtends, shape: T) => Rep<T>,
  230. handlerTransform?: (transform: Transform) => Transform
  231. ) => {
  232. const offset = useShapeDrag(shape);
  233. const transform = ref<Transform>();
  234. const status = useMouseShapeStatus(shape);
  235. const mode = useMode();
  236. const transformer = useTransformer();
  237. const transformIngShapes = useTransformIngShapes();
  238. const viewTransform = useViewerTransform();
  239. const can = useCan();
  240. const init = ($shape: T) =>
  241. watch(
  242. () => status.value.hover,
  243. (active, _, onCleanup) => {
  244. const parent = $shape.parent;
  245. if (!(active && parent)) return;
  246. const oldConfig: TransformerConfig = {};
  247. for (const key in transformerConfig) {
  248. oldConfig[key] = (transformer as any)[key]();
  249. (transformer as any)[key](transformerConfig[key]);
  250. }
  251. let rep: Rep<T>;
  252. if (replaceShape) {
  253. rep = replaceShape(transformer, $shape);
  254. } else {
  255. rep = {
  256. tempShape: $shape,
  257. destory: emptyFn,
  258. update: emptyFn,
  259. };
  260. transformer.nodes([$shape]);
  261. transformer.queueShapes.value = [$shape];
  262. }
  263. parent.add(transformer);
  264. const updateTransform = () => {
  265. if (!can.dragMode) return;
  266. let appleTransform = rep.tempShape.getTransform().copy();
  267. if (handlerTransform) {
  268. appleTransform = handlerTransform(appleTransform);
  269. setShapeTransform(rep.tempShape, appleTransform);
  270. }
  271. transform.value = appleTransform;
  272. };
  273. const downHandler = () => {
  274. if (isEnter) {
  275. mode.pop();
  276. }
  277. mode.push(Mode.update);
  278. isEnter = true;
  279. if (!can.dragMode) return;
  280. rep.update && rep.update();
  281. mode.add(Mode.draging);
  282. transformIngShapes.value.push($shape);
  283. };
  284. let isEnter = false;
  285. transformer.on("pointerdown.shapemer", downHandler);
  286. transformer.on("transform.shapemer", updateTransform);
  287. const stop = listener(
  288. $shape.getStage()!.container(),
  289. "pointerup",
  290. () => {
  291. if (isEnter) {
  292. mode.pop();
  293. transform.value = void 0;
  294. isEnter = false;
  295. const ndx = transformIngShapes.value.indexOf($shape);
  296. ~ndx && transformIngShapes.value.splice(ndx, 1);
  297. }
  298. }
  299. );
  300. // 拖拽时要更新矩阵
  301. let prevMoveTf: Transform | null = null;
  302. const stopDragWatch = watch(
  303. offset,
  304. (translate, oldTranslate) => {
  305. if (translate) {
  306. if (!oldTranslate) {
  307. rep.update && rep.update();
  308. }
  309. const moveTf = new Transform().translate(
  310. translate.x,
  311. translate.y
  312. );
  313. const finalTf = moveTf.copy();
  314. prevMoveTf && finalTf.multiply(prevMoveTf.invert());
  315. finalTf.multiply(rep.tempShape.getTransform());
  316. prevMoveTf = moveTf;
  317. setShapeTransform(rep.tempShape, finalTf);
  318. transformer.fire("transform");
  319. // updateTransform()
  320. } else {
  321. prevMoveTf = null;
  322. transform.value = void 0;
  323. }
  324. },
  325. { immediate: true }
  326. );
  327. const stopTransformerForceUpdate = watch(
  328. viewTransform,
  329. () => transformer.forceUpdate(),
  330. { flush: "post" }
  331. );
  332. onCleanup(() => {
  333. for (const key in oldConfig) {
  334. (transformer as any)[key](oldConfig[key]);
  335. }
  336. stopTransformerForceUpdate();
  337. stop();
  338. stopDragWatch();
  339. // parent.add($shape);
  340. // TODO: 有可能transformer已经转移
  341. if (transformer.queueShapes.value.includes($shape)) {
  342. transformer.nodes([]);
  343. transformer.queueShapes.value = [];
  344. // transformer.remove();
  345. }
  346. transform.value = void 0;
  347. rep.destory();
  348. if (isEnter) {
  349. mode.pop();
  350. const ndx = transformIngShapes.value.indexOf($shape);
  351. ~ndx && transformIngShapes.value.splice(ndx, 1);
  352. }
  353. transformer.off("pointerdown.shapemer", downHandler);
  354. transformer.off("transform.shapemer", updateTransform);
  355. });
  356. },
  357. { immediate: true }
  358. );
  359. watch(
  360. () => shape.value,
  361. (shape, _) => {
  362. if (!shape) return;
  363. watch(
  364. () => can.editMode || mode.include(Mode.update),
  365. (canEdit, _, onCleanup) => {
  366. if (canEdit) {
  367. const stop = init(shape.getStage());
  368. onCleanup(stop);
  369. } else {
  370. onCleanup(() => {});
  371. }
  372. },
  373. {immediate: true}
  374. );
  375. }
  376. );
  377. return transform;
  378. };
  379. export const cloneRepShape = <T extends EntityShape>(
  380. shape: T
  381. ): ReturnType<GetRepShape<T, any>> => {
  382. return {
  383. shape: shape.clone({
  384. fill: "rgb(0, 255, 0)",
  385. visible: false,
  386. strokeWidth: 0,
  387. }),
  388. update: (_, rep) => {
  389. setShapeTransform(rep, shape.getTransform());
  390. },
  391. };
  392. };
  393. export const transformerRepShapeHandler = <T extends EntityShape>(
  394. transformer: TransformerExtends,
  395. shape: T,
  396. repShape: T
  397. ) => {
  398. if (import.meta.env.DEV) {
  399. repShape.visible(true);
  400. repShape.opacity(0.1);
  401. }
  402. shape.parent!.add(repShape);
  403. repShape.zIndex(shape.getZIndex());
  404. transformer.nodes([repShape]);
  405. transformer.queueShapes.value = [shape];
  406. return [
  407. repShape,
  408. () => {
  409. repShape.remove();
  410. },
  411. ] as const;
  412. };
  413. type GetRepShape<T extends EntityShape, K extends object> = (shape: T) => {
  414. shape: T;
  415. update?: (data: K, shape: T) => void;
  416. };
  417. export type CustomTransformerProps<
  418. T extends BaseItem,
  419. S extends EntityShape
  420. > = {
  421. openSnap?: boolean;
  422. getRepShape?: GetRepShape<S, T>;
  423. beforeHandler?: (data: T, mat: Transform) => T;
  424. handler?: (data: T, mat: Transform) => Transform | void | true;
  425. callback?: (data: T) => void;
  426. transformerConfig?: TransformerConfig;
  427. };
  428. export const useCustomTransformer = <T extends BaseItem, S extends EntityShape>(
  429. shape: Ref<DC<S> | undefined>,
  430. data: Ref<T>,
  431. props: CustomTransformerProps<T, S>
  432. ) => {
  433. const { getRepShape, handler, callback, openSnap, transformerConfig } = props;
  434. const needSnap = openSnap && useComponentSnap(data.value.id);
  435. const transformer = useTransformer();
  436. let repResult: ReturnType<GetRepShape<S, T>>;
  437. const transform = useShapeTransformer(
  438. shape,
  439. transformerConfig,
  440. getRepShape &&
  441. ((transformer: TransformerExtends, shape) => {
  442. repResult = getRepShape(shape);
  443. const [_, destory] = transformerRepShapeHandler(
  444. transformer,
  445. shape,
  446. repResult.shape
  447. );
  448. return {
  449. tempShape: repResult.shape,
  450. update: () => {
  451. repResult.update && repResult.update(data.value, repResult.shape);
  452. },
  453. destory,
  454. };
  455. })
  456. );
  457. watch(transform, (current, oldTransform) => {
  458. if (current) {
  459. if (!handler) return;
  460. const snapData = props.beforeHandler
  461. ? props.beforeHandler(data.value, current)
  462. : data.value;
  463. let nTransform;
  464. if (needSnap && (nTransform = needSnap[0](snapData))) {
  465. current = nTransform.multiply(current);
  466. }
  467. const mat = handler(data.value, current);
  468. if (mat) {
  469. if (repResult.update) {
  470. repResult.update(data.value, repResult.shape);
  471. } else if (mat !== true) {
  472. setShapeTransform(repResult.shape, mat);
  473. }
  474. transformer.forceUpdate();
  475. }
  476. } else if (oldTransform) {
  477. needSnap && needSnap[1]();
  478. callback && callback(data.value);
  479. }
  480. });
  481. return transform;
  482. };
  483. export type LineTransformerData = BaseItem & {
  484. points: Pos[];
  485. attitude: number[];
  486. };
  487. export const useLineTransformer = <T extends LineTransformerData>(
  488. shape: Ref<DC<Line> | undefined>,
  489. data: Ref<T>,
  490. callback: (data: T) => void,
  491. genRepShape?: ($shape: Line) => Line
  492. ) => {
  493. let tempShape: Line;
  494. let inverAttitude: Transform;
  495. let stableVs = data.value.points;
  496. let tempVs = data.value.points;
  497. useCustomTransformer(shape, data, {
  498. openSnap: true,
  499. beforeHandler(data, mat) {
  500. const transfrom = mat.copy().multiply(inverAttitude);
  501. return {
  502. ...data,
  503. points: stableVs.map((v) => transfrom.point(v)),
  504. };
  505. },
  506. handler(data, mat) {
  507. // 顶点更新
  508. const transfrom = mat.copy().multiply(inverAttitude);
  509. data.points = tempVs = stableVs.map((v) => transfrom.point(v));
  510. },
  511. callback(data) {
  512. data.attitude = tempShape.getTransform().m;
  513. data.points = stableVs = tempVs;
  514. callback(data);
  515. },
  516. getRepShape($shape) {
  517. let repShape: Line;
  518. if (genRepShape) {
  519. repShape = genRepShape($shape);
  520. } else {
  521. repShape = cloneRepShape($shape).shape;
  522. }
  523. tempShape = repShape;
  524. const update = (data: T) => {
  525. const attitude = new Transform(data.attitude);
  526. const inverMat = attitude.copy().invert();
  527. setShapeTransform(repShape, attitude);
  528. const initVs = data.points.map((v) => inverMat.point(v));
  529. stableVs = tempVs = data.points;
  530. repShape.points(flatPositions(initVs));
  531. repShape.closed(true);
  532. inverAttitude = inverMat;
  533. };
  534. update(data.value);
  535. return {
  536. update,
  537. shape: repShape,
  538. };
  539. },
  540. });
  541. };
  542. export const useMatCompTransformer = <T extends BaseItem & { mat: number[] }>(
  543. shape: Ref<DC<EntityShape> | undefined>,
  544. data: Ref<T>,
  545. callback: (data: T) => void
  546. ) => {
  547. return useCustomTransformer(shape, data, {
  548. beforeHandler(data, mat) {
  549. return { ...data, mat: mat.m };
  550. },
  551. handler(data, mat) {
  552. data.mat = mat.m;
  553. },
  554. getRepShape: cloneRepShape,
  555. callback,
  556. openSnap: true,
  557. });
  558. };