diff --git a/packages/common/src/utils.ts b/packages/common/src/utils.ts index 7fa98eb2da..54eaa67cc4 100644 --- a/packages/common/src/utils.ts +++ b/packages/common/src/utils.ts @@ -1,9 +1,10 @@ -import { average } from "@excalidraw/math"; +import { average, pointFrom, type GlobalPoint } from "@excalidraw/math"; import type { ExcalidrawBindableElement, FontFamilyValues, FontString, + ExcalidrawElement, } from "@excalidraw/element/types"; import type { @@ -1201,3 +1202,17 @@ export const escapeDoubleQuotes = (str: string) => { export const castArray = (value: T | T[]): T[] => Array.isArray(value) ? value : [value]; + +export const elementCenterPoint = ( + element: ExcalidrawElement, + xOffset: number = 0, + yOffset: number = 0, +) => { + const { x, y, width, height } = element; + + const centerXPoint = x + width / 2 + xOffset; + + const centerYPoint = y + height / 2 + yOffset; + + return pointFrom(centerXPoint, centerYPoint); +}; diff --git a/packages/element/src/binding.ts b/packages/element/src/binding.ts index 7a67cf0a1f..5c32e8c81d 100644 --- a/packages/element/src/binding.ts +++ b/packages/element/src/binding.ts @@ -6,6 +6,7 @@ import { invariant, isDevEnv, isTestEnv, + elementCenterPoint, } from "@excalidraw/common"; import { @@ -904,13 +905,7 @@ export const getHeadingForElbowArrowSnap = ( if (!distance) { return vectorToHeading( - vectorFromPoint( - p, - pointFrom( - bindableElement.x + bindableElement.width / 2, - bindableElement.y + bindableElement.height / 2, - ), - ), + vectorFromPoint(p, elementCenterPoint(bindableElement)), ); } @@ -1040,10 +1035,7 @@ export const avoidRectangularCorner = ( element: ExcalidrawBindableElement, p: GlobalPoint, ): GlobalPoint => { - const center = pointFrom( - element.x + element.width / 2, - element.y + element.height / 2, - ); + const center = elementCenterPoint(element); const nonRotatedPoint = pointRotateRads(p, center, -element.angle as Radians); if (nonRotatedPoint[0] < element.x && nonRotatedPoint[1] < element.y) { @@ -1140,10 +1132,9 @@ export const snapToMid = ( tolerance: number = 0.05, ): GlobalPoint => { const { x, y, width, height, angle } = element; - const center = pointFrom( - x + width / 2 - 0.1, - y + height / 2 - 0.1, - ); + + const center = elementCenterPoint(element, -0.1, -0.1); + const nonRotated = pointRotateRads(p, center, -angle as Radians); // snap-to-center point is adaptive to element size, but we don't want to go @@ -1228,10 +1219,7 @@ const updateBoundPoint = ( startOrEnd === "startBinding" ? "start" : "end", elementsMap, ).fixedPoint; - const globalMidPoint = pointFrom( - bindableElement.x + bindableElement.width / 2, - bindableElement.y + bindableElement.height / 2, - ); + const globalMidPoint = elementCenterPoint(bindableElement); const global = pointFrom( bindableElement.x + fixedPoint[0] * bindableElement.width, bindableElement.y + fixedPoint[1] * bindableElement.height, @@ -1275,10 +1263,7 @@ const updateBoundPoint = ( elementsMap, ); - const center = pointFrom( - bindableElement.x + bindableElement.width / 2, - bindableElement.y + bindableElement.height / 2, - ); + const center = elementCenterPoint(bindableElement); const interceptorLength = pointDistance(adjacentPoint, edgePointAbsolute) + pointDistance(adjacentPoint, center) + @@ -1771,10 +1756,7 @@ const determineFocusDistance = ( // Another point on the line, in absolute coordinates (closer to element) b: GlobalPoint, ): number => { - const center = pointFrom( - element.x + element.width / 2, - element.y + element.height / 2, - ); + const center = elementCenterPoint(element); if (pointsEqual(a, b)) { return 0; @@ -1904,10 +1886,7 @@ const determineFocusPoint = ( focus: number, adjacentPoint: GlobalPoint, ): GlobalPoint => { - const center = pointFrom( - element.x + element.width / 2, - element.y + element.height / 2, - ); + const center = elementCenterPoint(element); if (focus === 0) { return center; @@ -2338,10 +2317,7 @@ export const getGlobalFixedPointForBindableElement = ( element.x + element.width * fixedX, element.y + element.height * fixedY, ), - pointFrom( - element.x + element.width / 2, - element.y + element.height / 2, - ), + elementCenterPoint(element), element.angle, ); }; diff --git a/packages/element/src/collision.ts b/packages/element/src/collision.ts index 0fabe98392..07b17bfde5 100644 --- a/packages/element/src/collision.ts +++ b/packages/element/src/collision.ts @@ -1,4 +1,4 @@ -import { isTransparent } from "@excalidraw/common"; +import { isTransparent, elementCenterPoint } from "@excalidraw/common"; import { curveIntersectLineSegment, isPointWithinBounds, @@ -16,7 +16,7 @@ import { } from "@excalidraw/math/ellipse"; import { isPointInShape, isPointOnShape } from "@excalidraw/utils/collision"; -import { getPolygonShape } from "@excalidraw/utils/shape"; +import { type GeometricShape, getPolygonShape } from "@excalidraw/utils/shape"; import type { GlobalPoint, @@ -26,8 +26,6 @@ import type { Radians, } from "@excalidraw/math"; -import type { GeometricShape } from "@excalidraw/utils/shape"; - import type { FrameNameBounds } from "@excalidraw/excalidraw/types"; import { getBoundTextShape, isPathALoop } from "./shapes"; @@ -191,10 +189,7 @@ const intersectRectanguloidWithLineSegment = ( l: LineSegment, offset: number = 0, ): GlobalPoint[] => { - const center = pointFrom( - element.x + element.width / 2, - element.y + element.height / 2, - ); + const center = elementCenterPoint(element); // To emulate a rotated rectangle we rotate the point in the inverse angle // instead. It's all the same distance-wise. const rotatedA = pointRotateRads( @@ -253,10 +248,7 @@ const intersectDiamondWithLineSegment = ( l: LineSegment, offset: number = 0, ): GlobalPoint[] => { - const center = pointFrom( - element.x + element.width / 2, - element.y + element.height / 2, - ); + const center = elementCenterPoint(element); // Rotate the point to the inverse direction to simulate the rotated diamond // points. It's all the same distance-wise. @@ -304,10 +296,7 @@ const intersectEllipseWithLineSegment = ( l: LineSegment, offset: number = 0, ): GlobalPoint[] => { - const center = pointFrom( - element.x + element.width / 2, - element.y + element.height / 2, - ); + const center = elementCenterPoint(element); const rotatedA = pointRotateRads(l[0], center, -element.angle as Radians); const rotatedB = pointRotateRads(l[1], center, -element.angle as Radians); diff --git a/packages/element/src/cropElement.ts b/packages/element/src/cropElement.ts index dd75f9360f..2bc930d668 100644 --- a/packages/element/src/cropElement.ts +++ b/packages/element/src/cropElement.ts @@ -14,6 +14,8 @@ import { } from "@excalidraw/math"; import { type Point } from "points-on-curve"; +import { elementCenterPoint } from "@excalidraw/common"; + import { getElementAbsoluteCoords, getResizedElementAbsoluteCoords, @@ -61,7 +63,7 @@ export const cropElement = ( const rotatedPointer = pointRotateRads( pointFrom(pointerX, pointerY), - pointFrom(element.x + element.width / 2, element.y + element.height / 2), + elementCenterPoint(element), -element.angle as Radians, ); diff --git a/packages/element/src/distance.ts b/packages/element/src/distance.ts index d9db939e4d..d261faf7df 100644 --- a/packages/element/src/distance.ts +++ b/packages/element/src/distance.ts @@ -1,12 +1,13 @@ import { curvePointDistance, distanceToLineSegment, - pointFrom, pointRotateRads, } from "@excalidraw/math"; import { ellipse, ellipseDistanceFromPoint } from "@excalidraw/math/ellipse"; +import { elementCenterPoint } from "@excalidraw/common"; + import type { GlobalPoint, Radians } from "@excalidraw/math"; import { @@ -53,10 +54,7 @@ const distanceToRectanguloidElement = ( element: ExcalidrawRectanguloidElement, p: GlobalPoint, ) => { - const center = pointFrom( - element.x + element.width / 2, - element.y + element.height / 2, - ); + const center = elementCenterPoint(element); // To emulate a rotated rectangle we rotate the point in the inverse angle // instead. It's all the same distance-wise. const rotatedPoint = pointRotateRads(p, center, -element.angle as Radians); @@ -84,10 +82,7 @@ const distanceToDiamondElement = ( element: ExcalidrawDiamondElement, p: GlobalPoint, ): number => { - const center = pointFrom( - element.x + element.width / 2, - element.y + element.height / 2, - ); + const center = elementCenterPoint(element); // Rotate the point to the inverse direction to simulate the rotated diamond // points. It's all the same distance-wise. @@ -115,10 +110,7 @@ const distanceToEllipseElement = ( element: ExcalidrawEllipseElement, p: GlobalPoint, ): number => { - const center = pointFrom( - element.x + element.width / 2, - element.y + element.height / 2, - ); + const center = elementCenterPoint(element); return ellipseDistanceFromPoint( // Instead of rotating the ellipse, rotate the point to the inverse angle pointRotateRads(p, center, -element.angle as Radians), diff --git a/packages/element/src/shapes.ts b/packages/element/src/shapes.ts index 1d6e13340f..96542c5385 100644 --- a/packages/element/src/shapes.ts +++ b/packages/element/src/shapes.ts @@ -4,6 +4,7 @@ import { LINE_CONFIRM_THRESHOLD, ROUNDNESS, invariant, + elementCenterPoint, } from "@excalidraw/common"; import { isPoint, @@ -297,7 +298,7 @@ export const aabbForElement = ( midY: element.y + element.height / 2, }; - const center = pointFrom(bbox.midX, bbox.midY); + const center = elementCenterPoint(element); const [topLeftX, topLeftY] = pointRotateRads( pointFrom(bbox.minX, bbox.minY), center, diff --git a/packages/element/src/utils.ts b/packages/element/src/utils.ts index 7042b5d8f1..57b1e4346c 100644 --- a/packages/element/src/utils.ts +++ b/packages/element/src/utils.ts @@ -10,6 +10,8 @@ import { type GlobalPoint, } from "@excalidraw/math"; +import { elementCenterPoint } from "@excalidraw/common"; + import type { Curve, LineSegment } from "@excalidraw/math"; import { getCornerRadius } from "./shapes"; @@ -68,10 +70,7 @@ export function deconstructRectanguloidElement( return [sides, []]; } - const center = pointFrom( - element.x + element.width / 2, - element.y + element.height / 2, - ); + const center = elementCenterPoint(element); const r = rectangle( pointFrom(element.x, element.y), @@ -254,10 +253,7 @@ export function deconstructDiamondElement( return [[topRight, bottomRight, bottomLeft, topLeft], []]; } - const center = pointFrom( - element.x + element.width / 2, - element.y + element.height / 2, - ); + const center = elementCenterPoint(element); const [top, right, bottom, left]: GlobalPoint[] = [ pointFrom(element.x + topX, element.y + topY), diff --git a/packages/excalidraw/tests/helpers/ui.ts b/packages/excalidraw/tests/helpers/ui.ts index 0e5e433674..32de489f15 100644 --- a/packages/excalidraw/tests/helpers/ui.ts +++ b/packages/excalidraw/tests/helpers/ui.ts @@ -20,7 +20,7 @@ import { isTextElement, isFrameLikeElement, } from "@excalidraw/element/typeChecks"; -import { KEYS, arrayToMap } from "@excalidraw/common"; +import { KEYS, arrayToMap, elementCenterPoint } from "@excalidraw/common"; import type { GlobalPoint, LocalPoint, Radians } from "@excalidraw/math"; @@ -151,7 +151,7 @@ export class Keyboard { const getElementPointForSelection = ( element: ExcalidrawElement, ): GlobalPoint => { - const { x, y, width, height, angle } = element; + const { x, y, width, angle } = element; const target = pointFrom( x + (isLinearElement(element) || isFreeDrawElement(element) ? 0 : width / 2), @@ -166,7 +166,7 @@ const getElementPointForSelection = ( (bounds[1] + bounds[3]) / 2, ); } else { - center = pointFrom(x + width / 2, y + height / 2); + center = elementCenterPoint(element); } if (isTextElement(element)) {