Browse Source

fix: Angle snapping around bindable objects incorrectly resolves (#10501)

Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
Co-authored-by: zsviczian <viczian.zsolt@gmail.com>
pull/10458/merge
Márk Tolmács 3 days ago committed by GitHub
parent
commit
f06484c6ab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 31
      packages/element/src/binding.ts
  2. 20
      packages/element/src/linearElementEditor.ts
  3. 12
      packages/excalidraw/actions/actionFinalize.tsx
  4. 2
      packages/excalidraw/components/App.tsx

31
packages/element/src/binding.ts

@ -146,6 +146,8 @@ export const isBindingEnabled = (appState: AppState): boolean => {
export const bindOrUnbindBindingElement = ( export const bindOrUnbindBindingElement = (
arrow: NonDeleted<ExcalidrawArrowElement>, arrow: NonDeleted<ExcalidrawArrowElement>,
draggingPoints: PointsPositionUpdates, draggingPoints: PointsPositionUpdates,
scenePointerX: number,
scenePointerY: number,
scene: Scene, scene: Scene,
appState: AppState, appState: AppState,
opts?: { opts?: {
@ -158,6 +160,8 @@ export const bindOrUnbindBindingElement = (
const { start, end } = getBindingStrategyForDraggingBindingElementEndpoints( const { start, end } = getBindingStrategyForDraggingBindingElementEndpoints(
arrow, arrow,
draggingPoints, draggingPoints,
scenePointerX,
scenePointerY,
scene.getNonDeletedElementsMap(), scene.getNonDeletedElementsMap(),
scene.getNonDeletedElements(), scene.getNonDeletedElements(),
appState, appState,
@ -557,6 +561,8 @@ const bindingStrategyForSimpleArrowEndpointDragging_complex = (
export const getBindingStrategyForDraggingBindingElementEndpoints = ( export const getBindingStrategyForDraggingBindingElementEndpoints = (
arrow: NonDeleted<ExcalidrawArrowElement>, arrow: NonDeleted<ExcalidrawArrowElement>,
draggingPoints: PointsPositionUpdates, draggingPoints: PointsPositionUpdates,
screenPointerX: number,
screenPointerY: number,
elementsMap: NonDeletedSceneElementsMap, elementsMap: NonDeletedSceneElementsMap,
elements: readonly Ordered<NonDeletedExcalidrawElement>[], elements: readonly Ordered<NonDeletedExcalidrawElement>[],
appState: AppState, appState: AppState,
@ -583,6 +589,8 @@ export const getBindingStrategyForDraggingBindingElementEndpoints = (
return getBindingStrategyForDraggingBindingElementEndpoints_simple( return getBindingStrategyForDraggingBindingElementEndpoints_simple(
arrow, arrow,
draggingPoints, draggingPoints,
screenPointerX,
screenPointerY,
elementsMap, elementsMap,
elements, elements,
appState, appState,
@ -593,6 +601,8 @@ export const getBindingStrategyForDraggingBindingElementEndpoints = (
const getBindingStrategyForDraggingBindingElementEndpoints_simple = ( const getBindingStrategyForDraggingBindingElementEndpoints_simple = (
arrow: NonDeleted<ExcalidrawArrowElement>, arrow: NonDeleted<ExcalidrawArrowElement>,
draggingPoints: PointsPositionUpdates, draggingPoints: PointsPositionUpdates,
scenePointerX: number,
scenePointerY: number,
elementsMap: NonDeletedSceneElementsMap, elementsMap: NonDeletedSceneElementsMap,
elements: readonly Ordered<NonDeletedExcalidrawElement>[], elements: readonly Ordered<NonDeletedExcalidrawElement>[],
appState: AppState, appState: AppState,
@ -670,7 +680,15 @@ const getBindingStrategyForDraggingBindingElementEndpoints_simple = (
elementsMap, elementsMap,
(e) => maxBindingDistance_simple(appState.zoom), (e) => maxBindingDistance_simple(appState.zoom),
); );
const pointInElement = hit && isPointInElement(globalPoint, hit, elementsMap); const pointInElement =
hit &&
(opts?.angleLocked
? isPointInElement(
pointFrom<GlobalPoint>(scenePointerX, scenePointerY),
hit,
elementsMap,
)
: isPointInElement(globalPoint, hit, elementsMap));
const otherBindableElement = otherBinding const otherBindableElement = otherBinding
? (elementsMap.get( ? (elementsMap.get(
otherBinding.elementId, otherBinding.elementId,
@ -944,6 +962,8 @@ export const bindOrUnbindBindingElements = (
bindOrUnbindBindingElement( bindOrUnbindBindingElement(
arrow, arrow,
new Map(), // No dragging points in this case new Map(), // No dragging points in this case
Infinity,
Infinity,
scene, scene,
appState, appState,
); );
@ -1146,7 +1166,14 @@ export const updateBindings = (
}, },
) => { ) => {
if (isArrowElement(latestElement)) { if (isArrowElement(latestElement)) {
bindOrUnbindBindingElement(latestElement, new Map(), scene, appState); bindOrUnbindBindingElement(
latestElement,
new Map(),
Infinity,
Infinity,
scene,
appState,
);
} else { } else {
updateBoundElements(latestElement, scene, { updateBoundElements(latestElement, scene, {
...options, ...options,

20
packages/element/src/linearElementEditor.ts

@ -343,6 +343,8 @@ export class LinearElementEditor {
[idx], [idx],
deltaX, deltaX,
deltaY, deltaY,
scenePointerX,
scenePointerY,
elementsMap, elementsMap,
element, element,
elements, elements,
@ -498,7 +500,6 @@ export class LinearElementEditor {
width + pivotPoint[0], width + pivotPoint[0],
height + pivotPoint[1], height + pivotPoint[1],
); );
deltaX = target[0] - draggingPoint[0]; deltaX = target[0] - draggingPoint[0];
deltaY = target[1] - draggingPoint[1]; deltaY = target[1] - draggingPoint[1];
} else { } else {
@ -519,6 +520,8 @@ export class LinearElementEditor {
selectedPointsIndices, selectedPointsIndices,
deltaX, deltaX,
deltaY, deltaY,
scenePointerX,
scenePointerY,
elementsMap, elementsMap,
element, element,
elements, elements,
@ -2066,6 +2069,8 @@ const pointDraggingUpdates = (
selectedPointsIndices: readonly number[], selectedPointsIndices: readonly number[],
deltaX: number, deltaX: number,
deltaY: number, deltaY: number,
scenePointerX: number,
scenePointerY: number,
elementsMap: NonDeletedSceneElementsMap, elementsMap: NonDeletedSceneElementsMap,
element: NonDeleted<ExcalidrawLinearElement>, element: NonDeleted<ExcalidrawLinearElement>,
elements: readonly Ordered<NonDeletedExcalidrawElement>[], elements: readonly Ordered<NonDeletedExcalidrawElement>[],
@ -2106,6 +2111,8 @@ const pointDraggingUpdates = (
const { start, end } = getBindingStrategyForDraggingBindingElementEndpoints( const { start, end } = getBindingStrategyForDraggingBindingElementEndpoints(
element, element,
naiveDraggingPoints, naiveDraggingPoints,
scenePointerX,
scenePointerY,
elementsMap, elementsMap,
elements, elements,
app.state, app.state,
@ -2228,10 +2235,15 @@ const pointDraggingUpdates = (
// We need to use a custom intersector to ensure that if there is a big "jump" // We need to use a custom intersector to ensure that if there is a big "jump"
// in the arrow's position, we can position it with outline avoidance // in the arrow's position, we can position it with outline avoidance
// pixel-perfectly and avoid "dancing" arrows. // pixel-perfectly and avoid "dancing" arrows.
const customIntersector = // NOTE: Direction matters here, so we create two intersectors
const startCustomIntersector =
start.focusPoint && end.focusPoint start.focusPoint && end.focusPoint
? lineSegment(start.focusPoint, end.focusPoint) ? lineSegment(start.focusPoint, end.focusPoint)
: undefined; : undefined;
const endCustomIntersector =
start.focusPoint && end.focusPoint
? lineSegment(end.focusPoint, start.focusPoint)
: undefined;
// Needed to handle a special case where an existing arrow is dragged over // Needed to handle a special case where an existing arrow is dragged over
// the same element it is bound to on the other side // the same element it is bound to on the other side
@ -2268,7 +2280,7 @@ const pointDraggingUpdates = (
nextArrow.endBinding, nextArrow.endBinding,
endBindable, endBindable,
elementsMap, elementsMap,
customIntersector, endCustomIntersector,
) || nextArrow.points[nextArrow.points.length - 1] ) || nextArrow.points[nextArrow.points.length - 1]
: nextArrow.points[nextArrow.points.length - 1]; : nextArrow.points[nextArrow.points.length - 1];
@ -2299,7 +2311,7 @@ const pointDraggingUpdates = (
nextArrow.startBinding, nextArrow.startBinding,
startBindable, startBindable,
elementsMap, elementsMap,
customIntersector, startCustomIntersector,
) || nextArrow.points[0] ) || nextArrow.points[0]
: nextArrow.points[0]; : nextArrow.points[0];

12
packages/excalidraw/actions/actionFinalize.tsx

@ -103,11 +103,19 @@ export const actionFinalize = register<FormData>({
return map; return map;
}, new Map()) ?? new Map(); }, new Map()) ?? new Map();
bindOrUnbindBindingElement(element, draggedPoints, scene, appState, { bindOrUnbindBindingElement(
element,
draggedPoints,
sceneCoords.x,
sceneCoords.y,
scene,
appState,
{
newArrow, newArrow,
altKey: event.altKey, altKey: event.altKey,
angleLocked: shouldRotateWithDiscreteAngle(event), angleLocked: shouldRotateWithDiscreteAngle(event),
}); },
);
} else if (isLineElement(element)) { } else if (isLineElement(element)) {
if ( if (
appState.selectedLinearElement?.isEditing && appState.selectedLinearElement?.isEditing &&

2
packages/excalidraw/components/App.tsx

@ -8617,6 +8617,8 @@ class App extends React.Component<AppProps, AppState> {
}, },
], ],
]), ]),
point[0],
point[1],
this.scene, this.scene,
this.state, this.state,
{ {

Loading…
Cancel
Save