Existing arrows now jump out
This commit is contained in:
parent
0dd76db5f0
commit
331790e9af
@ -25,6 +25,7 @@ import {
|
||||
|
||||
import {
|
||||
deconstructLinearOrFreeDrawElement,
|
||||
hitElementItself,
|
||||
isPathALoop,
|
||||
type Store,
|
||||
} from "@excalidraw/element";
|
||||
@ -43,6 +44,7 @@ import type {
|
||||
import type { Mutable } from "@excalidraw/common/utility-types";
|
||||
|
||||
import {
|
||||
bindLinearElement,
|
||||
bindOrUnbindLinearElement,
|
||||
getHoveredElementForBinding,
|
||||
getOutlineAvoidingPoint,
|
||||
@ -60,6 +62,7 @@ import { mutateElement } from "./mutateElement";
|
||||
import { getBoundTextElement, handleBindTextResize } from "./textElement";
|
||||
import {
|
||||
isArrowElement,
|
||||
isBindableElement,
|
||||
isBindingElement,
|
||||
isElbowArrow,
|
||||
isFixedPointBinding,
|
||||
@ -87,6 +90,8 @@ import type {
|
||||
FixedSegment,
|
||||
ExcalidrawElbowArrowElement,
|
||||
PointsPositionUpdates,
|
||||
NonDeletedExcalidrawElement,
|
||||
Ordered,
|
||||
} from "./types";
|
||||
|
||||
/**
|
||||
@ -136,6 +141,7 @@ export class LinearElementEditor {
|
||||
index: number | null;
|
||||
added: boolean;
|
||||
};
|
||||
arrowOtherPoint?: GlobalPoint;
|
||||
}>;
|
||||
|
||||
/** whether you're dragging a point */
|
||||
@ -280,6 +286,7 @@ export class LinearElementEditor {
|
||||
scenePointerX: number,
|
||||
scenePointerY: number,
|
||||
linearElementEditor: LinearElementEditor,
|
||||
thresholdCallback: (element: ExcalidrawElement) => number,
|
||||
): Pick<AppState, keyof AppState> | null {
|
||||
if (!linearElementEditor) {
|
||||
return null;
|
||||
@ -288,6 +295,8 @@ export class LinearElementEditor {
|
||||
const elementsMap = app.scene.getNonDeletedElementsMap();
|
||||
const element = LinearElementEditor.getElement(elementId, elementsMap);
|
||||
let customLineAngle = linearElementEditor.customLineAngle;
|
||||
let arrowOtherPoint: GlobalPoint | undefined =
|
||||
linearElementEditor.pointerDownState.arrowOtherPoint;
|
||||
if (!element) {
|
||||
return null;
|
||||
}
|
||||
@ -370,99 +379,39 @@ export class LinearElementEditor {
|
||||
scenePointerY - linearElementEditor.pointerOffset.y,
|
||||
event[KEYS.CTRL_OR_CMD] ? null : app.getEffectiveGridSize(),
|
||||
);
|
||||
|
||||
const deltaX = newDraggingPointPosition[0] - draggingPoint[0];
|
||||
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(
|
||||
element,
|
||||
app.scene,
|
||||
new Map(
|
||||
selectedPointsIndices.map((pointIndex) => {
|
||||
let newPointPosition: LocalPoint =
|
||||
pointIndex === lastClickedPoint
|
||||
? LinearElementEditor.createPointAt(
|
||||
element,
|
||||
elementsMap,
|
||||
scenePointerX - linearElementEditor.pointerOffset.x,
|
||||
scenePointerY - linearElementEditor.pointerOffset.y,
|
||||
event[KEYS.CTRL_OR_CMD]
|
||||
? null
|
||||
: app.getEffectiveGridSize(),
|
||||
)
|
||||
: 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,
|
||||
},
|
||||
];
|
||||
}),
|
||||
pointDraggingUpdates(
|
||||
selectedPointsIndices,
|
||||
deltaX,
|
||||
deltaY,
|
||||
elementsMap,
|
||||
lastClickedPoint,
|
||||
element,
|
||||
scenePointerX,
|
||||
scenePointerY,
|
||||
linearElementEditor,
|
||||
event[KEYS.CTRL_OR_CMD] ? null : app.getEffectiveGridSize(),
|
||||
elements,
|
||||
app.state.zoom,
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -541,6 +490,10 @@ export class LinearElementEditor {
|
||||
: -1,
|
||||
isDragging: true,
|
||||
customLineAngle,
|
||||
pointerDownState: {
|
||||
...linearElementEditor.pointerDownState,
|
||||
arrowOtherPoint,
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
@ -671,6 +624,10 @@ export class LinearElementEditor {
|
||||
isDragging: false,
|
||||
pointerOffset: { x: 0, y: 0 },
|
||||
customLineAngle: null,
|
||||
pointerDownState: {
|
||||
...editingLinearElement.pointerDownState,
|
||||
arrowOtherPoint: undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -2043,3 +2000,221 @@ const normalizeSelectedPoints = (
|
||||
nextPoints = nextPoints.sort((a, b) => a - b);
|
||||
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.y,
|
||||
linearElementEditor,
|
||||
(element) => this.getElementHitThreshold(element),
|
||||
);
|
||||
if (newState) {
|
||||
pointerDownState.lastCoords.x = pointerCoords.x;
|
||||
|
Loading…
x
Reference in New Issue
Block a user