Start grid point arrow align

This commit is contained in:
Mark Tolmacs 2025-03-07 13:03:19 +01:00
parent 40f25180ea
commit 5947af5b50
2 changed files with 148 additions and 117 deletions

View file

@ -238,14 +238,15 @@ export class LinearElementEditor {
}); });
} }
static getOutlineAvoidingPointOrNull( static getOutlineAvoidingPoint(
element: NonDeleted<ExcalidrawLinearElement>, element: NonDeleted<ExcalidrawLinearElement>,
coords: { x: number; y: number }, coords: GlobalPoint,
pointIndex: number, pointIndex: number,
app: AppClassProperties, app: AppClassProperties,
) { fallback?: GlobalPoint,
): GlobalPoint {
const hoveredElement = getHoveredElementForBinding( const hoveredElement = getHoveredElementForBinding(
coords, { x: coords[0], y: coords[1] },
app.scene.getNonDeletedElements(), app.scene.getNonDeletedElements(),
app.scene.getNonDeletedElementsMap(), app.scene.getNonDeletedElementsMap(),
app.state.zoom, app.state.zoom,
@ -254,11 +255,10 @@ export class LinearElementEditor {
); );
if (hoveredElement) { if (hoveredElement) {
const p = pointFrom<GlobalPoint>(coords.x, coords.y);
const newPoints = Array.from(element.points); const newPoints = Array.from(element.points);
newPoints[pointIndex] = pointFrom<LocalPoint>( newPoints[pointIndex] = pointFrom<LocalPoint>(
p[0] - element.x, coords[0] - element.x,
p[1] - element.y, coords[1] - element.y,
); );
return bindPointToSnapToElementOutline( return bindPointToSnapToElementOutline(
@ -272,27 +272,7 @@ export class LinearElementEditor {
); );
} }
return null; return fallback ?? coords;
}
static getOutlineAvoidingPoint(
element: NonDeleted<ExcalidrawLinearElement>,
coords: { x: number; y: number },
pointIndex: number,
app: AppClassProperties,
): GlobalPoint {
const p = LinearElementEditor.getOutlineAvoidingPointOrNull(
element,
coords,
pointIndex,
app,
);
if (p) {
return p;
}
return pointFrom<GlobalPoint>(coords.x, coords.y);
} }
/** /**
@ -411,10 +391,10 @@ export class LinearElementEditor {
globalNewPointPosition = globalNewPointPosition =
LinearElementEditor.getOutlineAvoidingPoint( LinearElementEditor.getOutlineAvoidingPoint(
element, element,
{ pointFrom<GlobalPoint>(
x: element.x + element.points[pointIndex][0] + deltaX, element.x + element.points[pointIndex][0] + deltaX,
y: element.y + element.points[pointIndex][1] + deltaY, element.y + element.points[pointIndex][1] + deltaY,
}, ),
pointIndex, pointIndex,
app, app,
); );

View file

@ -5966,32 +5966,26 @@ class App extends React.Component<AppProps, AppState> {
setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER); setCursor(this.interactiveCanvas, CURSOR_TYPE.POINTER);
} }
const outlineGlobalPoint =
LinearElementEditor.getOutlineAvoidingPointOrNull(
multiElement,
{
x: scenePointerX,
y: scenePointerY,
},
multiElement.points.length - 1,
this,
);
const nextPoint = outlineGlobalPoint
? pointFrom<LocalPoint>(
outlineGlobalPoint[0] - rx,
outlineGlobalPoint[1] - ry,
)
: pointFrom<LocalPoint>(
lastCommittedX + dxFromLastCommitted,
lastCommittedY + dyFromLastCommitted,
);
// update last uncommitted point // update last uncommitted point
mutateElement( mutateElement(
multiElement, multiElement,
{ {
points: [...points.slice(0, -1), nextPoint], points: [
...points.slice(0, -1),
pointTranslate<GlobalPoint, LocalPoint>(
LinearElementEditor.getOutlineAvoidingPoint(
multiElement,
pointFrom<GlobalPoint>(scenePointerX, scenePointerY),
multiElement.points.length - 1,
this,
pointFrom<GlobalPoint>(
multiElement.x + lastCommittedX + dxFromLastCommitted,
multiElement.y + lastCommittedY + dyFromLastCommitted,
),
),
vector(-multiElement.x, -multiElement.y),
),
],
}, },
false, false,
{ {
@ -7802,10 +7796,11 @@ class App extends React.Component<AppProps, AppState> {
? [currentItemStartArrowhead, currentItemEndArrowhead] ? [currentItemStartArrowhead, currentItemEndArrowhead]
: [null, null]; : [null, null];
const element = let element: NonDeleted<ExcalidrawLinearElement>;
elementType === "arrow" if (elementType === "arrow") {
? newArrowElement({ const arrow: Mutable<NonDeleted<ExcalidrawArrowElement>> =
type: elementType, newArrowElement({
type: "arrow",
x: gridX, x: gridX,
y: gridY, y: gridY,
strokeColor: this.state.currentItemStrokeColor, strokeColor: this.state.currentItemStrokeColor,
@ -7827,11 +7822,48 @@ class App extends React.Component<AppProps, AppState> {
frameId: topLayerFrame ? topLayerFrame.id : null, frameId: topLayerFrame ? topLayerFrame.id : null,
elbowed: this.state.currentItemArrowType === ARROW_TYPE.elbow, elbowed: this.state.currentItemArrowType === ARROW_TYPE.elbow,
fixedSegments: fixedSegments:
this.state.currentItemArrowType === ARROW_TYPE.elbow this.state.currentItemArrowType === ARROW_TYPE.elbow ? [] : null,
? [] });
: null,
}) const hoveredElement = getHoveredElementForBinding(
: newLinearElement({ { x: gridX, y: gridY },
this.scene.getNonDeletedElements(),
this.scene.getNonDeletedElementsMap(),
this.state.zoom,
true,
this.state.currentItemArrowType === ARROW_TYPE.elbow,
);
if (hoveredElement) {
[arrow.x, arrow.y] =
intersectElementWithLineSegment(
hoveredElement,
lineSegment(
pointFrom<GlobalPoint>(gridX, gridY),
pointFrom<GlobalPoint>(
gridX,
hoveredElement.y + hoveredElement.height / 2,
),
),
2 * FIXED_BINDING_DISTANCE,
)[0] ??
intersectElementWithLineSegment(
hoveredElement,
lineSegment(
pointFrom<GlobalPoint>(gridX, gridY),
pointFrom<GlobalPoint>(
hoveredElement.x + hoveredElement.width / 2,
gridY,
),
),
2 * FIXED_BINDING_DISTANCE,
)[0] ??
pointFrom<GlobalPoint>(gridX, gridY);
}
element = arrow;
} else {
element = newLinearElement({
type: elementType, type: elementType,
x: gridX, x: gridX,
y: gridY, y: gridY,
@ -7849,6 +7881,8 @@ class App extends React.Component<AppProps, AppState> {
locked: false, locked: false,
frameId: topLayerFrame ? topLayerFrame.id : null, frameId: topLayerFrame ? topLayerFrame.id : null,
}); });
}
this.setState((prevState) => { this.setState((prevState) => {
const nextSelectedElementIds = { const nextSelectedElementIds = {
...prevState.selectedElementIds, ...prevState.selectedElementIds,
@ -8163,12 +8197,6 @@ class App extends React.Component<AppProps, AppState> {
this.laserTrails.addPointToPath(pointerCoords.x, pointerCoords.y); this.laserTrails.addPointToPath(pointerCoords.x, pointerCoords.y);
} }
const [gridX, gridY] = getGridPoint(
pointerCoords.x,
pointerCoords.y,
event[KEYS.CTRL_OR_CMD] ? null : this.getEffectiveGridSize(),
);
// for arrows/lines, don't start dragging until a given threshold // for arrows/lines, don't start dragging until a given threshold
// to ensure we don't create a 2-point arrow by mistake when // to ensure we don't create a 2-point arrow by mistake when
// user clicks mouse in a way that it moves a tiny bit (thus // user clicks mouse in a way that it moves a tiny bit (thus
@ -8610,6 +8638,11 @@ class App extends React.Component<AppProps, AppState> {
} else if (isLinearElement(newElement)) { } else if (isLinearElement(newElement)) {
pointerDownState.drag.hasOccurred = true; pointerDownState.drag.hasOccurred = true;
const points = newElement.points; const points = newElement.points;
const [gridX, gridY] = getGridPoint(
pointerCoords.x,
pointerCoords.y,
event[KEYS.CTRL_OR_CMD] ? null : this.getEffectiveGridSize(),
);
let dx = gridX - newElement.x; let dx = gridX - newElement.x;
let dy = gridY - newElement.y; let dy = gridY - newElement.y;
@ -8626,7 +8659,22 @@ class App extends React.Component<AppProps, AppState> {
mutateElement( mutateElement(
newElement, newElement,
{ {
points: [...points, pointFrom<LocalPoint>(dx, dy)], points: [
...points,
pointTranslate<GlobalPoint, LocalPoint>(
LinearElementEditor.getOutlineAvoidingPoint(
newElement,
pointFrom<GlobalPoint>(pointerCoords.x, pointerCoords.y),
newElement.points.length - 1,
this,
pointFrom<GlobalPoint>(
newElement.x + dx,
newElement.y + dy,
),
),
vector(-newElement.x, -newElement.y),
),
],
}, },
false, false,
); );
@ -8634,20 +8682,23 @@ class App extends React.Component<AppProps, AppState> {
points.length === 2 || points.length === 2 ||
(points.length > 1 && isElbowArrow(newElement)) (points.length > 1 && isElbowArrow(newElement))
) { ) {
const globalPoint = LinearElementEditor.getOutlineAvoidingPoint(
newElement,
{ x: newElement.x + dx, y: newElement.y + dy },
1,
this,
);
mutateElement( mutateElement(
newElement, newElement,
{ {
points: [ points: [
...points.slice(0, -1), ...points.slice(0, -1),
pointFrom<LocalPoint>( pointTranslate<GlobalPoint, LocalPoint>(
globalPoint[0] - newElement.x, LinearElementEditor.getOutlineAvoidingPoint(
globalPoint[1] - newElement.y, newElement,
pointFrom<GlobalPoint>(pointerCoords.x, pointerCoords.y),
newElement.points.length - 1,
this,
pointFrom<GlobalPoint>(
newElement.x + dx,
newElement.y + dy,
),
),
vector(-newElement.x, -newElement.y),
), ),
], ],
}, },