From d6f0f34fe91a7fab25106f2b31b074c132815d36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rk=20Tolm=C3=A1cs?= Date: Mon, 23 Mar 2026 15:54:59 +0100 Subject: [PATCH] fix: Rotated rounded arrow center point (#10962) --- packages/element/src/collision.ts | 13 +++++++++-- packages/element/src/distance.ts | 8 +++++-- packages/element/src/linearElementEditor.ts | 15 ++++++++++-- packages/element/src/shape.ts | 23 ++++++------------- packages/element/src/textElement.ts | 1 + packages/element/src/utils.ts | 6 ++--- .../excalidraw/renderer/interactiveScene.ts | 1 + 7 files changed, 41 insertions(+), 26 deletions(-) diff --git a/packages/element/src/collision.ts b/packages/element/src/collision.ts index 46d2dbd46a..b17d563dbe 100644 --- a/packages/element/src/collision.ts +++ b/packages/element/src/collision.ts @@ -465,7 +465,12 @@ export const intersectElementWithLineSegment = ( case "line": case "freedraw": case "arrow": - return intersectLinearOrFreeDrawWithLineSegment(element, line, onlyFirst); + return intersectLinearOrFreeDrawWithLineSegment( + element, + line, + elementsMap, + onlyFirst, + ); } }; @@ -532,11 +537,15 @@ const lineIntersections = ( const intersectLinearOrFreeDrawWithLineSegment = ( element: ExcalidrawLinearElement | ExcalidrawFreeDrawElement, segment: LineSegment, + elementsMap: ElementsMap, onlyFirst = false, ): GlobalPoint[] => { // NOTE: This is the only one which return the decomposed elements // rotated! This is due to taking advantage of roughjs definitions. - const [lines, curves] = deconstructLinearOrFreeDrawElement(element); + const [lines, curves] = deconstructLinearOrFreeDrawElement( + element, + elementsMap, + ); const intersections: GlobalPoint[] = []; for (const l of lines) { diff --git a/packages/element/src/distance.ts b/packages/element/src/distance.ts index 4766ac9eef..c94652a1aa 100644 --- a/packages/element/src/distance.ts +++ b/packages/element/src/distance.ts @@ -48,7 +48,7 @@ export const distanceToElement = ( case "line": case "arrow": case "freedraw": - return distanceToLinearOrFreeDraElement(element, p); + return distanceToLinearOrFreeDraElement(element, elementsMap, p); } }; @@ -133,9 +133,13 @@ const distanceToEllipseElement = ( const distanceToLinearOrFreeDraElement = ( element: ExcalidrawLinearElement | ExcalidrawFreeDrawElement, + elementsMap: ElementsMap, p: GlobalPoint, ) => { - const [lines, curves] = deconstructLinearOrFreeDrawElement(element); + const [lines, curves] = deconstructLinearOrFreeDrawElement( + element, + elementsMap, + ); return Math.min( ...lines.map((s) => distanceToLineSegment(p, s)), ...curves.map((a) => curvePointDistance(a, p)), diff --git a/packages/element/src/linearElementEditor.ts b/packages/element/src/linearElementEditor.ts index 9390d56e58..8af8ecd858 100644 --- a/packages/element/src/linearElementEditor.ts +++ b/packages/element/src/linearElementEditor.ts @@ -800,6 +800,7 @@ export class LinearElementEditor { element.points[index + 1], index, appState.zoom, + elementsMap, ) ) { midpoints.push(null); @@ -809,6 +810,7 @@ export class LinearElementEditor { const segmentMidPoint = LinearElementEditor.getSegmentMidPoint( element, index + 1, + elementsMap, ); midpoints.push(segmentMidPoint); index++; @@ -896,6 +898,7 @@ export class LinearElementEditor { endPoint: P, index: number, zoom: Zoom, + elementsMap: ElementsMap, ) { if (isElbowArrow(element)) { if (index >= 0 && index < element.points.length) { @@ -910,7 +913,10 @@ export class LinearElementEditor { let distance = pointDistance(startPoint, endPoint); if (element.points.length > 2 && element.roundness) { - const [lines, curves] = deconstructLinearOrFreeDrawElement(element); + const [lines, curves] = deconstructLinearOrFreeDrawElement( + element, + elementsMap, + ); invariant( lines.length === 0 && curves.length > 0, @@ -930,6 +936,7 @@ export class LinearElementEditor { static getSegmentMidPoint( element: NonDeleted, index: number, + elementsMap: ElementsMap, ): GlobalPoint { if (isElbowArrow(element)) { invariant( @@ -942,7 +949,10 @@ export class LinearElementEditor { return pointFrom(element.x + p[0], element.y + p[1]); } - const [lines, curves] = deconstructLinearOrFreeDrawElement(element); + const [lines, curves] = deconstructLinearOrFreeDrawElement( + element, + elementsMap, + ); invariant( (lines.length === 0 && curves.length > 0) || @@ -1857,6 +1867,7 @@ export class LinearElementEditor { const midSegmentMidpoint = LinearElementEditor.getSegmentMidPoint( element, index + 1, + elementsMap, ); x = midSegmentMidpoint[0] - boundTextElement.width / 2; diff --git a/packages/element/src/shape.ts b/packages/element/src/shape.ts index 9bef1329af..158ace7519 100644 --- a/packages/element/src/shape.ts +++ b/packages/element/src/shape.ts @@ -57,8 +57,8 @@ import { headingForPointIsHorizontal } from "./heading"; import { canChangeRoundness } from "./comparisons"; import { + elementCenterPoint, getArrowheadPoints, - getCenterForBounds, getDiamondPoints, getElementAbsoluteCoords, } from "./bounds"; @@ -583,7 +583,11 @@ const getArrowheadShapes = ( export const generateLinearCollisionShape = ( element: ExcalidrawLinearElement | ExcalidrawFreeDrawElement, -) => { + elementsMap: ElementsMap, +): { + op: string; + data: number[]; +}[] => { const generator = new RoughGenerator(); const options: Options = { seed: element.seed, @@ -592,20 +596,7 @@ export const generateLinearCollisionShape = ( roughness: 0, preserveVertices: true, }; - const center = getCenterForBounds( - // Need a non-rotated center point - element.points.reduce( - (acc, point) => { - return [ - Math.min(element.x + point[0], acc[0]), - Math.min(element.y + point[1], acc[1]), - Math.max(element.x + point[0], acc[2]), - Math.max(element.y + point[1], acc[3]), - ]; - }, - [Infinity, Infinity, -Infinity, -Infinity], - ), - ); + const center = elementCenterPoint(element, elementsMap); switch (element.type) { case "line": diff --git a/packages/element/src/textElement.ts b/packages/element/src/textElement.ts index b891fdf2e9..9ff53d035c 100644 --- a/packages/element/src/textElement.ts +++ b/packages/element/src/textElement.ts @@ -347,6 +347,7 @@ export const getContainerCenter = ( midSegmentMidpoint = LinearElementEditor.getSegmentMidPoint( container, index + 1, + elementsMap, ); } return { x: midSegmentMidpoint[0], y: midSegmentMidpoint[1] }; diff --git a/packages/element/src/utils.ts b/packages/element/src/utils.ts index 96e09bcbf1..819cad562f 100644 --- a/packages/element/src/utils.ts +++ b/packages/element/src/utils.ts @@ -124,6 +124,7 @@ const setElementShapesCacheEntry = ( */ export function deconstructLinearOrFreeDrawElement( element: ExcalidrawLinearElement | ExcalidrawFreeDrawElement, + elementsMap: ElementsMap, ): [LineSegment[], Curve[]] { const cachedShape = getElementShapesCacheEntry(element, 0); @@ -131,10 +132,7 @@ export function deconstructLinearOrFreeDrawElement( return cachedShape; } - const ops = generateLinearCollisionShape(element) as { - op: string; - data: number[]; - }[]; + const ops = generateLinearCollisionShape(element, elementsMap); const lines = []; const curves = []; diff --git a/packages/excalidraw/renderer/interactiveScene.ts b/packages/excalidraw/renderer/interactiveScene.ts index 7d31bf9a32..30cadb0ef7 100644 --- a/packages/excalidraw/renderer/interactiveScene.ts +++ b/packages/excalidraw/renderer/interactiveScene.ts @@ -1156,6 +1156,7 @@ const renderLinearPointHandles = ( points[idx], idx, appState.zoom, + elementsMap, ) ) { renderSingleLinearPoint(