From cfa8631b0f9bd52cce61043f7448ac17f850a33a Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Wed, 27 Aug 2025 16:30:32 +0200 Subject: [PATCH] feat: Stroke width adaptive fixed binding distance --- packages/element/src/binding.ts | 50 ++++++++++++++++++------------ packages/element/src/elbowArrow.ts | 9 +++--- 2 files changed, 35 insertions(+), 24 deletions(-) diff --git a/packages/element/src/binding.ts b/packages/element/src/binding.ts index b3d6e85a7..1c5b520ef 100644 --- a/packages/element/src/binding.ts +++ b/packages/element/src/binding.ts @@ -102,6 +102,10 @@ export type BindingStrategy = export const FIXED_BINDING_DISTANCE = 5; +export const getFixedBindingDistance = ( + element: ExcalidrawBindableElement, +): number => FIXED_BINDING_DISTANCE + element.strokeWidth / 2; + export const shouldEnableBindingForPointerEvent = ( event: React.PointerEvent, ) => { @@ -1002,14 +1006,14 @@ export const bindPointToSnapToElementOutline = ( bindableElement, elementsMap, intersector, - FIXED_BINDING_DISTANCE, + getFixedBindingDistance(bindableElement), ).sort(pointDistanceSq)[0]; } else { const halfVector = vectorScale( vectorNormalize(vectorFromPoint(edgePoint, adjacentPoint)), pointDistance(edgePoint, adjacentPoint) + Math.max(bindableElement.width, bindableElement.height) + - FIXED_BINDING_DISTANCE * 2, + getFixedBindingDistance(bindableElement) * 2, ); const intersector = customIntersector ?? @@ -1024,7 +1028,7 @@ export const bindPointToSnapToElementOutline = ( bindableElement, elementsMap, intersector, - FIXED_BINDING_DISTANCE, + getFixedBindingDistance(bindableElement), ).sort( (g, h) => pointDistanceSq(g, adjacentPoint) - @@ -1098,15 +1102,15 @@ export const avoidRectangularCorner = ( if (nonRotatedPoint[0] < element.x && nonRotatedPoint[1] < element.y) { // Top left - if (nonRotatedPoint[1] - element.y > -FIXED_BINDING_DISTANCE) { + if (nonRotatedPoint[1] - element.y > -getFixedBindingDistance(element)) { return pointRotateRads( - pointFrom(element.x - FIXED_BINDING_DISTANCE, element.y), + pointFrom(element.x - getFixedBindingDistance(element), element.y), center, element.angle, ); } return pointRotateRads( - pointFrom(element.x, element.y - FIXED_BINDING_DISTANCE), + pointFrom(element.x, element.y - getFixedBindingDistance(element)), center, element.angle, ); @@ -1115,18 +1119,21 @@ export const avoidRectangularCorner = ( nonRotatedPoint[1] > element.y + element.height ) { // Bottom left - if (nonRotatedPoint[0] - element.x > -FIXED_BINDING_DISTANCE) { + if (nonRotatedPoint[0] - element.x > -getFixedBindingDistance(element)) { return pointRotateRads( pointFrom( element.x, - element.y + element.height + FIXED_BINDING_DISTANCE, + element.y + element.height + getFixedBindingDistance(element), ), center, element.angle, ); } return pointRotateRads( - pointFrom(element.x - FIXED_BINDING_DISTANCE, element.y + element.height), + pointFrom( + element.x - getFixedBindingDistance(element), + element.y + element.height, + ), center, element.angle, ); @@ -1137,12 +1144,12 @@ export const avoidRectangularCorner = ( // Bottom right if ( nonRotatedPoint[0] - element.x < - element.width + FIXED_BINDING_DISTANCE + element.width + getFixedBindingDistance(element) ) { return pointRotateRads( pointFrom( element.x + element.width, - element.y + element.height + FIXED_BINDING_DISTANCE, + element.y + element.height + getFixedBindingDistance(element), ), center, element.angle, @@ -1150,7 +1157,7 @@ export const avoidRectangularCorner = ( } return pointRotateRads( pointFrom( - element.x + element.width + FIXED_BINDING_DISTANCE, + element.x + element.width + getFixedBindingDistance(element), element.y + element.height, ), center, @@ -1163,19 +1170,22 @@ export const avoidRectangularCorner = ( // Top right if ( nonRotatedPoint[0] - element.x < - element.width + FIXED_BINDING_DISTANCE + element.width + getFixedBindingDistance(element) ) { return pointRotateRads( pointFrom( element.x + element.width, - element.y - FIXED_BINDING_DISTANCE, + element.y - getFixedBindingDistance(element), ), center, element.angle, ); } return pointRotateRads( - pointFrom(element.x + element.width + FIXED_BINDING_DISTANCE, element.y), + pointFrom( + element.x + element.width + getFixedBindingDistance(element), + element.y, + ), center, element.angle, ); @@ -1228,7 +1238,7 @@ const snapToMid = ( ) { // LEFT return pointRotateRads( - pointFrom(x - FIXED_BINDING_DISTANCE, center[1]), + pointFrom(x - getFixedBindingDistance(element), center[1]), center, angle, ); @@ -1239,7 +1249,7 @@ const snapToMid = ( ) { // TOP return pointRotateRads( - pointFrom(center[0], y - FIXED_BINDING_DISTANCE), + pointFrom(center[0], y - getFixedBindingDistance(element)), center, angle, ); @@ -1250,7 +1260,7 @@ const snapToMid = ( ) { // RIGHT return pointRotateRads( - pointFrom(x + width + FIXED_BINDING_DISTANCE, center[1]), + pointFrom(x + width + getFixedBindingDistance(element), center[1]), center, angle, ); @@ -1261,12 +1271,12 @@ const snapToMid = ( ) { // DOWN return pointRotateRads( - pointFrom(center[0], y + height + FIXED_BINDING_DISTANCE), + pointFrom(center[0], y + height + getFixedBindingDistance(element)), center, angle, ); } else if (element.type === "diamond") { - const distance = FIXED_BINDING_DISTANCE; + const distance = getFixedBindingDistance(element); const topLeft = pointFrom( x + width / 4 - distance, y + height / 4 - distance, diff --git a/packages/element/src/elbowArrow.ts b/packages/element/src/elbowArrow.ts index f19d257c6..861b95e22 100644 --- a/packages/element/src/elbowArrow.ts +++ b/packages/element/src/elbowArrow.ts @@ -29,6 +29,7 @@ import { FIXED_BINDING_DISTANCE, getHeadingForElbowArrowSnap, getGlobalFixedPointForBindableElement, + getFixedBindingDistance, } from "./binding"; import { distanceToElement } from "./distance"; import { @@ -1290,8 +1291,8 @@ const getElbowArrowData = ( offsetFromHeading( startHeading, arrow.startArrowhead - ? FIXED_BINDING_DISTANCE * 6 - : FIXED_BINDING_DISTANCE * 2, + ? getFixedBindingDistance(hoveredStartElement) * 6 + : getFixedBindingDistance(hoveredStartElement) * 2, 1, ), ) @@ -1303,8 +1304,8 @@ const getElbowArrowData = ( offsetFromHeading( endHeading, arrow.endArrowhead - ? FIXED_BINDING_DISTANCE * 6 - : FIXED_BINDING_DISTANCE * 2, + ? getFixedBindingDistance(hoveredEndElement) * 6 + : getFixedBindingDistance(hoveredEndElement) * 2, 1, ), )