diff --git a/packages/excalidraw/components/Stats/MultiPosition.tsx b/packages/excalidraw/components/Stats/MultiPosition.tsx index d7f2bbce3..6a6dc0c6d 100644 --- a/packages/excalidraw/components/Stats/MultiPosition.tsx +++ b/packages/excalidraw/components/Stats/MultiPosition.tsx @@ -1,6 +1,8 @@ import { pointFrom, pointRotateRads } from "@excalidraw/math"; import { useMemo } from "react"; +import { isTextElement } from "@excalidraw/element/typeChecks"; + import { getCommonBounds } from "@excalidraw/element/bounds"; import type { ElementsMap, ExcalidrawElement } from "@excalidraw/element/types"; @@ -12,14 +14,12 @@ import { getAtomicUnits, getStepSizedValue, isPropertyEditable, - moveElements, - moveGroupTo, STEP_SIZE, - type AtomicUnit, } from "./utils"; import { getElementsInAtomicUnit, moveElement } from "./utils"; import type { DragInputCallbackType } from "./DragInput"; +import type { AtomicUnit } from "./utils"; import type { AppState } from "../../types"; interface MultiPositionProps { @@ -31,6 +31,89 @@ interface MultiPositionProps { appState: AppState; } +const moveElements = ( + property: MultiPositionProps["property"], + changeInTopX: number, + changeInTopY: number, + originalElements: readonly ExcalidrawElement[], + originalElementsMap: ElementsMap, + scene: Scene, +) => { + for (let i = 0; i < originalElements.length; i++) { + const origElement = originalElements[i]; + + const [cx, cy] = [ + origElement.x + origElement.width / 2, + origElement.y + origElement.height / 2, + ]; + const [topLeftX, topLeftY] = pointRotateRads( + pointFrom(origElement.x, origElement.y), + pointFrom(cx, cy), + origElement.angle, + ); + + const newTopLeftX = + property === "x" ? Math.round(topLeftX + changeInTopX) : topLeftX; + + const newTopLeftY = + property === "y" ? Math.round(topLeftY + changeInTopY) : topLeftY; + + moveElement( + newTopLeftX, + newTopLeftY, + origElement, + scene, + originalElementsMap, + false, + ); + } +}; + +const moveGroupTo = ( + nextX: number, + nextY: number, + originalElements: ExcalidrawElement[], + originalElementsMap: ElementsMap, + scene: Scene, +) => { + const elementsMap = scene.getNonDeletedElementsMap(); + const [x1, y1, ,] = getCommonBounds(originalElements); + const offsetX = nextX - x1; + const offsetY = nextY - y1; + + for (let i = 0; i < originalElements.length; i++) { + const origElement = originalElements[i]; + + const latestElement = elementsMap.get(origElement.id); + if (!latestElement) { + continue; + } + + // bound texts are moved with their containers + if (!isTextElement(latestElement) || !latestElement.containerId) { + const [cx, cy] = [ + latestElement.x + latestElement.width / 2, + latestElement.y + latestElement.height / 2, + ]; + + const [topLeftX, topLeftY] = pointRotateRads( + pointFrom(latestElement.x, latestElement.y), + pointFrom(cx, cy), + latestElement.angle, + ); + + moveElement( + topLeftX + offsetX, + topLeftY + offsetY, + origElement, + scene, + originalElementsMap, + false, + ); + } + } +}; + const handlePositionChange: DragInputCallbackType< MultiPositionProps["property"] > = ({