resizeTest.ts 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. import {
  2. ExcalidrawElement,
  3. PointerType,
  4. NonDeletedExcalidrawElement,
  5. } from "./types";
  6. import {
  7. OMIT_SIDES_FOR_MULTIPLE_ELEMENTS,
  8. getTransformHandlesFromCoords,
  9. getTransformHandles,
  10. TransformHandleType,
  11. TransformHandle,
  12. MaybeTransformHandleType,
  13. } from "./transformHandles";
  14. import { AppState, Zoom } from "../types";
  15. const isInsideTransformHandle = (
  16. transformHandle: TransformHandle,
  17. x: number,
  18. y: number,
  19. ) =>
  20. x >= transformHandle[0] &&
  21. x <= transformHandle[0] + transformHandle[2] &&
  22. y >= transformHandle[1] &&
  23. y <= transformHandle[1] + transformHandle[3];
  24. export const resizeTest = (
  25. element: NonDeletedExcalidrawElement,
  26. appState: AppState,
  27. x: number,
  28. y: number,
  29. zoom: Zoom,
  30. pointerType: PointerType,
  31. ): MaybeTransformHandleType => {
  32. if (!appState.selectedElementIds[element.id]) {
  33. return false;
  34. }
  35. const { rotation: rotationTransformHandle, ...transformHandles } =
  36. getTransformHandles(element, zoom, pointerType);
  37. if (
  38. rotationTransformHandle &&
  39. isInsideTransformHandle(rotationTransformHandle, x, y)
  40. ) {
  41. return "rotation" as TransformHandleType;
  42. }
  43. const filter = Object.keys(transformHandles).filter((key) => {
  44. const transformHandle =
  45. transformHandles[key as Exclude<TransformHandleType, "rotation">]!;
  46. if (!transformHandle) {
  47. return false;
  48. }
  49. return isInsideTransformHandle(transformHandle, x, y);
  50. });
  51. if (filter.length > 0) {
  52. return filter[0] as TransformHandleType;
  53. }
  54. return false;
  55. };
  56. export const getElementWithTransformHandleType = (
  57. elements: readonly NonDeletedExcalidrawElement[],
  58. appState: AppState,
  59. scenePointerX: number,
  60. scenePointerY: number,
  61. zoom: Zoom,
  62. pointerType: PointerType,
  63. ) => {
  64. return elements.reduce((result, element) => {
  65. if (result) {
  66. return result;
  67. }
  68. const transformHandleType = resizeTest(
  69. element,
  70. appState,
  71. scenePointerX,
  72. scenePointerY,
  73. zoom,
  74. pointerType,
  75. );
  76. return transformHandleType ? { element, transformHandleType } : null;
  77. }, null as { element: NonDeletedExcalidrawElement; transformHandleType: MaybeTransformHandleType } | null);
  78. };
  79. export const getTransformHandleTypeFromCoords = (
  80. [x1, y1, x2, y2]: readonly [number, number, number, number],
  81. scenePointerX: number,
  82. scenePointerY: number,
  83. zoom: Zoom,
  84. pointerType: PointerType,
  85. ): MaybeTransformHandleType => {
  86. const transformHandles = getTransformHandlesFromCoords(
  87. [x1, y1, x2, y2],
  88. 0,
  89. zoom,
  90. pointerType,
  91. OMIT_SIDES_FOR_MULTIPLE_ELEMENTS,
  92. );
  93. const found = Object.keys(transformHandles).find((key) => {
  94. const transformHandle =
  95. transformHandles[key as Exclude<TransformHandleType, "rotation">]!;
  96. return (
  97. transformHandle &&
  98. isInsideTransformHandle(transformHandle, scenePointerX, scenePointerY)
  99. );
  100. });
  101. return (found || false) as MaybeTransformHandleType;
  102. };
  103. const RESIZE_CURSORS = ["ns", "nesw", "ew", "nwse"];
  104. const rotateResizeCursor = (cursor: string, angle: number) => {
  105. const index = RESIZE_CURSORS.indexOf(cursor);
  106. if (index >= 0) {
  107. const a = Math.round(angle / (Math.PI / 4));
  108. cursor = RESIZE_CURSORS[(index + a) % RESIZE_CURSORS.length];
  109. }
  110. return cursor;
  111. };
  112. /*
  113. * Returns bi-directional cursor for the element being resized
  114. */
  115. export const getCursorForResizingElement = (resizingElement: {
  116. element?: ExcalidrawElement;
  117. transformHandleType: MaybeTransformHandleType;
  118. }): string => {
  119. const { element, transformHandleType } = resizingElement;
  120. const shouldSwapCursors =
  121. element && Math.sign(element.height) * Math.sign(element.width) === -1;
  122. let cursor = null;
  123. switch (transformHandleType) {
  124. case "n":
  125. case "s":
  126. cursor = "ns";
  127. break;
  128. case "w":
  129. case "e":
  130. cursor = "ew";
  131. break;
  132. case "nw":
  133. case "se":
  134. if (shouldSwapCursors) {
  135. cursor = "nesw";
  136. } else {
  137. cursor = "nwse";
  138. }
  139. break;
  140. case "ne":
  141. case "sw":
  142. if (shouldSwapCursors) {
  143. cursor = "nwse";
  144. } else {
  145. cursor = "nesw";
  146. }
  147. break;
  148. case "rotation":
  149. return "grab";
  150. }
  151. if (cursor && element) {
  152. cursor = rotateResizeCursor(cursor, element.angle);
  153. }
  154. return cursor ? `${cursor}-resize` : "";
  155. };