David Luzar 4 лет назад
Родитель
Сommit
950bcd0b72

+ 30 - 27
src/components/App.tsx

@@ -9,7 +9,6 @@ import {
   newElement,
   newTextElement,
   duplicateElement,
-  resizeTest,
   isInvisiblySmallElement,
   isTextElement,
   textWysiwyg,
@@ -22,10 +21,10 @@ import {
   getSyncableElements,
   newLinearElement,
   resizeElements,
-  getElementWithResizeHandler,
+  getElementWithTransformHandleType,
   getResizeOffsetXY,
   getResizeArrowDirection,
-  getResizeHandlerFromCoords,
+  getTransformHandleTypeFromCoords,
   isNonDeletedElement,
   updateTextElement,
   dragSelectedElements,
@@ -176,6 +175,7 @@ import {
   isLinearElementSimpleAndAlreadyBound,
   isBindingEnabled,
 } from "../element/binding";
+import { MaybeTransformHandleType } from "../element/transformHandles";
 
 /**
  * @param func handler taking at most single parameter (event).
@@ -221,7 +221,7 @@ type PointerDownState = Readonly<{
   lastCoords: { x: number; y: number };
   resize: {
     // Handle when resizing, might change during the pointer interaction
-    handle: ReturnType<typeof resizeTest>;
+    handleType: MaybeTransformHandleType;
     // This is determined on the initial pointer down event
     isResizing: boolean;
     // This is determined on the initial pointer down event
@@ -2057,7 +2057,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
       !isOverScrollBar &&
       !this.state.editingLinearElement
     ) {
-      const elementWithResizeHandler = getElementWithResizeHandler(
+      const elementWithTransformHandleType = getElementWithTransformHandleType(
         elements,
         this.state,
         scenePointerX,
@@ -2065,23 +2065,26 @@ class App extends React.Component<ExcalidrawProps, AppState> {
         this.state.zoom,
         event.pointerType,
       );
-      if (elementWithResizeHandler && elementWithResizeHandler.resizeHandle) {
+      if (
+        elementWithTransformHandleType &&
+        elementWithTransformHandleType.transformHandleType
+      ) {
         document.documentElement.style.cursor = getCursorForResizingElement(
-          elementWithResizeHandler,
+          elementWithTransformHandleType,
         );
         return;
       }
     } else if (selectedElements.length > 1 && !isOverScrollBar) {
-      const resizeHandle = getResizeHandlerFromCoords(
+      const transformHandleType = getTransformHandleTypeFromCoords(
         getCommonBounds(selectedElements),
         scenePointerX,
         scenePointerY,
         this.state.zoom,
         event.pointerType,
       );
-      if (resizeHandle) {
+      if (transformHandleType) {
         document.documentElement.style.cursor = getCursorForResizingElement({
-          resizeHandle,
+          transformHandleType,
         });
         return;
       }
@@ -2363,7 +2366,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
       // we need to duplicate because we'll be updating this state
       lastCoords: { ...origin },
       resize: {
-        handle: false as ReturnType<typeof resizeTest>,
+        handleType: false,
         isResizing: false,
         offset: { x: 0, y: 0 },
         arrowDirection: "origin",
@@ -2446,7 +2449,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
       const elements = this.scene.getElements();
       const selectedElements = getSelectedElements(elements, this.state);
       if (selectedElements.length === 1 && !this.state.editingLinearElement) {
-        const elementWithResizeHandler = getElementWithResizeHandler(
+        const elementWithTransformHandleType = getElementWithTransformHandleType(
           elements,
           this.state,
           pointerDownState.origin.x,
@@ -2454,15 +2457,15 @@ class App extends React.Component<ExcalidrawProps, AppState> {
           this.state.zoom,
           event.pointerType,
         );
-        if (elementWithResizeHandler != null) {
+        if (elementWithTransformHandleType != null) {
           this.setState({
-            resizingElement: elementWithResizeHandler.element,
+            resizingElement: elementWithTransformHandleType.element,
           });
-          pointerDownState.resize.handle =
-            elementWithResizeHandler.resizeHandle;
+          pointerDownState.resize.handleType =
+            elementWithTransformHandleType.transformHandleType;
         }
       } else if (selectedElements.length > 1) {
-        pointerDownState.resize.handle = getResizeHandlerFromCoords(
+        pointerDownState.resize.handleType = getTransformHandleTypeFromCoords(
           getCommonBounds(selectedElements),
           pointerDownState.origin.x,
           pointerDownState.origin.y,
@@ -2470,14 +2473,14 @@ class App extends React.Component<ExcalidrawProps, AppState> {
           event.pointerType,
         );
       }
-      if (pointerDownState.resize.handle) {
+      if (pointerDownState.resize.handleType) {
         document.documentElement.style.cursor = getCursorForResizingElement({
-          resizeHandle: pointerDownState.resize.handle,
+          transformHandleType: pointerDownState.resize.handleType,
         });
         pointerDownState.resize.isResizing = true;
         pointerDownState.resize.offset = tupleToCoors(
           getResizeOffsetXY(
-            pointerDownState.resize.handle,
+            pointerDownState.resize.handleType,
             selectedElements,
             pointerDownState.origin.x,
             pointerDownState.origin.y,
@@ -2489,7 +2492,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
           selectedElements[0].points.length === 2
         ) {
           pointerDownState.resize.arrowDirection = getResizeArrowDirection(
-            pointerDownState.resize.handle,
+            pointerDownState.resize.handleType,
             selectedElements[0],
           );
         }
@@ -2794,13 +2797,13 @@ class App extends React.Component<ExcalidrawProps, AppState> {
           this.scene.getElements(),
           this.state,
         );
-        const resizeHandle = pointerDownState.resize.handle;
+        const transformHandleType = pointerDownState.resize.handleType;
         this.setState({
           // TODO: rename this state field to "isScaling" to distinguish
           // it from the generic "isResizing" which includes scaling and
           // rotating
-          isResizing: resizeHandle && resizeHandle !== "rotation",
-          isRotating: resizeHandle === "rotation",
+          isResizing: transformHandleType && transformHandleType !== "rotation",
+          isRotating: transformHandleType === "rotation",
         });
         const [resizeX, resizeY] = getGridPoint(
           pointerCoords.x - pointerDownState.resize.offset.x,
@@ -2809,9 +2812,9 @@ class App extends React.Component<ExcalidrawProps, AppState> {
         );
         if (
           resizeElements(
-            resizeHandle,
-            (newResizeHandle) => {
-              pointerDownState.resize.handle = newResizeHandle;
+            transformHandleType,
+            (newTransformHandle) => {
+              pointerDownState.resize.handleType = newTransformHandle;
             },
             selectedElements,
             pointerDownState.resize.arrowDirection,

+ 6 - 6
src/element/index.ts

@@ -23,16 +23,16 @@ export {
 
 export {
   OMIT_SIDES_FOR_MULTIPLE_ELEMENTS,
-  handlerRectanglesFromCoords,
-  handlerRectangles,
-} from "./handlerRectangles";
+  getTransformHandlesFromCoords,
+  getTransformHandles,
+} from "./transformHandles";
 export { hitTest } from "./collision";
 export {
   resizeTest,
   getCursorForResizingElement,
-  normalizeResizeHandle,
-  getElementWithResizeHandler,
-  getResizeHandlerFromCoords,
+  normalizeTransformHandleType,
+  getElementWithTransformHandleType,
+  getTransformHandleTypeFromCoords,
 } from "./resizeTest";
 export {
   resizeElements,

+ 74 - 55
src/element/resizeElements.ts

@@ -17,12 +17,15 @@ import { isLinearElement } from "./typeChecks";
 import { mutateElement } from "./mutateElement";
 import { getPerfectElementSize } from "./sizeHelpers";
 import {
-  resizeTest,
   getCursorForResizingElement,
-  normalizeResizeHandle,
+  normalizeTransformHandleType,
 } from "./resizeTest";
 import { measureText, getFontString } from "../utils";
 import { updateBoundElements } from "./binding";
+import {
+  TransformHandleType,
+  MaybeTransformHandleType,
+} from "./transformHandles";
 
 const normalizeAngle = (angle: number): number => {
   if (angle >= 2 * Math.PI) {
@@ -31,12 +34,10 @@ const normalizeAngle = (angle: number): number => {
   return angle;
 };
 
-type ResizeTestType = ReturnType<typeof resizeTest>;
-
 // Returns true when a resize (scaling/rotation) happened
 export const resizeElements = (
-  resizeHandle: ResizeTestType,
-  setResizeHandle: (nextResizeHandle: ResizeTestType) => void,
+  transformHandleType: MaybeTransformHandleType,
+  setTransformHandle: (nextTransformHandle: MaybeTransformHandleType) => void,
   selectedElements: readonly NonDeletedExcalidrawElement[],
   resizeArrowDirection: "origin" | "end",
   isRotateWithDiscreteAngle: boolean,
@@ -50,7 +51,7 @@ export const resizeElements = (
 ) => {
   if (selectedElements.length === 1) {
     const [element] = selectedElements;
-    if (resizeHandle === "rotation") {
+    if (transformHandleType === "rotation") {
       rotateSingleElement(
         element,
         pointerX,
@@ -61,10 +62,10 @@ export const resizeElements = (
     } else if (
       isLinearElement(element) &&
       element.points.length === 2 &&
-      (resizeHandle === "nw" ||
-        resizeHandle === "ne" ||
-        resizeHandle === "sw" ||
-        resizeHandle === "se")
+      (transformHandleType === "nw" ||
+        transformHandleType === "ne" ||
+        transformHandleType === "sw" ||
+        transformHandleType === "se")
     ) {
       resizeSingleTwoPointElement(
         element,
@@ -75,28 +76,30 @@ export const resizeElements = (
       );
     } else if (
       element.type === "text" &&
-      (resizeHandle === "nw" ||
-        resizeHandle === "ne" ||
-        resizeHandle === "sw" ||
-        resizeHandle === "se")
+      (transformHandleType === "nw" ||
+        transformHandleType === "ne" ||
+        transformHandleType === "sw" ||
+        transformHandleType === "se")
     ) {
       resizeSingleTextElement(
         element,
-        resizeHandle,
+        transformHandleType,
         isResizeCenterPoint,
         pointerX,
         pointerY,
       );
-    } else if (resizeHandle) {
+    } else if (transformHandleType) {
       resizeSingleElement(
         element,
-        resizeHandle,
+        transformHandleType,
         isResizeWithSidesSameLength,
         isResizeCenterPoint,
         pointerX,
         pointerY,
       );
-      setResizeHandle(normalizeResizeHandle(element, resizeHandle));
+      setTransformHandle(
+        normalizeTransformHandleType(element, transformHandleType),
+      );
       if (element.width < 0) {
         mutateElement(element, { width: -element.width });
       }
@@ -109,12 +112,12 @@ export const resizeElements = (
     // FIXME it is not very nice to have this here
     document.documentElement.style.cursor = getCursorForResizingElement({
       element,
-      resizeHandle,
+      transformHandleType,
     });
 
     return true;
   } else if (selectedElements.length > 1) {
-    if (resizeHandle === "rotation") {
+    if (transformHandleType === "rotation") {
       rotateMultipleElements(
         selectedElements,
         pointerX,
@@ -126,14 +129,14 @@ export const resizeElements = (
       );
       return true;
     } else if (
-      resizeHandle === "nw" ||
-      resizeHandle === "ne" ||
-      resizeHandle === "sw" ||
-      resizeHandle === "se"
+      transformHandleType === "nw" ||
+      transformHandleType === "ne" ||
+      transformHandleType === "sw" ||
+      transformHandleType === "se"
     ) {
       resizeMultipleElements(
         selectedElements,
-        resizeHandle,
+        transformHandleType,
         pointerX,
         pointerY,
       );
@@ -257,29 +260,29 @@ const measureFontSizeFromWH = (
   };
 };
 
-const getSidesForResizeHandle = (
-  resizeHandle: "n" | "s" | "w" | "e" | "nw" | "ne" | "sw" | "se",
+const getSidesForTransformHandle = (
+  transformHandleType: TransformHandleType,
   isResizeFromCenter: boolean,
 ) => {
   return {
     n:
-      /^(n|ne|nw)$/.test(resizeHandle) ||
-      (isResizeFromCenter && /^(s|se|sw)$/.test(resizeHandle)),
+      /^(n|ne|nw)$/.test(transformHandleType) ||
+      (isResizeFromCenter && /^(s|se|sw)$/.test(transformHandleType)),
     s:
-      /^(s|se|sw)$/.test(resizeHandle) ||
-      (isResizeFromCenter && /^(n|ne|nw)$/.test(resizeHandle)),
+      /^(s|se|sw)$/.test(transformHandleType) ||
+      (isResizeFromCenter && /^(n|ne|nw)$/.test(transformHandleType)),
     w:
-      /^(w|nw|sw)$/.test(resizeHandle) ||
-      (isResizeFromCenter && /^(e|ne|se)$/.test(resizeHandle)),
+      /^(w|nw|sw)$/.test(transformHandleType) ||
+      (isResizeFromCenter && /^(e|ne|se)$/.test(transformHandleType)),
     e:
-      /^(e|ne|se)$/.test(resizeHandle) ||
-      (isResizeFromCenter && /^(w|nw|sw)$/.test(resizeHandle)),
+      /^(e|ne|se)$/.test(transformHandleType) ||
+      (isResizeFromCenter && /^(w|nw|sw)$/.test(transformHandleType)),
   };
 };
 
 const resizeSingleTextElement = (
   element: NonDeleted<ExcalidrawTextElement>,
-  resizeHandle: "nw" | "ne" | "sw" | "se",
+  transformHandleType: "nw" | "ne" | "sw" | "se",
   isResizeFromCenter: boolean,
   pointerX: number,
   pointerY: number,
@@ -296,7 +299,7 @@ const resizeSingleTextElement = (
     -element.angle,
   );
   let scale;
-  switch (resizeHandle) {
+  switch (transformHandleType) {
     case "se":
       scale = Math.max(
         (rotatedX - x1) / (x2 - x1),
@@ -339,7 +342,7 @@ const resizeSingleTextElement = (
     const deltaX2 = (x2 - nextX2) / 2;
     const deltaY2 = (y2 - nextY2) / 2;
     const [nextElementX, nextElementY] = adjustXYWithRotation(
-      getSidesForResizeHandle(resizeHandle, isResizeFromCenter),
+      getSidesForTransformHandle(transformHandleType, isResizeFromCenter),
       element.x,
       element.y,
       element.angle,
@@ -361,7 +364,7 @@ const resizeSingleTextElement = (
 
 const resizeSingleElement = (
   element: NonDeletedExcalidrawElement,
-  resizeHandle: "n" | "s" | "w" | "e" | "nw" | "ne" | "sw" | "se",
+  transformHandleType: "n" | "s" | "w" | "e" | "nw" | "ne" | "sw" | "se",
   sidesWithSameLength: boolean,
   isResizeFromCenter: boolean,
   pointerX: number,
@@ -380,16 +383,32 @@ const resizeSingleElement = (
   );
   let scaleX = 1;
   let scaleY = 1;
-  if (resizeHandle === "e" || resizeHandle === "ne" || resizeHandle === "se") {
+  if (
+    transformHandleType === "e" ||
+    transformHandleType === "ne" ||
+    transformHandleType === "se"
+  ) {
     scaleX = (rotatedX - x1) / (x2 - x1);
   }
-  if (resizeHandle === "s" || resizeHandle === "sw" || resizeHandle === "se") {
+  if (
+    transformHandleType === "s" ||
+    transformHandleType === "sw" ||
+    transformHandleType === "se"
+  ) {
     scaleY = (rotatedY - y1) / (y2 - y1);
   }
-  if (resizeHandle === "w" || resizeHandle === "nw" || resizeHandle === "sw") {
+  if (
+    transformHandleType === "w" ||
+    transformHandleType === "nw" ||
+    transformHandleType === "sw"
+  ) {
     scaleX = (x2 - rotatedX) / (x2 - x1);
   }
-  if (resizeHandle === "n" || resizeHandle === "nw" || resizeHandle === "ne") {
+  if (
+    transformHandleType === "n" ||
+    transformHandleType === "nw" ||
+    transformHandleType === "ne"
+  ) {
     scaleY = (y2 - rotatedY) / (y2 - y1);
   }
   let nextWidth = element.width * scaleX;
@@ -419,7 +438,7 @@ const resizeSingleElement = (
     Math.abs(nextHeight),
   );
   const [flipDiffX, flipDiffY] = getFlipAdjustment(
-    resizeHandle,
+    transformHandleType,
     nextWidth,
     nextHeight,
     nextX1,
@@ -434,7 +453,7 @@ const resizeSingleElement = (
     element.angle,
   );
   const [nextElementX, nextElementY] = adjustXYWithRotation(
-    getSidesForResizeHandle(resizeHandle, isResizeFromCenter),
+    getSidesForTransformHandle(transformHandleType, isResizeFromCenter),
     element.x - flipDiffX,
     element.y - flipDiffY,
     element.angle,
@@ -461,7 +480,7 @@ const resizeSingleElement = (
 
 const resizeMultipleElements = (
   elements: readonly NonDeletedExcalidrawElement[],
-  resizeHandle: "nw" | "ne" | "sw" | "se",
+  transformHandleType: "nw" | "ne" | "sw" | "se",
   pointerX: number,
   pointerY: number,
 ) => {
@@ -472,7 +491,7 @@ const resizeMultipleElements = (
     origCoords: readonly [number, number, number, number],
     finalCoords: readonly [number, number, number, number],
   ) => { x: number; y: number };
-  switch (resizeHandle) {
+  switch (transformHandleType) {
     case "se":
       scale = Math.max(
         (pointerX - x1) / (x2 - x1),
@@ -651,7 +670,7 @@ const rotateMultipleElements = (
 };
 
 export const getResizeOffsetXY = (
-  resizeHandle: ResizeTestType,
+  transformHandleType: MaybeTransformHandleType,
   selectedElements: NonDeletedExcalidrawElement[],
   x: number,
   y: number,
@@ -664,7 +683,7 @@ export const getResizeOffsetXY = (
   const cy = (y1 + y2) / 2;
   const angle = selectedElements.length === 1 ? selectedElements[0].angle : 0;
   [x, y] = rotate(x, y, cx, cy, -angle);
-  switch (resizeHandle) {
+  switch (transformHandleType) {
     case "n":
       return rotate(x - (x1 + x2) / 2, y - y1, 0, 0, angle);
     case "s":
@@ -687,14 +706,14 @@ export const getResizeOffsetXY = (
 };
 
 export const getResizeArrowDirection = (
-  resizeHandle: ResizeTestType,
+  transformHandleType: MaybeTransformHandleType,
   element: NonDeleted<ExcalidrawLinearElement>,
 ): "origin" | "end" => {
   const [, [px, py]] = element.points;
   const isResizeEnd =
-    (resizeHandle === "nw" && (px < 0 || py < 0)) ||
-    (resizeHandle === "ne" && px >= 0) ||
-    (resizeHandle === "sw" && px <= 0) ||
-    (resizeHandle === "se" && (px > 0 || py > 0));
+    (transformHandleType === "nw" && (px < 0 || py < 0)) ||
+    (transformHandleType === "ne" && px >= 0) ||
+    (transformHandleType === "sw" && px <= 0) ||
+    (transformHandleType === "se" && (px > 0 || py > 0));
   return isResizeEnd ? "end" : "origin";
 };

+ 56 - 46
src/element/resizeTest.ts

@@ -6,22 +6,23 @@ import {
 
 import {
   OMIT_SIDES_FOR_MULTIPLE_ELEMENTS,
-  handlerRectanglesFromCoords,
-  handlerRectangles,
-} from "./handlerRectangles";
+  getTransformHandlesFromCoords,
+  getTransformHandles,
+  TransformHandleType,
+  TransformHandle,
+  MaybeTransformHandleType,
+} from "./transformHandles";
 import { AppState } from "../types";
 
-type HandlerRectanglesRet = keyof ReturnType<typeof handlerRectangles>;
-
-const isInHandlerRect = (
-  handler: [number, number, number, number],
+const isInsideTransformHandle = (
+  transformHandle: TransformHandle,
   x: number,
   y: number,
 ) =>
-  x >= handler[0] &&
-  x <= handler[0] + handler[2] &&
-  y >= handler[1] &&
-  y <= handler[1] + handler[3];
+  x >= transformHandle[0] &&
+  x <= transformHandle[0] + transformHandle[2] &&
+  y >= transformHandle[1] &&
+  y <= transformHandle[1] + transformHandle[3];
 
 export const resizeTest = (
   element: NonDeletedExcalidrawElement,
@@ -30,37 +31,41 @@ export const resizeTest = (
   y: number,
   zoom: number,
   pointerType: PointerType,
-): HandlerRectanglesRet | false => {
+): MaybeTransformHandleType => {
   if (!appState.selectedElementIds[element.id]) {
     return false;
   }
 
-  const { rotation: rotationHandler, ...handlers } = handlerRectangles(
-    element,
-    zoom,
-    pointerType,
-  );
+  const {
+    rotation: rotationTransformHandle,
+    ...transformHandles
+  } = getTransformHandles(element, zoom, pointerType);
 
-  if (rotationHandler && isInHandlerRect(rotationHandler, x, y)) {
-    return "rotation" as HandlerRectanglesRet;
+  if (
+    rotationTransformHandle &&
+    isInsideTransformHandle(rotationTransformHandle, x, y)
+  ) {
+    return "rotation" as TransformHandleType;
   }
 
-  const filter = Object.keys(handlers).filter((key) => {
-    const handler = handlers[key as Exclude<HandlerRectanglesRet, "rotation">]!;
-    if (!handler) {
+  const filter = Object.keys(transformHandles).filter((key) => {
+    const transformHandle = transformHandles[
+      key as Exclude<TransformHandleType, "rotation">
+    ]!;
+    if (!transformHandle) {
       return false;
     }
-    return isInHandlerRect(handler, x, y);
+    return isInsideTransformHandle(transformHandle, x, y);
   });
 
   if (filter.length > 0) {
-    return filter[0] as HandlerRectanglesRet;
+    return filter[0] as TransformHandleType;
   }
 
   return false;
 };
 
-export const getElementWithResizeHandler = (
+export const getElementWithTransformHandleType = (
   elements: readonly NonDeletedExcalidrawElement[],
   appState: AppState,
   scenePointerX: number,
@@ -72,7 +77,7 @@ export const getElementWithResizeHandler = (
     if (result) {
       return result;
     }
-    const resizeHandle = resizeTest(
+    const transformHandleType = resizeTest(
       element,
       appState,
       scenePointerX,
@@ -80,18 +85,18 @@ export const getElementWithResizeHandler = (
       zoom,
       pointerType,
     );
-    return resizeHandle ? { element, resizeHandle } : null;
-  }, null as { element: NonDeletedExcalidrawElement; resizeHandle: HandlerRectanglesRet } | null);
+    return transformHandleType ? { element, transformHandleType } : null;
+  }, null as { element: NonDeletedExcalidrawElement; transformHandleType: MaybeTransformHandleType } | null);
 };
 
-export const getResizeHandlerFromCoords = (
+export const getTransformHandleTypeFromCoords = (
   [x1, y1, x2, y2]: readonly [number, number, number, number],
   scenePointerX: number,
   scenePointerY: number,
   zoom: number,
   pointerType: PointerType,
-) => {
-  const handlers = handlerRectanglesFromCoords(
+): MaybeTransformHandleType => {
+  const transformHandles = getTransformHandlesFromCoords(
     [x1, y1, x2, y2],
     0,
     zoom,
@@ -99,11 +104,16 @@ export const getResizeHandlerFromCoords = (
     OMIT_SIDES_FOR_MULTIPLE_ELEMENTS,
   );
 
-  const found = Object.keys(handlers).find((key) => {
-    const handler = handlers[key as Exclude<HandlerRectanglesRet, "rotation">]!;
-    return handler && isInHandlerRect(handler, scenePointerX, scenePointerY);
+  const found = Object.keys(transformHandles).find((key) => {
+    const transformHandle = transformHandles[
+      key as Exclude<TransformHandleType, "rotation">
+    ]!;
+    return (
+      transformHandle &&
+      isInsideTransformHandle(transformHandle, scenePointerX, scenePointerY)
+    );
   });
-  return (found || false) as HandlerRectanglesRet;
+  return (found || false) as MaybeTransformHandleType;
 };
 
 const RESIZE_CURSORS = ["ns", "nesw", "ew", "nwse"];
@@ -121,14 +131,14 @@ const rotateResizeCursor = (cursor: string, angle: number) => {
  */
 export const getCursorForResizingElement = (resizingElement: {
   element?: ExcalidrawElement;
-  resizeHandle: ReturnType<typeof resizeTest>;
+  transformHandleType: MaybeTransformHandleType;
 }): string => {
-  const { element, resizeHandle } = resizingElement;
+  const { element, transformHandleType } = resizingElement;
   const shouldSwapCursors =
     element && Math.sign(element.height) * Math.sign(element.width) === -1;
   let cursor = null;
 
-  switch (resizeHandle) {
+  switch (transformHandleType) {
     case "n":
     case "s":
       cursor = "ns";
@@ -164,16 +174,16 @@ export const getCursorForResizingElement = (resizingElement: {
   return cursor ? `${cursor}-resize` : "";
 };
 
-export const normalizeResizeHandle = (
+export const normalizeTransformHandleType = (
   element: ExcalidrawElement,
-  resizeHandle: HandlerRectanglesRet,
-): HandlerRectanglesRet => {
+  transformHandleType: TransformHandleType,
+): TransformHandleType => {
   if (element.width >= 0 && element.height >= 0) {
-    return resizeHandle;
+    return transformHandleType;
   }
 
   if (element.width < 0 && element.height < 0) {
-    switch (resizeHandle) {
+    switch (transformHandleType) {
       case "nw":
         return "se";
       case "ne":
@@ -184,7 +194,7 @@ export const normalizeResizeHandle = (
         return "ne";
     }
   } else if (element.width < 0) {
-    switch (resizeHandle) {
+    switch (transformHandleType) {
       case "nw":
         return "ne";
       case "ne":
@@ -199,7 +209,7 @@ export const normalizeResizeHandle = (
         return "e";
     }
   } else {
-    switch (resizeHandle) {
+    switch (transformHandleType) {
       case "nw":
         return "sw";
       case "ne":
@@ -215,5 +225,5 @@ export const normalizeResizeHandle = (
     }
   }
 
-  return resizeHandle;
+  return transformHandleType;
 };

+ 80 - 71
src/element/handlerRectangles.ts → src/element/transformHandles.ts

@@ -3,19 +3,30 @@ import { ExcalidrawElement, PointerType } from "./types";
 import { getElementAbsoluteCoords, Bounds } from "./bounds";
 import { rotate } from "../math";
 
-type Sides = "n" | "s" | "w" | "e" | "nw" | "ne" | "sw" | "se" | "rotation";
-
-export type Handlers = Partial<
-  { [T in Sides]: [number, number, number, number] }
+export type TransformHandleType =
+  | "n"
+  | "s"
+  | "w"
+  | "e"
+  | "nw"
+  | "ne"
+  | "sw"
+  | "se"
+  | "rotation";
+
+export type TransformHandle = [number, number, number, number];
+export type TransformHandles = Partial<
+  { [T in TransformHandleType]: TransformHandle }
 >;
+export type MaybeTransformHandleType = TransformHandleType | false;
 
-const handleSizes: { [k in PointerType]: number } = {
+const transformHandleSizes: { [k in PointerType]: number } = {
   mouse: 8,
   pen: 16,
   touch: 28,
 };
 
-const ROTATION_HANDLER_GAP = 16;
+const ROTATION_RESIZE_HANDLE_GAP = 16;
 
 export const OMIT_SIDES_FOR_MULTIPLE_ELEMENTS = {
   e: true,
@@ -51,7 +62,7 @@ const OMIT_SIDES_FOR_LINE_BACKSLASH = {
   rotation: true,
 };
 
-const generateHandler = (
+const generateTransformHandle = (
   x: number,
   y: number,
   width: number,
@@ -59,24 +70,24 @@ const generateHandler = (
   cx: number,
   cy: number,
   angle: number,
-): [number, number, number, number] => {
+): TransformHandle => {
   const [xx, yy] = rotate(x + width / 2, y + height / 2, cx, cy, angle);
   return [xx - width / 2, yy - height / 2, width, height];
 };
 
-export const handlerRectanglesFromCoords = (
+export const getTransformHandlesFromCoords = (
   [x1, y1, x2, y2]: Bounds,
   angle: number,
   zoom: number,
-  pointerType: PointerType = "mouse",
-  omitSides: { [T in Sides]?: boolean } = {},
-): Handlers => {
-  const size = handleSizes[pointerType];
-  const handlerWidth = size / zoom;
-  const handlerHeight = size / zoom;
+  pointerType: PointerType = "touch",
+  omitSides: { [T in TransformHandleType]?: boolean } = {},
+): TransformHandles => {
+  const size = transformHandleSizes[pointerType];
+  const handleWidth = size / zoom;
+  const handleHeight = size / zoom;
 
-  const handlerMarginX = size / zoom;
-  const handlerMarginY = size / zoom;
+  const handleMarginX = size / zoom;
+  const handleMarginY = size / zoom;
 
   const width = x2 - x1;
   const height = y2 - y1;
@@ -85,116 +96,114 @@ export const handlerRectanglesFromCoords = (
 
   const dashedLineMargin = 4 / zoom;
 
-  const centeringOffset = (size - 8) / (2 * zoom);
+  const centeringOffset = 0;
 
-  const handlers: Partial<
-    { [T in Sides]: [number, number, number, number] }
-  > = {
+  const transformHandles: TransformHandles = {
     nw: omitSides["nw"]
       ? undefined
-      : generateHandler(
-          x1 - dashedLineMargin - handlerMarginX + centeringOffset,
-          y1 - dashedLineMargin - handlerMarginY + centeringOffset,
-          handlerWidth,
-          handlerHeight,
+      : generateTransformHandle(
+          x1 - dashedLineMargin - handleMarginX + centeringOffset,
+          y1 - dashedLineMargin - handleMarginY + centeringOffset,
+          handleWidth,
+          handleHeight,
           cx,
           cy,
           angle,
         ),
     ne: omitSides["ne"]
       ? undefined
-      : generateHandler(
+      : generateTransformHandle(
           x2 + dashedLineMargin - centeringOffset,
-          y1 - dashedLineMargin - handlerMarginY + centeringOffset,
-          handlerWidth,
-          handlerHeight,
+          y1 - dashedLineMargin - handleMarginY + centeringOffset,
+          handleWidth,
+          handleHeight,
           cx,
           cy,
           angle,
         ),
     sw: omitSides["sw"]
       ? undefined
-      : generateHandler(
-          x1 - dashedLineMargin - handlerMarginX + centeringOffset,
+      : generateTransformHandle(
+          x1 - dashedLineMargin - handleMarginX + centeringOffset,
           y2 + dashedLineMargin - centeringOffset,
-          handlerWidth,
-          handlerHeight,
+          handleWidth,
+          handleHeight,
           cx,
           cy,
           angle,
         ),
     se: omitSides["se"]
       ? undefined
-      : generateHandler(
+      : generateTransformHandle(
           x2 + dashedLineMargin - centeringOffset,
           y2 + dashedLineMargin - centeringOffset,
-          handlerWidth,
-          handlerHeight,
+          handleWidth,
+          handleHeight,
           cx,
           cy,
           angle,
         ),
     rotation: omitSides["rotation"]
       ? undefined
-      : generateHandler(
-          x1 + width / 2 - handlerWidth / 2,
+      : generateTransformHandle(
+          x1 + width / 2 - handleWidth / 2,
           y1 -
             dashedLineMargin -
-            handlerMarginY +
+            handleMarginY +
             centeringOffset -
-            ROTATION_HANDLER_GAP / zoom,
-          handlerWidth,
-          handlerHeight,
+            ROTATION_RESIZE_HANDLE_GAP / zoom,
+          handleWidth,
+          handleHeight,
           cx,
           cy,
           angle,
         ),
   };
 
-  // We only want to show height handlers (all cardinal directions)  above a certain size
-  const minimumSizeForEightHandlers = (5 * size) / zoom;
-  if (Math.abs(width) > minimumSizeForEightHandlers) {
+  // We only want to show height handles (all cardinal directions)  above a certain size
+  const minimumSizeForEightHandles = (5 * size) / zoom;
+  if (Math.abs(width) > minimumSizeForEightHandles) {
     if (!omitSides["n"]) {
-      handlers["n"] = generateHandler(
-        x1 + width / 2 - handlerWidth / 2,
-        y1 - dashedLineMargin - handlerMarginY + centeringOffset,
-        handlerWidth,
-        handlerHeight,
+      transformHandles["n"] = generateTransformHandle(
+        x1 + width / 2 - handleWidth / 2,
+        y1 - dashedLineMargin - handleMarginY + centeringOffset,
+        handleWidth,
+        handleHeight,
         cx,
         cy,
         angle,
       );
     }
     if (!omitSides["s"]) {
-      handlers["s"] = generateHandler(
-        x1 + width / 2 - handlerWidth / 2,
+      transformHandles["s"] = generateTransformHandle(
+        x1 + width / 2 - handleWidth / 2,
         y2 + dashedLineMargin - centeringOffset,
-        handlerWidth,
-        handlerHeight,
+        handleWidth,
+        handleHeight,
         cx,
         cy,
         angle,
       );
     }
   }
-  if (Math.abs(height) > minimumSizeForEightHandlers) {
+  if (Math.abs(height) > minimumSizeForEightHandles) {
     if (!omitSides["w"]) {
-      handlers["w"] = generateHandler(
-        x1 - dashedLineMargin - handlerMarginX + centeringOffset,
-        y1 + height / 2 - handlerHeight / 2,
-        handlerWidth,
-        handlerHeight,
+      transformHandles["w"] = generateTransformHandle(
+        x1 - dashedLineMargin - handleMarginX + centeringOffset,
+        y1 + height / 2 - handleHeight / 2,
+        handleWidth,
+        handleHeight,
         cx,
         cy,
         angle,
       );
     }
     if (!omitSides["e"]) {
-      handlers["e"] = generateHandler(
+      transformHandles["e"] = generateTransformHandle(
         x2 + dashedLineMargin - centeringOffset,
-        y1 + height / 2 - handlerHeight / 2,
-        handlerWidth,
-        handlerHeight,
+        y1 + height / 2 - handleHeight / 2,
+        handleWidth,
+        handleHeight,
         cx,
         cy,
         angle,
@@ -202,15 +211,15 @@ export const handlerRectanglesFromCoords = (
     }
   }
 
-  return handlers;
+  return transformHandles;
 };
 
-export const handlerRectangles = (
+export const getTransformHandles = (
   element: ExcalidrawElement,
   zoom: number,
-  pointerType: PointerType = "mouse",
-) => {
-  let omitSides: { [T in Sides]?: boolean } = {};
+  pointerType: PointerType = "touch",
+): TransformHandles => {
+  let omitSides: { [T in TransformHandleType]?: boolean } = {};
   if (
     element.type === "arrow" ||
     element.type === "line" ||
@@ -235,7 +244,7 @@ export const handlerRectangles = (
     omitSides = OMIT_SIDES_FOR_TEXT_ELEMENT;
   }
 
-  return handlerRectanglesFromCoords(
+  return getTransformHandlesFromCoords(
     getElementAbsoluteCoords(element),
     element.angle,
     zoom,

+ 26 - 25
src/renderer/renderScene.ts

@@ -14,8 +14,8 @@ import {
 import {
   getElementAbsoluteCoords,
   OMIT_SIDES_FOR_MULTIPLE_ELEMENTS,
-  handlerRectanglesFromCoords,
-  handlerRectangles,
+  getTransformHandlesFromCoords,
+  getTransformHandles,
   getElementBounds,
   getCommonBounds,
 } from "../element";
@@ -43,9 +43,10 @@ import {
   SuggestedPointBinding,
   isBindingEnabled,
 } from "../element/binding";
-import { Handlers } from "../element/handlerRectangles";
-
-type HandlerRectanglesRet = keyof ReturnType<typeof handlerRectangles>;
+import {
+  TransformHandles,
+  TransformHandleType,
+} from "../element/transformHandles";
 
 const strokeRectWithRotation = (
   context: CanvasRenderingContext2D,
@@ -362,18 +363,18 @@ export const renderScene = (
 
     const locallySelectedElements = getSelectedElements(elements, appState);
 
-    // Paint resize handlers
+    // Paint resize transformHandles
     context.translate(sceneState.scrollX, sceneState.scrollY);
     if (locallySelectedElements.length === 1) {
       context.fillStyle = oc.white;
-      const handlers = handlerRectangles(
+      const transformHandles = getTransformHandles(
         locallySelectedElements[0],
         sceneState.zoom,
       );
-      renderHandlers(
+      renderTransformHandles(
         context,
         sceneState,
-        handlers,
+        transformHandles,
         locallySelectedElements[0].angle,
       );
     } else if (locallySelectedElements.length > 1 && !appState.isRotating) {
@@ -396,14 +397,14 @@ export const renderScene = (
       );
       context.lineWidth = lineWidth;
       context.setLineDash(initialLineDash);
-      const handlers = handlerRectanglesFromCoords(
+      const transformHandles = getTransformHandlesFromCoords(
         [x1, y1, x2, y2],
         0,
         sceneState.zoom,
         undefined,
         OMIT_SIDES_FOR_MULTIPLE_ELEMENTS,
       );
-      renderHandlers(context, sceneState, handlers, 0);
+      renderTransformHandles(context, sceneState, transformHandles, 0);
     }
     context.translate(-sceneState.scrollX, -sceneState.scrollY);
   }
@@ -545,33 +546,33 @@ export const renderScene = (
   return { atLeastOneVisibleElement: visibleElements.length > 0, scrollBars };
 };
 
-const renderHandlers = (
+const renderTransformHandles = (
   context: CanvasRenderingContext2D,
   sceneState: SceneState,
-  handlers: Handlers,
+  transformHandles: TransformHandles,
   angle: number,
 ): void => {
-  Object.keys(handlers).forEach((key) => {
-    const handler = handlers[key as HandlerRectanglesRet];
-    if (handler !== undefined) {
+  Object.keys(transformHandles).forEach((key) => {
+    const transformHandle = transformHandles[key as TransformHandleType];
+    if (transformHandle !== undefined) {
       const lineWidth = context.lineWidth;
       context.lineWidth = 1 / sceneState.zoom;
       if (key === "rotation") {
         fillCircle(
           context,
-          handler[0] + handler[2] / 2,
-          handler[1] + handler[3] / 2,
-          handler[2] / 2,
+          transformHandle[0] + transformHandle[2] / 2,
+          transformHandle[1] + transformHandle[3] / 2,
+          transformHandle[2] / 2,
         );
       } else {
         strokeRectWithRotation(
           context,
-          handler[0],
-          handler[1],
-          handler[2],
-          handler[3],
-          handler[0] + handler[2] / 2,
-          handler[1] + handler[3] / 2,
+          transformHandle[0],
+          transformHandle[1],
+          transformHandle[2],
+          transformHandle[3],
+          transformHandle[0] + transformHandle[2] / 2,
+          transformHandle[1] + transformHandle[3] / 2,
           angle,
           true, // fill before stroke
         );

+ 10 - 8
src/tests/regressionTests.test.tsx

@@ -9,7 +9,7 @@ import { ToolName } from "./queries/toolQueries";
 import { KEYS, Key } from "../keys";
 import { setDateTimeForTests } from "../utils";
 import { ExcalidrawElement } from "../element/types";
-import { handlerRectangles } from "../element";
+import { getTransformHandles as _getTransformHandles } from "../element";
 import { queryByText } from "@testing-library/react";
 import { copiedStyles } from "../actions/actionStyles";
 
@@ -192,9 +192,9 @@ function getStateHistory() {
   return h.history.stateHistory;
 }
 
-type HandlerRectanglesRet = keyof ReturnType<typeof handlerRectangles>;
-const getResizeHandles = (pointerType: "mouse" | "touch" | "pen") => {
-  const rects = handlerRectangles(
+type HandlerRectanglesRet = keyof ReturnType<typeof _getTransformHandles>;
+const getTransformHandles = (pointerType: "mouse" | "touch" | "pen") => {
+  const rects = _getTransformHandles(
     getSelectedElement(),
     h.state.zoom,
     pointerType,
@@ -362,10 +362,12 @@ describe("regression tests", () => {
     mouse.down(10, 10);
     mouse.up(10, 10);
 
-    const resizeHandles = getResizeHandles("mouse");
-    delete resizeHandles.rotation; // exclude rotation handle
-    for (const handlePos in resizeHandles) {
-      const [x, y] = resizeHandles[handlePos as keyof typeof resizeHandles];
+    const transformHandles = getTransformHandles("mouse");
+    delete transformHandles.rotation; // exclude rotation handle
+    for (const handlePos in transformHandles) {
+      const [x, y] = transformHandles[
+        handlePos as keyof typeof transformHandles
+      ];
       const { width: prevWidth, height: prevHeight } = getSelectedElement();
       mouse.restorePosition(x, y);
       mouse.down();