mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-11-18 11:44:23 +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
|
||||
const originalStartGlobalPoint =
|
||||
LinearElementEditor.getPointAtIndexGlobalCoordinates(
|
||||
element,
|
||||
0,
|
||||
elementsMap,
|
||||
const updatedPoints = element.points.map((p, idx) => {
|
||||
const update = naiveDraggingPoints.get(idx);
|
||||
return update ? update.point : p;
|
||||
});
|
||||
|
||||
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>(
|
||||
originalStartGlobalPoint[0] + deltaX,
|
||||
originalStartGlobalPoint[1] + deltaY,
|
||||
)
|
||||
: originalStartGlobalPoint;
|
||||
const offsetStartLocalPoint = LinearElementEditor.pointFromAbsoluteCoords(
|
||||
element,
|
||||
offsetStartGlobalPoint,
|
||||
elementsMap,
|
||||
|
||||
const nextCoords = getElementPointsCoords(element, normalizedPoints);
|
||||
const prevCoords = getElementPointsCoords(element, element.points);
|
||||
const nextCenterX = (nextCoords[0] + nextCoords[2]) / 2;
|
||||
const nextCenterY = (nextCoords[1] + nextCoords[3]) / 2;
|
||||
const prevCenterX = (prevCoords[0] + prevCoords[2]) / 2;
|
||||
const prevCenterY = (prevCoords[1] + prevCoords[3]) / 2;
|
||||
const dX = prevCenterX - nextCenterX;
|
||||
const dY = prevCenterY - nextCenterY;
|
||||
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 = {
|
||||
...element,
|
||||
points: [
|
||||
offsetStartLocalPoint,
|
||||
...element.points
|
||||
.slice(1, -1)
|
||||
.map((p) =>
|
||||
pointFrom<LocalPoint>(
|
||||
p[0] + element.x - offsetStartGlobalPoint[0],
|
||||
p[1] + element.y - offsetStartGlobalPoint[1],
|
||||
),
|
||||
),
|
||||
offsetEndLocalPoint,
|
||||
],
|
||||
points: normalizedPoints,
|
||||
x: element.x + rotatedOffset[0],
|
||||
y: element.y + rotatedOffset[1],
|
||||
startBinding:
|
||||
updates.startBinding === undefined
|
||||
? element.startBinding
|
||||
@@ -2373,6 +2365,16 @@ const pointDraggingUpdates = (
|
||||
) || 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 =
|
||||
pointDistance(
|
||||
endLocalPoint,
|
||||
@@ -2403,9 +2405,9 @@ const pointDraggingUpdates = (
|
||||
return [
|
||||
idx,
|
||||
idx === 0
|
||||
? { point: startLocalPoint, isDragging: true }
|
||||
? { point: startLocalPointAbsolute, isDragging: true }
|
||||
: idx === element.points.length - 1
|
||||
? { point: endLocalPoint, isDragging: true }
|
||||
? { point: endLocalPointAbsolute, isDragging: true }
|
||||
: 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 { vi } from "vitest";
|
||||
|
||||
@@ -24,12 +24,13 @@ import {
|
||||
unmountComponent,
|
||||
} 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 * as textElementUtils from "../src/textElement";
|
||||
import { getBoundTextElementPosition, getBoundTextMaxWidth } from "../src";
|
||||
import { LinearElementEditor } from "../src";
|
||||
import { elementCenterPoint } from "../src/bounds";
|
||||
import { newArrowElement } from "../src";
|
||||
|
||||
import {
|
||||
@@ -59,7 +60,7 @@ describe("Test Linear Elements", () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
unmountComponent();
|
||||
localStorage.clear();
|
||||
//localStorage.clear();
|
||||
renderInteractiveScene.mockClear();
|
||||
renderStaticScene.mockClear();
|
||||
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", () => {
|
||||
|
||||
@@ -393,35 +393,14 @@ export const restoreElement = (
|
||||
...getSizeFromPoints(points),
|
||||
});
|
||||
case "arrow": {
|
||||
const {
|
||||
startArrowhead = null,
|
||||
endArrowhead = "arrow",
|
||||
angle = 0,
|
||||
} = element;
|
||||
const { startArrowhead = null, endArrowhead = "arrow" } = element;
|
||||
const x: number | undefined = element.x;
|
||||
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
|
||||
? [pointFrom(0, 0), pointFrom(element.width, element.height)]
|
||||
: 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 = {
|
||||
type: element.type,
|
||||
startBinding: repairBinding(
|
||||
@@ -443,7 +422,6 @@ export const restoreElement = (
|
||||
y,
|
||||
elbowed: (element as ExcalidrawArrowElement).elbowed,
|
||||
...getSizeFromPoints(points),
|
||||
angle: 0 as Radians,
|
||||
};
|
||||
|
||||
// TODO: Separate arrow from linear element
|
||||
|
||||
Reference in New Issue
Block a user