Multipoint arrows now have single point commit in binding zones

This commit is contained in:
Mark Tolmacs 2025-03-04 20:08:44 +01:00
parent 8ac508af11
commit 8c9666b8ab
3 changed files with 92 additions and 25 deletions

View file

@ -239,24 +239,23 @@ export class LinearElementEditor {
}); });
} }
static getOutlineAvoidingPoint( static getOutlineAvoidingPointOrNull(
element: NonDeleted<ExcalidrawLinearElement>, element: NonDeleted<ExcalidrawLinearElement>,
coords: { x: number; y: number }, coords: { x: number; y: number },
pointIndex: number, pointIndex: number,
app: AppClassProperties, app: AppClassProperties,
): GlobalPoint { ) {
const elbowed = isElbowArrow(element);
const hoveredElement = getHoveredElementForBinding( const hoveredElement = getHoveredElementForBinding(
coords, coords,
app.scene.getNonDeletedElements(), app.scene.getNonDeletedElements(),
app.scene.getNonDeletedElementsMap(), app.scene.getNonDeletedElementsMap(),
app.state.zoom, app.state.zoom,
true, true,
elbowed, isElbowArrow(element),
); );
const p = pointFrom<GlobalPoint>(coords.x, coords.y);
if (hoveredElement) { if (hoveredElement) {
const p = pointFrom<GlobalPoint>(coords.x, coords.y);
const newPoints = Array.from(element.points); const newPoints = Array.from(element.points);
newPoints[pointIndex] = pointFrom<LocalPoint>( newPoints[pointIndex] = pointFrom<LocalPoint>(
p[0] - element.x, p[0] - element.x,
@ -274,7 +273,27 @@ export class LinearElementEditor {
); );
} }
return p; return null;
}
static getOutlineAvoidingPoint(
element: NonDeleted<ExcalidrawLinearElement>,
coords: { x: number; y: number },
pointIndex: number,
app: AppClassProperties,
): GlobalPoint {
const p = LinearElementEditor.getOutlineAvoidingPointOrNull(
element,
coords,
pointIndex,
app,
);
if (p) {
return p;
}
return pointFrom<GlobalPoint>(coords.x, coords.y);
} }
/** /**

View file

@ -91,10 +91,26 @@ export const actionFinalize = register({
multiPointElement.type !== "freedraw" && multiPointElement.type !== "freedraw" &&
appState.lastPointerDownWith !== "touch" appState.lastPointerDownWith !== "touch"
) { ) {
const { points, lastCommittedPoint } = multiPointElement; const { x: rx, y: ry, points, lastCommittedPoint } = multiPointElement;
const lastGlobalPoint = pointFrom<GlobalPoint>(
rx + points[points.length - 1][0],
ry + points[points.length - 1][1],
);
const hoveredElementForBinding = getHoveredElementForBinding(
{
x: lastGlobalPoint[0],
y: lastGlobalPoint[1],
},
elements,
elementsMap,
app.state.zoom,
true,
isElbowArrow(multiPointElement),
);
if ( if (
!lastCommittedPoint || !hoveredElementForBinding &&
points[points.length - 1] !== lastCommittedPoint (!lastCommittedPoint ||
points[points.length - 1] !== lastCommittedPoint)
) { ) {
mutateElement(multiPointElement, { mutateElement(multiPointElement, {
points: multiPointElement.points.slice(0, -1), points: multiPointElement.points.slice(0, -1),

View file

@ -5991,17 +5991,33 @@ class App extends React.Component<AppProps, AppState> {
if (isPathALoop(points, this.state.zoom.value)) { if (isPathALoop(points, this.state.zoom.value)) {
setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER); setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER);
} }
const outlineGlobalPoint =
LinearElementEditor.getOutlineAvoidingPointOrNull(
multiElement,
{
x: scenePointerX,
y: scenePointerY,
},
multiElement.points.length - 1,
this,
);
const nextPoint = outlineGlobalPoint
? pointFrom<LocalPoint>(
outlineGlobalPoint[0] - rx,
outlineGlobalPoint[1] - ry,
)
: pointFrom<LocalPoint>(
lastCommittedX + dxFromLastCommitted,
lastCommittedY + dyFromLastCommitted,
);
// update last uncommitted point // update last uncommitted point
mutateElement( mutateElement(
multiElement, multiElement,
{ {
points: [ points: [...points.slice(0, -1), nextPoint],
...points.slice(0, -1),
pointFrom<LocalPoint>(
lastCommittedX + dxFromLastCommitted,
lastCommittedY + dyFromLastCommitted,
),
],
}, },
false, false,
{ {
@ -7751,18 +7767,34 @@ class App extends React.Component<AppProps, AppState> {
} }
const { x: rx, y: ry, lastCommittedPoint } = multiElement; const { x: rx, y: ry, lastCommittedPoint } = multiElement;
const lastGlobalPoint = pointFrom<GlobalPoint>(
rx + multiElement.points[multiElement.points.length - 1][0],
ry + multiElement.points[multiElement.points.length - 1][1],
);
const hoveredElementForBinding = getHoveredElementForBinding(
{
x: lastGlobalPoint[0],
y: lastGlobalPoint[1],
},
this.scene.getNonDeletedElements(),
this.scene.getNonDeletedElementsMap(),
this.state.zoom,
true,
isElbowArrow(multiElement),
);
// clicking inside commit zone → finalize arrow // clicking inside commit zone → finalize arrow
if ( if (
multiElement.points.length > 1 && !!hoveredElementForBinding ||
lastCommittedPoint && (multiElement.points.length > 1 &&
pointDistance( lastCommittedPoint &&
pointFrom( pointDistance(
pointerDownState.origin.x - rx, pointFrom(
pointerDownState.origin.y - ry, pointerDownState.origin.x - rx,
), pointerDownState.origin.y - ry,
lastCommittedPoint, ),
) < LINE_CONFIRM_THRESHOLD lastCommittedPoint,
) < LINE_CONFIRM_THRESHOLD)
) { ) {
this.actionManager.executeAction(actionFinalize); this.actionManager.executeAction(actionFinalize);
return; return;