use-transformer.ts 18 KB

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