fix polygon behavior when adding/removing/moving points within line editor
This commit is contained in:
parent
ac1ad31921
commit
d1fa9005b9
@ -88,11 +88,41 @@ import type {
|
|||||||
PointsPositionUpdates,
|
PointsPositionUpdates,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalizes line points so that the start point is at [0,0]. This is
|
||||||
|
* expected in various parts of the codebase.
|
||||||
|
*
|
||||||
|
* Also returns the offsets - [0,0] if no normalization needed.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
const getNormalizedPoints = ({
|
||||||
|
points,
|
||||||
|
}: {
|
||||||
|
points: ExcalidrawLinearElement["points"];
|
||||||
|
}): {
|
||||||
|
points: LocalPoint[];
|
||||||
|
offsetX: number;
|
||||||
|
offsetY: number;
|
||||||
|
} => {
|
||||||
|
const offsetX = points[0][0];
|
||||||
|
const offsetY = points[0][1];
|
||||||
|
|
||||||
|
return {
|
||||||
|
points: points.map((p) => {
|
||||||
|
return pointFrom(p[0] - offsetX, p[1] - offsetY);
|
||||||
|
}),
|
||||||
|
offsetX,
|
||||||
|
offsetY,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const editorMidPointsCache: {
|
const editorMidPointsCache: {
|
||||||
version: number | null;
|
version: number | null;
|
||||||
points: (GlobalPoint | null)[];
|
points: (GlobalPoint | null)[];
|
||||||
zoom: number | null;
|
zoom: number | null;
|
||||||
} = { version: null, points: [], zoom: null };
|
} = { version: null, points: [], zoom: null };
|
||||||
|
|
||||||
export class LinearElementEditor {
|
export class LinearElementEditor {
|
||||||
public readonly elementId: ExcalidrawElement["id"] & {
|
public readonly elementId: ExcalidrawElement["id"] & {
|
||||||
_brand: "excalidrawLinearElementId";
|
_brand: "excalidrawLinearElementId";
|
||||||
@ -135,7 +165,11 @@ export class LinearElementEditor {
|
|||||||
};
|
};
|
||||||
if (!pointsEqual(element.points[0], pointFrom(0, 0))) {
|
if (!pointsEqual(element.points[0], pointFrom(0, 0))) {
|
||||||
console.error("Linear element is not normalized", Error().stack);
|
console.error("Linear element is not normalized", Error().stack);
|
||||||
LinearElementEditor.normalizePoints(element, elementsMap);
|
mutateElement(
|
||||||
|
element,
|
||||||
|
elementsMap,
|
||||||
|
LinearElementEditor.getNormalizeElementPointsAndCoords(element),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
this.selectedPointsIndices = null;
|
this.selectedPointsIndices = null;
|
||||||
this.lastUncommittedPoint = null;
|
this.lastUncommittedPoint = null;
|
||||||
@ -1033,7 +1067,7 @@ export class LinearElementEditor {
|
|||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
LinearElementEditor.addPoints(element, app.scene, [{ point: newPoint }]);
|
LinearElementEditor.addPoints(element, app.scene, [newPoint]);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...appState.editingLinearElement,
|
...appState.editingLinearElement,
|
||||||
@ -1176,40 +1210,23 @@ export class LinearElementEditor {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalizes line points so that the start point is at [0,0]. This is
|
* Normalizes line points so that the start point is at [0,0]. This is
|
||||||
* expected in various parts of the codebase. Also returns new x/y to account
|
* expected in various parts of the codebase.
|
||||||
* for the potential normalization.
|
*
|
||||||
|
* Also returns normalized x and y coords to account for the normalization
|
||||||
|
* of the points.
|
||||||
*/
|
*/
|
||||||
static getNormalizedPoints(element: ExcalidrawLinearElement): {
|
static getNormalizeElementPointsAndCoords(element: ExcalidrawLinearElement) {
|
||||||
points: LocalPoint[];
|
const { points, offsetX, offsetY } = getNormalizedPoints(element);
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
} {
|
|
||||||
const { points } = element;
|
|
||||||
|
|
||||||
const offsetX = points[0][0];
|
|
||||||
const offsetY = points[0][1];
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
points: points.map((p) => {
|
points,
|
||||||
return pointFrom(p[0] - offsetX, p[1] - offsetY);
|
|
||||||
}),
|
|
||||||
x: element.x + offsetX,
|
x: element.x + offsetX,
|
||||||
y: element.y + offsetY,
|
y: element.y + offsetY,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// element-mutating methods
|
// element-mutating methods
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
static normalizePoints(
|
|
||||||
element: NonDeleted<ExcalidrawLinearElement>,
|
|
||||||
elementsMap: ElementsMap,
|
|
||||||
) {
|
|
||||||
mutateElement(
|
|
||||||
element,
|
|
||||||
elementsMap,
|
|
||||||
LinearElementEditor.getNormalizedPoints(element),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static duplicateSelectedPoints(appState: AppState, scene: Scene): AppState {
|
static duplicateSelectedPoints(appState: AppState, scene: Scene): AppState {
|
||||||
invariant(
|
invariant(
|
||||||
appState.editingLinearElement,
|
appState.editingLinearElement,
|
||||||
@ -1291,51 +1308,43 @@ export class LinearElementEditor {
|
|||||||
app: AppClassProperties,
|
app: AppClassProperties,
|
||||||
pointIndices: readonly number[],
|
pointIndices: readonly number[],
|
||||||
) {
|
) {
|
||||||
// Handle loop lock behavior
|
const isUncommittedPoint =
|
||||||
|
app.state.editingLinearElement?.lastUncommittedPoint ===
|
||||||
|
element.points[element.points.length - 1];
|
||||||
|
|
||||||
|
// break polygon if deleting start/end point
|
||||||
if (isLineElement(element) && element.loopLock) {
|
if (isLineElement(element) && element.loopLock) {
|
||||||
if (
|
if (
|
||||||
pointIndices.includes(0) ||
|
pointIndices.includes(0) ||
|
||||||
(pointIndices.includes(element.points.length - 1) &&
|
(pointIndices.includes(element.points.length - 1) &&
|
||||||
// don't disable polygon if cleaning up uncommitted point
|
// don't disable polygon if cleaning up uncommitted point
|
||||||
app.state.editingLinearElement?.lastUncommittedPoint !==
|
!isUncommittedPoint)
|
||||||
element.points[element.points.length - 1])
|
|
||||||
) {
|
) {
|
||||||
app.scene.mutateElement(element, { loopLock: false });
|
app.scene.mutateElement(element, { loopLock: false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let offsetX = 0;
|
const nextPoints = element.points.filter((_, idx) => {
|
||||||
let offsetY = 0;
|
|
||||||
|
|
||||||
const isDeletingOriginPoint = pointIndices.includes(0);
|
|
||||||
|
|
||||||
// if deleting first point, make the next to be [0,0] and recalculate
|
|
||||||
// positions of the rest with respect to it
|
|
||||||
if (isDeletingOriginPoint) {
|
|
||||||
const firstNonDeletedPoint = element.points.find((point, idx) => {
|
|
||||||
return !pointIndices.includes(idx);
|
return !pointIndices.includes(idx);
|
||||||
});
|
});
|
||||||
if (firstNonDeletedPoint) {
|
|
||||||
offsetX = firstNonDeletedPoint[0];
|
|
||||||
offsetY = firstNonDeletedPoint[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextPoints = element.points.reduce((acc: LocalPoint[], p, idx) => {
|
if (isUncommittedPoint && isLineElement(element) && element.loopLock) {
|
||||||
if (!pointIndices.includes(idx)) {
|
nextPoints[0] = pointFrom(
|
||||||
acc.push(
|
nextPoints[nextPoints.length - 1][0],
|
||||||
!acc.length
|
nextPoints[nextPoints.length - 1][1],
|
||||||
? pointFrom(0, 0)
|
|
||||||
: pointFrom(p[0] - offsetX, p[1] - offsetY),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return acc;
|
|
||||||
}, []);
|
const {
|
||||||
|
points: normalizedPoints,
|
||||||
|
offsetX,
|
||||||
|
offsetY,
|
||||||
|
} = getNormalizedPoints({ points: nextPoints });
|
||||||
|
|
||||||
LinearElementEditor._updatePoints(
|
LinearElementEditor._updatePoints(
|
||||||
element,
|
element,
|
||||||
app.scene,
|
app.scene,
|
||||||
nextPoints,
|
normalizedPoints,
|
||||||
offsetX,
|
offsetX,
|
||||||
offsetY,
|
offsetY,
|
||||||
);
|
);
|
||||||
@ -1344,16 +1353,27 @@ export class LinearElementEditor {
|
|||||||
static addPoints(
|
static addPoints(
|
||||||
element: NonDeleted<ExcalidrawLinearElement>,
|
element: NonDeleted<ExcalidrawLinearElement>,
|
||||||
scene: Scene,
|
scene: Scene,
|
||||||
targetPoints: { point: LocalPoint }[],
|
addedPoints: LocalPoint[],
|
||||||
) {
|
) {
|
||||||
const offsetX = 0;
|
const nextPoints = [...element.points, ...addedPoints];
|
||||||
const offsetY = 0;
|
|
||||||
|
if (isLineElement(element) && element.loopLock) {
|
||||||
|
nextPoints[0] = pointFrom(
|
||||||
|
nextPoints[nextPoints.length - 1][0],
|
||||||
|
nextPoints[nextPoints.length - 1][1],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
points: normalizedPoints,
|
||||||
|
offsetX,
|
||||||
|
offsetY,
|
||||||
|
} = getNormalizedPoints({ points: nextPoints });
|
||||||
|
|
||||||
const nextPoints = [...element.points, ...targetPoints.map((x) => x.point)];
|
|
||||||
LinearElementEditor._updatePoints(
|
LinearElementEditor._updatePoints(
|
||||||
element,
|
element,
|
||||||
scene,
|
scene,
|
||||||
nextPoints,
|
normalizedPoints,
|
||||||
offsetX,
|
offsetX,
|
||||||
offsetY,
|
offsetY,
|
||||||
);
|
);
|
||||||
@ -1396,12 +1416,11 @@ export class LinearElementEditor {
|
|||||||
// all the other points in the opposite direction by delta to
|
// all the other points in the opposite direction by delta to
|
||||||
// offset it. We do the same with actual element.x/y position, so
|
// offset it. We do the same with actual element.x/y position, so
|
||||||
// this hacks are completely transparent to the user.
|
// this hacks are completely transparent to the user.
|
||||||
const [deltaX, deltaY] =
|
|
||||||
|
const updatedOriginPoint =
|
||||||
pointUpdates.get(0)?.point ?? pointFrom<LocalPoint>(0, 0);
|
pointUpdates.get(0)?.point ?? pointFrom<LocalPoint>(0, 0);
|
||||||
const [offsetX, offsetY] = pointFrom<LocalPoint>(
|
|
||||||
deltaX - points[0][0],
|
const [offsetX, offsetY] = updatedOriginPoint;
|
||||||
deltaY - points[0][1],
|
|
||||||
);
|
|
||||||
|
|
||||||
const nextPoints = isElbowArrow(element)
|
const nextPoints = isElbowArrow(element)
|
||||||
? [
|
? [
|
||||||
@ -1571,6 +1590,7 @@ export class LinearElementEditor {
|
|||||||
isDragging: options?.isDragging ?? false,
|
isDragging: options?.isDragging ?? false,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
// TODO do we need to get precise coords here just to calc centers?
|
||||||
const nextCoords = getElementPointsCoords(element, nextPoints);
|
const nextCoords = getElementPointsCoords(element, nextPoints);
|
||||||
const prevCoords = getElementPointsCoords(element, element.points);
|
const prevCoords = getElementPointsCoords(element, element.points);
|
||||||
const nextCenterX = (nextCoords[0] + nextCoords[2]) / 2;
|
const nextCenterX = (nextCoords[0] + nextCoords[2]) / 2;
|
||||||
@ -1579,7 +1599,7 @@ export class LinearElementEditor {
|
|||||||
const prevCenterY = (prevCoords[1] + prevCoords[3]) / 2;
|
const prevCenterY = (prevCoords[1] + prevCoords[3]) / 2;
|
||||||
const dX = prevCenterX - nextCenterX;
|
const dX = prevCenterX - nextCenterX;
|
||||||
const dY = prevCenterY - nextCenterY;
|
const dY = prevCenterY - nextCenterY;
|
||||||
const rotated = pointRotateRads(
|
const rotatedOffset = pointRotateRads(
|
||||||
pointFrom(offsetX, offsetY),
|
pointFrom(offsetX, offsetY),
|
||||||
pointFrom(dX, dY),
|
pointFrom(dX, dY),
|
||||||
element.angle,
|
element.angle,
|
||||||
@ -1587,8 +1607,8 @@ export class LinearElementEditor {
|
|||||||
scene.mutateElement(element, {
|
scene.mutateElement(element, {
|
||||||
...otherUpdates,
|
...otherUpdates,
|
||||||
points: nextPoints,
|
points: nextPoints,
|
||||||
x: element.x + rotated[0],
|
x: element.x + rotatedOffset[0],
|
||||||
y: element.y + rotated[1],
|
y: element.y + rotatedOffset[1],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -296,8 +296,10 @@ export type FixedPointBinding = Merge<
|
|||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
type Index = number;
|
||||||
|
|
||||||
export type PointsPositionUpdates = Map<
|
export type PointsPositionUpdates = Map<
|
||||||
number,
|
Index,
|
||||||
{ point: LocalPoint; isDragging?: boolean }
|
{ point: LocalPoint; isDragging?: boolean }
|
||||||
>;
|
>;
|
||||||
|
|
||||||
@ -335,7 +337,7 @@ export type ExcalidrawLineElement = ExcalidrawLinearElement &
|
|||||||
export type FixedSegment = {
|
export type FixedSegment = {
|
||||||
start: LocalPoint;
|
start: LocalPoint;
|
||||||
end: LocalPoint;
|
end: LocalPoint;
|
||||||
index: number;
|
index: Index;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ExcalidrawArrowElement = ExcalidrawLinearElement &
|
export type ExcalidrawArrowElement = ExcalidrawLinearElement &
|
||||||
|
@ -324,7 +324,8 @@ const restoreElement = (
|
|||||||
: element.points;
|
: element.points;
|
||||||
|
|
||||||
if (points[0][0] !== 0 || points[0][1] !== 0) {
|
if (points[0][0] !== 0 || points[0][1] !== 0) {
|
||||||
({ points, x, y } = LinearElementEditor.getNormalizedPoints(element));
|
({ points, x, y } =
|
||||||
|
LinearElementEditor.getNormalizeElementPointsAndCoords(element));
|
||||||
}
|
}
|
||||||
|
|
||||||
return restoreElementWithProperties(element, {
|
return restoreElementWithProperties(element, {
|
||||||
@ -355,7 +356,8 @@ const restoreElement = (
|
|||||||
: element.points;
|
: element.points;
|
||||||
|
|
||||||
if (points[0][0] !== 0 || points[0][1] !== 0) {
|
if (points[0][0] !== 0 || points[0][1] !== 0) {
|
||||||
({ points, x, y } = LinearElementEditor.getNormalizedPoints(element));
|
({ points, x, y } =
|
||||||
|
LinearElementEditor.getNormalizeElementPointsAndCoords(element));
|
||||||
}
|
}
|
||||||
|
|
||||||
const base = {
|
const base = {
|
||||||
|
@ -469,7 +469,7 @@ const bindLinearElementToElement = (
|
|||||||
|
|
||||||
Object.assign(
|
Object.assign(
|
||||||
linearElement,
|
linearElement,
|
||||||
LinearElementEditor.getNormalizedPoints({
|
LinearElementEditor.getNormalizeElementPointsAndCoords({
|
||||||
...linearElement,
|
...linearElement,
|
||||||
points: newPoints,
|
points: newPoints,
|
||||||
}),
|
}),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user