mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-11-18 19:54:35 +01:00
fix: Arrow angle
This commit is contained in:
@@ -2243,44 +2243,36 @@ const pointDraggingUpdates = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Simulate the updated arrow for the bind point calculation
|
// Simulate the updated arrow for the bind point calculation
|
||||||
const originalStartGlobalPoint =
|
const updatedPoints = element.points.map((p, idx) => {
|
||||||
LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
const update = naiveDraggingPoints.get(idx);
|
||||||
element,
|
return update ? update.point : p;
|
||||||
0,
|
});
|
||||||
elementsMap,
|
|
||||||
|
const offsetX = updatedPoints[0][0];
|
||||||
|
const offsetY = updatedPoints[0][1];
|
||||||
|
const normalizedPoints = updatedPoints.map((p) =>
|
||||||
|
pointFrom<LocalPoint>(p[0] - offsetX, p[1] - offsetY),
|
||||||
);
|
);
|
||||||
const offsetStartGlobalPoint = startIsDragged
|
|
||||||
? pointFrom<GlobalPoint>(
|
const nextCoords = getElementPointsCoords(element, normalizedPoints);
|
||||||
originalStartGlobalPoint[0] + deltaX,
|
const prevCoords = getElementPointsCoords(element, element.points);
|
||||||
originalStartGlobalPoint[1] + deltaY,
|
const nextCenterX = (nextCoords[0] + nextCoords[2]) / 2;
|
||||||
)
|
const nextCenterY = (nextCoords[1] + nextCoords[3]) / 2;
|
||||||
: originalStartGlobalPoint;
|
const prevCenterX = (prevCoords[0] + prevCoords[2]) / 2;
|
||||||
const offsetStartLocalPoint = LinearElementEditor.pointFromAbsoluteCoords(
|
const prevCenterY = (prevCoords[1] + prevCoords[3]) / 2;
|
||||||
element,
|
const dX = prevCenterX - nextCenterX;
|
||||||
offsetStartGlobalPoint,
|
const dY = prevCenterY - nextCenterY;
|
||||||
elementsMap,
|
const rotatedOffset = pointRotateRads(
|
||||||
|
pointFrom(offsetX, offsetY),
|
||||||
|
pointFrom(dX, dY),
|
||||||
|
element.angle,
|
||||||
);
|
);
|
||||||
const offsetEndLocalPoint = endIsDragged
|
|
||||||
? pointFrom<LocalPoint>(
|
|
||||||
element.points[element.points.length - 1][0] + deltaX,
|
|
||||||
element.points[element.points.length - 1][1] + deltaY,
|
|
||||||
)
|
|
||||||
: element.points[element.points.length - 1];
|
|
||||||
|
|
||||||
const nextArrow = {
|
const nextArrow = {
|
||||||
...element,
|
...element,
|
||||||
points: [
|
points: normalizedPoints,
|
||||||
offsetStartLocalPoint,
|
x: element.x + rotatedOffset[0],
|
||||||
...element.points
|
y: element.y + rotatedOffset[1],
|
||||||
.slice(1, -1)
|
|
||||||
.map((p) =>
|
|
||||||
pointFrom<LocalPoint>(
|
|
||||||
p[0] + element.x - offsetStartGlobalPoint[0],
|
|
||||||
p[1] + element.y - offsetStartGlobalPoint[1],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
offsetEndLocalPoint,
|
|
||||||
],
|
|
||||||
startBinding:
|
startBinding:
|
||||||
updates.startBinding === undefined
|
updates.startBinding === undefined
|
||||||
? element.startBinding
|
? element.startBinding
|
||||||
@@ -2373,6 +2365,16 @@ const pointDraggingUpdates = (
|
|||||||
) || nextArrow.points[0]
|
) || nextArrow.points[0]
|
||||||
: nextArrow.points[0];
|
: nextArrow.points[0];
|
||||||
|
|
||||||
|
const startOffset = pointFrom<LocalPoint>(offsetX, offsetY);
|
||||||
|
const startLocalPointAbsolute = pointFrom<LocalPoint>(
|
||||||
|
startLocalPoint[0] + startOffset[0],
|
||||||
|
startLocalPoint[1] + startOffset[1],
|
||||||
|
);
|
||||||
|
const endLocalPointAbsolute = pointFrom<LocalPoint>(
|
||||||
|
endLocalPoint[0] + startOffset[0],
|
||||||
|
endLocalPoint[1] + startOffset[1],
|
||||||
|
);
|
||||||
|
|
||||||
const endChanged =
|
const endChanged =
|
||||||
pointDistance(
|
pointDistance(
|
||||||
endLocalPoint,
|
endLocalPoint,
|
||||||
@@ -2403,9 +2405,9 @@ const pointDraggingUpdates = (
|
|||||||
return [
|
return [
|
||||||
idx,
|
idx,
|
||||||
idx === 0
|
idx === 0
|
||||||
? { point: startLocalPoint, isDragging: true }
|
? { point: startLocalPointAbsolute, isDragging: true }
|
||||||
: idx === element.points.length - 1
|
: idx === element.points.length - 1
|
||||||
? { point: endLocalPoint, isDragging: true }
|
? { point: endLocalPointAbsolute, isDragging: true }
|
||||||
: naiveDraggingPoints.get(idx)!,
|
: naiveDraggingPoints.get(idx)!,
|
||||||
];
|
];
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { pointCenter, pointFrom } from "@excalidraw/math";
|
import { pointCenter, pointFrom, pointRotateRads } from "@excalidraw/math";
|
||||||
import { act, queryByTestId, queryByText } from "@testing-library/react";
|
import { act, queryByTestId, queryByText } from "@testing-library/react";
|
||||||
import { vi } from "vitest";
|
import { vi } from "vitest";
|
||||||
|
|
||||||
@@ -24,12 +24,13 @@ import {
|
|||||||
unmountComponent,
|
unmountComponent,
|
||||||
} from "@excalidraw/excalidraw/tests/test-utils";
|
} from "@excalidraw/excalidraw/tests/test-utils";
|
||||||
|
|
||||||
import type { GlobalPoint, LocalPoint } from "@excalidraw/math";
|
import type { GlobalPoint, LocalPoint, Radians } from "@excalidraw/math";
|
||||||
|
|
||||||
import { wrapText } from "../src";
|
import { wrapText } from "../src";
|
||||||
import * as textElementUtils from "../src/textElement";
|
import * as textElementUtils from "../src/textElement";
|
||||||
import { getBoundTextElementPosition, getBoundTextMaxWidth } from "../src";
|
import { getBoundTextElementPosition, getBoundTextMaxWidth } from "../src";
|
||||||
import { LinearElementEditor } from "../src";
|
import { LinearElementEditor } from "../src";
|
||||||
|
import { elementCenterPoint } from "../src/bounds";
|
||||||
import { newArrowElement } from "../src";
|
import { newArrowElement } from "../src";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -59,7 +60,7 @@ describe("Test Linear Elements", () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
unmountComponent();
|
unmountComponent();
|
||||||
localStorage.clear();
|
//localStorage.clear();
|
||||||
renderInteractiveScene.mockClear();
|
renderInteractiveScene.mockClear();
|
||||||
renderStaticScene.mockClear();
|
renderStaticScene.mockClear();
|
||||||
reseed(7);
|
reseed(7);
|
||||||
@@ -954,6 +955,62 @@ describe("Test Linear Elements", () => {
|
|||||||
]
|
]
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps rotated arrow start point aligned with pointer while dragging", () => {
|
||||||
|
const arrow = createThreePointerLinearElement("arrow");
|
||||||
|
const angle = 1.2;
|
||||||
|
h.app.scene.mutateElement(arrow, { angle: angle as Radians });
|
||||||
|
|
||||||
|
const elementsMap = h.app.scene.getNonDeletedElementsMap();
|
||||||
|
const center = elementCenterPoint(arrow, elementsMap);
|
||||||
|
const expectedStart = pointRotateRads(
|
||||||
|
pointFrom<GlobalPoint>(
|
||||||
|
arrow.x + arrow.points[0][0],
|
||||||
|
arrow.y + arrow.points[0][1],
|
||||||
|
),
|
||||||
|
center,
|
||||||
|
angle as Radians,
|
||||||
|
);
|
||||||
|
const actualStart = LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
||||||
|
arrow,
|
||||||
|
0,
|
||||||
|
elementsMap,
|
||||||
|
);
|
||||||
|
|
||||||
|
const initialOffset = {
|
||||||
|
x: expectedStart[0] - actualStart[0],
|
||||||
|
y: expectedStart[1] - actualStart[1],
|
||||||
|
};
|
||||||
|
expect(Math.hypot(initialOffset.x, initialOffset.y)).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
API.setSelectedElements([arrow]);
|
||||||
|
enterLineEditingMode(arrow, true);
|
||||||
|
|
||||||
|
const dragOffset = { x: 25, y: -15 };
|
||||||
|
const dragTarget = pointFrom<GlobalPoint>(
|
||||||
|
expectedStart[0] + dragOffset.x,
|
||||||
|
expectedStart[1] + dragOffset.y,
|
||||||
|
);
|
||||||
|
|
||||||
|
mouse.downAt(expectedStart[0], expectedStart[1]);
|
||||||
|
mouse.moveTo(dragTarget[0], dragTarget[1]);
|
||||||
|
mouse.upAt(dragTarget[0], dragTarget[1]);
|
||||||
|
|
||||||
|
const updatedMap = h.app.scene.getNonDeletedElementsMap();
|
||||||
|
const movedStart = LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
||||||
|
arrow,
|
||||||
|
0,
|
||||||
|
updatedMap,
|
||||||
|
);
|
||||||
|
|
||||||
|
const finalOffset = {
|
||||||
|
x: dragTarget[0] - movedStart[0],
|
||||||
|
y: dragTarget[1] - movedStart[1],
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(finalOffset.x).toBeCloseTo(initialOffset.x, 6);
|
||||||
|
expect(finalOffset.y).toBeCloseTo(initialOffset.y, 6);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Test bound text element", () => {
|
describe("Test bound text element", () => {
|
||||||
|
|||||||
@@ -393,35 +393,14 @@ export const restoreElement = (
|
|||||||
...getSizeFromPoints(points),
|
...getSizeFromPoints(points),
|
||||||
});
|
});
|
||||||
case "arrow": {
|
case "arrow": {
|
||||||
const {
|
const { startArrowhead = null, endArrowhead = "arrow" } = element;
|
||||||
startArrowhead = null,
|
|
||||||
endArrowhead = "arrow",
|
|
||||||
angle = 0,
|
|
||||||
} = element;
|
|
||||||
const x: number | undefined = element.x;
|
const x: number | undefined = element.x;
|
||||||
const y: number | undefined = element.y;
|
const y: number | undefined = element.y;
|
||||||
let points: readonly LocalPoint[] | undefined = // migrate old arrow model to new one
|
const points: readonly LocalPoint[] | undefined = // migrate old arrow model to new one
|
||||||
!Array.isArray(element.points) || element.points.length < 2
|
!Array.isArray(element.points) || element.points.length < 2
|
||||||
? [pointFrom(0, 0), pointFrom(element.width, element.height)]
|
? [pointFrom(0, 0), pointFrom(element.width, element.height)]
|
||||||
: element.points;
|
: element.points;
|
||||||
|
|
||||||
if (angle !== 0) {
|
|
||||||
points = LinearElementEditor.getPointsGlobalCoordinates(
|
|
||||||
element,
|
|
||||||
elementsMap,
|
|
||||||
).map((point) =>
|
|
||||||
LinearElementEditor.pointFromAbsoluteCoords(
|
|
||||||
element as ExcalidrawArrowElement,
|
|
||||||
pointRotateRads(
|
|
||||||
point,
|
|
||||||
elementCenterPoint(element, elementsMap),
|
|
||||||
angle,
|
|
||||||
),
|
|
||||||
elementsMap,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const base = {
|
const base = {
|
||||||
type: element.type,
|
type: element.type,
|
||||||
startBinding: repairBinding(
|
startBinding: repairBinding(
|
||||||
@@ -443,7 +422,6 @@ export const restoreElement = (
|
|||||||
y,
|
y,
|
||||||
elbowed: (element as ExcalidrawArrowElement).elbowed,
|
elbowed: (element as ExcalidrawArrowElement).elbowed,
|
||||||
...getSizeFromPoints(points),
|
...getSizeFromPoints(points),
|
||||||
angle: 0 as Radians,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Separate arrow from linear element
|
// TODO: Separate arrow from linear element
|
||||||
|
|||||||
Reference in New Issue
Block a user