From f12f7e4b50ae7c947757be7bd2708d738603482d Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Fri, 11 Apr 2025 10:24:02 +0200 Subject: [PATCH 01/23] Fix sentry#6530117915 --- packages/element/src/elbowArrow.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/element/src/elbowArrow.ts b/packages/element/src/elbowArrow.ts index a70e265bc..503d2f02f 100644 --- a/packages/element/src/elbowArrow.ts +++ b/packages/element/src/elbowArrow.ts @@ -742,6 +742,7 @@ const handleEndpointDrag = ( // Calculate the moving second point connection and add the start point { + startIsSpecial = arrow.startIsSpecial && globalUpdatedPoints.length > 2; const secondPoint = globalUpdatedPoints[startIsSpecial ? 2 : 1]; const thirdPoint = globalUpdatedPoints[startIsSpecial ? 3 : 2]; const startIsHorizontal = headingIsHorizontal(startHeading); @@ -802,6 +803,7 @@ const handleEndpointDrag = ( // Calculate the moving second to last point connection { + endIsSpecial = arrow.endIsSpecial && globalUpdatedPoints.length > 2; const secondToLastPoint = globalUpdatedPoints[globalUpdatedPoints.length - (endIsSpecial ? 3 : 2)]; const thirdToLastPoint = From 1ee3676784f8b310e50975fbd9380fd8599dd9a4 Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Fri, 11 Apr 2025 15:39:31 +0200 Subject: [PATCH 02/23] Move visal debug to @excalidraw/util --- excalidraw-app/components/DebugCanvas.tsx | 2 +- packages/{excalidraw => utils/src}/visualdebug.ts | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename packages/{excalidraw => utils/src}/visualdebug.ts (100%) diff --git a/excalidraw-app/components/DebugCanvas.tsx b/excalidraw-app/components/DebugCanvas.tsx index e83a62647..48a619e42 100644 --- a/excalidraw-app/components/DebugCanvas.tsx +++ b/excalidraw-app/components/DebugCanvas.tsx @@ -18,7 +18,7 @@ import { } from "@excalidraw/math"; import { isCurve } from "@excalidraw/math/curve"; -import type { DebugElement } from "@excalidraw/excalidraw/visualdebug"; +import type { DebugElement } from "@excalidraw/utils/visualdebug"; import type { Curve } from "@excalidraw/math"; diff --git a/packages/excalidraw/visualdebug.ts b/packages/utils/src/visualdebug.ts similarity index 100% rename from packages/excalidraw/visualdebug.ts rename to packages/utils/src/visualdebug.ts From a8338cdb5a3b58a80fa19da1c6ec294f4dabdbb7 Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Fri, 11 Apr 2025 21:08:55 +0200 Subject: [PATCH 03/23] More adaptive elbow dongle offset --- packages/element/src/binding.ts | 1 + packages/element/src/elbowArrow.ts | 42 ++++++++++++++++++++++++++++-- packages/element/src/shapes.ts | 9 +++++++ 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/packages/element/src/binding.ts b/packages/element/src/binding.ts index 5c32e8c81..0ef3fda54 100644 --- a/packages/element/src/binding.ts +++ b/packages/element/src/binding.ts @@ -981,6 +981,7 @@ export const bindPointToSnapToElementOutline = ( otherPoint, ), ), + 0.1, )[0]; } else { intersection = intersectElementWithLineSegment( diff --git a/packages/element/src/elbowArrow.ts b/packages/element/src/elbowArrow.ts index 503d2f02f..8e502d283 100644 --- a/packages/element/src/elbowArrow.ts +++ b/packages/element/src/elbowArrow.ts @@ -53,7 +53,7 @@ import { type SceneElementsMap, } from "./types"; -import { aabbForElement, pointInsideBounds } from "./shapes"; +import { aabbForElement, aabbForPoints, pointInsideBounds } from "./shapes"; import type { Bounds } from "./bounds"; import type { Heading } from "./heading"; @@ -108,7 +108,30 @@ type ElbowArrowData = { }; const DEDUP_TRESHOLD = 1; -export const BASE_PADDING = 40; + +const calculatePadding = ( + aabb: Bounds, + startHeading: Heading, + endHeading: Heading, +) => { + const width = aabb[2] - aabb[0]; + const height = aabb[3] - aabb[1]; + const size = Math.max(width, height); + + return compareHeading(startHeading, flipHeading(endHeading)) || size > 50 + ? 40 + : Math.min( + Math.max( + headingIsHorizontal(startHeading) ? width / 2 - 1 : height / 2 - 1, + 10, + ), + Math.max( + headingIsHorizontal(endHeading) ? width / 2 - 1 : height / 2 - 1, + 10, + ), + 40, + ); +}; const handleSegmentRenormalization = ( arrow: ExcalidrawElbowArrowElement, @@ -464,6 +487,11 @@ const handleSegmentMove = ( hoveredStartElement: ExcalidrawBindableElement | null, hoveredEndElement: ExcalidrawBindableElement | null, ): ElementUpdate => { + const BASE_PADDING = calculatePadding( + aabbForElement(arrow), + startHeading, + endHeading, + ); const activelyModifiedSegmentIdx = fixedSegments .map((segment, i) => { if ( @@ -708,6 +736,11 @@ const handleEndpointDrag = ( hoveredStartElement: ExcalidrawBindableElement | null, hoveredEndElement: ExcalidrawBindableElement | null, ) => { + const BASE_PADDING = calculatePadding( + aabbForPoints([startGlobalPoint, endGlobalPoint]), + startHeading, + endHeading, + ); let startIsSpecial = arrow.startIsSpecial ?? null; let endIsSpecial = arrow.endIsSpecial ?? null; const globalUpdatedPoints = updatedPoints.map((p, i) => @@ -1298,6 +1331,11 @@ const getElbowArrowData = ( endGlobalPoint[0] + 2, endGlobalPoint[1] + 2, ] as Bounds; + const BASE_PADDING = calculatePadding( + aabbForPoints([startGlobalPoint, endGlobalPoint]), + startHeading, + endHeading, + ); const startElementBounds = hoveredStartElement ? aabbForElement( hoveredStartElement, diff --git a/packages/element/src/shapes.ts b/packages/element/src/shapes.ts index 96542c538..29afb17c2 100644 --- a/packages/element/src/shapes.ts +++ b/packages/element/src/shapes.ts @@ -282,6 +282,15 @@ export const mapIntervalToBezierT =

