mirror of
				https://github.com/excalidraw/excalidraw.git
				synced 2025-11-04 12:54:23 +01:00 
			
		
		
		
	* change resize math to absolute instead of delta * typings * small change for width on rotation * apply absolute resize to other sides * revert&change math.ts * polish, polish, polish * refactor with offset * eliminate nextX * rename to offsetPointer * fix curved lines * prefer arrow function * remove unused variables/comments for now Co-authored-by: daishi <daishi@axlight.com>
This commit is contained in:
		@@ -2,7 +2,7 @@ import { AppState } from "../types";
 | 
			
		||||
import { SHIFT_LOCKING_ANGLE } from "../constants";
 | 
			
		||||
import { getSelectedElements, globalSceneState } from "../scene";
 | 
			
		||||
import { rescalePoints } from "../points";
 | 
			
		||||
import { rotate, adjustXYWithRotation } from "../math";
 | 
			
		||||
import { rotate, resizeXYWidthHightWithRotation } from "../math";
 | 
			
		||||
import {
 | 
			
		||||
  ExcalidrawLinearElement,
 | 
			
		||||
  NonDeletedExcalidrawElement,
 | 
			
		||||
@@ -27,7 +27,7 @@ export type ResizeArrowFnType = (
 | 
			
		||||
  deltaY: number,
 | 
			
		||||
  pointerX: number,
 | 
			
		||||
  pointerY: number,
 | 
			
		||||
  perfect: boolean,
 | 
			
		||||
  sidesWithSameLength: boolean,
 | 
			
		||||
) => void;
 | 
			
		||||
 | 
			
		||||
const arrowResizeOrigin: ResizeArrowFnType = (
 | 
			
		||||
@@ -37,7 +37,7 @@ const arrowResizeOrigin: ResizeArrowFnType = (
 | 
			
		||||
  deltaY,
 | 
			
		||||
  pointerX,
 | 
			
		||||
  pointerY,
 | 
			
		||||
  perfect,
 | 
			
		||||
  sidesWithSameLength,
 | 
			
		||||
) => {
 | 
			
		||||
  const [px, py] = element.points[pointIndex];
 | 
			
		||||
  let x = element.x + deltaX;
 | 
			
		||||
@@ -45,7 +45,7 @@ const arrowResizeOrigin: ResizeArrowFnType = (
 | 
			
		||||
  let pointX = px - deltaX;
 | 
			
		||||
  let pointY = py - deltaY;
 | 
			
		||||
 | 
			
		||||
  if (perfect) {
 | 
			
		||||
  if (sidesWithSameLength) {
 | 
			
		||||
    const { width, height } = getPerfectElementSize(
 | 
			
		||||
      element.type,
 | 
			
		||||
      px + element.x - pointerX,
 | 
			
		||||
@@ -73,10 +73,10 @@ const arrowResizeEnd: ResizeArrowFnType = (
 | 
			
		||||
  deltaY,
 | 
			
		||||
  pointerX,
 | 
			
		||||
  pointerY,
 | 
			
		||||
  perfect,
 | 
			
		||||
  sidesWithSameLength,
 | 
			
		||||
) => {
 | 
			
		||||
  const [px, py] = element.points[pointIndex];
 | 
			
		||||
  if (perfect) {
 | 
			
		||||
  if (sidesWithSameLength) {
 | 
			
		||||
    const { width, height } = getPerfectElementSize(
 | 
			
		||||
      element.type,
 | 
			
		||||
      pointerX - element.x,
 | 
			
		||||
@@ -96,7 +96,31 @@ const arrowResizeEnd: ResizeArrowFnType = (
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function resizeElements(
 | 
			
		||||
const applyResizeArrowFn = (
 | 
			
		||||
  element: NonDeleted<ExcalidrawLinearElement>,
 | 
			
		||||
  resizeArrowFn: ResizeArrowFnType | null,
 | 
			
		||||
  setResizeArrowFn: (fn: ResizeArrowFnType) => void,
 | 
			
		||||
  isResizeEnd: boolean,
 | 
			
		||||
  sidesWithSameLength: boolean,
 | 
			
		||||
  x: number,
 | 
			
		||||
  y: number,
 | 
			
		||||
  lastX: number,
 | 
			
		||||
  lastY: number,
 | 
			
		||||
) => {
 | 
			
		||||
  const angle = element.angle;
 | 
			
		||||
  const [deltaX, deltaY] = rotate(x - lastX, y - lastY, 0, 0, -angle);
 | 
			
		||||
  if (!resizeArrowFn) {
 | 
			
		||||
    if (isResizeEnd) {
 | 
			
		||||
      resizeArrowFn = arrowResizeEnd;
 | 
			
		||||
    } else {
 | 
			
		||||
      resizeArrowFn = arrowResizeOrigin;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  resizeArrowFn(element, 1, deltaX, deltaY, x, y, sidesWithSameLength);
 | 
			
		||||
  setResizeArrowFn(resizeArrowFn);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const resizeElements = (
 | 
			
		||||
  resizeHandle: ResizeTestType,
 | 
			
		||||
  setResizeHandle: (nextResizeHandle: ResizeTestType) => void,
 | 
			
		||||
  appState: AppState,
 | 
			
		||||
@@ -104,11 +128,11 @@ export function resizeElements(
 | 
			
		||||
  resizeArrowFn: ResizeArrowFnType | null,
 | 
			
		||||
  setResizeArrowFn: (fn: ResizeArrowFnType) => void,
 | 
			
		||||
  event: PointerEvent,
 | 
			
		||||
  x: number,
 | 
			
		||||
  y: number,
 | 
			
		||||
  xPointer: number,
 | 
			
		||||
  yPointer: number,
 | 
			
		||||
  lastX: number,
 | 
			
		||||
  lastY: number,
 | 
			
		||||
) {
 | 
			
		||||
) => {
 | 
			
		||||
  setAppState({
 | 
			
		||||
    isResizing: resizeHandle !== "rotation",
 | 
			
		||||
    isRotating: resizeHandle === "rotation",
 | 
			
		||||
@@ -117,224 +141,79 @@ export function resizeElements(
 | 
			
		||||
    globalSceneState.getElements(),
 | 
			
		||||
    appState,
 | 
			
		||||
  );
 | 
			
		||||
  const handleOffset = 4 / appState.zoom; // XXX import constant
 | 
			
		||||
  const dashedLinePadding = 4 / appState.zoom; // XXX import constant
 | 
			
		||||
  const offsetPointer = handleOffset + dashedLinePadding;
 | 
			
		||||
  const minSize = handleOffset * 4;
 | 
			
		||||
  if (selectedElements.length === 1) {
 | 
			
		||||
    const element = selectedElements[0];
 | 
			
		||||
    const angle = element.angle;
 | 
			
		||||
    // reverse rotate delta
 | 
			
		||||
    const [deltaX, deltaY] = rotate(x - lastX, y - lastY, 0, 0, -angle);
 | 
			
		||||
    switch (resizeHandle) {
 | 
			
		||||
      case "nw":
 | 
			
		||||
        if (isLinearElement(element) && element.points.length === 2) {
 | 
			
		||||
          const [, p1] = element.points;
 | 
			
		||||
 | 
			
		||||
          if (!resizeArrowFn) {
 | 
			
		||||
            if (p1[0] < 0 || p1[1] < 0) {
 | 
			
		||||
              resizeArrowFn = arrowResizeEnd;
 | 
			
		||||
            } else {
 | 
			
		||||
              resizeArrowFn = arrowResizeOrigin;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
          resizeArrowFn(element, 1, deltaX, deltaY, x, y, event.shiftKey);
 | 
			
		||||
          setResizeArrowFn(resizeArrowFn);
 | 
			
		||||
        } else {
 | 
			
		||||
          const width = element.width - deltaX;
 | 
			
		||||
          const height = event.shiftKey ? width : element.height - deltaY;
 | 
			
		||||
          const dY = element.height - height;
 | 
			
		||||
          mutateElement(element, {
 | 
			
		||||
            width,
 | 
			
		||||
            height,
 | 
			
		||||
            ...adjustXYWithRotation("nw", element, deltaX, dY, angle),
 | 
			
		||||
            ...(isLinearElement(element) && width !== 0 && height !== 0
 | 
			
		||||
              ? {
 | 
			
		||||
                  points: rescalePoints(
 | 
			
		||||
                    0,
 | 
			
		||||
                    width,
 | 
			
		||||
                    rescalePoints(1, height, element.points),
 | 
			
		||||
                  ),
 | 
			
		||||
                }
 | 
			
		||||
              : {}),
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
      case "ne":
 | 
			
		||||
        if (isLinearElement(element) && element.points.length === 2) {
 | 
			
		||||
          const [, p1] = element.points;
 | 
			
		||||
          if (!resizeArrowFn) {
 | 
			
		||||
            if (p1[0] >= 0) {
 | 
			
		||||
              resizeArrowFn = arrowResizeEnd;
 | 
			
		||||
            } else {
 | 
			
		||||
              resizeArrowFn = arrowResizeOrigin;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
          resizeArrowFn(element, 1, deltaX, deltaY, x, y, event.shiftKey);
 | 
			
		||||
          setResizeArrowFn(resizeArrowFn);
 | 
			
		||||
        } else {
 | 
			
		||||
          const width = element.width + deltaX;
 | 
			
		||||
          const height = event.shiftKey ? width : element.height - deltaY;
 | 
			
		||||
          const dY = element.height - height;
 | 
			
		||||
          mutateElement(element, {
 | 
			
		||||
            width,
 | 
			
		||||
            height,
 | 
			
		||||
            ...adjustXYWithRotation("ne", element, deltaX, dY, angle),
 | 
			
		||||
            ...(isLinearElement(element) && width !== 0 && height !== 0
 | 
			
		||||
              ? {
 | 
			
		||||
                  points: rescalePoints(
 | 
			
		||||
                    0,
 | 
			
		||||
                    width,
 | 
			
		||||
                    rescalePoints(1, height, element.points),
 | 
			
		||||
                  ),
 | 
			
		||||
                }
 | 
			
		||||
              : {}),
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
      case "sw":
 | 
			
		||||
        if (isLinearElement(element) && element.points.length === 2) {
 | 
			
		||||
          const [, p1] = element.points;
 | 
			
		||||
          if (!resizeArrowFn) {
 | 
			
		||||
            if (p1[0] <= 0) {
 | 
			
		||||
              resizeArrowFn = arrowResizeEnd;
 | 
			
		||||
            } else {
 | 
			
		||||
              resizeArrowFn = arrowResizeOrigin;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
          resizeArrowFn(element, 1, deltaX, deltaY, x, y, event.shiftKey);
 | 
			
		||||
          setResizeArrowFn(resizeArrowFn);
 | 
			
		||||
        } else {
 | 
			
		||||
          const width = element.width - deltaX;
 | 
			
		||||
          const height = event.shiftKey ? width : element.height + deltaY;
 | 
			
		||||
          const dY = height - element.height;
 | 
			
		||||
          mutateElement(element, {
 | 
			
		||||
            width,
 | 
			
		||||
            height,
 | 
			
		||||
            ...adjustXYWithRotation("sw", element, deltaX, dY, angle),
 | 
			
		||||
            ...(isLinearElement(element) && width !== 0 && height !== 0
 | 
			
		||||
              ? {
 | 
			
		||||
                  points: rescalePoints(
 | 
			
		||||
                    0,
 | 
			
		||||
                    width,
 | 
			
		||||
                    rescalePoints(1, height, element.points),
 | 
			
		||||
                  ),
 | 
			
		||||
                }
 | 
			
		||||
              : {}),
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
      case "se":
 | 
			
		||||
        if (isLinearElement(element) && element.points.length === 2) {
 | 
			
		||||
          const [, p1] = element.points;
 | 
			
		||||
          if (!resizeArrowFn) {
 | 
			
		||||
            if (p1[0] > 0 || p1[1] > 0) {
 | 
			
		||||
              resizeArrowFn = arrowResizeEnd;
 | 
			
		||||
            } else {
 | 
			
		||||
              resizeArrowFn = arrowResizeOrigin;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
          resizeArrowFn(element, 1, deltaX, deltaY, x, y, event.shiftKey);
 | 
			
		||||
          setResizeArrowFn(resizeArrowFn);
 | 
			
		||||
        } else {
 | 
			
		||||
          const width = element.width + deltaX;
 | 
			
		||||
          const height = event.shiftKey ? width : element.height + deltaY;
 | 
			
		||||
          const dY = height - element.height;
 | 
			
		||||
          mutateElement(element, {
 | 
			
		||||
            width,
 | 
			
		||||
            height,
 | 
			
		||||
            ...adjustXYWithRotation("se", element, deltaX, dY, angle),
 | 
			
		||||
            ...(isLinearElement(element) && width !== 0 && height !== 0
 | 
			
		||||
              ? {
 | 
			
		||||
                  points: rescalePoints(
 | 
			
		||||
                    0,
 | 
			
		||||
                    width,
 | 
			
		||||
                    rescalePoints(1, height, element.points),
 | 
			
		||||
                  ),
 | 
			
		||||
                }
 | 
			
		||||
              : {}),
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
      case "n": {
 | 
			
		||||
        const height = element.height - deltaY;
 | 
			
		||||
 | 
			
		||||
        if (isLinearElement(element) && height !== 0) {
 | 
			
		||||
          mutateElement(element, {
 | 
			
		||||
            height,
 | 
			
		||||
            ...adjustXYWithRotation("n", element, 0, deltaY, angle),
 | 
			
		||||
            points: rescalePoints(1, height, element.points),
 | 
			
		||||
          });
 | 
			
		||||
        } else {
 | 
			
		||||
          mutateElement(element, {
 | 
			
		||||
            height,
 | 
			
		||||
            ...adjustXYWithRotation("n", element, 0, deltaY, angle),
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        break;
 | 
			
		||||
    const [element] = selectedElements;
 | 
			
		||||
    if (resizeHandle === "rotation") {
 | 
			
		||||
      const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
 | 
			
		||||
      const cx = (x1 + x2) / 2;
 | 
			
		||||
      const cy = (y1 + y2) / 2;
 | 
			
		||||
      let angle = (5 * Math.PI) / 2 + Math.atan2(yPointer - cy, xPointer - cx);
 | 
			
		||||
      if (event.shiftKey) {
 | 
			
		||||
        angle += SHIFT_LOCKING_ANGLE / 2;
 | 
			
		||||
        angle -= angle % SHIFT_LOCKING_ANGLE;
 | 
			
		||||
      }
 | 
			
		||||
      case "w": {
 | 
			
		||||
        const width = element.width - deltaX;
 | 
			
		||||
 | 
			
		||||
        if (isLinearElement(element) && width !== 0) {
 | 
			
		||||
          mutateElement(element, {
 | 
			
		||||
            width,
 | 
			
		||||
            ...adjustXYWithRotation("w", element, deltaX, 0, angle),
 | 
			
		||||
            points: rescalePoints(0, width, element.points),
 | 
			
		||||
          });
 | 
			
		||||
        } else {
 | 
			
		||||
          mutateElement(element, {
 | 
			
		||||
            width,
 | 
			
		||||
            ...adjustXYWithRotation("w", element, deltaX, 0, angle),
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
      if (angle >= 2 * Math.PI) {
 | 
			
		||||
        angle -= 2 * Math.PI;
 | 
			
		||||
      }
 | 
			
		||||
      case "s": {
 | 
			
		||||
        const height = element.height + deltaY;
 | 
			
		||||
 | 
			
		||||
        if (isLinearElement(element) && height !== 0) {
 | 
			
		||||
          mutateElement(element, {
 | 
			
		||||
            height,
 | 
			
		||||
            ...adjustXYWithRotation("s", element, 0, deltaY, angle),
 | 
			
		||||
            points: rescalePoints(1, height, element.points),
 | 
			
		||||
          });
 | 
			
		||||
        } else {
 | 
			
		||||
          mutateElement(element, {
 | 
			
		||||
            height,
 | 
			
		||||
            ...adjustXYWithRotation("s", element, 0, deltaY, angle),
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      case "e": {
 | 
			
		||||
        const width = element.width + deltaX;
 | 
			
		||||
 | 
			
		||||
        if (isLinearElement(element) && width !== 0) {
 | 
			
		||||
          mutateElement(element, {
 | 
			
		||||
            width,
 | 
			
		||||
            ...adjustXYWithRotation("e", element, deltaX, 0, angle),
 | 
			
		||||
            points: rescalePoints(0, width, element.points),
 | 
			
		||||
          });
 | 
			
		||||
        } else {
 | 
			
		||||
          mutateElement(element, {
 | 
			
		||||
            width,
 | 
			
		||||
            ...adjustXYWithRotation("e", element, deltaX, 0, angle),
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      case "rotation": {
 | 
			
		||||
        const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
 | 
			
		||||
        const cx = (x1 + x2) / 2;
 | 
			
		||||
        const cy = (y1 + y2) / 2;
 | 
			
		||||
        let angle = (5 * Math.PI) / 2 + Math.atan2(y - cy, x - cx);
 | 
			
		||||
        if (event.shiftKey) {
 | 
			
		||||
          angle += SHIFT_LOCKING_ANGLE / 2;
 | 
			
		||||
          angle -= angle % SHIFT_LOCKING_ANGLE;
 | 
			
		||||
        }
 | 
			
		||||
        if (angle >= 2 * Math.PI) {
 | 
			
		||||
          angle -= 2 * Math.PI;
 | 
			
		||||
        }
 | 
			
		||||
        mutateElement(element, { angle });
 | 
			
		||||
        break;
 | 
			
		||||
      mutateElement(element, { angle });
 | 
			
		||||
    } else if (
 | 
			
		||||
      isLinearElement(element) &&
 | 
			
		||||
      element.points.length === 2 &&
 | 
			
		||||
      (resizeHandle === "nw" ||
 | 
			
		||||
        resizeHandle === "ne" ||
 | 
			
		||||
        resizeHandle === "sw" ||
 | 
			
		||||
        resizeHandle === "se")
 | 
			
		||||
    ) {
 | 
			
		||||
      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));
 | 
			
		||||
      applyResizeArrowFn(
 | 
			
		||||
        element,
 | 
			
		||||
        resizeArrowFn,
 | 
			
		||||
        setResizeArrowFn,
 | 
			
		||||
        isResizeEnd,
 | 
			
		||||
        event.shiftKey,
 | 
			
		||||
        xPointer,
 | 
			
		||||
        yPointer,
 | 
			
		||||
        lastX,
 | 
			
		||||
        lastY,
 | 
			
		||||
      );
 | 
			
		||||
    } else if (resizeHandle) {
 | 
			
		||||
      const [x1, y1] = getElementAbsoluteCoords(element);
 | 
			
		||||
      const resized = resizeXYWidthHightWithRotation(
 | 
			
		||||
        resizeHandle,
 | 
			
		||||
        x1,
 | 
			
		||||
        y1,
 | 
			
		||||
        element.width,
 | 
			
		||||
        element.height,
 | 
			
		||||
        x1 - element.x,
 | 
			
		||||
        y1 - element.y,
 | 
			
		||||
        element.angle,
 | 
			
		||||
        xPointer,
 | 
			
		||||
        yPointer,
 | 
			
		||||
        offsetPointer,
 | 
			
		||||
        event.shiftKey,
 | 
			
		||||
      );
 | 
			
		||||
      if (resized.width !== 0 && resized.height !== 0) {
 | 
			
		||||
        mutateElement(element, {
 | 
			
		||||
          ...resized,
 | 
			
		||||
          ...(isLinearElement(element)
 | 
			
		||||
            ? {
 | 
			
		||||
                points: rescalePoints(
 | 
			
		||||
                  0,
 | 
			
		||||
                  resized.width,
 | 
			
		||||
                  rescalePoints(1, resized.height, element.points),
 | 
			
		||||
                ),
 | 
			
		||||
              }
 | 
			
		||||
            : {}),
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -359,15 +238,12 @@ export function resizeElements(
 | 
			
		||||
    return true;
 | 
			
		||||
  } else if (selectedElements.length > 1) {
 | 
			
		||||
    const [x1, y1, x2, y2] = getCommonBounds(selectedElements);
 | 
			
		||||
    const handleOffset = 4 / appState.zoom; // XXX import constant
 | 
			
		||||
    const dashedLinePadding = 4 / appState.zoom; // XXX import constant
 | 
			
		||||
    const minSize = handleOffset * 4;
 | 
			
		||||
    const minScale = Math.max(minSize / (x2 - x1), minSize / (y2 - y1));
 | 
			
		||||
    switch (resizeHandle) {
 | 
			
		||||
      case "se": {
 | 
			
		||||
        const scale = Math.max(
 | 
			
		||||
          (x - handleOffset - dashedLinePadding - x1) / (x2 - x1),
 | 
			
		||||
          (y - handleOffset - dashedLinePadding - y1) / (y2 - y1),
 | 
			
		||||
          (xPointer - offsetPointer - x1) / (x2 - x1),
 | 
			
		||||
          (yPointer - offsetPointer - y1) / (y2 - y1),
 | 
			
		||||
        );
 | 
			
		||||
        if (scale > minScale) {
 | 
			
		||||
          selectedElements.forEach((element) => {
 | 
			
		||||
@@ -382,8 +258,8 @@ export function resizeElements(
 | 
			
		||||
      }
 | 
			
		||||
      case "nw": {
 | 
			
		||||
        const scale = Math.max(
 | 
			
		||||
          (x2 - handleOffset - dashedLinePadding - x) / (x2 - x1),
 | 
			
		||||
          (y2 - handleOffset - dashedLinePadding - y) / (y2 - y1),
 | 
			
		||||
          (x2 - offsetPointer - xPointer) / (x2 - x1),
 | 
			
		||||
          (y2 - offsetPointer - yPointer) / (y2 - y1),
 | 
			
		||||
        );
 | 
			
		||||
        if (scale > minScale) {
 | 
			
		||||
          selectedElements.forEach((element) => {
 | 
			
		||||
@@ -398,8 +274,8 @@ export function resizeElements(
 | 
			
		||||
      }
 | 
			
		||||
      case "ne": {
 | 
			
		||||
        const scale = Math.max(
 | 
			
		||||
          (x - handleOffset - dashedLinePadding - x1) / (x2 - x1),
 | 
			
		||||
          (y2 - handleOffset - dashedLinePadding - y) / (y2 - y1),
 | 
			
		||||
          (xPointer - offsetPointer - x1) / (x2 - x1),
 | 
			
		||||
          (y2 - offsetPointer - yPointer) / (y2 - y1),
 | 
			
		||||
        );
 | 
			
		||||
        if (scale > minScale) {
 | 
			
		||||
          selectedElements.forEach((element) => {
 | 
			
		||||
@@ -414,8 +290,8 @@ export function resizeElements(
 | 
			
		||||
      }
 | 
			
		||||
      case "sw": {
 | 
			
		||||
        const scale = Math.max(
 | 
			
		||||
          (x2 - handleOffset - dashedLinePadding - x) / (x2 - x1),
 | 
			
		||||
          (y - handleOffset - dashedLinePadding - y1) / (y2 - y1),
 | 
			
		||||
          (x2 - offsetPointer - xPointer) / (x2 - x1),
 | 
			
		||||
          (yPointer - offsetPointer - y1) / (y2 - y1),
 | 
			
		||||
        );
 | 
			
		||||
        if (scale > minScale) {
 | 
			
		||||
          selectedElements.forEach((element) => {
 | 
			
		||||
@@ -431,12 +307,12 @@ export function resizeElements(
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return false;
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function canResizeMutlipleElements(
 | 
			
		||||
export const canResizeMutlipleElements = (
 | 
			
		||||
  elements: readonly NonDeletedExcalidrawElement[],
 | 
			
		||||
) {
 | 
			
		||||
) => {
 | 
			
		||||
  return elements.every((element) =>
 | 
			
		||||
    ["rectangle", "diamond", "ellipse"].includes(element.type),
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										15
									
								
								src/math.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/math.test.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
import { rotate } from "./math";
 | 
			
		||||
 | 
			
		||||
describe("rotate", () => {
 | 
			
		||||
  it("should rotate over (x2, y2) and return the rotated coordinates for (x1, y1)", () => {
 | 
			
		||||
    const x1 = 10;
 | 
			
		||||
    const y1 = 20;
 | 
			
		||||
    const x2 = 20;
 | 
			
		||||
    const y2 = 30;
 | 
			
		||||
    const angle = Math.PI / 2;
 | 
			
		||||
    const [rotatedX, rotatedY] = rotate(x1, y1, x2, y2, angle);
 | 
			
		||||
    expect([rotatedX, rotatedY]).toEqual([30, 20]);
 | 
			
		||||
    const res2 = rotate(rotatedX, rotatedY, x2, y2, -angle);
 | 
			
		||||
    expect(res2).toEqual([x1, x2]);
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										88
									
								
								src/math.ts
									
									
									
									
									
								
							
							
						
						
									
										88
									
								
								src/math.ts
									
									
									
									
									
								
							@@ -56,32 +56,92 @@ export function rotate(
 | 
			
		||||
  ];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function adjustXYWithRotation(
 | 
			
		||||
const adjustXYWithRotation = (
 | 
			
		||||
  side: "n" | "s" | "w" | "e" | "nw" | "ne" | "sw" | "se",
 | 
			
		||||
  position: { x: number; y: number },
 | 
			
		||||
  x: number,
 | 
			
		||||
  y: number,
 | 
			
		||||
  angle: number,
 | 
			
		||||
  deltaX: number,
 | 
			
		||||
  deltaY: number,
 | 
			
		||||
  angle: number,
 | 
			
		||||
) {
 | 
			
		||||
  let { x, y } = position;
 | 
			
		||||
) => {
 | 
			
		||||
  const cos = Math.cos(angle);
 | 
			
		||||
  const sin = Math.sin(angle);
 | 
			
		||||
  deltaX /= 2;
 | 
			
		||||
  deltaY /= 2;
 | 
			
		||||
  if (side === "e" || side === "ne" || side === "se") {
 | 
			
		||||
    x -= (deltaX / 2) * (1 - Math.cos(angle));
 | 
			
		||||
    y -= (deltaX / 2) * -Math.sin(angle);
 | 
			
		||||
    x += deltaX * (1 - cos);
 | 
			
		||||
    y += deltaX * -sin;
 | 
			
		||||
  }
 | 
			
		||||
  if (side === "s" || side === "sw" || side === "se") {
 | 
			
		||||
    x -= (deltaY / 2) * Math.sin(angle);
 | 
			
		||||
    y -= (deltaY / 2) * (1 - Math.cos(angle));
 | 
			
		||||
    x += deltaY * sin;
 | 
			
		||||
    y += deltaY * (1 - cos);
 | 
			
		||||
  }
 | 
			
		||||
  if (side === "w" || side === "nw" || side === "sw") {
 | 
			
		||||
    x += (deltaX / 2) * (1 + Math.cos(angle));
 | 
			
		||||
    y += (deltaX / 2) * Math.sin(angle);
 | 
			
		||||
    x += deltaX * (1 + cos);
 | 
			
		||||
    y += deltaX * sin;
 | 
			
		||||
  }
 | 
			
		||||
  if (side === "n" || side === "nw" || side === "ne") {
 | 
			
		||||
    x += (deltaY / 2) * -Math.sin(angle);
 | 
			
		||||
    y += (deltaY / 2) * (1 + Math.cos(angle));
 | 
			
		||||
    x += deltaY * -sin;
 | 
			
		||||
    y += deltaY * (1 + cos);
 | 
			
		||||
  }
 | 
			
		||||
  return { x, y };
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const resizeXYWidthHightWithRotation = (
 | 
			
		||||
  side: "n" | "s" | "w" | "e" | "nw" | "ne" | "sw" | "se",
 | 
			
		||||
  x: number,
 | 
			
		||||
  y: number,
 | 
			
		||||
  width: number,
 | 
			
		||||
  height: number,
 | 
			
		||||
  offsetX: number,
 | 
			
		||||
  offsetY: number,
 | 
			
		||||
  angle: number,
 | 
			
		||||
  xPointer: number,
 | 
			
		||||
  yPointer: number,
 | 
			
		||||
  offsetPointer: number,
 | 
			
		||||
  sidesWithSameLength: boolean,
 | 
			
		||||
) => {
 | 
			
		||||
  // center point for rotation
 | 
			
		||||
  const cx = x + width / 2;
 | 
			
		||||
  const cy = y + height / 2;
 | 
			
		||||
 | 
			
		||||
  // rotation with current angle
 | 
			
		||||
  const [rotatedX, rotatedY] = rotate(xPointer, yPointer, cx, cy, -angle);
 | 
			
		||||
 | 
			
		||||
  let scaleX = 1;
 | 
			
		||||
  let scaleY = 1;
 | 
			
		||||
  if (side === "e" || side === "ne" || side === "se") {
 | 
			
		||||
    scaleX = (rotatedX - offsetPointer - x) / width;
 | 
			
		||||
  }
 | 
			
		||||
  if (side === "s" || side === "sw" || side === "se") {
 | 
			
		||||
    scaleY = (rotatedY - offsetPointer - y) / height;
 | 
			
		||||
  }
 | 
			
		||||
  if (side === "w" || side === "nw" || side === "sw") {
 | 
			
		||||
    scaleX = (x + width - offsetPointer - rotatedX) / width;
 | 
			
		||||
  }
 | 
			
		||||
  if (side === "n" || side === "nw" || side === "ne") {
 | 
			
		||||
    scaleY = (y + height - offsetPointer - rotatedY) / height;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let nextWidth = width * scaleX;
 | 
			
		||||
  let nextHeight = height * scaleY;
 | 
			
		||||
  if (sidesWithSameLength) {
 | 
			
		||||
    nextWidth = nextHeight = Math.max(nextWidth, nextHeight);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    width: nextWidth,
 | 
			
		||||
    height: nextHeight,
 | 
			
		||||
    ...adjustXYWithRotation(
 | 
			
		||||
      side,
 | 
			
		||||
      x - offsetX,
 | 
			
		||||
      y - offsetY,
 | 
			
		||||
      angle,
 | 
			
		||||
      width - nextWidth,
 | 
			
		||||
      height - nextHeight,
 | 
			
		||||
    ),
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getPointOnAPath = (point: Point, path: Point[]) => {
 | 
			
		||||
  const [px, py] = point;
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user