|
|
|
@ -17,12 +17,15 @@ import { isLinearElement } from "./typeChecks";
|
|
|
|
|
import { mutateElement } from "./mutateElement";
|
|
|
|
|
import { getPerfectElementSize } from "./sizeHelpers";
|
|
|
|
|
import {
|
|
|
|
|
resizeTest,
|
|
|
|
|
getCursorForResizingElement,
|
|
|
|
|
normalizeResizeHandle,
|
|
|
|
|
normalizeTransformHandleType,
|
|
|
|
|
} from "./resizeTest";
|
|
|
|
|
import { measureText, getFontString } from "../utils";
|
|
|
|
|
import { updateBoundElements } from "./binding";
|
|
|
|
|
import {
|
|
|
|
|
TransformHandleType,
|
|
|
|
|
MaybeTransformHandleType,
|
|
|
|
|
} from "./transformHandles";
|
|
|
|
|
|
|
|
|
|
const normalizeAngle = (angle: number): number => {
|
|
|
|
|
if (angle >= 2 * Math.PI) {
|
|
|
|
@ -31,12 +34,10 @@ const normalizeAngle = (angle: number): number => {
|
|
|
|
|
return angle;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type ResizeTestType = ReturnType<typeof resizeTest>;
|
|
|
|
|
|
|
|
|
|
// Returns true when a resize (scaling/rotation) happened
|
|
|
|
|
export const resizeElements = (
|
|
|
|
|
resizeHandle: ResizeTestType,
|
|
|
|
|
setResizeHandle: (nextResizeHandle: ResizeTestType) => void,
|
|
|
|
|
transformHandleType: MaybeTransformHandleType,
|
|
|
|
|
setTransformHandle: (nextTransformHandle: MaybeTransformHandleType) => void,
|
|
|
|
|
selectedElements: readonly NonDeletedExcalidrawElement[],
|
|
|
|
|
resizeArrowDirection: "origin" | "end",
|
|
|
|
|
isRotateWithDiscreteAngle: boolean,
|
|
|
|
@ -50,7 +51,7 @@ export const resizeElements = (
|
|
|
|
|
) => {
|
|
|
|
|
if (selectedElements.length === 1) {
|
|
|
|
|
const [element] = selectedElements;
|
|
|
|
|
if (resizeHandle === "rotation") {
|
|
|
|
|
if (transformHandleType === "rotation") {
|
|
|
|
|
rotateSingleElement(
|
|
|
|
|
element,
|
|
|
|
|
pointerX,
|
|
|
|
@ -61,10 +62,10 @@ export const resizeElements = (
|
|
|
|
|
} else if (
|
|
|
|
|
isLinearElement(element) &&
|
|
|
|
|
element.points.length === 2 &&
|
|
|
|
|
(resizeHandle === "nw" ||
|
|
|
|
|
resizeHandle === "ne" ||
|
|
|
|
|
resizeHandle === "sw" ||
|
|
|
|
|
resizeHandle === "se")
|
|
|
|
|
(transformHandleType === "nw" ||
|
|
|
|
|
transformHandleType === "ne" ||
|
|
|
|
|
transformHandleType === "sw" ||
|
|
|
|
|
transformHandleType === "se")
|
|
|
|
|
) {
|
|
|
|
|
resizeSingleTwoPointElement(
|
|
|
|
|
element,
|
|
|
|
@ -75,28 +76,30 @@ export const resizeElements = (
|
|
|
|
|
);
|
|
|
|
|
} else if (
|
|
|
|
|
element.type === "text" &&
|
|
|
|
|
(resizeHandle === "nw" ||
|
|
|
|
|
resizeHandle === "ne" ||
|
|
|
|
|
resizeHandle === "sw" ||
|
|
|
|
|
resizeHandle === "se")
|
|
|
|
|
(transformHandleType === "nw" ||
|
|
|
|
|
transformHandleType === "ne" ||
|
|
|
|
|
transformHandleType === "sw" ||
|
|
|
|
|
transformHandleType === "se")
|
|
|
|
|
) {
|
|
|
|
|
resizeSingleTextElement(
|
|
|
|
|
element,
|
|
|
|
|
resizeHandle,
|
|
|
|
|
transformHandleType,
|
|
|
|
|
isResizeCenterPoint,
|
|
|
|
|
pointerX,
|
|
|
|
|
pointerY,
|
|
|
|
|
);
|
|
|
|
|
} else if (resizeHandle) {
|
|
|
|
|
} else if (transformHandleType) {
|
|
|
|
|
resizeSingleElement(
|
|
|
|
|
element,
|
|
|
|
|
resizeHandle,
|
|
|
|
|
transformHandleType,
|
|
|
|
|
isResizeWithSidesSameLength,
|
|
|
|
|
isResizeCenterPoint,
|
|
|
|
|
pointerX,
|
|
|
|
|
pointerY,
|
|
|
|
|
);
|
|
|
|
|
setResizeHandle(normalizeResizeHandle(element, resizeHandle));
|
|
|
|
|
setTransformHandle(
|
|
|
|
|
normalizeTransformHandleType(element, transformHandleType),
|
|
|
|
|
);
|
|
|
|
|
if (element.width < 0) {
|
|
|
|
|
mutateElement(element, { width: -element.width });
|
|
|
|
|
}
|
|
|
|
@ -109,12 +112,12 @@ export const resizeElements = (
|
|
|
|
|
// FIXME it is not very nice to have this here
|
|
|
|
|
document.documentElement.style.cursor = getCursorForResizingElement({
|
|
|
|
|
element,
|
|
|
|
|
resizeHandle,
|
|
|
|
|
transformHandleType,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
} else if (selectedElements.length > 1) {
|
|
|
|
|
if (resizeHandle === "rotation") {
|
|
|
|
|
if (transformHandleType === "rotation") {
|
|
|
|
|
rotateMultipleElements(
|
|
|
|
|
selectedElements,
|
|
|
|
|
pointerX,
|
|
|
|
@ -126,14 +129,14 @@ export const resizeElements = (
|
|
|
|
|
);
|
|
|
|
|
return true;
|
|
|
|
|
} else if (
|
|
|
|
|
resizeHandle === "nw" ||
|
|
|
|
|
resizeHandle === "ne" ||
|
|
|
|
|
resizeHandle === "sw" ||
|
|
|
|
|
resizeHandle === "se"
|
|
|
|
|
transformHandleType === "nw" ||
|
|
|
|
|
transformHandleType === "ne" ||
|
|
|
|
|
transformHandleType === "sw" ||
|
|
|
|
|
transformHandleType === "se"
|
|
|
|
|
) {
|
|
|
|
|
resizeMultipleElements(
|
|
|
|
|
selectedElements,
|
|
|
|
|
resizeHandle,
|
|
|
|
|
transformHandleType,
|
|
|
|
|
pointerX,
|
|
|
|
|
pointerY,
|
|
|
|
|
);
|
|
|
|
@ -257,29 +260,29 @@ const measureFontSizeFromWH = (
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getSidesForResizeHandle = (
|
|
|
|
|
resizeHandle: "n" | "s" | "w" | "e" | "nw" | "ne" | "sw" | "se",
|
|
|
|
|
const getSidesForTransformHandle = (
|
|
|
|
|
transformHandleType: TransformHandleType,
|
|
|
|
|
isResizeFromCenter: boolean,
|
|
|
|
|
) => {
|
|
|
|
|
return {
|
|
|
|
|
n:
|
|
|
|
|
/^(n|ne|nw)$/.test(resizeHandle) ||
|
|
|
|
|
(isResizeFromCenter && /^(s|se|sw)$/.test(resizeHandle)),
|
|
|
|
|
/^(n|ne|nw)$/.test(transformHandleType) ||
|
|
|
|
|
(isResizeFromCenter && /^(s|se|sw)$/.test(transformHandleType)),
|
|
|
|
|
s:
|
|
|
|
|
/^(s|se|sw)$/.test(resizeHandle) ||
|
|
|
|
|
(isResizeFromCenter && /^(n|ne|nw)$/.test(resizeHandle)),
|
|
|
|
|
/^(s|se|sw)$/.test(transformHandleType) ||
|
|
|
|
|
(isResizeFromCenter && /^(n|ne|nw)$/.test(transformHandleType)),
|
|
|
|
|
w:
|
|
|
|
|
/^(w|nw|sw)$/.test(resizeHandle) ||
|
|
|
|
|
(isResizeFromCenter && /^(e|ne|se)$/.test(resizeHandle)),
|
|
|
|
|
/^(w|nw|sw)$/.test(transformHandleType) ||
|
|
|
|
|
(isResizeFromCenter && /^(e|ne|se)$/.test(transformHandleType)),
|
|
|
|
|
e:
|
|
|
|
|
/^(e|ne|se)$/.test(resizeHandle) ||
|
|
|
|
|
(isResizeFromCenter && /^(w|nw|sw)$/.test(resizeHandle)),
|
|
|
|
|
/^(e|ne|se)$/.test(transformHandleType) ||
|
|
|
|
|
(isResizeFromCenter && /^(w|nw|sw)$/.test(transformHandleType)),
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const resizeSingleTextElement = (
|
|
|
|
|
element: NonDeleted<ExcalidrawTextElement>,
|
|
|
|
|
resizeHandle: "nw" | "ne" | "sw" | "se",
|
|
|
|
|
transformHandleType: "nw" | "ne" | "sw" | "se",
|
|
|
|
|
isResizeFromCenter: boolean,
|
|
|
|
|
pointerX: number,
|
|
|
|
|
pointerY: number,
|
|
|
|
@ -296,7 +299,7 @@ const resizeSingleTextElement = (
|
|
|
|
|
-element.angle,
|
|
|
|
|
);
|
|
|
|
|
let scale;
|
|
|
|
|
switch (resizeHandle) {
|
|
|
|
|
switch (transformHandleType) {
|
|
|
|
|
case "se":
|
|
|
|
|
scale = Math.max(
|
|
|
|
|
(rotatedX - x1) / (x2 - x1),
|
|
|
|
@ -339,7 +342,7 @@ const resizeSingleTextElement = (
|
|
|
|
|
const deltaX2 = (x2 - nextX2) / 2;
|
|
|
|
|
const deltaY2 = (y2 - nextY2) / 2;
|
|
|
|
|
const [nextElementX, nextElementY] = adjustXYWithRotation(
|
|
|
|
|
getSidesForResizeHandle(resizeHandle, isResizeFromCenter),
|
|
|
|
|
getSidesForTransformHandle(transformHandleType, isResizeFromCenter),
|
|
|
|
|
element.x,
|
|
|
|
|
element.y,
|
|
|
|
|
element.angle,
|
|
|
|
@ -361,7 +364,7 @@ const resizeSingleTextElement = (
|
|
|
|
|
|
|
|
|
|
const resizeSingleElement = (
|
|
|
|
|
element: NonDeletedExcalidrawElement,
|
|
|
|
|
resizeHandle: "n" | "s" | "w" | "e" | "nw" | "ne" | "sw" | "se",
|
|
|
|
|
transformHandleType: "n" | "s" | "w" | "e" | "nw" | "ne" | "sw" | "se",
|
|
|
|
|
sidesWithSameLength: boolean,
|
|
|
|
|
isResizeFromCenter: boolean,
|
|
|
|
|
pointerX: number,
|
|
|
|
@ -380,16 +383,32 @@ const resizeSingleElement = (
|
|
|
|
|
);
|
|
|
|
|
let scaleX = 1;
|
|
|
|
|
let scaleY = 1;
|
|
|
|
|
if (resizeHandle === "e" || resizeHandle === "ne" || resizeHandle === "se") {
|
|
|
|
|
if (
|
|
|
|
|
transformHandleType === "e" ||
|
|
|
|
|
transformHandleType === "ne" ||
|
|
|
|
|
transformHandleType === "se"
|
|
|
|
|
) {
|
|
|
|
|
scaleX = (rotatedX - x1) / (x2 - x1);
|
|
|
|
|
}
|
|
|
|
|
if (resizeHandle === "s" || resizeHandle === "sw" || resizeHandle === "se") {
|
|
|
|
|
if (
|
|
|
|
|
transformHandleType === "s" ||
|
|
|
|
|
transformHandleType === "sw" ||
|
|
|
|
|
transformHandleType === "se"
|
|
|
|
|
) {
|
|
|
|
|
scaleY = (rotatedY - y1) / (y2 - y1);
|
|
|
|
|
}
|
|
|
|
|
if (resizeHandle === "w" || resizeHandle === "nw" || resizeHandle === "sw") {
|
|
|
|
|
if (
|
|
|
|
|
transformHandleType === "w" ||
|
|
|
|
|
transformHandleType === "nw" ||
|
|
|
|
|
transformHandleType === "sw"
|
|
|
|
|
) {
|
|
|
|
|
scaleX = (x2 - rotatedX) / (x2 - x1);
|
|
|
|
|
}
|
|
|
|
|
if (resizeHandle === "n" || resizeHandle === "nw" || resizeHandle === "ne") {
|
|
|
|
|
if (
|
|
|
|
|
transformHandleType === "n" ||
|
|
|
|
|
transformHandleType === "nw" ||
|
|
|
|
|
transformHandleType === "ne"
|
|
|
|
|
) {
|
|
|
|
|
scaleY = (y2 - rotatedY) / (y2 - y1);
|
|
|
|
|
}
|
|
|
|
|
let nextWidth = element.width * scaleX;
|
|
|
|
@ -419,7 +438,7 @@ const resizeSingleElement = (
|
|
|
|
|
Math.abs(nextHeight),
|
|
|
|
|
);
|
|
|
|
|
const [flipDiffX, flipDiffY] = getFlipAdjustment(
|
|
|
|
|
resizeHandle,
|
|
|
|
|
transformHandleType,
|
|
|
|
|
nextWidth,
|
|
|
|
|
nextHeight,
|
|
|
|
|
nextX1,
|
|
|
|
@ -434,7 +453,7 @@ const resizeSingleElement = (
|
|
|
|
|
element.angle,
|
|
|
|
|
);
|
|
|
|
|
const [nextElementX, nextElementY] = adjustXYWithRotation(
|
|
|
|
|
getSidesForResizeHandle(resizeHandle, isResizeFromCenter),
|
|
|
|
|
getSidesForTransformHandle(transformHandleType, isResizeFromCenter),
|
|
|
|
|
element.x - flipDiffX,
|
|
|
|
|
element.y - flipDiffY,
|
|
|
|
|
element.angle,
|
|
|
|
@ -461,7 +480,7 @@ const resizeSingleElement = (
|
|
|
|
|
|
|
|
|
|
const resizeMultipleElements = (
|
|
|
|
|
elements: readonly NonDeletedExcalidrawElement[],
|
|
|
|
|
resizeHandle: "nw" | "ne" | "sw" | "se",
|
|
|
|
|
transformHandleType: "nw" | "ne" | "sw" | "se",
|
|
|
|
|
pointerX: number,
|
|
|
|
|
pointerY: number,
|
|
|
|
|
) => {
|
|
|
|
@ -472,7 +491,7 @@ const resizeMultipleElements = (
|
|
|
|
|
origCoords: readonly [number, number, number, number],
|
|
|
|
|
finalCoords: readonly [number, number, number, number],
|
|
|
|
|
) => { x: number; y: number };
|
|
|
|
|
switch (resizeHandle) {
|
|
|
|
|
switch (transformHandleType) {
|
|
|
|
|
case "se":
|
|
|
|
|
scale = Math.max(
|
|
|
|
|
(pointerX - x1) / (x2 - x1),
|
|
|
|
@ -651,7 +670,7 @@ const rotateMultipleElements = (
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const getResizeOffsetXY = (
|
|
|
|
|
resizeHandle: ResizeTestType,
|
|
|
|
|
transformHandleType: MaybeTransformHandleType,
|
|
|
|
|
selectedElements: NonDeletedExcalidrawElement[],
|
|
|
|
|
x: number,
|
|
|
|
|
y: number,
|
|
|
|
@ -664,7 +683,7 @@ export const getResizeOffsetXY = (
|
|
|
|
|
const cy = (y1 + y2) / 2;
|
|
|
|
|
const angle = selectedElements.length === 1 ? selectedElements[0].angle : 0;
|
|
|
|
|
[x, y] = rotate(x, y, cx, cy, -angle);
|
|
|
|
|
switch (resizeHandle) {
|
|
|
|
|
switch (transformHandleType) {
|
|
|
|
|
case "n":
|
|
|
|
|
return rotate(x - (x1 + x2) / 2, y - y1, 0, 0, angle);
|
|
|
|
|
case "s":
|
|
|
|
@ -687,14 +706,14 @@ export const getResizeOffsetXY = (
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const getResizeArrowDirection = (
|
|
|
|
|
resizeHandle: ResizeTestType,
|
|
|
|
|
transformHandleType: MaybeTransformHandleType,
|
|
|
|
|
element: NonDeleted<ExcalidrawLinearElement>,
|
|
|
|
|
): "origin" | "end" => {
|
|
|
|
|
const [, [px, py]] = element.points;
|
|
|
|
|
const isResizeEnd =
|
|
|
|
|
(resizeHandle === "nw" && (px < 0 || py < 0)) ||
|
|
|
|
|
(resizeHandle === "ne" && px >= 0) ||
|
|
|
|
|
(resizeHandle === "sw" && px <= 0) ||
|
|
|
|
|
(resizeHandle === "se" && (px > 0 || py > 0));
|
|
|
|
|
(transformHandleType === "nw" && (px < 0 || py < 0)) ||
|
|
|
|
|
(transformHandleType === "ne" && px >= 0) ||
|
|
|
|
|
(transformHandleType === "sw" && px <= 0) ||
|
|
|
|
|
(transformHandleType === "se" && (px > 0 || py > 0));
|
|
|
|
|
return isResizeEnd ? "end" : "origin";
|
|
|
|
|
};
|
|
|
|
|