( ); }; +export const aabbForPoints = ( + points: Point[], +): Bounds => [ + Math.min(...points.map((point) => point[0])), + Math.min(...points.map((point) => point[1])), + Math.max(...points.map((point) => point[0])), + Math.max(...points.map((point) => point[1])), +]; + /** * Get the axis-aligned bounding box for a given element */ From eaa869620e130b08bea372dd00ce2f56297d83a0 Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Fri, 11 Apr 2025 21:53:23 +0200 Subject: [PATCH 04/23] Fine tuning --- packages/element/src/elbowArrow.ts | 4 +- packages/element/tests/elbowArrow.test.tsx | 48 +++++++++++----------- packages/element/tests/resize.test.tsx | 8 ++-- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/packages/element/src/elbowArrow.ts b/packages/element/src/elbowArrow.ts index 8e502d283..74e64fa64 100644 --- a/packages/element/src/elbowArrow.ts +++ b/packages/element/src/elbowArrow.ts @@ -117,8 +117,8 @@ const calculatePadding = ( const width = aabb[2] - aabb[0]; const height = aabb[3] - aabb[1]; const size = Math.max(width, height); - - return compareHeading(startHeading, flipHeading(endHeading)) || size > 50 + // || compareHeading(startHeading, flipHeading(endHeading)) + return size > 55 ? 40 : Math.min( Math.max( diff --git a/packages/element/tests/elbowArrow.test.tsx b/packages/element/tests/elbowArrow.test.tsx index b8b5a8b85..bb49d063b 100644 --- a/packages/element/tests/elbowArrow.test.tsx +++ b/packages/element/tests/elbowArrow.test.tsx @@ -77,9 +77,9 @@ describe("elbow arrow segment move", () => { expect(arrow.points).toCloselyEqualPoints([ [0, 0], - [110, 0], - [110, 200], - [190, 200], + [109.92, 0], + [109.92, 200], + [189.85, 200], ]); mouse.reset(); @@ -88,9 +88,9 @@ describe("elbow arrow segment move", () => { expect(arrow.points).toCloselyEqualPoints([ [0, 0], - [110, 0], - [110, 200], - [190, 200], + [109.92, 0], + [109.92, 200], + [189.85, 200], ]); }); @@ -198,11 +198,11 @@ describe("elbow arrow routing", () => { points: [pointFrom(0, 0), pointFrom(90, 200)], }); - expect(arrow.points).toEqual([ + expect(arrow.points).toCloselyEqualPoints([ [0, 0], - [45, 0], - [45, 200], - [90, 200], + [44.92, 0], + [44.92, 200], + [89.85, 200], ]); }); }); @@ -252,11 +252,11 @@ describe("elbow arrow ui", () => { expect(arrow.type).toBe("arrow"); expect(arrow.elbowed).toBe(true); - expect(arrow.points).toEqual([ + expect(arrow.points).toCloselyEqualPoints([ [0, 0], - [45, 0], - [45, 200], - [90, 200], + [44.92, 0], + [44.92, 200], + [89.85, 200], ]); }); @@ -296,9 +296,9 @@ describe("elbow arrow ui", () => { expect(arrow.points.map((point) => point.map(Math.round))).toEqual([ [0, 0], - [35, 0], - [35, 165], - [103, 165], + [34, 0], + [34, 165], + [104, 165], ]); }); @@ -350,11 +350,11 @@ describe("elbow arrow ui", () => { expect(duplicatedArrow.id).not.toBe(originalArrowId); expect(duplicatedArrow.type).toBe("arrow"); expect(duplicatedArrow.elbowed).toBe(true); - expect(duplicatedArrow.points).toEqual([ + expect(duplicatedArrow.points).toCloselyEqualPoints([ [0, 0], - [45, 0], - [45, 200], - [90, 200], + [44.92, 0], + [44.92, 200], + [89.85, 200], ]); expect(arrow.startBinding).not.toBe(null); expect(arrow.endBinding).not.toBe(null); @@ -404,11 +404,11 @@ describe("elbow arrow ui", () => { expect(duplicatedArrow.id).not.toBe(originalArrowId); expect(duplicatedArrow.type).toBe("arrow"); expect(duplicatedArrow.elbowed).toBe(true); - expect(duplicatedArrow.points).toEqual([ + expect(duplicatedArrow.points).toCloselyEqualPoints([ [0, 0], [0, 100], - [90, 100], - [90, 200], + [89.85, 100], + [89.85, 200], ]); }); }); diff --git a/packages/element/tests/resize.test.tsx b/packages/element/tests/resize.test.tsx index f3804e2a2..8fed6a990 100644 --- a/packages/element/tests/resize.test.tsx +++ b/packages/element/tests/resize.test.tsx @@ -510,12 +510,12 @@ describe("arrow element", () => { h.state, )[0] as ExcalidrawElbowArrowElement; - expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(1); + expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(1.05); expect(arrow.startBinding?.fixedPoint?.[1]).toBeCloseTo(0.75); UI.resize(rectangle, "se", [-200, -150]); - expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(1); + expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(1.05); expect(arrow.startBinding?.fixedPoint?.[1]).toBeCloseTo(0.75); }); @@ -538,11 +538,11 @@ describe("arrow element", () => { h.state, )[0] as ExcalidrawElbowArrowElement; - expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(1); + expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(1.05); expect(arrow.startBinding?.fixedPoint?.[1]).toBeCloseTo(0.75); UI.resize([rectangle, arrow], "nw", [300, 350]); - expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(0); + expect(arrow.startBinding?.fixedPoint?.[0]).toBeCloseTo(-0.05); expect(arrow.startBinding?.fixedPoint?.[1]).toBeCloseTo(0.25); }); }); From c06b78c1b24a5d5323b74a416b39bd6db47e297c Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Sun, 13 Apr 2025 13:08:22 +0200 Subject: [PATCH 05/23] Further fine-tune adaptive padding --- packages/element/src/elbowArrow.ts | 4 ++-- packages/element/tests/elbowArrow.test.tsx | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/element/src/elbowArrow.ts b/packages/element/src/elbowArrow.ts index 74e64fa64..227207c52 100644 --- a/packages/element/src/elbowArrow.ts +++ b/packages/element/src/elbowArrow.ts @@ -117,8 +117,8 @@ const calculatePadding = ( const width = aabb[2] - aabb[0]; const height = aabb[3] - aabb[1]; const size = Math.max(width, height); - // || compareHeading(startHeading, flipHeading(endHeading)) - return size > 55 + + return size > 75 ? 40 : Math.min( Math.max( diff --git a/packages/element/tests/elbowArrow.test.tsx b/packages/element/tests/elbowArrow.test.tsx index bb49d063b..1c3d8cf6e 100644 --- a/packages/element/tests/elbowArrow.test.tsx +++ b/packages/element/tests/elbowArrow.test.tsx @@ -294,11 +294,11 @@ describe("elbow arrow ui", () => { ) as HTMLInputElement; UI.updateInput(inputAngle, String("40")); - expect(arrow.points.map((point) => point.map(Math.round))).toEqual([ + expect(arrow.points).toCloselyEqualPoints([ [0, 0], - [34, 0], - [34, 165], - [104, 165], + [34.9292, 0], + [34.48768, 164.6246], + [104.333, 164.6246], ]); }); From d5e33730ab068eb2aca80253711728c90a5ddfbf Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Sun, 13 Apr 2025 13:19:45 +0200 Subject: [PATCH 06/23] Reduce scope of coverage reports --- vitest.config.mts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/vitest.config.mts b/vitest.config.mts index 353f84ccf..207ebbf64 100644 --- a/vitest.config.mts +++ b/vitest.config.mts @@ -69,5 +69,12 @@ export default defineConfig({ statements: 60, }, }, + exclude: [ + "excalidraw-app/components/DebugCanvas.tsx", + "packages/utils/src/visualdebug.ts", + "node_modules", + "examples", + "scripts", + ], }, }); From 25d6e517c9c96d9dc3b94db23ab4073918fad22a Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Sun, 13 Apr 2025 13:29:21 +0200 Subject: [PATCH 07/23] Revert attempt to exclude some files from coverage --- vitest.config.mts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/vitest.config.mts b/vitest.config.mts index 207ebbf64..353f84ccf 100644 --- a/vitest.config.mts +++ b/vitest.config.mts @@ -69,12 +69,5 @@ export default defineConfig({ statements: 60, }, }, - exclude: [ - "excalidraw-app/components/DebugCanvas.tsx", - "packages/utils/src/visualdebug.ts", - "node_modules", - "examples", - "scripts", - ], }, }); From aa91a3d61065f55ecd765f3323b2978b4211e734 Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Sun, 13 Apr 2025 13:52:24 +0200 Subject: [PATCH 08/23] Adaptive segment unification --- packages/element/src/elbowArrow.ts | 31 +++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/packages/element/src/elbowArrow.ts b/packages/element/src/elbowArrow.ts index 227207c52..b9c602b9b 100644 --- a/packages/element/src/elbowArrow.ts +++ b/packages/element/src/elbowArrow.ts @@ -107,7 +107,10 @@ type ElbowArrowData = { hoveredEndElement: ExcalidrawBindableElement | null; }; -const DEDUP_TRESHOLD = 1; +const calculateDedupTreshhold = ( + a: Point, + b: Point, +) => 1 + pointDistance(a, b) / 100; const calculatePadding = ( aabb: Bounds, @@ -207,7 +210,11 @@ const handleSegmentRenormalization = ( if ( // Remove segments that are too short - pointDistance(points[i - 2], points[i - 1]) < DEDUP_TRESHOLD + pointDistance(points[i - 2], points[i - 1]) < + calculateDedupTreshhold( + points[i - 3] ?? points[i - 3], + points[i] ?? points[i - 1], + ) ) { const prevPrevSegmentIdx = nextFixedSegments?.findIndex((segment) => segment.index === i - 2) ?? @@ -2228,7 +2235,10 @@ const removeElbowArrowShortSegments = ( const prev = points[idx - 1]; const prevDist = pointDistance(prev, p); - return prevDist > DEDUP_TRESHOLD; + return ( + prevDist > + calculateDedupTreshhold(points[idx - 2] ?? prev, points[idx + 1] ?? p) + ); }); } @@ -2333,13 +2343,16 @@ const gridAddressesEqual = (a: GridAddress, b: GridAddress): boolean => export const validateElbowPoints =

( points: readonly P[], - tolerance: number = DEDUP_TRESHOLD, + tolerance?: number, ) => points .slice(1) - .map( - (p, i) => - Math.abs(p[0] - points[i][0]) < tolerance || - Math.abs(p[1] - points[i][1]) < tolerance, - ) + .map((p, i) => { + const t = + tolerance ?? + calculateDedupTreshhold(points[i - 1] ?? points[i], points[i + 2] ?? p); + return ( + Math.abs(p[0] - points[i][0]) < t || Math.abs(p[1] - points[i][1]) < t + ); + }) .every(Boolean); From 7abbb2afa39013a4a90b912e06694abeb13bf557 Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Mon, 14 Apr 2025 18:52:51 +0200 Subject: [PATCH 09/23] New heuristic based on minimal arrow extent Signed-off-by: Mark Tolmacs --- packages/element/src/elbowArrow.ts | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/packages/element/src/elbowArrow.ts b/packages/element/src/elbowArrow.ts index b9c602b9b..02cc4da67 100644 --- a/packages/element/src/elbowArrow.ts +++ b/packages/element/src/elbowArrow.ts @@ -123,17 +123,7 @@ const calculatePadding = ( return size > 75 ? 40 - : Math.min( - Math.max( - headingIsHorizontal(startHeading) ? width / 2 - 1 : height / 2 - 1, - 10, - ), - Math.max( - headingIsHorizontal(endHeading) ? width / 2 - 1 : height / 2 - 1, - 10, - ), - 40, - ); + : Math.min(Math.max(Math.min(width / 2 - 1, height / 2 - 1), 10), 40); }; const handleSegmentRenormalization = ( From 25fb43f5b71848aa0d37bd987a1c1898db05a13d Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Mon, 14 Apr 2025 19:05:31 +0200 Subject: [PATCH 10/23] Snapshot update Signed-off-by: Mark Tolmacs --- .../excalidraw/tests/__snapshots__/history.test.tsx.snap | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap index 9ffb97128..cf7f059ae 100644 --- a/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap @@ -926,11 +926,12 @@ History { ], ], "startBinding": null, + "y": 0, }, "inserted": { "endBinding": { "elementId": "id166", - "focus": -0, + "focus": "-0.00000", "gap": 1, }, "points": [ @@ -945,9 +946,10 @@ History { ], "startBinding": { "elementId": "id165", - "focus": 0, + "focus": "0.00000", "gap": 1, }, + "y": "-0.00000", }, }, }, From c2de1304b7d3fa7932b3405bcce96116a21f794f Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Tue, 15 Apr 2025 16:13:01 +0200 Subject: [PATCH 11/23] Add snapshot update Signed-off-by: Mark Tolmacs --- .../excalidraw/tests/__snapshots__/history.test.tsx.snap | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap index cf7f059ae..9ffb97128 100644 --- a/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap @@ -926,12 +926,11 @@ History { ], ], "startBinding": null, - "y": 0, }, "inserted": { "endBinding": { "elementId": "id166", - "focus": "-0.00000", + "focus": -0, "gap": 1, }, "points": [ @@ -946,10 +945,9 @@ History { ], "startBinding": { "elementId": "id165", - "focus": "0.00000", + "focus": 0, "gap": 1, }, - "y": "-0.00000", }, }, }, From 52445aeb68889cd09f63e160c3ccc982f43fe70f Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Mon, 21 Apr 2025 14:56:36 +0200 Subject: [PATCH 12/23] Fix a particular routing issue --- packages/element/src/elbowArrow.ts | 82 +++++++++++++++++----- packages/element/tests/elbowArrow.test.tsx | 4 +- 2 files changed, 65 insertions(+), 21 deletions(-) diff --git a/packages/element/src/elbowArrow.ts b/packages/element/src/elbowArrow.ts index 02cc4da67..0256d4fad 100644 --- a/packages/element/src/elbowArrow.ts +++ b/packages/element/src/elbowArrow.ts @@ -66,6 +66,8 @@ import type { NonDeletedExcalidrawElement, } from "./types"; +import { debugDrawBounds } from "@excalidraw/utils/visualdebug"; + type GridAddress = [number, number] & { _brand: "gridaddress" }; type Node = { @@ -1418,9 +1420,15 @@ const getElbowArrowData = ( BASE_PADDING, ), boundsOverlap, - hoveredStartElement && aabbForElement(hoveredStartElement), - hoveredEndElement && aabbForElement(hoveredEndElement), + hoveredStartElement + ? aabbForElement(hoveredStartElement) + : startPointBounds, + hoveredEndElement ? aabbForElement(hoveredEndElement) : endPointBounds, ); + debugDrawBounds(endElementBounds); + // dynamicAABBs.forEach((aabb) => { + // debugDrawBounds(aabb); + // }); const startDonglePosition = getDonglePosition( dynamicAABBs[0], startHeading, @@ -1691,11 +1699,11 @@ const generateDynamicAABBs = ( a: Bounds, b: Bounds, common: Bounds, - startDifference?: [number, number, number, number], - endDifference?: [number, number, number, number], - disableSideHack?: boolean, - startElementBounds?: Bounds | null, - endElementBounds?: Bounds | null, + startDifference: [number, number, number, number], + endDifference: [number, number, number, number], + disableSideHack: boolean, + startElementBounds: Bounds, + endElementBounds: Bounds, ): Bounds[] => { const startEl = startElementBounds ?? a; const endEl = endElementBounds ?? b; @@ -1775,15 +1783,24 @@ const generateDynamicAABBs = ( (second[0] + second[2]) / 2, (second[1] + second[3]) / 2, ]; - if (b[0] > a[2] && a[1] > b[3]) { + if ( + endElementBounds[0] > startElementBounds[2] && + startElementBounds[1] > endElementBounds[3] + ) { // BOTTOM LEFT const cX = first[2] + (second[0] - first[2]) / 2; const cY = second[3] + (first[1] - second[3]) / 2; if ( vectorCross( - vector(a[2] - endCenterX, a[1] - endCenterY), - vector(a[0] - endCenterX, a[3] - endCenterY), + vector( + startElementBounds[2] - endCenterX, + startElementBounds[1] - endCenterY, + ), + vector( + startElementBounds[0] - endCenterX, + startElementBounds[3] - endCenterY, + ), ) > 0 ) { return [ @@ -1796,15 +1813,24 @@ const generateDynamicAABBs = ( [first[0], cY, first[2], first[3]], [second[0], second[1], second[2], cY], ]; - } else if (a[2] < b[0] && a[3] < b[1]) { + } else if ( + startElementBounds[2] < endElementBounds[0] && + startElementBounds[3] < endElementBounds[1] + ) { // TOP LEFT const cX = first[2] + (second[0] - first[2]) / 2; const cY = first[3] + (second[1] - first[3]) / 2; if ( vectorCross( - vector(a[0] - endCenterX, a[1] - endCenterY), - vector(a[2] - endCenterX, a[3] - endCenterY), + vector( + startElementBounds[0] - endCenterX, + startElementBounds[1] - endCenterY, + ), + vector( + startElementBounds[2] - endCenterX, + startElementBounds[3] - endCenterY, + ), ) > 0 ) { return [ @@ -1817,15 +1843,24 @@ const generateDynamicAABBs = ( [first[0], first[1], cX, first[3]], [cX, second[1], second[2], second[3]], ]; - } else if (a[0] > b[2] && a[3] < b[1]) { + } else if ( + startElementBounds[0] > endElementBounds[2] && + startElementBounds[3] < endElementBounds[1] + ) { // TOP RIGHT const cX = second[2] + (first[0] - second[2]) / 2; const cY = first[3] + (second[1] - first[3]) / 2; if ( vectorCross( - vector(a[2] - endCenterX, a[1] - endCenterY), - vector(a[0] - endCenterX, a[3] - endCenterY), + vector( + startElementBounds[2] - endCenterX, + startElementBounds[1] - endCenterY, + ), + vector( + startElementBounds[0] - endCenterX, + startElementBounds[3] - endCenterY, + ), ) > 0 ) { return [ @@ -1838,15 +1873,24 @@ const generateDynamicAABBs = ( [first[0], first[1], first[2], cY], [second[0], cY, second[2], second[3]], ]; - } else if (a[0] > b[2] && a[1] > b[3]) { + } else if ( + startElementBounds[0] > endElementBounds[2] && + startElementBounds[1] > endElementBounds[3] + ) { // BOTTOM RIGHT const cX = second[2] + (first[0] - second[2]) / 2; const cY = second[3] + (first[1] - second[3]) / 2; if ( vectorCross( - vector(a[0] - endCenterX, a[1] - endCenterY), - vector(a[2] - endCenterX, a[3] - endCenterY), + vector( + startElementBounds[0] - endCenterX, + startElementBounds[1] - endCenterY, + ), + vector( + startElementBounds[2] - endCenterX, + startElementBounds[3] - endCenterY, + ), ) > 0 ) { return [ diff --git a/packages/element/tests/elbowArrow.test.tsx b/packages/element/tests/elbowArrow.test.tsx index 1c3d8cf6e..30136be74 100644 --- a/packages/element/tests/elbowArrow.test.tsx +++ b/packages/element/tests/elbowArrow.test.tsx @@ -296,8 +296,8 @@ describe("elbow arrow ui", () => { expect(arrow.points).toCloselyEqualPoints([ [0, 0], - [34.9292, 0], - [34.48768, 164.6246], + [34.7084, 0], + [34.7084, 164.6246], [104.333, 164.6246], ]); }); From 230e47fd525730c14743b29e86d2111f26596b38 Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Mon, 21 Apr 2025 14:56:58 +0200 Subject: [PATCH 13/23] Remove debug --- packages/element/src/elbowArrow.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/element/src/elbowArrow.ts b/packages/element/src/elbowArrow.ts index 0256d4fad..be188da21 100644 --- a/packages/element/src/elbowArrow.ts +++ b/packages/element/src/elbowArrow.ts @@ -66,8 +66,6 @@ import type { NonDeletedExcalidrawElement, } from "./types"; -import { debugDrawBounds } from "@excalidraw/utils/visualdebug"; - type GridAddress = [number, number] & { _brand: "gridaddress" }; type Node = { @@ -1425,10 +1423,6 @@ const getElbowArrowData = ( : startPointBounds, hoveredEndElement ? aabbForElement(hoveredEndElement) : endPointBounds, ); - debugDrawBounds(endElementBounds); - // dynamicAABBs.forEach((aabb) => { - // debugDrawBounds(aabb); - // }); const startDonglePosition = getDonglePosition( dynamicAABBs[0], startHeading, From 41711af21099d0d772fa78fec365f3786c1f561c Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Mon, 21 Apr 2025 17:24:13 +0200 Subject: [PATCH 14/23] Adjust padding so smaller objects have smaller padding --- packages/element/src/elbowArrow.ts | 38 ++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/packages/element/src/elbowArrow.ts b/packages/element/src/elbowArrow.ts index be188da21..143bf3415 100644 --- a/packages/element/src/elbowArrow.ts +++ b/packages/element/src/elbowArrow.ts @@ -110,18 +110,30 @@ type ElbowArrowData = { const calculateDedupTreshhold = ( a: Point, b: Point, -) => 1 + pointDistance(a, b) / 100; +) => 1 + pointDistance(a, b) / 300; const calculatePadding = ( aabb: Bounds, - startHeading: Heading, - endHeading: Heading, + startBoundingBox: Bounds, + endBoundingBox: Bounds, ) => { const width = aabb[2] - aabb[0]; const height = aabb[3] - aabb[1]; const size = Math.max(width, height); + const startExtent = Math.max( + startBoundingBox[2] - startBoundingBox[0], + startBoundingBox[3] - startBoundingBox[1], + 10, + ); + const endExtent = Math.max( + endBoundingBox[2] - endBoundingBox[0], + endBoundingBox[3] - endBoundingBox[1], + 10, + ); - return size > 75 + return Math.min(startExtent, endExtent) < 80 + ? Math.min(startExtent, endExtent) / 2 + : size > 75 ? 40 : Math.min(Math.max(Math.min(width / 2 - 1, height / 2 - 1), 10), 40); }; @@ -486,8 +498,10 @@ const handleSegmentMove = ( ): ElementUpdate => { const BASE_PADDING = calculatePadding( aabbForElement(arrow), - startHeading, - endHeading, + hoveredStartElement + ? aabbForElement(hoveredStartElement) + : [10, 10, 10, 10], + hoveredEndElement ? aabbForElement(hoveredEndElement) : [10, 10, 10, 10], ); const activelyModifiedSegmentIdx = fixedSegments .map((segment, i) => { @@ -735,8 +749,10 @@ const handleEndpointDrag = ( ) => { const BASE_PADDING = calculatePadding( aabbForPoints([startGlobalPoint, endGlobalPoint]), - startHeading, - endHeading, + hoveredStartElement + ? aabbForElement(hoveredStartElement) + : [10, 10, 10, 10], + hoveredEndElement ? aabbForElement(hoveredEndElement) : [10, 10, 10, 10], ); let startIsSpecial = arrow.startIsSpecial ?? null; let endIsSpecial = arrow.endIsSpecial ?? null; @@ -1330,8 +1346,10 @@ const getElbowArrowData = ( ] as Bounds; const BASE_PADDING = calculatePadding( aabbForPoints([startGlobalPoint, endGlobalPoint]), - startHeading, - endHeading, + hoveredStartElement + ? aabbForElement(hoveredStartElement) + : [10, 10, 10, 10], + hoveredEndElement ? aabbForElement(hoveredEndElement) : [10, 10, 10, 10], ); const startElementBounds = hoveredStartElement ? aabbForElement( From ece841326ba21f1a218760df6164ce373a9da07c Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Fri, 25 Apr 2025 14:25:40 +0200 Subject: [PATCH 15/23] Refine corner avoidance --- packages/element/src/binding.ts | 41 ++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/packages/element/src/binding.ts b/packages/element/src/binding.ts index 8a4ca3ee6..6e720e182 100644 --- a/packages/element/src/binding.ts +++ b/packages/element/src/binding.ts @@ -45,6 +45,12 @@ import { import { intersectElementWithLineSegment } from "./collision"; import { distanceToBindableElement } from "./distance"; import { + compareHeading, + HEADING_DOWN, + HEADING_LEFT, + HEADING_RIGHT, + HEADING_UP, + headingForPoint, headingForPointFromElement, headingIsHorizontal, vectorToHeading, @@ -87,6 +93,7 @@ import type { SceneElementsMap, FixedPointBinding, } from "./types"; +import { debugDrawLine, debugDrawPoint } from "@excalidraw/utils/visualdebug"; export type SuggestedBinding = | NonDeleted @@ -1040,7 +1047,14 @@ export const avoidRectangularCorner = ( if (nonRotatedPoint[0] < element.x && nonRotatedPoint[1] < element.y) { // Top left - if (nonRotatedPoint[1] - element.y > -FIXED_BINDING_DISTANCE) { + const heading = headingForPoint( + nonRotatedPoint, + pointFrom(element.x, element.y), + ); + if ( + compareHeading(heading, HEADING_DOWN) || + compareHeading(heading, HEADING_LEFT) + ) { return pointRotateRads( pointFrom(element.x - FIXED_BINDING_DISTANCE, element.y), center, @@ -1057,7 +1071,14 @@ export const avoidRectangularCorner = ( nonRotatedPoint[1] > element.y + element.height ) { // Bottom left - if (nonRotatedPoint[0] - element.x > -FIXED_BINDING_DISTANCE) { + const heading = headingForPoint( + nonRotatedPoint, + pointFrom(element.x, element.y + element.height), + ); + if ( + compareHeading(heading, HEADING_DOWN) || + compareHeading(heading, HEADING_RIGHT) + ) { return pointRotateRads( pointFrom( element.x, @@ -1077,9 +1098,13 @@ export const avoidRectangularCorner = ( nonRotatedPoint[1] > element.y + element.height ) { // Bottom right + const heading = headingForPoint( + nonRotatedPoint, + pointFrom(element.x + element.width, element.y + element.height), + ); if ( - nonRotatedPoint[0] - element.x < - element.width + FIXED_BINDING_DISTANCE + compareHeading(heading, HEADING_DOWN) || + compareHeading(heading, HEADING_LEFT) ) { return pointRotateRads( pointFrom( @@ -1103,9 +1128,13 @@ export const avoidRectangularCorner = ( nonRotatedPoint[1] < element.y ) { // Top right + const heading = headingForPoint( + nonRotatedPoint, + pointFrom(element.x + element.width, element.y), + ); if ( - nonRotatedPoint[0] - element.x < - element.width + FIXED_BINDING_DISTANCE + compareHeading(heading, HEADING_UP) || + compareHeading(heading, HEADING_LEFT) ) { return pointRotateRads( pointFrom( From b8fdd7ef2369ea4dd640cab6a261011d4b9c9b24 Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Fri, 25 Apr 2025 14:26:01 +0200 Subject: [PATCH 16/23] Remove unneeded imports --- packages/element/src/binding.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/element/src/binding.ts b/packages/element/src/binding.ts index 6e720e182..cc71ab930 100644 --- a/packages/element/src/binding.ts +++ b/packages/element/src/binding.ts @@ -93,7 +93,6 @@ import type { SceneElementsMap, FixedPointBinding, } from "./types"; -import { debugDrawLine, debugDrawPoint } from "@excalidraw/utils/visualdebug"; export type SuggestedBinding = | NonDeleted From c7c6a4c3f1451763e59ad27143a056d06a1a4c61 Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Fri, 25 Apr 2025 14:33:34 +0200 Subject: [PATCH 17/23] Fix test --- packages/element/tests/elbowArrow.test.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/element/tests/elbowArrow.test.tsx b/packages/element/tests/elbowArrow.test.tsx index 02326d2a3..334c6c3d2 100644 --- a/packages/element/tests/elbowArrow.test.tsx +++ b/packages/element/tests/elbowArrow.test.tsx @@ -195,15 +195,15 @@ describe("elbow arrow routing", () => { expect(arrow.startBinding).not.toBe(null); expect(arrow.endBinding).not.toBe(null); - h.app.scene.mutateElement(arrow, { + scene.mutateElement(arrow, { points: [pointFrom(0, 0), pointFrom(90, 200)], }); expect(arrow.points).toCloselyEqualPoints([ [0, 0], - [44.92, 0], - [44.92, 200], - [89.85, 200], + [44.9292, 0], + [44.9292, 200], + [89.8585, 200], ]); }); }); From 9a2bd189048886e0c1fc772b69811cfdd86da490 Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Fri, 25 Apr 2025 18:54:18 +0200 Subject: [PATCH 18/23] Further adjustments for edge cases --- packages/element/src/binding.ts | 12 ++++++- packages/element/src/elbowArrow.ts | 19 +++++----- packages/element/tests/elbowArrow.test.tsx | 40 +++++++++++----------- 3 files changed, 40 insertions(+), 31 deletions(-) diff --git a/packages/element/src/binding.ts b/packages/element/src/binding.ts index 812b6cb31..c018424bd 100644 --- a/packages/element/src/binding.ts +++ b/packages/element/src/binding.ts @@ -951,7 +951,6 @@ export const bindPointToSnapToElementOutline = ( otherPoint, ), ), - 0.1, )[0]; } else { intersection = intersectElementWithLineSegment( @@ -1116,6 +1115,17 @@ export const avoidRectangularCorner = ( ); } + // Break up explicit border bindings to have better elbow arrow routing + if (p[0] === element.x) { + return pointFrom(p[0] - FIXED_BINDING_DISTANCE, p[1]); + } else if (p[0] === element.x + element.width) { + return pointFrom(p[0] + FIXED_BINDING_DISTANCE, p[1]); + } else if (p[1] === element.y) { + return pointFrom(p[0], p[1] - FIXED_BINDING_DISTANCE); + } else if (p[1] === element.y + element.height) { + return pointFrom(p[0], p[1] + FIXED_BINDING_DISTANCE); + } + return p; }; diff --git a/packages/element/src/elbowArrow.ts b/packages/element/src/elbowArrow.ts index aabcadd1d..54b696620 100644 --- a/packages/element/src/elbowArrow.ts +++ b/packages/element/src/elbowArrow.ts @@ -391,6 +391,10 @@ const handleSegmentRelease = ( null, ); + if (!restoredPoints) { + return {}; + } + const nextPoints: GlobalPoint[] = []; // First part of the arrow are the old points @@ -2181,16 +2185,11 @@ const normalizeArrowElementUpdate = ( nextFixedSegments: readonly FixedSegment[] | null, startIsSpecial?: ExcalidrawElbowArrowElement["startIsSpecial"], endIsSpecial?: ExcalidrawElbowArrowElement["startIsSpecial"], -): { - points: LocalPoint[]; - x: number; - y: number; - width: number; - height: number; - fixedSegments: readonly FixedSegment[] | null; - startIsSpecial?: ExcalidrawElbowArrowElement["startIsSpecial"]; - endIsSpecial?: ExcalidrawElbowArrowElement["startIsSpecial"]; -} => { +): ElementUpdate => { + if (global.length === 0) { + return {}; + } + const offsetX = global[0][0]; const offsetY = global[0][1]; let points = global.map((p) => diff --git a/packages/element/tests/elbowArrow.test.tsx b/packages/element/tests/elbowArrow.test.tsx index 334c6c3d2..e7e38d7e6 100644 --- a/packages/element/tests/elbowArrow.test.tsx +++ b/packages/element/tests/elbowArrow.test.tsx @@ -78,9 +78,9 @@ describe("elbow arrow segment move", () => { expect(arrow.points).toCloselyEqualPoints([ [0, 0], - [109.92, 0], - [109.92, 200], - [189.85, 200], + [110, 0], + [110, 200], + [190, 200], ]); mouse.reset(); @@ -89,9 +89,9 @@ describe("elbow arrow segment move", () => { expect(arrow.points).toCloselyEqualPoints([ [0, 0], - [109.92, 0], - [109.92, 200], - [189.85, 200], + [110, 0], + [110, 200], + [190, 200], ]); }); @@ -201,9 +201,9 @@ describe("elbow arrow routing", () => { expect(arrow.points).toCloselyEqualPoints([ [0, 0], - [44.9292, 0], - [44.9292, 200], - [89.8585, 200], + [45, 0], + [45, 200], + [90, 200], ]); }); }); @@ -255,9 +255,9 @@ describe("elbow arrow ui", () => { expect(arrow.elbowed).toBe(true); expect(arrow.points).toCloselyEqualPoints([ [0, 0], - [44.92, 0], - [44.92, 200], - [89.85, 200], + [45, 0], + [45, 200], + [90, 200], ]); }); @@ -297,9 +297,9 @@ describe("elbow arrow ui", () => { expect(arrow.points).toCloselyEqualPoints([ [0, 0], - [34.7084, 0], - [34.7084, 164.6246], - [104.333, 164.6246], + [34.7791, 0], + [34.7791, 164.67], + [102.931, 164.67], ]); }); @@ -353,9 +353,9 @@ describe("elbow arrow ui", () => { expect(duplicatedArrow.elbowed).toBe(true); expect(duplicatedArrow.points).toCloselyEqualPoints([ [0, 0], - [44.92, 0], - [44.92, 200], - [89.85, 200], + [45, 0], + [45, 200], + [90, 200], ]); expect(arrow.startBinding).not.toBe(null); expect(arrow.endBinding).not.toBe(null); @@ -408,8 +408,8 @@ describe("elbow arrow ui", () => { expect(duplicatedArrow.points).toCloselyEqualPoints([ [0, 0], [0, 100], - [89.85, 100], - [89.85, 200], + [90, 100], + [90, 200], ]); }); }); From e6ade3b627af888b6ebbe7bb38cf3fbcbde2c45a Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Fri, 25 Apr 2025 18:58:05 +0200 Subject: [PATCH 19/23] Fix test --- packages/element/tests/elbowArrow.test.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/element/tests/elbowArrow.test.tsx b/packages/element/tests/elbowArrow.test.tsx index e7e38d7e6..b805b81ff 100644 --- a/packages/element/tests/elbowArrow.test.tsx +++ b/packages/element/tests/elbowArrow.test.tsx @@ -199,7 +199,7 @@ describe("elbow arrow routing", () => { points: [pointFrom(0, 0), pointFrom(90, 200)], }); - expect(arrow.points).toCloselyEqualPoints([ + expect(arrow.points).toEqual([ [0, 0], [45, 0], [45, 200], @@ -253,7 +253,7 @@ describe("elbow arrow ui", () => { expect(arrow.type).toBe("arrow"); expect(arrow.elbowed).toBe(true); - expect(arrow.points).toCloselyEqualPoints([ + expect(arrow.points).toEqual([ [0, 0], [45, 0], [45, 200], @@ -351,7 +351,7 @@ describe("elbow arrow ui", () => { expect(duplicatedArrow.id).not.toBe(originalArrowId); expect(duplicatedArrow.type).toBe("arrow"); expect(duplicatedArrow.elbowed).toBe(true); - expect(duplicatedArrow.points).toCloselyEqualPoints([ + expect(duplicatedArrow.points).toEqual([ [0, 0], [45, 0], [45, 200], @@ -405,7 +405,7 @@ describe("elbow arrow ui", () => { expect(duplicatedArrow.id).not.toBe(originalArrowId); expect(duplicatedArrow.type).toBe("arrow"); expect(duplicatedArrow.elbowed).toBe(true); - expect(duplicatedArrow.points).toCloselyEqualPoints([ + expect(duplicatedArrow.points).toEqual([ [0, 0], [0, 100], [90, 100], From 7d0d6aec7a5c578c025ffbecaea8ae8e9d247bcb Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Mon, 28 Apr 2025 19:31:30 +0200 Subject: [PATCH 20/23] Another algo forpaddings --- packages/element/src/elbowArrow.ts | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/packages/element/src/elbowArrow.ts b/packages/element/src/elbowArrow.ts index 54b696620..fc9f24357 100644 --- a/packages/element/src/elbowArrow.ts +++ b/packages/element/src/elbowArrow.ts @@ -116,25 +116,18 @@ const calculatePadding = ( startBoundingBox: Bounds, endBoundingBox: Bounds, ) => { - const width = aabb[2] - aabb[0]; - const height = aabb[3] - aabb[1]; - const size = Math.max(width, height); - const startExtent = Math.max( - startBoundingBox[2] - startBoundingBox[0], - startBoundingBox[3] - startBoundingBox[1], - 10, + return Math.min( + Math.hypot( + startBoundingBox[2] - startBoundingBox[0], + startBoundingBox[3] - startBoundingBox[1], + ) / 4, + Math.hypot( + endBoundingBox[2] - endBoundingBox[0], + endBoundingBox[3] - endBoundingBox[1], + ) / 4, + Math.hypot(aabb[2] - aabb[0], aabb[3] - aabb[1]) / 4, + 40, ); - const endExtent = Math.max( - endBoundingBox[2] - endBoundingBox[0], - endBoundingBox[3] - endBoundingBox[1], - 10, - ); - - return Math.min(startExtent, endExtent) < 80 - ? Math.min(startExtent, endExtent) / 2 - : size > 75 - ? 40 - : Math.min(Math.max(Math.min(width / 2 - 1, height / 2 - 1), 10), 40); }; const handleSegmentRenormalization = ( From 28066034d78bd08cc491c8e1396d84aad4be5d8e Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Mon, 28 Apr 2025 19:37:50 +0200 Subject: [PATCH 21/23] Arrowhead padding --- packages/element/src/elbowArrow.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/element/src/elbowArrow.ts b/packages/element/src/elbowArrow.ts index fc9f24357..47497476a 100644 --- a/packages/element/src/elbowArrow.ts +++ b/packages/element/src/elbowArrow.ts @@ -1351,7 +1351,7 @@ const getElbowArrowData = ( offsetFromHeading( startHeading, arrow.startArrowhead - ? FIXED_BINDING_DISTANCE * 6 + ? FIXED_BINDING_DISTANCE : FIXED_BINDING_DISTANCE * 2, 1, ), @@ -1363,7 +1363,7 @@ const getElbowArrowData = ( offsetFromHeading( endHeading, arrow.endArrowhead - ? FIXED_BINDING_DISTANCE * 6 + ? FIXED_BINDING_DISTANCE : FIXED_BINDING_DISTANCE * 2, 1, ), From 541725ff5af79d328901639cb2dd4b625175f4ea Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Mon, 28 Apr 2025 19:49:19 +0200 Subject: [PATCH 22/23] Test fixes --- packages/element/src/elbowArrow.ts | 36 +++++++++---------- .../excalidraw/actions/actionFlip.test.tsx | 8 ++--- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/packages/element/src/elbowArrow.ts b/packages/element/src/elbowArrow.ts index 47497476a..40f5a51c5 100644 --- a/packages/element/src/elbowArrow.ts +++ b/packages/element/src/elbowArrow.ts @@ -1345,29 +1345,25 @@ const getElbowArrowData = ( : [10, 10, 10, 10], hoveredEndElement ? aabbForElement(hoveredEndElement) : [10, 10, 10, 10], ); + const startOffsets = offsetFromHeading( + startHeading, + arrow.startArrowhead + ? FIXED_BINDING_DISTANCE * 4 + : FIXED_BINDING_DISTANCE * 2, + 1, + ); + const endOffsets = offsetFromHeading( + endHeading, + arrow.endArrowhead + ? FIXED_BINDING_DISTANCE * 4 + : FIXED_BINDING_DISTANCE * 2, + 1, + ); const startElementBounds = hoveredStartElement - ? aabbForElement( - hoveredStartElement, - offsetFromHeading( - startHeading, - arrow.startArrowhead - ? FIXED_BINDING_DISTANCE - : FIXED_BINDING_DISTANCE * 2, - 1, - ), - ) + ? aabbForElement(hoveredStartElement, startOffsets) : startPointBounds; const endElementBounds = hoveredEndElement - ? aabbForElement( - hoveredEndElement, - offsetFromHeading( - endHeading, - arrow.endArrowhead - ? FIXED_BINDING_DISTANCE - : FIXED_BINDING_DISTANCE * 2, - 1, - ), - ) + ? aabbForElement(hoveredEndElement, endOffsets) : endPointBounds; const boundsOverlap = pointInsideBounds( diff --git a/packages/excalidraw/actions/actionFlip.test.tsx b/packages/excalidraw/actions/actionFlip.test.tsx index 23e4ffc12..e2a30d269 100644 --- a/packages/excalidraw/actions/actionFlip.test.tsx +++ b/packages/excalidraw/actions/actionFlip.test.tsx @@ -73,12 +73,12 @@ describe("flipping re-centers selection", () => { API.executeAction(actionFlipHorizontal); const rec1 = h.elements.find((el) => el.id === "rec1")!; - expect(rec1.x).toBeCloseTo(100, 0); - expect(rec1.y).toBeCloseTo(100, 0); + expect(rec1.x).toBeCloseTo(97.8678, 0); + expect(rec1.y).toBeCloseTo(97.444, 0); const rec2 = h.elements.find((el) => el.id === "rec2")!; - expect(rec2.x).toBeCloseTo(220, 0); - expect(rec2.y).toBeCloseTo(250, 0); + expect(rec2.x).toBeCloseTo(218, 0); + expect(rec2.y).toBeCloseTo(247, 0); }); }); From 82cef23c3dc9726be7eec7bb8d4a527b77fd86b6 Mon Sep 17 00:00:00 2001 From: dwelle <5153846+dwelle@users.noreply.github.com> Date: Wed, 30 Apr 2025 10:39:54 +0200 Subject: [PATCH 23/23] DEBUG --- packages/element/src/elbowArrow.ts | 53 +++++++++++++++++++----------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/packages/element/src/elbowArrow.ts b/packages/element/src/elbowArrow.ts index 40f5a51c5..85c80f093 100644 --- a/packages/element/src/elbowArrow.ts +++ b/packages/element/src/elbowArrow.ts @@ -65,6 +65,8 @@ import type { NonDeletedExcalidrawElement, } from "./types"; +import { debugDrawBounds } from "@excalidraw/utils/visualdebug"; + type GridAddress = [number, number] & { _brand: "gridaddress" }; type Node = { @@ -116,17 +118,20 @@ const calculatePadding = ( startBoundingBox: Bounds, endBoundingBox: Bounds, ) => { - return Math.min( - Math.hypot( - startBoundingBox[2] - startBoundingBox[0], - startBoundingBox[3] - startBoundingBox[1], - ) / 4, - Math.hypot( - endBoundingBox[2] - endBoundingBox[0], - endBoundingBox[3] - endBoundingBox[1], - ) / 4, - Math.hypot(aabb[2] - aabb[0], aabb[3] - aabb[1]) / 4, - 40, + return Math.max( + Math.min( + Math.hypot( + startBoundingBox[2] - startBoundingBox[0], + startBoundingBox[3] - startBoundingBox[1], + ) / 4, + Math.hypot( + endBoundingBox[2] - endBoundingBox[0], + endBoundingBox[3] - endBoundingBox[1], + ) / 4, + Math.hypot(aabb[2] - aabb[0], aabb[3] - aabb[1]) / 4, + 40, + ), + 30, ); }; @@ -1347,16 +1352,12 @@ const getElbowArrowData = ( ); const startOffsets = offsetFromHeading( startHeading, - arrow.startArrowhead - ? FIXED_BINDING_DISTANCE * 4 - : FIXED_BINDING_DISTANCE * 2, + arrow.startArrowhead ? FIXED_BINDING_DISTANCE * 4 : FIXED_BINDING_DISTANCE, 1, ); const endOffsets = offsetFromHeading( endHeading, - arrow.endArrowhead - ? FIXED_BINDING_DISTANCE * 4 - : FIXED_BINDING_DISTANCE * 2, + arrow.endArrowhead ? FIXED_BINDING_DISTANCE * 4 : FIXED_BINDING_DISTANCE, 1, ); const startElementBounds = hoveredStartElement @@ -1406,7 +1407,7 @@ const getElbowArrowData = ( : BASE_PADDING - (arrow.startArrowhead ? FIXED_BINDING_DISTANCE * 6 - : FIXED_BINDING_DISTANCE * 2), + : FIXED_BINDING_DISTANCE), BASE_PADDING, ), boundsOverlap @@ -1422,7 +1423,7 @@ const getElbowArrowData = ( : BASE_PADDING - (arrow.endArrowhead ? FIXED_BINDING_DISTANCE * 6 - : FIXED_BINDING_DISTANCE * 2), + : FIXED_BINDING_DISTANCE), BASE_PADDING, ), boundsOverlap, @@ -1431,6 +1432,20 @@ const getElbowArrowData = ( : startPointBounds, hoveredEndElement ? aabbForElement(hoveredEndElement) : endPointBounds, ); + + debugDrawBounds(startElementBounds, { + permanent: false, + color: "red", + }); + debugDrawBounds(endElementBounds, { + permanent: false, + color: "green", + }); + debugDrawBounds(dynamicAABBs, { + permanent: false, + color: "blue", + }); + const startDonglePosition = getDonglePosition( dynamicAABBs[0], startHeading,