mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-09-24 09:50:56 +02:00
chore: More point dragging centralization
This commit is contained in:
@@ -43,6 +43,7 @@ import type {
|
||||
} from "@excalidraw/excalidraw/types";
|
||||
|
||||
import {
|
||||
calculateFixedPointForNonElbowArrowBinding,
|
||||
getBindingStrategyForDraggingBindingElementEndpoints,
|
||||
getStartGlobalEndLocalPointsForSimpleArrowBinding,
|
||||
maybeSuggestBindingsForBindingElementAtCoords,
|
||||
@@ -117,6 +118,12 @@ const getNormalizedPoints = ({
|
||||
};
|
||||
};
|
||||
|
||||
type PointMoveOtherUpdates = {
|
||||
startBinding?: FixedPointBinding | null;
|
||||
endBinding?: FixedPointBinding | null;
|
||||
moveMidPointsWithElement?: boolean | null;
|
||||
};
|
||||
|
||||
export class LinearElementEditor {
|
||||
public readonly elementId: ExcalidrawElement["id"] & {
|
||||
_brand: "excalidrawLinearElementId";
|
||||
@@ -282,7 +289,7 @@ export class LinearElementEditor {
|
||||
scenePointerX: number,
|
||||
scenePointerY: number,
|
||||
linearElementEditor: LinearElementEditor,
|
||||
): Pick<AppState, keyof AppState> | null {
|
||||
): Pick<AppState, "suggestedBinding" | "selectedLinearElement"> | null {
|
||||
if (!linearElementEditor) {
|
||||
return null;
|
||||
}
|
||||
@@ -356,10 +363,7 @@ export class LinearElementEditor {
|
||||
const deltaX = reference[0] - draggingPoint[0];
|
||||
const deltaY = reference[1] - draggingPoint[1];
|
||||
|
||||
LinearElementEditor.movePoints(
|
||||
element,
|
||||
app.scene,
|
||||
pointDraggingUpdates(
|
||||
const { positions, updates } = pointDraggingUpdates(
|
||||
selectedPointsIndices,
|
||||
deltaX,
|
||||
deltaY,
|
||||
@@ -367,59 +371,19 @@ export class LinearElementEditor {
|
||||
element,
|
||||
elements,
|
||||
app,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
const scenePointer = pointFrom<GlobalPoint>(
|
||||
|
||||
LinearElementEditor.movePoints(element, app.scene, positions, updates);
|
||||
} else if (
|
||||
shouldAllowDraggingPoint(
|
||||
element,
|
||||
scenePointerX,
|
||||
scenePointerY,
|
||||
);
|
||||
// Do not allow dragging the bound arrow closer to the shape than
|
||||
// the dragging threshold
|
||||
let allowDrag = true;
|
||||
if (isSimpleArrow(element)) {
|
||||
if (selectedPointsIndices.includes(0) && element.startBinding) {
|
||||
const boundElement = elementsMap.get(
|
||||
element.startBinding.elementId,
|
||||
)!;
|
||||
const dist = distanceToElement(
|
||||
boundElement,
|
||||
selectedPointsIndices,
|
||||
elementsMap,
|
||||
scenePointer,
|
||||
);
|
||||
const inside = isPointInElement(
|
||||
scenePointer,
|
||||
boundElement,
|
||||
elementsMap,
|
||||
);
|
||||
allowDrag = allowDrag && (dist > DRAGGING_THRESHOLD || inside);
|
||||
if (allowDrag) {
|
||||
unbindBindingElement(element, "start", app.scene);
|
||||
}
|
||||
}
|
||||
if (
|
||||
selectedPointsIndices.includes(element.points.length - 1) &&
|
||||
element.endBinding
|
||||
app,
|
||||
)
|
||||
) {
|
||||
const boundElement = elementsMap.get(element.endBinding.elementId)!;
|
||||
const dist = distanceToElement(
|
||||
boundElement,
|
||||
elementsMap,
|
||||
scenePointer,
|
||||
);
|
||||
const inside = isPointInElement(
|
||||
scenePointer,
|
||||
boundElement,
|
||||
elementsMap,
|
||||
);
|
||||
allowDrag = allowDrag && (dist > DRAGGING_THRESHOLD || inside);
|
||||
if (allowDrag) {
|
||||
unbindBindingElement(element, "end", app.scene);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (allowDrag) {
|
||||
const newDraggingPointPosition = LinearElementEditor.createPointAt(
|
||||
element,
|
||||
elementsMap,
|
||||
@@ -430,10 +394,7 @@ export class LinearElementEditor {
|
||||
const deltaX = newDraggingPointPosition[0] - draggingPoint[0];
|
||||
const deltaY = newDraggingPointPosition[1] - draggingPoint[1];
|
||||
|
||||
LinearElementEditor.movePoints(
|
||||
element,
|
||||
app.scene,
|
||||
pointDraggingUpdates(
|
||||
const { positions, updates } = pointDraggingUpdates(
|
||||
selectedPointsIndices,
|
||||
deltaX,
|
||||
deltaY,
|
||||
@@ -441,9 +402,9 @@ export class LinearElementEditor {
|
||||
element,
|
||||
elements,
|
||||
app,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
LinearElementEditor.movePoints(element, app.scene, positions, updates);
|
||||
}
|
||||
|
||||
const boundTextElement = getBoundTextElement(element, elementsMap);
|
||||
@@ -1987,7 +1948,10 @@ export const pointDraggingUpdates = (
|
||||
element: NonDeleted<ExcalidrawLinearElement>,
|
||||
elements: readonly Ordered<NonDeletedExcalidrawElement>[],
|
||||
app: AppClassProperties,
|
||||
): PointsPositionUpdates => {
|
||||
): {
|
||||
positions: PointsPositionUpdates;
|
||||
updates?: PointMoveOtherUpdates;
|
||||
} => {
|
||||
const naiveDraggingPoints = new Map(
|
||||
selectedPointsIndices.map((pointIndex) => {
|
||||
return [
|
||||
@@ -2005,7 +1969,9 @@ export const pointDraggingUpdates = (
|
||||
|
||||
// Linear elements have no special logic
|
||||
if (!isArrowElement(element) || isElbowArrow(element)) {
|
||||
return naiveDraggingPoints;
|
||||
return {
|
||||
positions: naiveDraggingPoints,
|
||||
};
|
||||
}
|
||||
|
||||
const startIsDragged = selectedPointsIndices.includes(0);
|
||||
@@ -2014,7 +1980,9 @@ export const pointDraggingUpdates = (
|
||||
);
|
||||
|
||||
if (startIsDragged === endIsDragged) {
|
||||
return naiveDraggingPoints;
|
||||
return {
|
||||
positions: naiveDraggingPoints,
|
||||
};
|
||||
}
|
||||
|
||||
const { start, end } = getBindingStrategyForDraggingBindingElementEndpoints(
|
||||
@@ -2090,7 +2058,22 @@ export const pointDraggingUpdates = (
|
||||
new Set([0, element.points.length - 1, ...selectedPointsIndices]),
|
||||
);
|
||||
|
||||
return new Map(
|
||||
return {
|
||||
updates: start.mode
|
||||
? {
|
||||
startBinding: {
|
||||
elementId: start.element.id,
|
||||
mode: start.mode,
|
||||
...calculateFixedPointForNonElbowArrowBinding(
|
||||
element,
|
||||
start.element,
|
||||
"start",
|
||||
elementsMap,
|
||||
),
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
positions: new Map(
|
||||
indices.map((idx) => {
|
||||
return [
|
||||
idx,
|
||||
@@ -2101,5 +2084,49 @@ export const pointDraggingUpdates = (
|
||||
: naiveDraggingPoints.get(idx)!,
|
||||
];
|
||||
}),
|
||||
);
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
const shouldAllowDraggingPoint = (
|
||||
element: ExcalidrawLinearElement,
|
||||
scenePointerX: number,
|
||||
scenePointerY: number,
|
||||
selectedPointsIndices: readonly number[],
|
||||
elementsMap: Readonly<NonDeletedSceneElementsMap>,
|
||||
app: AppClassProperties,
|
||||
) => {
|
||||
if (!isSimpleArrow(element)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const scenePointer = pointFrom<GlobalPoint>(scenePointerX, scenePointerY);
|
||||
// Do not allow dragging the bound arrow closer to the shape than
|
||||
// the dragging threshold
|
||||
let allowDrag = true;
|
||||
if (isSimpleArrow(element)) {
|
||||
if (selectedPointsIndices.includes(0) && element.startBinding) {
|
||||
const boundElement = elementsMap.get(element.startBinding.elementId)!;
|
||||
const dist = distanceToElement(boundElement, elementsMap, scenePointer);
|
||||
const inside = isPointInElement(scenePointer, boundElement, elementsMap);
|
||||
allowDrag = allowDrag && (dist > DRAGGING_THRESHOLD || inside);
|
||||
if (allowDrag) {
|
||||
unbindBindingElement(element, "start", app.scene);
|
||||
}
|
||||
}
|
||||
if (
|
||||
selectedPointsIndices.includes(element.points.length - 1) &&
|
||||
element.endBinding
|
||||
) {
|
||||
const boundElement = elementsMap.get(element.endBinding.elementId)!;
|
||||
const dist = distanceToElement(boundElement, elementsMap, scenePointer);
|
||||
const inside = isPointInElement(scenePointer, boundElement, elementsMap);
|
||||
allowDrag = allowDrag && (dist > DRAGGING_THRESHOLD || inside);
|
||||
if (allowDrag) {
|
||||
unbindBindingElement(element, "end", app.scene);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return allowDrag;
|
||||
};
|
||||
|
@@ -243,7 +243,6 @@ import {
|
||||
getBindingStrategyForDraggingBindingElementEndpoints,
|
||||
getStartGlobalEndLocalPointsForSimpleArrowBinding,
|
||||
mutateElement,
|
||||
pointDraggingUpdates,
|
||||
} from "@excalidraw/element";
|
||||
|
||||
import type { GlobalPoint, LocalPoint, Radians } from "@excalidraw/math";
|
||||
@@ -965,34 +964,17 @@ class App extends React.Component<AppProps, AppState> {
|
||||
});
|
||||
});
|
||||
|
||||
const elementsMap = this.scene.getNonDeletedElementsMap();
|
||||
const elements = this.scene.getNonDeletedElements();
|
||||
const newDraggingPointPosition = LinearElementEditor.createPointAt(
|
||||
arrow,
|
||||
elementsMap,
|
||||
const event =
|
||||
this.lastPointerMoveEvent ?? this.lastPointerDownEvent?.nativeEvent;
|
||||
invariant(event, "Last event must exist");
|
||||
const newState = LinearElementEditor.handlePointDragging(
|
||||
event,
|
||||
this,
|
||||
x - this.state.selectedLinearElement.pointerOffset.x,
|
||||
y - this.state.selectedLinearElement.pointerOffset.y,
|
||||
this.getEffectiveGridSize(),
|
||||
);
|
||||
const draggingPoint =
|
||||
arrow.points[
|
||||
this.state.selectedLinearElement.pointerDownState.lastClickedPoint
|
||||
];
|
||||
const deltaX = newDraggingPointPosition[0] - draggingPoint[0];
|
||||
const deltaY = newDraggingPointPosition[1] - draggingPoint[1];
|
||||
LinearElementEditor.movePoints(
|
||||
arrow,
|
||||
this.scene,
|
||||
pointDraggingUpdates(
|
||||
startDragged ? [0] : endDragged ? [arrow.points.length - 1] : [],
|
||||
deltaX,
|
||||
deltaY,
|
||||
elementsMap,
|
||||
arrow,
|
||||
elements,
|
||||
this,
|
||||
),
|
||||
this.state.selectedLinearElement,
|
||||
);
|
||||
this.setState(newState);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -8419,27 +8401,32 @@ class App extends React.Component<AppProps, AppState> {
|
||||
this.setState((prevState) => {
|
||||
let linearElementEditor = null;
|
||||
let nextSelectedElementIds = prevState.selectedElementIds;
|
||||
if (isBindingElement(element)) {
|
||||
const linearElement = new LinearElementEditor(
|
||||
if (isLinearElement(element)) {
|
||||
linearElementEditor = new LinearElementEditor(
|
||||
element,
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
|
||||
if (isBindingElement(element)) {
|
||||
linearElementEditor = {
|
||||
...linearElement,
|
||||
...linearElementEditor,
|
||||
selectedPointsIndices: [1],
|
||||
pointerDownState: {
|
||||
...linearElement.pointerDownState,
|
||||
...linearElementEditor.pointerDownState,
|
||||
lastClickedIsEndPoint: true,
|
||||
lastClickedPoint: 1,
|
||||
arrowOriginalStartPoint: pointFrom<GlobalPoint>(
|
||||
pointerDownState.origin.x,
|
||||
pointerDownState.origin.y,
|
||||
),
|
||||
},
|
||||
selectedPointsIndices: [1],
|
||||
};
|
||||
nextSelectedElementIds = makeNextSelectedElementIds(
|
||||
{ [element.id]: true },
|
||||
prevState,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...prevState,
|
||||
@@ -9297,83 +9284,37 @@ class App extends React.Component<AppProps, AppState> {
|
||||
pointerDownState.drag.hasOccurred = true;
|
||||
const points = newElement.points;
|
||||
|
||||
// Update arrow points
|
||||
let startBinding = newElement.startBinding;
|
||||
let startGlobalPoint =
|
||||
this.state.selectedLinearElement?.pointerDownState
|
||||
?.arrowOriginalStartPoint ??
|
||||
LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
||||
newElement,
|
||||
0,
|
||||
elementsMap,
|
||||
);
|
||||
let endLocalPoint = pointFrom<LocalPoint>(
|
||||
gridX - newElement.x,
|
||||
gridY - newElement.y,
|
||||
);
|
||||
|
||||
// Simple arrows need both their start and end points adjusted
|
||||
if (isBindingElement(newElement) && !isElbowArrow(newElement)) {
|
||||
const point = pointFrom<LocalPoint>(
|
||||
pointerCoords.x - newElement.x,
|
||||
pointerCoords.y - newElement.y,
|
||||
);
|
||||
const { start, end } =
|
||||
getBindingStrategyForDraggingBindingElementEndpoints(
|
||||
newElement,
|
||||
new Map([
|
||||
[newElement.points.length - 1, { point, isDragging: true }],
|
||||
]),
|
||||
elementsMap,
|
||||
this.scene.getNonDeletedElements(),
|
||||
this.state,
|
||||
{ newArrow: !!this.state.newElement },
|
||||
);
|
||||
|
||||
if (start.mode) {
|
||||
startBinding = {
|
||||
elementId: start.element.id,
|
||||
mode: start.mode,
|
||||
...calculateFixedPointForNonElbowArrowBinding(
|
||||
newElement,
|
||||
start.element,
|
||||
"start",
|
||||
elementsMap,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
[startGlobalPoint, endLocalPoint] =
|
||||
getStartGlobalEndLocalPointsForSimpleArrowBinding(
|
||||
newElement,
|
||||
start,
|
||||
end,
|
||||
startGlobalPoint,
|
||||
endLocalPoint,
|
||||
elementsMap,
|
||||
);
|
||||
}
|
||||
|
||||
invariant(
|
||||
points.length > 1,
|
||||
"Do not create linear elements with less than 2 points",
|
||||
);
|
||||
|
||||
if (isElbowArrow(newElement) || points.length === 2) {
|
||||
this.scene.mutateElement(
|
||||
let linearElementEditor = this.state.selectedLinearElement;
|
||||
if (!linearElementEditor) {
|
||||
linearElementEditor = new LinearElementEditor(
|
||||
newElement,
|
||||
{
|
||||
x: startGlobalPoint[0],
|
||||
y: startGlobalPoint[1],
|
||||
points: [pointFrom<LocalPoint>(0, 0), endLocalPoint],
|
||||
startBinding,
|
||||
},
|
||||
{ isDragging: true, informMutation: false },
|
||||
this.scene.getNonDeletedElementsMap(),
|
||||
);
|
||||
linearElementEditor = {
|
||||
...linearElementEditor,
|
||||
selectedPointsIndices: [1],
|
||||
pointerDownState: {
|
||||
...linearElementEditor.pointerDownState,
|
||||
lastClickedIsEndPoint: true,
|
||||
lastClickedPoint: 1,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
this.setState({
|
||||
newElement,
|
||||
...LinearElementEditor.handlePointDragging(
|
||||
event,
|
||||
this,
|
||||
gridX,
|
||||
gridY,
|
||||
linearElementEditor,
|
||||
)!,
|
||||
});
|
||||
|
||||
if (isBindingElement(newElement, false)) {
|
||||
|
Reference in New Issue
Block a user