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 { normalizeAngle, resizeSingleElement } from "../element/resizeElements";
|
||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
import { getTransformHandles } from "../element/transformHandles";
|
import { getTransformHandles } from "../element/transformHandles";
|
||||||
import { isFreeDrawElement, isLinearElement } from "../element/typeChecks";
|
|
||||||
import { updateBoundElements } from "../element/binding";
|
import { updateBoundElements } from "../element/binding";
|
||||||
import { LinearElementEditor } from "../element/linearElementEditor";
|
|
||||||
import { arrayToMap } from "../utils";
|
import { arrayToMap } from "../utils";
|
||||||
|
import {
|
||||||
|
getElementAbsoluteCoords,
|
||||||
|
getElementPointsCoords,
|
||||||
|
} from "../element/bounds";
|
||||||
|
import { isLinearElement } from "../element/typeChecks";
|
||||||
|
import { LinearElementEditor } from "../element/linearElementEditor";
|
||||||
|
|
||||||
const enableActionFlipHorizontal = (
|
const enableActionFlipHorizontal = (
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
@ -118,13 +122,6 @@ const flipElement = (
|
|||||||
const height = element.height;
|
const height = element.height;
|
||||||
const originalAngle = normalizeAngle(element.angle);
|
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
|
// Rotate back to zero, if necessary
|
||||||
mutateElement(element, {
|
mutateElement(element, {
|
||||||
angle: normalizeAngle(0),
|
angle: normalizeAngle(0),
|
||||||
@ -132,7 +129,6 @@ const flipElement = (
|
|||||||
// Flip unrotated by pulling TransformHandle to opposite side
|
// Flip unrotated by pulling TransformHandle to opposite side
|
||||||
const transformHandles = getTransformHandles(element, appState.zoom);
|
const transformHandles = getTransformHandles(element, appState.zoom);
|
||||||
let usingNWHandle = true;
|
let usingNWHandle = true;
|
||||||
let newNCoordsX = 0;
|
|
||||||
let nHandle = transformHandles.nw;
|
let nHandle = transformHandles.nw;
|
||||||
if (!nHandle) {
|
if (!nHandle) {
|
||||||
// Use ne handle instead
|
// 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)) {
|
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++) {
|
for (let index = 1; index < element.points.length; index++) {
|
||||||
LinearElementEditor.movePoints(element, [
|
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);
|
LinearElementEditor.normalizePoints(element);
|
||||||
} else {
|
} else {
|
||||||
// calculate new x-coord for transformation
|
const elWidth = initialPointsCoords
|
||||||
newNCoordsX = usingNWHandle ? element.x + 2 * width : element.x - 2 * width;
|
? initialPointsCoords[2] - initialPointsCoords[0]
|
||||||
|
: initialElementAbsoluteCoords[2] - initialElementAbsoluteCoords[0];
|
||||||
|
|
||||||
|
const startPoint = initialPointsCoords
|
||||||
|
? [initialPointsCoords[0], initialPointsCoords[1]]
|
||||||
|
: [initialElementAbsoluteCoords[0], initialElementAbsoluteCoords[1]];
|
||||||
|
|
||||||
resizeSingleElement(
|
resizeSingleElement(
|
||||||
new Map().set(element.id, element),
|
new Map().set(element.id, element),
|
||||||
true,
|
false,
|
||||||
element,
|
element,
|
||||||
usingNWHandle ? "nw" : "ne",
|
usingNWHandle ? "nw" : "ne",
|
||||||
false,
|
true,
|
||||||
newNCoordsX,
|
usingNWHandle ? startPoint[0] + elWidth : startPoint[0] - elWidth,
|
||||||
nHandle[1],
|
startPoint[1],
|
||||||
);
|
);
|
||||||
// fix the size to account for handle sizes
|
|
||||||
mutateElement(element, {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rotate by (360 degrees - original angle)
|
// Rotate by (360 degrees - original angle)
|
||||||
@ -186,9 +203,34 @@ const flipElement = (
|
|||||||
mutateElement(element, {
|
mutateElement(element, {
|
||||||
x: originalX + finalOffsetX,
|
x: originalX + finalOffsetX,
|
||||||
y: originalY,
|
y: originalY,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
});
|
});
|
||||||
|
|
||||||
updateBoundElements(element);
|
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) => {
|
const rotateElement = (element: ExcalidrawElement, rotationAngle: number) => {
|
||||||
|
@ -79,6 +79,8 @@ const createAndReturnOneDraw = (angle: number = 0) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const FLIP_PRECISION_DECIMALS = 7;
|
||||||
|
|
||||||
// Rectangle element
|
// Rectangle element
|
||||||
|
|
||||||
it("flips an unrotated rectangle horizontally correctly", () => {
|
it("flips an unrotated rectangle horizontally correctly", () => {
|
||||||
@ -408,9 +410,15 @@ it("flips an unrotated arrow horizontally correctly", () => {
|
|||||||
h.app.actionManager.executeAction(actionFlipHorizontal);
|
h.app.actionManager.executeAction(actionFlipHorizontal);
|
||||||
|
|
||||||
// Check if width and height did not change
|
// 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", () => {
|
it("flips an unrotated arrow vertically correctly", () => {
|
||||||
@ -422,9 +430,15 @@ it("flips an unrotated arrow vertically correctly", () => {
|
|||||||
h.app.actionManager.executeAction(actionFlipVertical);
|
h.app.actionManager.executeAction(actionFlipVertical);
|
||||||
|
|
||||||
// Check if width and height did not change
|
// 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
|
//@TODO fix the tests with rotation
|
||||||
@ -439,10 +453,15 @@ it.skip("flips a rotated arrow horizontally correctly", () => {
|
|||||||
h.app.actionManager.executeAction(actionFlipHorizontal);
|
h.app.actionManager.executeAction(actionFlipHorizontal);
|
||||||
|
|
||||||
// Check if width and height did not change
|
// Check if width and height did not change
|
||||||
expect(API.getSelectedElements()[0].width).toEqual(originalWidth);
|
expect(API.getSelectedElements()[0].width).toBeCloseTo(
|
||||||
|
originalWidth,
|
||||||
expect(API.getSelectedElements()[0].height).toEqual(originalHeight);
|
FLIP_PRECISION_DECIMALS,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(API.getSelectedElements()[0].height).toBeCloseTo(
|
||||||
|
originalHeight,
|
||||||
|
FLIP_PRECISION_DECIMALS,
|
||||||
|
);
|
||||||
// Check angle
|
// Check angle
|
||||||
expect(API.getSelectedElements()[0].angle).toBeCloseTo(expectedAngle);
|
expect(API.getSelectedElements()[0].angle).toBeCloseTo(expectedAngle);
|
||||||
});
|
});
|
||||||
@ -458,9 +477,15 @@ it.skip("flips a rotated arrow vertically correctly", () => {
|
|||||||
h.app.actionManager.executeAction(actionFlipVertical);
|
h.app.actionManager.executeAction(actionFlipVertical);
|
||||||
|
|
||||||
// Check if width and height did not change
|
// 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
|
// Check angle
|
||||||
expect(API.getSelectedElements()[0].angle).toBeCloseTo(expectedAngle);
|
expect(API.getSelectedElements()[0].angle).toBeCloseTo(expectedAngle);
|
||||||
@ -477,9 +502,15 @@ it("flips an unrotated line horizontally correctly", () => {
|
|||||||
h.app.actionManager.executeAction(actionFlipHorizontal);
|
h.app.actionManager.executeAction(actionFlipHorizontal);
|
||||||
|
|
||||||
// Check if width and height did not change
|
// 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", () => {
|
it("flips an unrotated line vertically correctly", () => {
|
||||||
@ -491,9 +522,15 @@ it("flips an unrotated line vertically correctly", () => {
|
|||||||
h.app.actionManager.executeAction(actionFlipVertical);
|
h.app.actionManager.executeAction(actionFlipVertical);
|
||||||
|
|
||||||
// Check if width and height did not change
|
// 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", () => {
|
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);
|
h.app.actionManager.executeAction(actionFlipHorizontal);
|
||||||
|
|
||||||
// Check if width and height did not change
|
// 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
|
// Check angle
|
||||||
expect(API.getSelectedElements()[0].angle).toBeCloseTo(expectedAngle);
|
expect(API.getSelectedElements()[0].angle).toBeCloseTo(expectedAngle);
|
||||||
@ -528,9 +571,15 @@ it.skip("flips a rotated line vertically correctly", () => {
|
|||||||
h.app.actionManager.executeAction(actionFlipVertical);
|
h.app.actionManager.executeAction(actionFlipVertical);
|
||||||
|
|
||||||
// Check if width and height did not change
|
// 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
|
// Check angle
|
||||||
expect(API.getSelectedElements()[0].angle).toBeCloseTo(expectedAngle);
|
expect(API.getSelectedElements()[0].angle).toBeCloseTo(expectedAngle);
|
||||||
@ -549,9 +598,9 @@ it("flips an unrotated drawing horizontally correctly", () => {
|
|||||||
h.app.actionManager.executeAction(actionFlipHorizontal);
|
h.app.actionManager.executeAction(actionFlipHorizontal);
|
||||||
|
|
||||||
// Check if width and height did not change
|
// 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", () => {
|
it("flips an unrotated drawing vertically correctly", () => {
|
||||||
@ -565,9 +614,9 @@ it("flips an unrotated drawing vertically correctly", () => {
|
|||||||
h.app.actionManager.executeAction(actionFlipVertical);
|
h.app.actionManager.executeAction(actionFlipVertical);
|
||||||
|
|
||||||
// Check if width and height did not change
|
// 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", () => {
|
it("flips a rotated drawing horizontally correctly", () => {
|
||||||
@ -584,9 +633,9 @@ it("flips a rotated drawing horizontally correctly", () => {
|
|||||||
h.app.actionManager.executeAction(actionFlipHorizontal);
|
h.app.actionManager.executeAction(actionFlipHorizontal);
|
||||||
|
|
||||||
// Check if width and height did not change
|
// 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
|
// Check angle
|
||||||
expect(draw.angle).toBeCloseTo(expectedAngle);
|
expect(draw.angle).toBeCloseTo(expectedAngle);
|
||||||
@ -606,9 +655,16 @@ it("flips a rotated drawing vertically correctly", () => {
|
|||||||
h.app.actionManager.executeAction(actionFlipVertical);
|
h.app.actionManager.executeAction(actionFlipVertical);
|
||||||
|
|
||||||
// Check if width and height did not change
|
// 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
|
// Check angle
|
||||||
expect(API.getSelectedElement().angle).toBeCloseTo(expectedAngle);
|
expect(API.getSelectedElement().angle).toBeCloseTo(expectedAngle);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user