diff --git a/packages/element/src/binding.ts b/packages/element/src/binding.ts index 8b67972d54..8d708f2516 100644 --- a/packages/element/src/binding.ts +++ b/packages/element/src/binding.ts @@ -665,41 +665,46 @@ const getBindingStrategyForDraggingBindingElementEndpoints_simple = ( otherBinding.elementId, ) as NonDeleted) : undefined; - const otherFocusPoint = - otherBinding && - otherBindableElement && - getGlobalFixedPointForBindableElement( - otherBinding.fixedPoint, - otherBindableElement, - elementsMap, - ); - const otherPoint = LinearElementEditor.getPointAtIndexGlobalCoordinates( - arrow, - startDragged ? -1 : 0, - elementsMap, - ); - const otherFocusPointIsInElement = - otherBindableElement && - otherFocusPoint && - isPointInElement(otherFocusPoint, otherBindableElement, elementsMap); + // const otherFocusPoint = + // otherBinding && + // otherBindableElement && + // getGlobalFixedPointForBindableElement( + // otherBinding.fixedPoint, + // otherBindableElement, + // elementsMap, + // ); + // const otherPoint = LinearElementEditor.getPointAtIndexGlobalCoordinates( + // arrow, + // startDragged ? -1 : 0, + // elementsMap, + // ); + // const otherFocusPointIsInElement = + // otherBindableElement && + // otherFocusPoint && + // isPointInElement(otherFocusPoint, otherBindableElement, elementsMap); - // Handle outside-outside binding with the same element - if ( - otherBinding && - otherBinding.elementId === hit?.id && - !pointInElement && - !otherFocusPointIsInElement - ) { + // Handle outside-outside binding to the same element + if (otherBinding && otherBinding.elementId === hit?.id) { const [startFixedPoint, endFixedPoint] = getGlobalFixedPoints( arrow, elementsMap, ); + invariant( + !opts?.newArrow || appState.selectedLinearElement?.initialState.origin, + "appState.selectedLinearElement.initialState.origin must be defined for new arrows", + ); + return { start: { mode: "inside", element: hit, - focusPoint: startDragged ? globalPoint : startFixedPoint, + focusPoint: startDragged + ? globalPoint + : // NOTE: Can only affect the start point because new arrows always drag the end point + opts?.newArrow + ? appState.selectedLinearElement!.initialState.origin! + : startFixedPoint, }, end: { mode: "inside", @@ -709,8 +714,31 @@ const getBindingStrategyForDraggingBindingElementEndpoints_simple = ( }; } + // Handle special alt key case to inside bind no matter what + if (opts?.altKey) { + return { + start: + startDragged && hit + ? { + mode: "inside", + element: hit, + focusPoint: globalPoint, + } + : start, + end: + endDragged && hit + ? { + mode: "inside", + element: hit, + focusPoint: globalPoint, + } + : end, + }; + } + + // Handle normal cases const current: BindingStrategy = hit - ? pointInElement || opts?.altKey + ? pointInElement ? { mode: "inside", element: hit, @@ -731,27 +759,13 @@ const getBindingStrategyForDraggingBindingElementEndpoints_simple = ( : { mode: null }; const other: BindingStrategy = - !opts?.altKey && opts?.newArrow && otherBindableElement - ? otherBindableElement.id === hit?.id - ? { - mode: "inside", - element: otherBindableElement, - focusPoint: otherPoint, - } - : { - mode: "orbit", - element: otherBindableElement, - focusPoint: - otherFocusPointIsInElement && !opts?.initialBinding - ? otherFocusPoint - : projectFixedPointOntoDiagonal( - arrow, - otherPoint, - otherBindableElement, - startDragged ? "end" : "start", - elementsMap, - ) || otherPoint, - } + otherBindableElement && + appState.selectedLinearElement?.initialState.altFocusPoint + ? { + mode: "orbit", + element: otherBindableElement, + focusPoint: appState.selectedLinearElement.initialState.altFocusPoint, + } : { mode: undefined }; return { @@ -2128,7 +2142,7 @@ export class BindableElement { } export const getGlobalFixedPointForBindableElement = ( - fixedPointRatio: [number, number], + fixedPointRatio: FixedPoint, element: ExcalidrawBindableElement, elementsMap: ElementsMap, ): GlobalPoint => { diff --git a/packages/element/src/linearElementEditor.ts b/packages/element/src/linearElementEditor.ts index 0fabe6c194..38dc3d5c6a 100644 --- a/packages/element/src/linearElementEditor.ts +++ b/packages/element/src/linearElementEditor.ts @@ -29,6 +29,7 @@ import { getHoveredElementForBinding, isPathALoop, moveArrowAboveBindable, + projectFixedPointOntoDiagonal, type Store, } from "@excalidraw/element"; @@ -139,6 +140,7 @@ export class LinearElementEditor { added: boolean; }; arrowStartIsInside: boolean; + altFocusPoint: Readonly | null; }>; /** whether you're dragging a point */ @@ -189,6 +191,7 @@ export class LinearElementEditor { added: false, }, arrowStartIsInside: false, + altFocusPoint: null, }; this.hoverPointIndex = -1; this.segmentMidPointHoveredCoords = null; @@ -395,9 +398,28 @@ export class LinearElementEditor { return null; } + const startBindingElement = + isBindingElement(element) && + element.startBinding && + (elementsMap.get( + element.startBinding.elementId, + ) as ExcalidrawBindableElement | null); const newLinearElementEditor = { ...linearElementEditor, customLineAngle, + initialState: { + ...linearElementEditor.initialState, + altFocusPoint: + !linearElementEditor.initialState.altFocusPoint && startBindingElement + ? projectFixedPointOntoDiagonal( + element, + pointFrom(element.x, element.y), + startBindingElement, + "start", + elementsMap, + ) + : linearElementEditor.initialState.altFocusPoint, + }, }; return { @@ -582,6 +604,12 @@ export class LinearElementEditor { : null; const newHoverPointIndex = newLastClickedPoint; + const startBindingElement = + isBindingElement(element) && + element.startBinding && + (elementsMap.get( + element.startBinding.elementId, + ) as ExcalidrawBindableElement | null); const newLinearElementEditor = { ...linearElementEditor, @@ -589,6 +617,16 @@ export class LinearElementEditor { initialState: { ...linearElementEditor.initialState, lastClickedPoint: newLastClickedPoint, + altFocusPoint: + !linearElementEditor.initialState.altFocusPoint && startBindingElement + ? projectFixedPointOntoDiagonal( + element, + pointFrom(element.x, element.y), + startBindingElement, + "start", + elementsMap, + ) + : linearElementEditor.initialState.altFocusPoint, }, segmentMidPointHoveredCoords: newSelectedMidPointHoveredCoords, hoverPointIndex: newHoverPointIndex, @@ -959,6 +997,7 @@ export class LinearElementEditor { appState, elementsMap, ); + const point = pointFrom(scenePointer.x, scenePointer.y); let segmentMidpointIndex = null; if (segmentMidpoint) { @@ -990,7 +1029,7 @@ export class LinearElementEditor { initialState: { prevSelectedPointsIndices: linearElementEditor.selectedPointsIndices, lastClickedPoint: -1, - origin: pointFrom(scenePointer.x, scenePointer.y), + origin: point, segmentMidpoint: { value: segmentMidpoint, index: segmentMidpointIndex, @@ -999,6 +1038,7 @@ export class LinearElementEditor { arrowStartIsInside: !!app.state.newElement && (app.state.bindMode === "inside" || app.state.bindMode === "skip"), + altFocusPoint: null, }, selectedPointsIndices: [element.points.length - 1], lastUncommittedPoint: null, @@ -1051,7 +1091,7 @@ export class LinearElementEditor { initialState: { prevSelectedPointsIndices: linearElementEditor.selectedPointsIndices, lastClickedPoint: clickedPointIndex, - origin: pointFrom(scenePointer.x, scenePointer.y), + origin: point, segmentMidpoint: { value: segmentMidpoint, index: segmentMidpointIndex, @@ -1060,6 +1100,7 @@ export class LinearElementEditor { arrowStartIsInside: !!app.state.newElement && (app.state.bindMode === "inside" || app.state.bindMode === "skip"), + altFocusPoint: null, }, selectedPointsIndices: nextSelectedPointsIndices, pointerOffset: targetPoint @@ -2280,22 +2321,23 @@ const pointDraggingUpdates = ( )! as ExcalidrawBindableElement) : null; - const startLocalPoint = endIsDraggingOverStartElement - ? nextArrow.points[0] - : startIsDraggingOverEndElement && - app.state.bindMode !== "inside" && - getFeatureFlag("COMPLEX_BINDINGS") - ? nextArrow.points[nextArrow.points.length - 1] - : startBindable - ? updateBoundPoint( - nextArrow, - "startBinding", - nextArrow.startBinding, - startBindable, - elementsMap, - customIntersector, - ) || nextArrow.points[0] - : nextArrow.points[0]; + const startLocalPoint = + endIsDraggingOverStartElement && getFeatureFlag("COMPLEX_BINDINGS") + ? nextArrow.points[0] + : startIsDraggingOverEndElement && + app.state.bindMode !== "inside" && + getFeatureFlag("COMPLEX_BINDINGS") + ? nextArrow.points[nextArrow.points.length - 1] + : startBindable + ? updateBoundPoint( + nextArrow, + "startBinding", + nextArrow.startBinding, + startBindable, + elementsMap, + customIntersector, + ) || nextArrow.points[0] + : nextArrow.points[0]; const endChanged = pointDistance( @@ -2337,49 +2379,6 @@ const pointDraggingUpdates = ( }; }; -// const shouldAllowDraggingPoint = ( -// element: ExcalidrawLinearElement, -// scenePointerX: number, -// scenePointerY: number, -// selectedPointsIndices: readonly number[], -// elementsMap: Readonly, -// app: AppClassProperties, -// ) => { -// if (!isSimpleArrow(element)) { -// return true; -// } - -// const scenePointer = pointFrom(scenePointerX, scenePointerY); - -// // Do not allow dragging the bound arrow closer to the shape than -// // the dragging threshold -// let allowDrag = true; - -// if (selectedPointsIndices.includes(0) && element.startBinding) { -// const boundElement = elementsMap.get( -// element.startBinding.elementId, -// )! as ExcalidrawBindableElement; -// const dist = distanceToElement(boundElement, elementsMap, scenePointer); -// const inside = isPointInElement(scenePointer, boundElement, elementsMap); -// allowDrag = -// allowDrag && (dist > getFixedBindingDistance(boundElement) || inside); -// } -// if ( -// selectedPointsIndices.includes(element.points.length - 1) && -// element.endBinding -// ) { -// const boundElement = elementsMap.get( -// element.endBinding.elementId, -// )! as ExcalidrawBindableElement; -// const dist = distanceToElement(boundElement, elementsMap, scenePointer); -// const inside = isPointInElement(scenePointer, boundElement, elementsMap); -// allowDrag = -// allowDrag && (dist > getFixedBindingDistance(boundElement) || inside); -// } - -// return allowDrag; -// }; - const determineCustomLinearAngle = ( pivotPoint: LocalPoint, draggedPoint: LocalPoint, diff --git a/packages/element/src/utils.ts b/packages/element/src/utils.ts index 01d13ffc38..4813c8861e 100644 --- a/packages/element/src/utils.ts +++ b/packages/element/src/utils.ts @@ -31,6 +31,7 @@ import { elementCenterPoint, getDiamondPoints } from "./bounds"; import { generateLinearCollisionShape } from "./shape"; +import { isPointInElement } from "./collision"; import { LinearElementEditor } from "./linearElementEditor"; import { isRectangularElement } from "./typeChecks"; @@ -557,14 +558,17 @@ export const projectFixedPointOntoDiagonal = ( element: ExcalidrawElement, startOrEnd: "start" | "end", elementsMap: ElementsMap, -) => { +): GlobalPoint | null => { + invariant(arrow.points.length >= 2, "Arrow must have at least two points"); + if (arrow.width < 1 && arrow.height < 1) { + return null; + } + const [diagonalOne, diagonalTwo] = getDiagonalsForBindableElement( element, elementsMap, ); - invariant(arrow.points.length >= 2, "Arrow must have at least two points"); - const a = LinearElementEditor.getPointAtIndexGlobalCoordinates( arrow, startOrEnd === "start" ? 1 : arrow.points.length - 2, @@ -587,9 +591,12 @@ export const projectFixedPointOntoDiagonal = ( const d1 = p1 && pointDistance(a, p1); const d2 = p2 && pointDistance(a, p2); + let p = null; if (d1 != null && d2 != null) { - return d1 < d2 ? p1 : p2; + p = d1 < d2 ? p1 : p2; + } else { + p = p1 || p2 || null; } - return p1 || p2 || null; + return p && isPointInElement(p, element, elementsMap) ? p : null; };