mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
Start grid point arrow align
This commit is contained in:
parent
528e6aa2df
commit
1a87aa8e55
2 changed files with 148 additions and 117 deletions
|
@ -239,14 +239,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,
|
||||||
|
@ -255,11 +256,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(
|
||||||
|
@ -273,27 +273,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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -412,10 +392,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,
|
||||||
);
|
);
|
||||||
|
|
|
@ -5992,32 +5992,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,
|
||||||
{
|
{
|
||||||
|
@ -7838,53 +7832,93 @@ 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({
|
||||||
x: gridX,
|
type: "arrow",
|
||||||
y: gridY,
|
x: gridX,
|
||||||
strokeColor: this.state.currentItemStrokeColor,
|
y: gridY,
|
||||||
backgroundColor: this.state.currentItemBackgroundColor,
|
strokeColor: this.state.currentItemStrokeColor,
|
||||||
fillStyle: this.state.currentItemFillStyle,
|
backgroundColor: this.state.currentItemBackgroundColor,
|
||||||
strokeWidth: this.state.currentItemStrokeWidth,
|
fillStyle: this.state.currentItemFillStyle,
|
||||||
strokeStyle: this.state.currentItemStrokeStyle,
|
strokeWidth: this.state.currentItemStrokeWidth,
|
||||||
roughness: this.state.currentItemRoughness,
|
strokeStyle: this.state.currentItemStrokeStyle,
|
||||||
opacity: this.state.currentItemOpacity,
|
roughness: this.state.currentItemRoughness,
|
||||||
roundness:
|
opacity: this.state.currentItemOpacity,
|
||||||
this.state.currentItemArrowType === ARROW_TYPE.round
|
roundness:
|
||||||
? { type: ROUNDNESS.PROPORTIONAL_RADIUS }
|
this.state.currentItemArrowType === ARROW_TYPE.round
|
||||||
: // note, roundness doesn't have any effect for elbow arrows,
|
? { type: ROUNDNESS.PROPORTIONAL_RADIUS }
|
||||||
// but it's best to set it to null as well
|
: // note, roundness doesn't have any effect for elbow arrows,
|
||||||
null,
|
// but it's best to set it to null as well
|
||||||
startArrowhead,
|
null,
|
||||||
endArrowhead,
|
startArrowhead,
|
||||||
locked: false,
|
endArrowhead,
|
||||||
frameId: topLayerFrame ? topLayerFrame.id : null,
|
locked: false,
|
||||||
elbowed: this.state.currentItemArrowType === ARROW_TYPE.elbow,
|
frameId: topLayerFrame ? topLayerFrame.id : null,
|
||||||
fixedSegments:
|
elbowed: this.state.currentItemArrowType === ARROW_TYPE.elbow,
|
||||||
this.state.currentItemArrowType === ARROW_TYPE.elbow
|
fixedSegments:
|
||||||
? []
|
this.state.currentItemArrowType === ARROW_TYPE.elbow ? [] : null,
|
||||||
: null,
|
});
|
||||||
})
|
|
||||||
: newLinearElement({
|
const hoveredElement = getHoveredElementForBinding(
|
||||||
type: elementType,
|
{ x: gridX, y: gridY },
|
||||||
x: gridX,
|
this.scene.getNonDeletedElements(),
|
||||||
y: gridY,
|
this.scene.getNonDeletedElementsMap(),
|
||||||
strokeColor: this.state.currentItemStrokeColor,
|
this.state.zoom,
|
||||||
backgroundColor: this.state.currentItemBackgroundColor,
|
true,
|
||||||
fillStyle: this.state.currentItemFillStyle,
|
this.state.currentItemArrowType === ARROW_TYPE.elbow,
|
||||||
strokeWidth: this.state.currentItemStrokeWidth,
|
);
|
||||||
strokeStyle: this.state.currentItemStrokeStyle,
|
|
||||||
roughness: this.state.currentItemRoughness,
|
if (hoveredElement) {
|
||||||
opacity: this.state.currentItemOpacity,
|
[arrow.x, arrow.y] =
|
||||||
roundness:
|
intersectElementWithLineSegment(
|
||||||
this.state.currentItemRoundness === "round"
|
hoveredElement,
|
||||||
? { type: ROUNDNESS.PROPORTIONAL_RADIUS }
|
lineSegment(
|
||||||
: null,
|
pointFrom<GlobalPoint>(gridX, gridY),
|
||||||
locked: false,
|
pointFrom<GlobalPoint>(
|
||||||
frameId: topLayerFrame ? topLayerFrame.id : null,
|
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,
|
||||||
|
strokeColor: this.state.currentItemStrokeColor,
|
||||||
|
backgroundColor: this.state.currentItemBackgroundColor,
|
||||||
|
fillStyle: this.state.currentItemFillStyle,
|
||||||
|
strokeWidth: this.state.currentItemStrokeWidth,
|
||||||
|
strokeStyle: this.state.currentItemStrokeStyle,
|
||||||
|
roughness: this.state.currentItemRoughness,
|
||||||
|
opacity: this.state.currentItemOpacity,
|
||||||
|
roundness:
|
||||||
|
this.state.currentItemRoundness === "round"
|
||||||
|
? { type: ROUNDNESS.PROPORTIONAL_RADIUS }
|
||||||
|
: null,
|
||||||
|
locked: false,
|
||||||
|
frameId: topLayerFrame ? topLayerFrame.id : null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.setState((prevState) => {
|
this.setState((prevState) => {
|
||||||
const nextSelectedElementIds = {
|
const nextSelectedElementIds = {
|
||||||
...prevState.selectedElementIds,
|
...prevState.selectedElementIds,
|
||||||
|
@ -8199,12 +8233,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
|
||||||
|
@ -8691,6 +8719,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;
|
||||||
|
|
||||||
|
@ -8707,7 +8740,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,
|
||||||
);
|
);
|
||||||
|
@ -8715,20 +8763,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),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue