feat: add snapping on top of angle locking when both enabled
This commit is contained in:
parent
07640dd756
commit
c1a54455bb
@ -7,6 +7,8 @@ import {
|
|||||||
type LocalPoint,
|
type LocalPoint,
|
||||||
pointDistance,
|
pointDistance,
|
||||||
vectorFromPoint,
|
vectorFromPoint,
|
||||||
|
line,
|
||||||
|
linesIntersectAt,
|
||||||
} from "@excalidraw/math";
|
} from "@excalidraw/math";
|
||||||
|
|
||||||
import { getCurvePathOps } from "@excalidraw/utils/shape";
|
import { getCurvePathOps } from "@excalidraw/utils/shape";
|
||||||
@ -25,7 +27,7 @@ import {
|
|||||||
snapLinearElementPoint,
|
snapLinearElementPoint,
|
||||||
} from "@excalidraw/element/snapping";
|
} from "@excalidraw/element/snapping";
|
||||||
|
|
||||||
import type { Store } from "@excalidraw/element";
|
import { ShapeCache, type Store } from "@excalidraw/element";
|
||||||
|
|
||||||
import type { Radians } from "@excalidraw/math";
|
import type { Radians } from "@excalidraw/math";
|
||||||
|
|
||||||
@ -60,8 +62,6 @@ import {
|
|||||||
isFixedPointBinding,
|
isFixedPointBinding,
|
||||||
} from "./typeChecks";
|
} from "./typeChecks";
|
||||||
|
|
||||||
import { ShapeCache } from "./ShapeCache";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
isPathALoop,
|
isPathALoop,
|
||||||
getBezierCurveLength,
|
getBezierCurveLength,
|
||||||
@ -327,7 +327,6 @@ export class LinearElementEditor {
|
|||||||
: 0
|
: 0
|
||||||
: linearElementEditor.pointerDownState.lastClickedPoint;
|
: linearElementEditor.pointerDownState.lastClickedPoint;
|
||||||
|
|
||||||
// point that's being dragged (out of all selected points)
|
|
||||||
const draggingPoint = element.points[lastClickedPoint];
|
const draggingPoint = element.points[lastClickedPoint];
|
||||||
|
|
||||||
let _snapLines: SnapLine[] = [];
|
let _snapLines: SnapLine[] = [];
|
||||||
@ -348,13 +347,119 @@ export class LinearElementEditor {
|
|||||||
element.points[selectedIndex][0] - referencePoint[0],
|
element.points[selectedIndex][0] - referencePoint[0],
|
||||||
);
|
);
|
||||||
|
|
||||||
const [width, height] = LinearElementEditor._getShiftLockedDelta(
|
const referencePointCoords =
|
||||||
element,
|
LinearElementEditor.getPointGlobalCoordinates(
|
||||||
elementsMap,
|
element,
|
||||||
referencePoint,
|
referencePoint,
|
||||||
pointFrom(scenePointerX, scenePointerY),
|
elementsMap,
|
||||||
event[KEYS.CTRL_OR_CMD] ? null : app.getEffectiveGridSize(),
|
);
|
||||||
customLineAngle,
|
|
||||||
|
const [gridX, gridY] = getGridPoint(
|
||||||
|
scenePointerX,
|
||||||
|
scenePointerY,
|
||||||
|
event[KEYS.CTRL_OR_CMD] || isElbowArrow(element)
|
||||||
|
? null
|
||||||
|
: app.getEffectiveGridSize(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let dxFromReference = gridX - referencePointCoords[0];
|
||||||
|
let dyFromReference = gridY - referencePointCoords[1];
|
||||||
|
|
||||||
|
if (shouldRotateWithDiscreteAngle(event)) {
|
||||||
|
({ width: dxFromReference, height: dyFromReference } =
|
||||||
|
getLockedLinearCursorAlignSize(
|
||||||
|
referencePointCoords[0],
|
||||||
|
referencePointCoords[1],
|
||||||
|
gridX,
|
||||||
|
gridY,
|
||||||
|
customLineAngle,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
const effectiveGridX = referencePointCoords[0] + dxFromReference;
|
||||||
|
const effectiveGridY = referencePointCoords[1] + dyFromReference;
|
||||||
|
|
||||||
|
let newDraggingPointPosition = pointFrom(
|
||||||
|
effectiveGridX,
|
||||||
|
effectiveGridY,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isElbowArrow(element)) {
|
||||||
|
const { snapOffset, snapLines } = snapLinearElementPoint(
|
||||||
|
scene.getNonDeletedElements(),
|
||||||
|
element,
|
||||||
|
lastClickedPoint,
|
||||||
|
{ x: effectiveGridX, y: effectiveGridY },
|
||||||
|
app,
|
||||||
|
event,
|
||||||
|
elementsMap,
|
||||||
|
{ includeSelfPoints: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
_snapLines = snapLines;
|
||||||
|
|
||||||
|
if (snapLines.length > 0 && shouldRotateWithDiscreteAngle(event)) {
|
||||||
|
const angleLine = line<GlobalPoint>(
|
||||||
|
pointFrom(effectiveGridX, effectiveGridY),
|
||||||
|
pointFrom(referencePointCoords[0], referencePointCoords[1]),
|
||||||
|
);
|
||||||
|
|
||||||
|
const firstSnapLine = snapLines[0];
|
||||||
|
if (
|
||||||
|
firstSnapLine.type === "points" &&
|
||||||
|
firstSnapLine.points.length > 1
|
||||||
|
) {
|
||||||
|
const snapLine = line(
|
||||||
|
firstSnapLine.points[0],
|
||||||
|
firstSnapLine.points[1],
|
||||||
|
);
|
||||||
|
const intersection = linesIntersectAt<GlobalPoint>(
|
||||||
|
angleLine,
|
||||||
|
snapLine,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (intersection) {
|
||||||
|
dxFromReference = intersection[0] - referencePointCoords[0];
|
||||||
|
dyFromReference = intersection[1] - referencePointCoords[1];
|
||||||
|
|
||||||
|
const furthestPoint = firstSnapLine.points.reduce(
|
||||||
|
(furthest, point) => {
|
||||||
|
const distance = pointDistance(intersection, point);
|
||||||
|
if (distance > furthest.distance) {
|
||||||
|
return { point, distance };
|
||||||
|
}
|
||||||
|
return furthest;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
point: firstSnapLine.points[0],
|
||||||
|
distance: pointDistance(
|
||||||
|
intersection,
|
||||||
|
firstSnapLine.points[0],
|
||||||
|
),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
firstSnapLine.points = [furthestPoint.point, intersection];
|
||||||
|
_snapLines = [firstSnapLine];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (snapLines.length > 0) {
|
||||||
|
const snappedGridX = effectiveGridX + snapOffset.x;
|
||||||
|
const snappedGridY = effectiveGridY + snapOffset.y;
|
||||||
|
dxFromReference = snappedGridX - referencePointCoords[0];
|
||||||
|
dyFromReference = snappedGridY - referencePointCoords[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const [rotatedX, rotatedY] = pointRotateRads(
|
||||||
|
pointFrom(dxFromReference, dyFromReference),
|
||||||
|
pointFrom(0, 0),
|
||||||
|
-element.angle as Radians,
|
||||||
|
);
|
||||||
|
|
||||||
|
newDraggingPointPosition = pointFrom(
|
||||||
|
referencePoint[0] + rotatedX,
|
||||||
|
referencePoint[1] + rotatedY,
|
||||||
);
|
);
|
||||||
|
|
||||||
LinearElementEditor.movePoints(
|
LinearElementEditor.movePoints(
|
||||||
@ -364,14 +469,11 @@ export class LinearElementEditor {
|
|||||||
[
|
[
|
||||||
selectedIndex,
|
selectedIndex,
|
||||||
{
|
{
|
||||||
point: pointFrom(
|
point: newDraggingPointPosition,
|
||||||
width + referencePoint[0],
|
|
||||||
height + referencePoint[1],
|
|
||||||
),
|
|
||||||
isDragging: selectedIndex === lastClickedPoint,
|
isDragging: selectedIndex === lastClickedPoint,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
]),
|
]) as PointsPositionUpdates,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Apply object snapping for the point being dragged
|
// Apply object snapping for the point being dragged
|
||||||
@ -388,7 +490,7 @@ export class LinearElementEditor {
|
|||||||
app,
|
app,
|
||||||
event,
|
event,
|
||||||
elementsMap,
|
elementsMap,
|
||||||
{ includeSelfPoints: true }, // Include element's own points for snapping when editing
|
{ includeSelfPoints: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
_snapLines = snapLines;
|
_snapLines = snapLines;
|
||||||
@ -415,15 +517,7 @@ export class LinearElementEditor {
|
|||||||
selectedPointsIndices.map((pointIndex) => {
|
selectedPointsIndices.map((pointIndex) => {
|
||||||
const newPointPosition: LocalPoint =
|
const newPointPosition: LocalPoint =
|
||||||
pointIndex === lastClickedPoint
|
pointIndex === lastClickedPoint
|
||||||
? LinearElementEditor.createPointAt(
|
? newDraggingPointPosition
|
||||||
element,
|
|
||||||
elementsMap,
|
|
||||||
snappedPointerX,
|
|
||||||
snappedPointerY,
|
|
||||||
event[KEYS.CTRL_OR_CMD]
|
|
||||||
? null
|
|
||||||
: app.getEffectiveGridSize(),
|
|
||||||
)
|
|
||||||
: pointFrom(
|
: pointFrom(
|
||||||
element.points[pointIndex][0] + deltaX,
|
element.points[pointIndex][0] + deltaX,
|
||||||
element.points[pointIndex][1] + deltaY,
|
element.points[pointIndex][1] + deltaY,
|
||||||
@ -1053,20 +1147,122 @@ export class LinearElementEditor {
|
|||||||
let newPoint: LocalPoint;
|
let newPoint: LocalPoint;
|
||||||
let snapLines: SnapLine[] = [];
|
let snapLines: SnapLine[] = [];
|
||||||
|
|
||||||
if (shouldRotateWithDiscreteAngle(event) && points.length >= 2) {
|
const [gridX, gridY] = getGridPoint(
|
||||||
const lastCommittedPoint = points[points.length - 2];
|
scenePointerX,
|
||||||
|
scenePointerY,
|
||||||
|
event[KEYS.CTRL_OR_CMD] || isElbowArrow(element)
|
||||||
|
? null
|
||||||
|
: app.getEffectiveGridSize(),
|
||||||
|
);
|
||||||
|
|
||||||
const [width, height] = LinearElementEditor._getShiftLockedDelta(
|
const [lastCommittedX, lastCommittedY] = points[points.length - 2] ?? [
|
||||||
|
0, 0,
|
||||||
|
];
|
||||||
|
|
||||||
|
const lastCommittedPointCoords =
|
||||||
|
LinearElementEditor.getPointGlobalCoordinates(
|
||||||
element,
|
element,
|
||||||
|
pointFrom(lastCommittedX, lastCommittedY),
|
||||||
elementsMap,
|
elementsMap,
|
||||||
lastCommittedPoint,
|
);
|
||||||
pointFrom(scenePointerX, scenePointerY),
|
|
||||||
event[KEYS.CTRL_OR_CMD] ? null : app.getEffectiveGridSize(),
|
let dxFromLastCommitted = gridX - lastCommittedPointCoords[0];
|
||||||
|
let dyFromLastCommitted = gridY - lastCommittedPointCoords[1];
|
||||||
|
|
||||||
|
if (shouldRotateWithDiscreteAngle(event) && points.length >= 2) {
|
||||||
|
({ width: dxFromLastCommitted, height: dyFromLastCommitted } =
|
||||||
|
getLockedLinearCursorAlignSize(
|
||||||
|
lastCommittedPointCoords[0],
|
||||||
|
lastCommittedPointCoords[1],
|
||||||
|
gridX,
|
||||||
|
gridY,
|
||||||
|
));
|
||||||
|
|
||||||
|
const effectiveGridX = lastCommittedPointCoords[0] + dxFromLastCommitted;
|
||||||
|
const effectiveGridY = lastCommittedPointCoords[1] + dyFromLastCommitted;
|
||||||
|
|
||||||
|
if (!isElbowArrow(element)) {
|
||||||
|
const { snapOffset, snapLines: _snapLines } = snapLinearElementPoint(
|
||||||
|
app.scene.getNonDeletedElements(),
|
||||||
|
element,
|
||||||
|
points.length - 1,
|
||||||
|
{ x: effectiveGridX, y: effectiveGridY },
|
||||||
|
app,
|
||||||
|
event,
|
||||||
|
elementsMap,
|
||||||
|
{ includeSelfPoints: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
snapLines = _snapLines;
|
||||||
|
|
||||||
|
if (_snapLines.length > 0 && shouldRotateWithDiscreteAngle(event)) {
|
||||||
|
const angleLine = line<GlobalPoint>(
|
||||||
|
pointFrom(effectiveGridX, effectiveGridY),
|
||||||
|
pointFrom(lastCommittedPointCoords[0], lastCommittedPointCoords[1]),
|
||||||
|
);
|
||||||
|
|
||||||
|
const firstSnapLine = _snapLines[0];
|
||||||
|
if (
|
||||||
|
firstSnapLine.type === "points" &&
|
||||||
|
firstSnapLine.points.length > 1
|
||||||
|
) {
|
||||||
|
const snapLine = line(
|
||||||
|
firstSnapLine.points[0],
|
||||||
|
firstSnapLine.points[1],
|
||||||
|
);
|
||||||
|
const intersection = linesIntersectAt<GlobalPoint>(
|
||||||
|
angleLine,
|
||||||
|
snapLine,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (intersection) {
|
||||||
|
dxFromLastCommitted =
|
||||||
|
intersection[0] - lastCommittedPointCoords[0];
|
||||||
|
dyFromLastCommitted =
|
||||||
|
intersection[1] - lastCommittedPointCoords[1];
|
||||||
|
|
||||||
|
const furthestPoint = firstSnapLine.points.reduce(
|
||||||
|
(furthest, point) => {
|
||||||
|
const distance = pointDistance(intersection, point);
|
||||||
|
if (distance > furthest.distance) {
|
||||||
|
return { point, distance };
|
||||||
|
}
|
||||||
|
return furthest;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
point: firstSnapLine.points[0],
|
||||||
|
distance: pointDistance(
|
||||||
|
intersection,
|
||||||
|
firstSnapLine.points[0],
|
||||||
|
),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
firstSnapLine.points = [furthestPoint.point, intersection];
|
||||||
|
snapLines = [firstSnapLine];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
snapLines = [];
|
||||||
|
}
|
||||||
|
} else if (_snapLines.length > 0) {
|
||||||
|
const snappedGridX = effectiveGridX + snapOffset.x;
|
||||||
|
const snappedGridY = effectiveGridY + snapOffset.y;
|
||||||
|
dxFromLastCommitted = snappedGridX - lastCommittedPointCoords[0];
|
||||||
|
dyFromLastCommitted = snappedGridY - lastCommittedPointCoords[1];
|
||||||
|
} else {
|
||||||
|
snapLines = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const [rotatedX, rotatedY] = pointRotateRads(
|
||||||
|
pointFrom(dxFromLastCommitted, dyFromLastCommitted),
|
||||||
|
pointFrom(0, 0),
|
||||||
|
-element.angle as Radians,
|
||||||
);
|
);
|
||||||
|
|
||||||
newPoint = pointFrom(
|
newPoint = pointFrom(
|
||||||
width + lastCommittedPoint[0],
|
lastCommittedX + rotatedX,
|
||||||
height + lastCommittedPoint[1],
|
lastCommittedY + rotatedY,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const originalPointerX =
|
const originalPointerX =
|
||||||
@ -1107,7 +1303,7 @@ export class LinearElementEditor {
|
|||||||
app.scene,
|
app.scene,
|
||||||
new Map([
|
new Map([
|
||||||
[
|
[
|
||||||
element.points.length - 1,
|
points.length - 1,
|
||||||
{
|
{
|
||||||
point: newPoint,
|
point: newPoint,
|
||||||
},
|
},
|
||||||
@ -1117,6 +1313,7 @@ export class LinearElementEditor {
|
|||||||
} else {
|
} else {
|
||||||
LinearElementEditor.addPoints(element, app.scene, [newPoint]);
|
LinearElementEditor.addPoints(element, app.scene, [newPoint]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...appState.editingLinearElement,
|
...appState.editingLinearElement,
|
||||||
lastUncommittedPoint: element.points[element.points.length - 1],
|
lastUncommittedPoint: element.points[element.points.length - 1],
|
||||||
|
@ -2,6 +2,7 @@ import {
|
|||||||
isCloseTo,
|
isCloseTo,
|
||||||
pointFrom,
|
pointFrom,
|
||||||
pointRotateRads,
|
pointRotateRads,
|
||||||
|
pointsEqual,
|
||||||
rangeInclusive,
|
rangeInclusive,
|
||||||
rangeIntersection,
|
rangeIntersection,
|
||||||
rangesOverlap,
|
rangesOverlap,
|
||||||
@ -196,7 +197,7 @@ export const areRoughlyEqual = (a: number, b: number, precision = 0.01) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getLinearElementPoints = (
|
export const getLinearElementPoints = (
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawLinearElement,
|
||||||
elementsMap: ElementsMap,
|
elementsMap: ElementsMap,
|
||||||
options: {
|
options: {
|
||||||
dragOffset?: Vector2D;
|
dragOffset?: Vector2D;
|
||||||
@ -205,13 +206,11 @@ export const getLinearElementPoints = (
|
|||||||
): GlobalPoint[] => {
|
): GlobalPoint[] => {
|
||||||
const { dragOffset, excludePointIndex } = options;
|
const { dragOffset, excludePointIndex } = options;
|
||||||
|
|
||||||
// Only process linear elements
|
if (isElbowArrow(element)) {
|
||||||
if (element.type !== "line" && element.type !== "arrow") {
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const linearElement = element as ExcalidrawLinearElement;
|
if (!element.points || element.points.length === 0) {
|
||||||
if (!linearElement.points || linearElement.points.length === 0) {
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,13 +224,13 @@ export const getLinearElementPoints = (
|
|||||||
|
|
||||||
const globalPoints: GlobalPoint[] = [];
|
const globalPoints: GlobalPoint[] = [];
|
||||||
|
|
||||||
for (let i = 0; i < linearElement.points.length; i++) {
|
for (let i = 0; i < element.points.length; i++) {
|
||||||
// Skip the point being edited if specified
|
// Skip the point being edited if specified
|
||||||
if (excludePointIndex !== undefined && i === excludePointIndex) {
|
if (excludePointIndex !== undefined && i === excludePointIndex) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const localPoint = linearElement.points[i];
|
const localPoint = element.points[i];
|
||||||
const globalX = elementX + localPoint[0];
|
const globalX = elementX + localPoint[0];
|
||||||
const globalY = elementY + localPoint[1];
|
const globalY = elementY + localPoint[1];
|
||||||
|
|
||||||
@ -747,6 +746,21 @@ export const getReferenceSnapPointsForLinearElementPoint = (
|
|||||||
const elementPoints = getLinearElementPoints(editingElement, elementsMap, {
|
const elementPoints = getLinearElementPoints(editingElement, elementsMap, {
|
||||||
excludePointIndex: editingPointIndex >= 0 ? editingPointIndex : undefined,
|
excludePointIndex: editingPointIndex >= 0 ? editingPointIndex : undefined,
|
||||||
});
|
});
|
||||||
|
const shouldSkipFirstOrLast =
|
||||||
|
editingElement.points.length > 2 &&
|
||||||
|
pointsEqual(
|
||||||
|
editingElement.points[0],
|
||||||
|
editingElement.points[editingElement.points.length - 1],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (shouldSkipFirstOrLast) {
|
||||||
|
if (editingPointIndex === 0) {
|
||||||
|
elementPoints.pop();
|
||||||
|
}
|
||||||
|
if (editingPointIndex === editingElement.points.length - 1) {
|
||||||
|
elementPoints.shift();
|
||||||
|
}
|
||||||
|
}
|
||||||
allSnapPoints.push(...elementPoints);
|
allSnapPoints.push(...elementPoints);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,8 @@ import {
|
|||||||
vectorSubtract,
|
vectorSubtract,
|
||||||
vectorDot,
|
vectorDot,
|
||||||
vectorNormalize,
|
vectorNormalize,
|
||||||
|
line,
|
||||||
|
linesIntersectAt,
|
||||||
} from "@excalidraw/math";
|
} from "@excalidraw/math";
|
||||||
import { isPointInShape } from "@excalidraw/utils/collision";
|
import { isPointInShape } from "@excalidraw/utils/collision";
|
||||||
import { getSelectionBoxShape } from "@excalidraw/utils/shape";
|
import { getSelectionBoxShape } from "@excalidraw/utils/shape";
|
||||||
@ -306,9 +308,9 @@ import {
|
|||||||
snapLinearElementPoint,
|
snapLinearElementPoint,
|
||||||
} from "@excalidraw/element";
|
} from "@excalidraw/element";
|
||||||
|
|
||||||
import type { ElementUpdate } from "@excalidraw/element";
|
import type { LocalPoint, GlobalPoint, Radians } from "@excalidraw/math";
|
||||||
|
|
||||||
import type { LocalPoint, Radians } from "@excalidraw/math";
|
import type { ElementUpdate } from "@excalidraw/element";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ExcalidrawBindableElement,
|
ExcalidrawBindableElement,
|
||||||
@ -5948,6 +5950,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
flushSync(() => {
|
flushSync(() => {
|
||||||
this.setState({
|
this.setState({
|
||||||
editingLinearElement,
|
editingLinearElement,
|
||||||
|
snapLines: editingLinearElement.snapLines,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -6043,7 +6046,9 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
let dxFromLastCommitted = gridX - rx - lastCommittedX;
|
let dxFromLastCommitted = gridX - rx - lastCommittedX;
|
||||||
let dyFromLastCommitted = gridY - ry - lastCommittedY;
|
let dyFromLastCommitted = gridY - ry - lastCommittedY;
|
||||||
|
|
||||||
if (shouldRotateWithDiscreteAngle(event)) {
|
const rotateWithDiscreteAngle = shouldRotateWithDiscreteAngle(event);
|
||||||
|
|
||||||
|
if (rotateWithDiscreteAngle) {
|
||||||
({ width: dxFromLastCommitted, height: dyFromLastCommitted } =
|
({ width: dxFromLastCommitted, height: dyFromLastCommitted } =
|
||||||
getLockedLinearCursorAlignSize(
|
getLockedLinearCursorAlignSize(
|
||||||
// actual coordinate of the last committed point
|
// actual coordinate of the last committed point
|
||||||
@ -6053,27 +6058,94 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
gridX,
|
gridX,
|
||||||
gridY,
|
gridY,
|
||||||
));
|
));
|
||||||
} else if (!isElbowArrow(multiElement)) {
|
}
|
||||||
|
|
||||||
|
const effectiveGridX = lastCommittedX + dxFromLastCommitted + rx;
|
||||||
|
const effectiveGridY = lastCommittedY + dyFromLastCommitted + ry;
|
||||||
|
|
||||||
|
if (!isElbowArrow(multiElement)) {
|
||||||
const { snapOffset, snapLines } = snapLinearElementPoint(
|
const { snapOffset, snapLines } = snapLinearElementPoint(
|
||||||
this.scene.getNonDeletedElements(),
|
this.scene.getNonDeletedElements(),
|
||||||
multiElement,
|
multiElement,
|
||||||
points.length - 1,
|
points.length - 1,
|
||||||
{ x: gridX, y: gridY },
|
{ x: effectiveGridX, y: effectiveGridY },
|
||||||
this,
|
this,
|
||||||
event,
|
event,
|
||||||
this.scene.getNonDeletedElementsMap(),
|
this.scene.getNonDeletedElementsMap(),
|
||||||
{ includeSelfPoints: true },
|
{ includeSelfPoints: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
const snappedGridX = gridX + snapOffset.x;
|
if (snapLines.length > 0) {
|
||||||
const snappedGridY = gridY + snapOffset.y;
|
if (rotateWithDiscreteAngle) {
|
||||||
|
// Create line from effective position to last committed point
|
||||||
|
const angleLine = line<GlobalPoint>(
|
||||||
|
pointFrom(effectiveGridX, effectiveGridY),
|
||||||
|
pointFrom(lastCommittedX + rx, lastCommittedY + ry),
|
||||||
|
);
|
||||||
|
|
||||||
dxFromLastCommitted = snappedGridX - rx - lastCommittedX;
|
// Find intersection with first snap line
|
||||||
dyFromLastCommitted = snappedGridY - ry - lastCommittedY;
|
const firstSnapLine = snapLines[0];
|
||||||
|
if (
|
||||||
|
firstSnapLine.type === "points" &&
|
||||||
|
firstSnapLine.points.length > 1
|
||||||
|
) {
|
||||||
|
const snapLine = line(
|
||||||
|
firstSnapLine.points[0],
|
||||||
|
firstSnapLine.points[1],
|
||||||
|
);
|
||||||
|
const intersection = linesIntersectAt<GlobalPoint>(
|
||||||
|
angleLine,
|
||||||
|
snapLine,
|
||||||
|
);
|
||||||
|
|
||||||
this.setState({
|
if (intersection) {
|
||||||
snapLines,
|
dxFromLastCommitted = intersection[0] - rx - lastCommittedX;
|
||||||
});
|
dyFromLastCommitted = intersection[1] - ry - lastCommittedY;
|
||||||
|
|
||||||
|
// Find the furthest point from the intersection
|
||||||
|
const furthestPoint = firstSnapLine.points.reduce(
|
||||||
|
(furthest, point) => {
|
||||||
|
const distance = pointDistance(intersection, point);
|
||||||
|
if (distance > furthest.distance) {
|
||||||
|
return { point, distance };
|
||||||
|
}
|
||||||
|
return furthest;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
point: firstSnapLine.points[0],
|
||||||
|
distance: pointDistance(
|
||||||
|
intersection,
|
||||||
|
firstSnapLine.points[0],
|
||||||
|
),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
firstSnapLine.points = [furthestPoint.point, intersection];
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
snapLines: [firstSnapLine],
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
snapLines: [firstSnapLine],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const snappedGridX = effectiveGridX + snapOffset.x;
|
||||||
|
const snappedGridY = effectiveGridY + snapOffset.y;
|
||||||
|
dxFromLastCommitted = snappedGridX - rx - lastCommittedX;
|
||||||
|
dyFromLastCommitted = snappedGridY - ry - lastCommittedY;
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
snapLines,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
snapLines: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPathALoop(points, this.state.zoom.value)) {
|
if (isPathALoop(points, this.state.zoom.value)) {
|
||||||
@ -8804,27 +8876,10 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
let dx = gridX - newElement.x;
|
let dx = gridX - newElement.x;
|
||||||
let dy = gridY - newElement.y;
|
let dy = gridY - newElement.y;
|
||||||
|
|
||||||
// snap a two-point line/arrow as well
|
const rotateWithDiscreteAngle =
|
||||||
if (!isElbowArrow(newElement)) {
|
shouldRotateWithDiscreteAngle(event) && points.length === 2;
|
||||||
const { snapOffset, snapLines } = snapLinearElementPoint(
|
|
||||||
this.scene.getNonDeletedElements(),
|
|
||||||
newElement,
|
|
||||||
points.length - 1,
|
|
||||||
{ x: gridX, y: gridY },
|
|
||||||
this,
|
|
||||||
event,
|
|
||||||
this.scene.getNonDeletedElementsMap(),
|
|
||||||
{ includeSelfPoints: true },
|
|
||||||
);
|
|
||||||
const snappedGridX = gridX + snapOffset.x;
|
|
||||||
const snappedGridY = gridY + snapOffset.y;
|
|
||||||
dx = snappedGridX - newElement.x;
|
|
||||||
dy = snappedGridY - newElement.y;
|
|
||||||
|
|
||||||
this.setState({ snapLines });
|
if (rotateWithDiscreteAngle) {
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldRotateWithDiscreteAngle(event) && points.length === 2) {
|
|
||||||
({ width: dx, height: dy } = getLockedLinearCursorAlignSize(
|
({ width: dx, height: dy } = getLockedLinearCursorAlignSize(
|
||||||
newElement.x,
|
newElement.x,
|
||||||
newElement.y,
|
newElement.y,
|
||||||
@ -8833,6 +8888,90 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const effectiveGridX = newElement.x + dx;
|
||||||
|
const effectiveGridY = newElement.y + dy;
|
||||||
|
|
||||||
|
// Snap a two-point line/arrow as well
|
||||||
|
if (!isElbowArrow(newElement)) {
|
||||||
|
const { snapOffset, snapLines } = snapLinearElementPoint(
|
||||||
|
this.scene.getNonDeletedElements(),
|
||||||
|
newElement,
|
||||||
|
points.length - 1,
|
||||||
|
{ x: effectiveGridX, y: effectiveGridY },
|
||||||
|
this,
|
||||||
|
event,
|
||||||
|
this.scene.getNonDeletedElementsMap(),
|
||||||
|
{ includeSelfPoints: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
if (snapLines.length > 0) {
|
||||||
|
if (rotateWithDiscreteAngle) {
|
||||||
|
const angleLine = line<GlobalPoint>(
|
||||||
|
pointFrom(effectiveGridX, effectiveGridY),
|
||||||
|
pointFrom(newElement.x, newElement.y),
|
||||||
|
);
|
||||||
|
|
||||||
|
const firstSnapLine = snapLines.find(
|
||||||
|
(snapLine) =>
|
||||||
|
snapLine.type === "points" && snapLine.points.length > 2,
|
||||||
|
);
|
||||||
|
if (firstSnapLine && firstSnapLine.points.length > 1) {
|
||||||
|
const snapLine = line(
|
||||||
|
firstSnapLine.points[0],
|
||||||
|
firstSnapLine.points[1],
|
||||||
|
);
|
||||||
|
const intersection = linesIntersectAt<GlobalPoint>(
|
||||||
|
angleLine,
|
||||||
|
snapLine,
|
||||||
|
);
|
||||||
|
if (intersection) {
|
||||||
|
dx = intersection[0] - newElement.x;
|
||||||
|
dy = intersection[1] - newElement.y;
|
||||||
|
|
||||||
|
// Find the furthest point from the intersection
|
||||||
|
const furthestPoint = firstSnapLine.points.reduce(
|
||||||
|
(furthest, point) => {
|
||||||
|
const distance = pointDistance(intersection, point);
|
||||||
|
if (distance > furthest.distance) {
|
||||||
|
return { point, distance };
|
||||||
|
}
|
||||||
|
return furthest;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
point: firstSnapLine.points[0],
|
||||||
|
distance: pointDistance(
|
||||||
|
intersection,
|
||||||
|
firstSnapLine.points[0],
|
||||||
|
),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
firstSnapLine.points = [furthestPoint.point, intersection];
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
snapLines: [firstSnapLine],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
snapLines: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dx = gridX + snapOffset.x - newElement.x;
|
||||||
|
dy = gridY + snapOffset.y - newElement.y;
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
snapLines,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
snapLines: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (points.length === 1) {
|
if (points.length === 1) {
|
||||||
this.scene.mutateElement(
|
this.scene.mutateElement(
|
||||||
newElement,
|
newElement,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user