fix: free draw flip not scaling correctly (#5752)
This commit is contained in:
parent
941b2d7042
commit
55110bf1b8
@ -6,10 +6,14 @@ import { ExcalidrawElement, NonDeleted } from "../element/types";
|
||||
import { normalizeAngle, resizeSingleElement } from "../element/resizeElements";
|
||||
import { AppState } from "../types";
|
||||
import { getTransformHandles } from "../element/transformHandles";
|
||||
import { isFreeDrawElement, isLinearElement } from "../element/typeChecks";
|
||||
import { updateBoundElements } from "../element/binding";
|
||||
import { LinearElementEditor } from "../element/linearElementEditor";
|
||||
import { arrayToMap } from "../utils";
|
||||
import {
|
||||
getElementAbsoluteCoords,
|
||||
getElementPointsCoords,
|
||||
} from "../element/bounds";
|
||||
import { isLinearElement } from "../element/typeChecks";
|
||||
import { LinearElementEditor } from "../element/linearElementEditor";
|
||||
|
||||
const enableActionFlipHorizontal = (
|
||||
elements: readonly ExcalidrawElement[],
|
||||
@ -118,13 +122,6 @@ const flipElement = (
|
||||
const height = element.height;
|
||||
const originalAngle = normalizeAngle(element.angle);
|
||||
|
||||
let finalOffsetX = 0;
|
||||
if (isLinearElement(element) || isFreeDrawElement(element)) {
|
||||
finalOffsetX =
|
||||
element.points.reduce((max, point) => Math.max(max, point[0]), 0) * 2 -
|
||||
element.width;
|
||||
}
|
||||
|
||||
// Rotate back to zero, if necessary
|
||||
mutateElement(element, {
|
||||
angle: normalizeAngle(0),
|
||||
@ -132,7 +129,6 @@ const flipElement = (
|
||||
// Flip unrotated by pulling TransformHandle to opposite side
|
||||
const transformHandles = getTransformHandles(element, appState.zoom);
|
||||
let usingNWHandle = true;
|
||||
let newNCoordsX = 0;
|
||||
let nHandle = transformHandles.nw;
|
||||
if (!nHandle) {
|
||||
// Use ne handle instead
|
||||
@ -146,30 +142,51 @@ const flipElement = (
|
||||
}
|
||||
}
|
||||
|
||||
let finalOffsetX = 0;
|
||||
if (isLinearElement(element) && element.points.length < 3) {
|
||||
finalOffsetX =
|
||||
element.points.reduce((max, point) => Math.max(max, point[0]), 0) * 2 -
|
||||
element.width;
|
||||
}
|
||||
|
||||
let initialPointsCoords;
|
||||
if (isLinearElement(element)) {
|
||||
initialPointsCoords = getElementPointsCoords(
|
||||
element,
|
||||
element.points,
|
||||
element.strokeSharpness,
|
||||
);
|
||||
}
|
||||
const initialElementAbsoluteCoords = getElementAbsoluteCoords(element);
|
||||
|
||||
if (isLinearElement(element) && element.points.length < 3) {
|
||||
for (let index = 1; index < element.points.length; index++) {
|
||||
LinearElementEditor.movePoints(element, [
|
||||
{ index, point: [-element.points[index][0], element.points[index][1]] },
|
||||
{
|
||||
index,
|
||||
point: [-element.points[index][0], element.points[index][1]],
|
||||
},
|
||||
]);
|
||||
}
|
||||
LinearElementEditor.normalizePoints(element);
|
||||
} else {
|
||||
// calculate new x-coord for transformation
|
||||
newNCoordsX = usingNWHandle ? element.x + 2 * width : element.x - 2 * width;
|
||||
const elWidth = initialPointsCoords
|
||||
? initialPointsCoords[2] - initialPointsCoords[0]
|
||||
: initialElementAbsoluteCoords[2] - initialElementAbsoluteCoords[0];
|
||||
|
||||
const startPoint = initialPointsCoords
|
||||
? [initialPointsCoords[0], initialPointsCoords[1]]
|
||||
: [initialElementAbsoluteCoords[0], initialElementAbsoluteCoords[1]];
|
||||
|
||||
resizeSingleElement(
|
||||
new Map().set(element.id, element),
|
||||
true,
|
||||
false,
|
||||
element,
|
||||
usingNWHandle ? "nw" : "ne",
|
||||
false,
|
||||
newNCoordsX,
|
||||
nHandle[1],
|
||||
true,
|
||||
usingNWHandle ? startPoint[0] + elWidth : startPoint[0] - elWidth,
|
||||
startPoint[1],
|
||||
);
|
||||
// fix the size to account for handle sizes
|
||||
mutateElement(element, {
|
||||
width,
|
||||
height,
|
||||
});
|
||||
}
|
||||
|
||||
// Rotate by (360 degrees - original angle)
|
||||
@ -186,9 +203,34 @@ const flipElement = (
|
||||
mutateElement(element, {
|
||||
x: originalX + finalOffsetX,
|
||||
y: originalY,
|
||||
width,
|
||||
height,
|
||||
});
|
||||
|
||||
updateBoundElements(element);
|
||||
|
||||
if (initialPointsCoords && isLinearElement(element)) {
|
||||
// Adjusting origin because when a beizer curve path exceeds min/max points it offsets the origin.
|
||||
// There's still room for improvement since when the line roughness is > 1
|
||||
// we still have a small offset of the origin when fliipping the element.
|
||||
const finalPointsCoords = getElementPointsCoords(
|
||||
element,
|
||||
element.points,
|
||||
element.strokeSharpness,
|
||||
);
|
||||
|
||||
const topLeftCoordsDiff = initialPointsCoords[0] - finalPointsCoords[0];
|
||||
const topRightCoordDiff = initialPointsCoords[2] - finalPointsCoords[2];
|
||||
|
||||
const coordsDiff = topLeftCoordsDiff + topRightCoordDiff;
|
||||
|
||||
mutateElement(element, {
|
||||
x: element.x + coordsDiff * 0.5,
|
||||
y: element.y,
|
||||
width,
|
||||
height,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const rotateElement = (element: ExcalidrawElement, rotationAngle: number) => {
|
||||
|
@ -79,6 +79,8 @@ const createAndReturnOneDraw = (angle: number = 0) => {
|
||||
});
|
||||
};
|
||||
|
||||
const FLIP_PRECISION_DECIMALS = 7;
|
||||
|
||||
// Rectangle element
|
||||
|
||||
it("flips an unrotated rectangle horizontally correctly", () => {
|
||||
@ -408,9 +410,15 @@ it("flips an unrotated arrow horizontally correctly", () => {
|
||||
h.app.actionManager.executeAction(actionFlipHorizontal);
|
||||
|
||||
// Check if width and height did not change
|
||||
expect(API.getSelectedElements()[0].width).toEqual(originalWidth);
|
||||
expect(API.getSelectedElements()[0].width).toBeCloseTo(
|
||||
originalWidth,
|
||||
FLIP_PRECISION_DECIMALS,
|
||||
);
|
||||
|
||||
expect(API.getSelectedElements()[0].height).toEqual(originalHeight);
|
||||
expect(API.getSelectedElements()[0].height).toBeCloseTo(
|
||||
originalHeight,
|
||||
FLIP_PRECISION_DECIMALS,
|
||||
);
|
||||
});
|
||||
|
||||
it("flips an unrotated arrow vertically correctly", () => {
|
||||
@ -422,9 +430,15 @@ it("flips an unrotated arrow vertically correctly", () => {
|
||||
h.app.actionManager.executeAction(actionFlipVertical);
|
||||
|
||||
// Check if width and height did not change
|
||||
expect(API.getSelectedElements()[0].width).toEqual(originalWidth);
|
||||
expect(API.getSelectedElements()[0].width).toBeCloseTo(
|
||||
originalWidth,
|
||||
FLIP_PRECISION_DECIMALS,
|
||||
);
|
||||
|
||||
expect(API.getSelectedElements()[0].height).toEqual(originalHeight);
|
||||
expect(API.getSelectedElements()[0].height).toBeCloseTo(
|
||||
originalHeight,
|
||||
FLIP_PRECISION_DECIMALS,
|
||||
);
|
||||
});
|
||||
|
||||
//@TODO fix the tests with rotation
|
||||
@ -439,10 +453,15 @@ it.skip("flips a rotated arrow horizontally correctly", () => {
|
||||
h.app.actionManager.executeAction(actionFlipHorizontal);
|
||||
|
||||
// Check if width and height did not change
|
||||
expect(API.getSelectedElements()[0].width).toEqual(originalWidth);
|
||||
|
||||
expect(API.getSelectedElements()[0].height).toEqual(originalHeight);
|
||||
expect(API.getSelectedElements()[0].width).toBeCloseTo(
|
||||
originalWidth,
|
||||
FLIP_PRECISION_DECIMALS,
|
||||
);
|
||||
|
||||
expect(API.getSelectedElements()[0].height).toBeCloseTo(
|
||||
originalHeight,
|
||||
FLIP_PRECISION_DECIMALS,
|
||||
);
|
||||
// Check angle
|
||||
expect(API.getSelectedElements()[0].angle).toBeCloseTo(expectedAngle);
|
||||
});
|
||||
@ -458,9 +477,15 @@ it.skip("flips a rotated arrow vertically correctly", () => {
|
||||
h.app.actionManager.executeAction(actionFlipVertical);
|
||||
|
||||
// Check if width and height did not change
|
||||
expect(API.getSelectedElements()[0].width).toEqual(originalWidth);
|
||||
expect(API.getSelectedElements()[0].width).toBeCloseTo(
|
||||
originalWidth,
|
||||
FLIP_PRECISION_DECIMALS,
|
||||
);
|
||||
|
||||
expect(API.getSelectedElements()[0].height).toEqual(originalHeight);
|
||||
expect(API.getSelectedElements()[0].height).toBeCloseTo(
|
||||
originalHeight,
|
||||
FLIP_PRECISION_DECIMALS,
|
||||
);
|
||||
|
||||
// Check angle
|
||||
expect(API.getSelectedElements()[0].angle).toBeCloseTo(expectedAngle);
|
||||
@ -477,9 +502,15 @@ it("flips an unrotated line horizontally correctly", () => {
|
||||
h.app.actionManager.executeAction(actionFlipHorizontal);
|
||||
|
||||
// Check if width and height did not change
|
||||
expect(API.getSelectedElements()[0].width).toEqual(originalWidth);
|
||||
expect(API.getSelectedElements()[0].width).toBeCloseTo(
|
||||
originalWidth,
|
||||
FLIP_PRECISION_DECIMALS,
|
||||
);
|
||||
|
||||
expect(API.getSelectedElements()[0].height).toEqual(originalHeight);
|
||||
expect(API.getSelectedElements()[0].height).toBeCloseTo(
|
||||
originalHeight,
|
||||
FLIP_PRECISION_DECIMALS,
|
||||
);
|
||||
});
|
||||
|
||||
it("flips an unrotated line vertically correctly", () => {
|
||||
@ -491,9 +522,15 @@ it("flips an unrotated line vertically correctly", () => {
|
||||
h.app.actionManager.executeAction(actionFlipVertical);
|
||||
|
||||
// Check if width and height did not change
|
||||
expect(API.getSelectedElements()[0].width).toEqual(originalWidth);
|
||||
expect(API.getSelectedElements()[0].width).toBeCloseTo(
|
||||
originalWidth,
|
||||
FLIP_PRECISION_DECIMALS,
|
||||
);
|
||||
|
||||
expect(API.getSelectedElements()[0].height).toEqual(originalHeight);
|
||||
expect(API.getSelectedElements()[0].height).toBeCloseTo(
|
||||
originalHeight,
|
||||
FLIP_PRECISION_DECIMALS,
|
||||
);
|
||||
});
|
||||
|
||||
it.skip("flips a rotated line horizontally correctly", () => {
|
||||
@ -508,9 +545,15 @@ it.skip("flips a rotated line horizontally correctly", () => {
|
||||
h.app.actionManager.executeAction(actionFlipHorizontal);
|
||||
|
||||
// Check if width and height did not change
|
||||
expect(API.getSelectedElements()[0].width).toEqual(originalWidth);
|
||||
expect(API.getSelectedElements()[0].width).toBeCloseTo(
|
||||
originalWidth,
|
||||
FLIP_PRECISION_DECIMALS,
|
||||
);
|
||||
|
||||
expect(API.getSelectedElements()[0].height).toEqual(originalHeight);
|
||||
expect(API.getSelectedElements()[0].height).toBeCloseTo(
|
||||
originalHeight,
|
||||
FLIP_PRECISION_DECIMALS,
|
||||
);
|
||||
|
||||
// Check angle
|
||||
expect(API.getSelectedElements()[0].angle).toBeCloseTo(expectedAngle);
|
||||
@ -528,9 +571,15 @@ it.skip("flips a rotated line vertically correctly", () => {
|
||||
h.app.actionManager.executeAction(actionFlipVertical);
|
||||
|
||||
// Check if width and height did not change
|
||||
expect(API.getSelectedElements()[0].width).toEqual(originalWidth);
|
||||
expect(API.getSelectedElements()[0].width).toBeCloseTo(
|
||||
originalWidth,
|
||||
FLIP_PRECISION_DECIMALS,
|
||||
);
|
||||
|
||||
expect(API.getSelectedElements()[0].height).toEqual(originalHeight);
|
||||
expect(API.getSelectedElements()[0].height).toBeCloseTo(
|
||||
originalHeight,
|
||||
FLIP_PRECISION_DECIMALS,
|
||||
);
|
||||
|
||||
// Check angle
|
||||
expect(API.getSelectedElements()[0].angle).toBeCloseTo(expectedAngle);
|
||||
@ -549,9 +598,9 @@ it("flips an unrotated drawing horizontally correctly", () => {
|
||||
h.app.actionManager.executeAction(actionFlipHorizontal);
|
||||
|
||||
// Check if width and height did not change
|
||||
expect(draw.width).toEqual(originalWidth);
|
||||
expect(draw.width).toBeCloseTo(originalWidth, FLIP_PRECISION_DECIMALS);
|
||||
|
||||
expect(draw.height).toEqual(originalHeight);
|
||||
expect(draw.height).toBeCloseTo(originalHeight, FLIP_PRECISION_DECIMALS);
|
||||
});
|
||||
|
||||
it("flips an unrotated drawing vertically correctly", () => {
|
||||
@ -565,9 +614,9 @@ it("flips an unrotated drawing vertically correctly", () => {
|
||||
h.app.actionManager.executeAction(actionFlipVertical);
|
||||
|
||||
// Check if width and height did not change
|
||||
expect(draw.width).toEqual(originalWidth);
|
||||
expect(draw.width).toBeCloseTo(originalWidth, FLIP_PRECISION_DECIMALS);
|
||||
|
||||
expect(draw.height).toEqual(originalHeight);
|
||||
expect(draw.height).toBeCloseTo(originalHeight, FLIP_PRECISION_DECIMALS);
|
||||
});
|
||||
|
||||
it("flips a rotated drawing horizontally correctly", () => {
|
||||
@ -584,9 +633,9 @@ it("flips a rotated drawing horizontally correctly", () => {
|
||||
h.app.actionManager.executeAction(actionFlipHorizontal);
|
||||
|
||||
// Check if width and height did not change
|
||||
expect(draw.width).toEqual(originalWidth);
|
||||
expect(draw.width).toBeCloseTo(originalWidth, FLIP_PRECISION_DECIMALS);
|
||||
|
||||
expect(draw.height).toEqual(originalHeight);
|
||||
expect(draw.height).toBeCloseTo(originalHeight, FLIP_PRECISION_DECIMALS);
|
||||
|
||||
// Check angle
|
||||
expect(draw.angle).toBeCloseTo(expectedAngle);
|
||||
@ -606,9 +655,16 @@ it("flips a rotated drawing vertically correctly", () => {
|
||||
h.app.actionManager.executeAction(actionFlipVertical);
|
||||
|
||||
// Check if width and height did not change
|
||||
expect(API.getSelectedElement().width).toEqual(originalWidth);
|
||||
|
||||
expect(API.getSelectedElement().height).toEqual(originalHeight);
|
||||
expect(API.getSelectedElement().width).toBeCloseTo(
|
||||
originalWidth,
|
||||
FLIP_PRECISION_DECIMALS,
|
||||
);
|
||||
|
||||
expect(API.getSelectedElement().height).toBeCloseTo(
|
||||
originalHeight,
|
||||
FLIP_PRECISION_DECIMALS,
|
||||
);
|
||||
|
||||
// Check angle
|
||||
expect(API.getSelectedElement().angle).toBeCloseTo(expectedAngle);
|
||||
|
Loading…
x
Reference in New Issue
Block a user