feat: Diagonal binding point

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
This commit is contained in:
Mark Tolmacs
2025-11-05 21:46:59 +01:00
parent a6c633c040
commit d27bb72510

View File

@@ -1,6 +1,8 @@
import {
KEYS,
arrayToMap,
debugDrawLine,
debugDrawPoint,
getFeatureFlag,
invariant,
isTransparent,
@@ -19,6 +21,7 @@ import {
vectorScale,
vectorNormalize,
PRECISION,
lineSegmentIntersectionPoints,
} from "@excalidraw/math";
import type { LineSegment, LocalPoint, Radians } from "@excalidraw/math";
@@ -54,6 +57,7 @@ import {
isBindableElement,
isBoundToContainer,
isElbowArrow,
isRectangularElement,
isRectanguloidElement,
isTextElement,
} from "./typeChecks";
@@ -685,13 +689,14 @@ const getBindingStrategyForDraggingBindingElementEndpoints_simple = (
: {
mode: "orbit",
element: hit,
focusPoint: opts?.finalize
? LinearElementEditor.getPointAtIndexGlobalCoordinates(
focusPoint:
projectFixedPointOntoDiagonal(
arrow,
startDragged ? 0 : -1,
globalPoint,
hit,
startDragged ? "start" : "end",
elementsMap,
)
: globalPoint,
) || globalPoint,
}
: { mode: null };
@@ -701,6 +706,105 @@ const getBindingStrategyForDraggingBindingElementEndpoints_simple = (
};
};
const projectFixedPointOntoDiagonal = (
arrow: ExcalidrawArrowElement,
point: GlobalPoint,
element: ExcalidrawElement,
startOrEnd: "start" | "end",
elementsMap: ElementsMap,
) => {
const center = elementCenterPoint(element, elementsMap);
const diagonalOne = isRectangularElement(element)
? lineSegment<GlobalPoint>(
pointRotateRads(
pointFrom<GlobalPoint>(element.x, element.y),
center,
element.angle,
),
pointRotateRads(
pointFrom<GlobalPoint>(
element.x + element.width,
element.y + element.height,
),
center,
element.angle,
),
)
: lineSegment<GlobalPoint>(
pointRotateRads(
pointFrom<GlobalPoint>(element.x + element.width / 2, element.y),
center,
element.angle,
),
pointRotateRads(
pointFrom<GlobalPoint>(
element.x + element.width / 2,
element.y + element.height,
),
center,
element.angle,
),
);
const diagonalTwo = isRectangularElement(element)
? lineSegment<GlobalPoint>(
pointRotateRads(
pointFrom<GlobalPoint>(element.x + element.width, element.y),
center,
element.angle,
),
pointRotateRads(
pointFrom<GlobalPoint>(element.x, element.y + element.height),
center,
element.angle,
),
)
: lineSegment<GlobalPoint>(
pointRotateRads(
pointFrom<GlobalPoint>(element.x, element.y + element.height / 2),
center,
element.angle,
),
pointRotateRads(
pointFrom<GlobalPoint>(
element.x + element.width,
element.y + element.height / 2,
),
center,
element.angle,
),
);
invariant(arrow.points.length >= 2, "Arrow must have at least two points");
const a = LinearElementEditor.getPointAtIndexGlobalCoordinates(
arrow,
startOrEnd === "start" ? 1 : arrow.points.length - 2,
elementsMap,
);
const b = pointFromVector<GlobalPoint>(
vectorScale(
vectorFromPoint(point, a),
2 * pointDistance(a, point) +
Math.max(
pointDistance(diagonalOne[0], diagonalOne[1]),
pointDistance(diagonalTwo[0], diagonalTwo[1]),
),
),
a,
);
const intersector = lineSegment<GlobalPoint>(a, b);
const p1 = lineSegmentIntersectionPoints(diagonalOne, intersector);
const p2 = lineSegmentIntersectionPoints(diagonalTwo, intersector);
const d1 = p1 && pointDistance(a, p1);
const d2 = p2 && pointDistance(a, p2);
if (d1 != null && d2 != null) {
return d1 < d2 ? p1 : p2;
}
return p1 || p2 || null;
};
const getBindingStrategyForDraggingBindingElementEndpoints_complex = (
arrow: NonDeleted<ExcalidrawArrowElement>,
draggingPoints: PointsPositionUpdates,