Existing arrows now jump out
This commit is contained in:
parent
0dd76db5f0
commit
331790e9af
@ -25,6 +25,7 @@ import {
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
deconstructLinearOrFreeDrawElement,
|
deconstructLinearOrFreeDrawElement,
|
||||||
|
hitElementItself,
|
||||||
isPathALoop,
|
isPathALoop,
|
||||||
type Store,
|
type Store,
|
||||||
} from "@excalidraw/element";
|
} from "@excalidraw/element";
|
||||||
@ -43,6 +44,7 @@ import type {
|
|||||||
import type { Mutable } from "@excalidraw/common/utility-types";
|
import type { Mutable } from "@excalidraw/common/utility-types";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
bindLinearElement,
|
||||||
bindOrUnbindLinearElement,
|
bindOrUnbindLinearElement,
|
||||||
getHoveredElementForBinding,
|
getHoveredElementForBinding,
|
||||||
getOutlineAvoidingPoint,
|
getOutlineAvoidingPoint,
|
||||||
@ -60,6 +62,7 @@ import { mutateElement } from "./mutateElement";
|
|||||||
import { getBoundTextElement, handleBindTextResize } from "./textElement";
|
import { getBoundTextElement, handleBindTextResize } from "./textElement";
|
||||||
import {
|
import {
|
||||||
isArrowElement,
|
isArrowElement,
|
||||||
|
isBindableElement,
|
||||||
isBindingElement,
|
isBindingElement,
|
||||||
isElbowArrow,
|
isElbowArrow,
|
||||||
isFixedPointBinding,
|
isFixedPointBinding,
|
||||||
@ -87,6 +90,8 @@ import type {
|
|||||||
FixedSegment,
|
FixedSegment,
|
||||||
ExcalidrawElbowArrowElement,
|
ExcalidrawElbowArrowElement,
|
||||||
PointsPositionUpdates,
|
PointsPositionUpdates,
|
||||||
|
NonDeletedExcalidrawElement,
|
||||||
|
Ordered,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -136,6 +141,7 @@ export class LinearElementEditor {
|
|||||||
index: number | null;
|
index: number | null;
|
||||||
added: boolean;
|
added: boolean;
|
||||||
};
|
};
|
||||||
|
arrowOtherPoint?: GlobalPoint;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
/** whether you're dragging a point */
|
/** whether you're dragging a point */
|
||||||
@ -280,6 +286,7 @@ export class LinearElementEditor {
|
|||||||
scenePointerX: number,
|
scenePointerX: number,
|
||||||
scenePointerY: number,
|
scenePointerY: number,
|
||||||
linearElementEditor: LinearElementEditor,
|
linearElementEditor: LinearElementEditor,
|
||||||
|
thresholdCallback: (element: ExcalidrawElement) => number,
|
||||||
): Pick<AppState, keyof AppState> | null {
|
): Pick<AppState, keyof AppState> | null {
|
||||||
if (!linearElementEditor) {
|
if (!linearElementEditor) {
|
||||||
return null;
|
return null;
|
||||||
@ -288,6 +295,8 @@ export class LinearElementEditor {
|
|||||||
const elementsMap = app.scene.getNonDeletedElementsMap();
|
const elementsMap = app.scene.getNonDeletedElementsMap();
|
||||||
const element = LinearElementEditor.getElement(elementId, elementsMap);
|
const element = LinearElementEditor.getElement(elementId, elementsMap);
|
||||||
let customLineAngle = linearElementEditor.customLineAngle;
|
let customLineAngle = linearElementEditor.customLineAngle;
|
||||||
|
let arrowOtherPoint: GlobalPoint | undefined =
|
||||||
|
linearElementEditor.pointerDownState.arrowOtherPoint;
|
||||||
if (!element) {
|
if (!element) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -370,99 +379,39 @@ export class LinearElementEditor {
|
|||||||
scenePointerY - linearElementEditor.pointerOffset.y,
|
scenePointerY - linearElementEditor.pointerOffset.y,
|
||||||
event[KEYS.CTRL_OR_CMD] ? null : app.getEffectiveGridSize(),
|
event[KEYS.CTRL_OR_CMD] ? null : app.getEffectiveGridSize(),
|
||||||
);
|
);
|
||||||
|
|
||||||
const deltaX = newDraggingPointPosition[0] - draggingPoint[0];
|
const deltaX = newDraggingPointPosition[0] - draggingPoint[0];
|
||||||
const deltaY = newDraggingPointPosition[1] - draggingPoint[1];
|
const deltaY = newDraggingPointPosition[1] - draggingPoint[1];
|
||||||
|
const elements = app.scene.getNonDeletedElements();
|
||||||
|
|
||||||
|
arrowOtherPoint = pointDraggingOtherEndpoint(
|
||||||
|
element,
|
||||||
|
elementsMap,
|
||||||
|
selectedPointsIndices,
|
||||||
|
scenePointerX,
|
||||||
|
scenePointerY,
|
||||||
|
linearElementEditor,
|
||||||
|
app.scene,
|
||||||
|
elements,
|
||||||
|
app.state.zoom,
|
||||||
|
thresholdCallback,
|
||||||
|
);
|
||||||
|
|
||||||
LinearElementEditor.movePoints(
|
LinearElementEditor.movePoints(
|
||||||
element,
|
element,
|
||||||
app.scene,
|
app.scene,
|
||||||
new Map(
|
pointDraggingUpdates(
|
||||||
selectedPointsIndices.map((pointIndex) => {
|
selectedPointsIndices,
|
||||||
let newPointPosition: LocalPoint =
|
deltaX,
|
||||||
pointIndex === lastClickedPoint
|
deltaY,
|
||||||
? LinearElementEditor.createPointAt(
|
elementsMap,
|
||||||
element,
|
lastClickedPoint,
|
||||||
elementsMap,
|
element,
|
||||||
scenePointerX - linearElementEditor.pointerOffset.x,
|
scenePointerX,
|
||||||
scenePointerY - linearElementEditor.pointerOffset.y,
|
scenePointerY,
|
||||||
event[KEYS.CTRL_OR_CMD]
|
linearElementEditor,
|
||||||
? null
|
event[KEYS.CTRL_OR_CMD] ? null : app.getEffectiveGridSize(),
|
||||||
: app.getEffectiveGridSize(),
|
elements,
|
||||||
)
|
app.state.zoom,
|
||||||
: pointFrom(
|
|
||||||
element.points[pointIndex][0] + deltaX,
|
|
||||||
element.points[pointIndex][1] + deltaY,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
|
||||||
pointIndex === 0 ||
|
|
||||||
pointIndex === element.points.length - 1
|
|
||||||
) {
|
|
||||||
const [, , , , cx, cy] = getElementAbsoluteCoords(
|
|
||||||
element,
|
|
||||||
elementsMap,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
let newGlobalPointPosition = pointRotateRads(
|
|
||||||
pointFrom<GlobalPoint>(
|
|
||||||
element.x + newPointPosition[0],
|
|
||||||
element.y + newPointPosition[1],
|
|
||||||
),
|
|
||||||
pointFrom<GlobalPoint>(cx, cy),
|
|
||||||
element.angle,
|
|
||||||
);
|
|
||||||
const hoveredElement = getHoveredElementForBinding(
|
|
||||||
{
|
|
||||||
x: newGlobalPointPosition[0],
|
|
||||||
y: newGlobalPointPosition[1],
|
|
||||||
},
|
|
||||||
app.scene.getNonDeletedElements(),
|
|
||||||
elementsMap,
|
|
||||||
app.state.zoom,
|
|
||||||
true,
|
|
||||||
isElbowArrow(element),
|
|
||||||
);
|
|
||||||
|
|
||||||
const otherBinding =
|
|
||||||
element[pointIndex === 0 ? "endBinding" : "startBinding"];
|
|
||||||
|
|
||||||
// Allow binding inside the element if both ends are inside
|
|
||||||
if (
|
|
||||||
isArrowElement(element) &&
|
|
||||||
!(
|
|
||||||
hoveredElement?.id === otherBinding?.elementId &&
|
|
||||||
hoveredElement != null
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
newGlobalPointPosition = getOutlineAvoidingPoint(
|
|
||||||
element,
|
|
||||||
hoveredElement,
|
|
||||||
newGlobalPointPosition,
|
|
||||||
pointIndex,
|
|
||||||
elementsMap,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
newPointPosition = LinearElementEditor.createPointAt(
|
|
||||||
element,
|
|
||||||
elementsMap,
|
|
||||||
newGlobalPointPosition[0] -
|
|
||||||
linearElementEditor.pointerOffset.x,
|
|
||||||
newGlobalPointPosition[1] -
|
|
||||||
linearElementEditor.pointerOffset.y,
|
|
||||||
null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
pointIndex,
|
|
||||||
{
|
|
||||||
point: newPointPosition,
|
|
||||||
isDragging: pointIndex === lastClickedPoint,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -541,6 +490,10 @@ export class LinearElementEditor {
|
|||||||
: -1,
|
: -1,
|
||||||
isDragging: true,
|
isDragging: true,
|
||||||
customLineAngle,
|
customLineAngle,
|
||||||
|
pointerDownState: {
|
||||||
|
...linearElementEditor.pointerDownState,
|
||||||
|
arrowOtherPoint,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -671,6 +624,10 @@ export class LinearElementEditor {
|
|||||||
isDragging: false,
|
isDragging: false,
|
||||||
pointerOffset: { x: 0, y: 0 },
|
pointerOffset: { x: 0, y: 0 },
|
||||||
customLineAngle: null,
|
customLineAngle: null,
|
||||||
|
pointerDownState: {
|
||||||
|
...editingLinearElement.pointerDownState,
|
||||||
|
arrowOtherPoint: undefined,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2043,3 +2000,221 @@ const normalizeSelectedPoints = (
|
|||||||
nextPoints = nextPoints.sort((a, b) => a - b);
|
nextPoints = nextPoints.sort((a, b) => a - b);
|
||||||
return nextPoints.length ? nextPoints : null;
|
return nextPoints.length ? nextPoints : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const pointDraggingUpdates = (
|
||||||
|
selectedPointsIndices: readonly number[],
|
||||||
|
deltaX: number,
|
||||||
|
deltaY: number,
|
||||||
|
elementsMap: NonDeletedSceneElementsMap,
|
||||||
|
lastClickedPoint: number,
|
||||||
|
element: NonDeleted<ExcalidrawLinearElement>,
|
||||||
|
scenePointerX: number,
|
||||||
|
scenePointerY: number,
|
||||||
|
linearElementEditor: LinearElementEditor,
|
||||||
|
gridSize: NullableGridSize,
|
||||||
|
elements: readonly Ordered<NonDeletedExcalidrawElement>[],
|
||||||
|
zoom: AppState["zoom"],
|
||||||
|
): PointsPositionUpdates => {
|
||||||
|
return new Map(
|
||||||
|
selectedPointsIndices.map((pointIndex) => {
|
||||||
|
let newPointPosition: LocalPoint =
|
||||||
|
pointIndex === lastClickedPoint
|
||||||
|
? LinearElementEditor.createPointAt(
|
||||||
|
element,
|
||||||
|
elementsMap,
|
||||||
|
scenePointerX - linearElementEditor.pointerOffset.x,
|
||||||
|
scenePointerY - linearElementEditor.pointerOffset.y,
|
||||||
|
gridSize,
|
||||||
|
)
|
||||||
|
: pointFrom(
|
||||||
|
element.points[pointIndex][0] + deltaX,
|
||||||
|
element.points[pointIndex][1] + deltaY,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (pointIndex === 0 || pointIndex === element.points.length - 1) {
|
||||||
|
const [, , , , cx, cy] = getElementAbsoluteCoords(
|
||||||
|
element,
|
||||||
|
elementsMap,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
let newGlobalPointPosition = pointRotateRads(
|
||||||
|
pointFrom<GlobalPoint>(
|
||||||
|
element.x + newPointPosition[0],
|
||||||
|
element.y + newPointPosition[1],
|
||||||
|
),
|
||||||
|
pointFrom<GlobalPoint>(cx, cy),
|
||||||
|
element.angle,
|
||||||
|
);
|
||||||
|
const hoveredElement = getHoveredElementForBinding(
|
||||||
|
{
|
||||||
|
x: newGlobalPointPosition[0],
|
||||||
|
y: newGlobalPointPosition[1],
|
||||||
|
},
|
||||||
|
elements,
|
||||||
|
elementsMap,
|
||||||
|
zoom,
|
||||||
|
true,
|
||||||
|
isElbowArrow(element),
|
||||||
|
);
|
||||||
|
|
||||||
|
const otherBinding =
|
||||||
|
element[pointIndex === 0 ? "endBinding" : "startBinding"];
|
||||||
|
|
||||||
|
// Allow binding inside the element if both ends are inside
|
||||||
|
if (
|
||||||
|
isArrowElement(element) &&
|
||||||
|
!(
|
||||||
|
hoveredElement?.id === otherBinding?.elementId &&
|
||||||
|
hoveredElement != null
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
newGlobalPointPosition = getOutlineAvoidingPoint(
|
||||||
|
element,
|
||||||
|
hoveredElement,
|
||||||
|
newGlobalPointPosition,
|
||||||
|
pointIndex,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
newPointPosition = LinearElementEditor.createPointAt(
|
||||||
|
element,
|
||||||
|
elementsMap,
|
||||||
|
newGlobalPointPosition[0] - linearElementEditor.pointerOffset.x,
|
||||||
|
newGlobalPointPosition[1] - linearElementEditor.pointerOffset.y,
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
pointIndex,
|
||||||
|
{
|
||||||
|
point: newPointPosition,
|
||||||
|
isDragging: pointIndex === lastClickedPoint,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const pointDraggingOtherEndpoint = (
|
||||||
|
element: NonDeleted<ExcalidrawLinearElement>,
|
||||||
|
elementsMap: NonDeletedSceneElementsMap,
|
||||||
|
selectedPointsIndices: readonly number[],
|
||||||
|
scenePointerX: number,
|
||||||
|
scenePointerY: number,
|
||||||
|
linearElementEditor: LinearElementEditor,
|
||||||
|
scene: Scene,
|
||||||
|
elements: readonly Ordered<NonDeletedExcalidrawElement>[],
|
||||||
|
zoom: AppState["zoom"],
|
||||||
|
thresholdCallback: (element: ExcalidrawElement) => number,
|
||||||
|
) => {
|
||||||
|
let arrowOtherPoint = linearElementEditor.pointerDownState.arrowOtherPoint;
|
||||||
|
|
||||||
|
if (isArrowElement(element) && !isElbowArrow(element)) {
|
||||||
|
const startPointIsIncluded = selectedPointsIndices.includes(0);
|
||||||
|
const endPointIsIncluded = selectedPointsIndices.includes(
|
||||||
|
element.points.length - 1,
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
// Make sure that not both of the endpoints are selected
|
||||||
|
(startPointIsIncluded || endPointIsIncluded) &&
|
||||||
|
startPointIsIncluded !== endPointIsIncluded
|
||||||
|
) {
|
||||||
|
const otherBinding =
|
||||||
|
element[startPointIsIncluded ? "endBinding" : "startBinding"];
|
||||||
|
if (
|
||||||
|
// The other end is bound
|
||||||
|
otherBinding
|
||||||
|
) {
|
||||||
|
const otherElement = elementsMap.get(otherBinding.elementId);
|
||||||
|
|
||||||
|
invariant(
|
||||||
|
isBindableElement(otherElement),
|
||||||
|
"Other element should exist in elementsMap at all times and be a bindable element",
|
||||||
|
);
|
||||||
|
|
||||||
|
let newOtherPointPosition;
|
||||||
|
|
||||||
|
// Only avoid shape if the start and end point is not inside
|
||||||
|
// the same element
|
||||||
|
if (
|
||||||
|
!hitElementItself({
|
||||||
|
point: pointFrom(scenePointerX, scenePointerY),
|
||||||
|
element: otherElement,
|
||||||
|
elementsMap,
|
||||||
|
threshold: thresholdCallback(otherElement),
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
// If we don't have a restore point, that means we need to jump out
|
||||||
|
// of the element but first, create the restore point
|
||||||
|
if (!arrowOtherPoint) {
|
||||||
|
arrowOtherPoint = LinearElementEditor.getPointGlobalCoordinates(
|
||||||
|
element,
|
||||||
|
element.points[
|
||||||
|
startPointIsIncluded ? element.points.length - 1 : 0
|
||||||
|
],
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find a snap point outside the element
|
||||||
|
const newOtherGlobalPoint = getOutlineAvoidingPoint(
|
||||||
|
element,
|
||||||
|
otherElement,
|
||||||
|
arrowOtherPoint,
|
||||||
|
startPointIsIncluded ? element.points.length - 1 : 0,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
|
||||||
|
newOtherPointPosition = LinearElementEditor.createPointAt(
|
||||||
|
element,
|
||||||
|
elementsMap,
|
||||||
|
newOtherGlobalPoint[0] - linearElementEditor.pointerOffset.x,
|
||||||
|
newOtherGlobalPoint[1] - linearElementEditor.pointerOffset.y,
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Restore the saved point if we are back inside the element
|
||||||
|
else if (arrowOtherPoint) {
|
||||||
|
console.warn("RESTORE");
|
||||||
|
|
||||||
|
newOtherPointPosition = LinearElementEditor.createPointAt(
|
||||||
|
element,
|
||||||
|
elementsMap,
|
||||||
|
arrowOtherPoint[0] - linearElementEditor.pointerOffset.x,
|
||||||
|
arrowOtherPoint[1] - linearElementEditor.pointerOffset.y,
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
|
arrowOtherPoint = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, move the other endpoint if needed
|
||||||
|
if (newOtherPointPosition) {
|
||||||
|
LinearElementEditor.movePoints(
|
||||||
|
element,
|
||||||
|
scene,
|
||||||
|
new Map([
|
||||||
|
[
|
||||||
|
startPointIsIncluded ? element.points.length - 1 : 0,
|
||||||
|
{
|
||||||
|
point: newOtherPointPosition,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
bindLinearElement(
|
||||||
|
element,
|
||||||
|
otherElement,
|
||||||
|
startPointIsIncluded ? "end" : "start",
|
||||||
|
scene,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return arrowOtherPoint;
|
||||||
|
};
|
||||||
|
@ -8249,6 +8249,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
pointerCoords.x,
|
pointerCoords.x,
|
||||||
pointerCoords.y,
|
pointerCoords.y,
|
||||||
linearElementEditor,
|
linearElementEditor,
|
||||||
|
(element) => this.getElementHitThreshold(element),
|
||||||
);
|
);
|
||||||
if (newState) {
|
if (newState) {
|
||||||
pointerDownState.lastCoords.x = pointerCoords.x;
|
pointerDownState.lastCoords.x = pointerCoords.x;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user