fix: do not snap to each other when moving multiple points together

This commit is contained in:
Ryan Di 2025-06-26 17:22:42 +10:00
parent a0f7edadec
commit 0e197ef5c4
3 changed files with 37 additions and 65 deletions

View File

@ -370,17 +370,12 @@ export class LinearElementEditor {
const effectiveGridX = referencePointCoords[0] + dxFromReference;
const effectiveGridY = referencePointCoords[1] + dyFromReference;
let newDraggingPointPosition = pointFrom(
effectiveGridX,
effectiveGridY,
);
if (!isElbowArrow(element)) {
const { snapOffset, snapLines } = snapLinearElementPoint(
app.scene.getNonDeletedElements(),
element,
lastClickedPoint,
{ x: effectiveGridX, y: effectiveGridY },
pointFrom<GlobalPoint>(effectiveGridX, effectiveGridY),
app,
event,
elementsMap,
@ -448,7 +443,7 @@ export class LinearElementEditor {
-element.angle as Radians,
);
newDraggingPointPosition = pointFrom(
const newDraggingPointPosition = pointFrom(
referencePoint[0] + rotatedX,
referencePoint[1] + rotatedY,
);
@ -477,11 +472,11 @@ export class LinearElementEditor {
app.scene.getNonDeletedElements(),
element,
lastClickedPoint,
{ x: originalPointerX, y: originalPointerY },
pointFrom(originalPointerX, originalPointerY),
app,
event,
elementsMap,
{ includeSelfPoints: true },
{ includeSelfPoints: true, selectedPointsIndices },
);
_snapLines = snapLines;
@ -1223,7 +1218,7 @@ export class LinearElementEditor {
app.scene.getNonDeletedElements(),
element,
points.length - 1,
{ x: effectiveGridX, y: effectiveGridY },
pointFrom(effectiveGridX, effectiveGridY),
app,
event,
elementsMap,
@ -1311,7 +1306,7 @@ export class LinearElementEditor {
app.scene.getNonDeletedElements(),
element,
points.length - 1,
{ x: originalPointerX, y: originalPointerY },
pointFrom(originalPointerX, originalPointerY),
app,
event,
elementsMap,

View File

@ -2,7 +2,6 @@ import {
isCloseTo,
pointFrom,
pointRotateRads,
pointsEqual,
rangeInclusive,
rangeIntersection,
rangesOverlap,
@ -198,13 +197,12 @@ export const areRoughlyEqual = (a: number, b: number, precision = 0.01) => {
export const getLinearElementPoints = (
element: ExcalidrawLinearElement,
elementsMap: ElementsMap,
options: {
dragOffset?: Vector2D;
excludePointIndex?: number;
excludePointsIndices?: readonly number[];
} = {},
): GlobalPoint[] => {
const { dragOffset, excludePointIndex } = options;
const { dragOffset, excludePointsIndices } = options;
if (isElbowArrow(element)) {
return [];
@ -226,27 +224,25 @@ export const getLinearElementPoints = (
for (let i = 0; i < element.points.length; i++) {
// Skip the point being edited if specified
if (excludePointIndex !== undefined && i === excludePointIndex) {
if (
excludePointsIndices?.length &&
excludePointsIndices.find((index) => index === i) !== undefined
) {
continue;
}
const localPoint = element.points[i];
const globalX = elementX + localPoint[0];
const globalY = elementY + localPoint[1];
const point = element.points[i];
const globalX = elementX + point[0];
const globalY = elementY + point[1];
// Apply rotation if element is rotated
if (element.angle !== 0) {
const cx = elementX + element.width / 2;
const cy = elementY + element.height / 2;
const rotated = pointRotateRads<GlobalPoint>(
pointFrom(globalX, globalY),
pointFrom(cx, cy),
element.angle,
);
globalPoints.push(pointFrom(round(rotated[0]), round(rotated[1])));
} else {
globalPoints.push(pointFrom(round(globalX), round(globalY)));
}
const cx = elementX + element.width / 2;
const cy = elementY + element.height / 2;
const rotated = pointRotateRads<GlobalPoint>(
pointFrom(globalX, globalY),
pointFrom(cx, cy),
element.angle,
);
globalPoints.push(pointFrom(round(rotated[0]), round(rotated[1])));
}
return globalPoints;
@ -296,7 +292,7 @@ export const getElementsCorners = (
!boundingBoxCorners
) {
// For linear elements, use actual points instead of bounding box
const linearPoints = getLinearElementPoints(element, elementsMap, {
const linearPoints = getLinearElementPoints(element, {
dragOffset,
});
result = linearPoints;
@ -714,6 +710,7 @@ export const getReferenceSnapPointsForLinearElementPoint = (
elementsMap: ElementsMap,
options: {
includeSelfPoints?: boolean;
selectedPointsIndices?: readonly number[];
} = {},
) => {
const { includeSelfPoints = false } = options;
@ -743,24 +740,9 @@ export const getReferenceSnapPointsForLinearElementPoint = (
// Include other points from the same linear element when creating new points or in editing mode
if (includeSelfPoints) {
const elementPoints = getLinearElementPoints(editingElement, elementsMap, {
excludePointIndex: editingPointIndex >= 0 ? editingPointIndex : undefined,
const elementPoints = getLinearElementPoints(editingElement, {
excludePointsIndices: options.selectedPointsIndices,
});
const shouldSkipFirstOrLast =
editingElement.points.length > 2 &&
pointsEqual(
editingElement.points[0],
editingElement.points[editingElement.points.length - 1],
);
if (shouldSkipFirstOrLast) {
if (editingPointIndex === 0) {
elementPoints.pop();
}
if (editingPointIndex === editingElement.points.length - 1) {
elementPoints.shift();
}
}
allSnapPoints.push(...elementPoints);
}
@ -771,12 +753,13 @@ export const snapLinearElementPoint = (
elements: readonly NonDeletedExcalidrawElement[],
editingElement: ExcalidrawLinearElement,
editingPointIndex: number,
pointPosition: Vector2D,
pointerPosition: GlobalPoint,
app: AppClassProperties,
event: KeyboardModifiersObject,
elementsMap: ElementsMap,
options: {
includeSelfPoints?: boolean;
selectedPointsIndices?: readonly number[];
} = {},
) => {
if (
@ -808,16 +791,10 @@ export const snapLinearElementPoint = (
options,
);
// Create a snap point for the current point position
const currentPointGlobal = pointFrom<GlobalPoint>(
pointPosition.x,
pointPosition.y,
);
// Find nearest snaps
for (const referencePoint of referenceSnapPoints) {
const offsetX = referencePoint[0] - currentPointGlobal[0];
const offsetY = referencePoint[1] - currentPointGlobal[1];
const offsetX = referencePoint[0] - pointerPosition[0];
const offsetY = referencePoint[1] - pointerPosition[1];
if (Math.abs(offsetX) <= minOffset.x) {
if (Math.abs(offsetX) < minOffset.x) {
@ -826,7 +803,7 @@ export const snapLinearElementPoint = (
nearestSnapsX.push({
type: "point",
points: [currentPointGlobal, referencePoint],
points: [pointerPosition, referencePoint],
offset: offsetX,
});
@ -840,7 +817,7 @@ export const snapLinearElementPoint = (
nearestSnapsY.push({
type: "point",
points: [currentPointGlobal, referencePoint],
points: [pointerPosition, referencePoint],
offset: offsetY,
});
@ -859,8 +836,8 @@ export const snapLinearElementPoint = (
if (snapOffset.x !== 0 || snapOffset.y !== 0) {
// Recalculate snap lines with the snapped position
const snappedPosition = pointFrom<GlobalPoint>(
pointPosition.x + snapOffset.x,
pointPosition.y + snapOffset.y,
pointerPosition[0] + snapOffset.x,
pointerPosition[1] + snapOffset.y,
);
const snappedSnapsX: Snaps = [];

View File

@ -5992,7 +5992,7 @@ class App extends React.Component<AppProps, AppState> {
this.scene.getNonDeletedElements(),
multiElement,
points.length - 1,
{ x: effectiveGridX, y: effectiveGridY },
pointFrom(effectiveGridX, effectiveGridY),
this,
event,
this.scene.getNonDeletedElementsMap(),
@ -8795,7 +8795,7 @@ class App extends React.Component<AppProps, AppState> {
this.scene.getNonDeletedElements(),
newElement,
points.length - 1,
{ x: effectiveGridX, y: effectiveGridY },
pointFrom(effectiveGridX, effectiveGridY),
this,
event,
this.scene.getNonDeletedElementsMap(),