Start grid point arrow align

This commit is contained in:
Mark Tolmacs 2025-03-07 13:03:19 +01:00
parent 528e6aa2df
commit 1a87aa8e55
2 changed files with 148 additions and 117 deletions

View file

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

View file

@ -5992,32 +5992,26 @@ class App extends React.Component<AppProps, AppState> {
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
mutateElement(
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,
{
@ -7838,10 +7832,11 @@ class App extends React.Component<AppProps, AppState> {
? [currentItemStartArrowhead, currentItemEndArrowhead]
: [null, null];
const element =
elementType === "arrow"
? newArrowElement({
type: elementType,
let element: NonDeleted<ExcalidrawLinearElement>;
if (elementType === "arrow") {
const arrow: Mutable<NonDeleted<ExcalidrawArrowElement>> =
newArrowElement({
type: "arrow",
x: gridX,
y: gridY,
strokeColor: this.state.currentItemStrokeColor,
@ -7863,11 +7858,48 @@ class App extends React.Component<AppProps, AppState> {
frameId: topLayerFrame ? topLayerFrame.id : null,
elbowed: this.state.currentItemArrowType === ARROW_TYPE.elbow,
fixedSegments:
this.state.currentItemArrowType === ARROW_TYPE.elbow
? []
: null,
})
: newLinearElement({
this.state.currentItemArrowType === ARROW_TYPE.elbow ? [] : null,
});
const hoveredElement = getHoveredElementForBinding(
{ 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,
x: gridX,
y: gridY,
@ -7885,6 +7917,8 @@ class App extends React.Component<AppProps, AppState> {
locked: false,
frameId: topLayerFrame ? topLayerFrame.id : null,
});
}
this.setState((prevState) => {
const nextSelectedElementIds = {
...prevState.selectedElementIds,
@ -8199,12 +8233,6 @@ class App extends React.Component<AppProps, AppState> {
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
// 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
@ -8691,6 +8719,11 @@ class App extends React.Component<AppProps, AppState> {
} else if (isLinearElement(newElement)) {
pointerDownState.drag.hasOccurred = true;
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 dy = gridY - newElement.y;
@ -8707,7 +8740,22 @@ class App extends React.Component<AppProps, AppState> {
mutateElement(
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,
);
@ -8715,20 +8763,23 @@ class App extends React.Component<AppProps, AppState> {
points.length === 2 ||
(points.length > 1 && isElbowArrow(newElement))
) {
const globalPoint = LinearElementEditor.getOutlineAvoidingPoint(
newElement,
{ x: newElement.x + dx, y: newElement.y + dy },
1,
this,
);
mutateElement(
newElement,
{
points: [
...points.slice(0, -1),
pointFrom<LocalPoint>(
globalPoint[0] - newElement.x,
globalPoint[1] - newElement.y,
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),
),
],
},