|
@@ -2,7 +2,7 @@ import { AppState } from "../types";
|
|
|
import { SHIFT_LOCKING_ANGLE } from "../constants";
|
|
|
import { getSelectedElements, globalSceneState } from "../scene";
|
|
|
import { rescalePoints } from "../points";
|
|
|
-import { rotate, adjustXYWithRotation } from "../math";
|
|
|
+import { rotate, resizeXYWidthHightWithRotation } from "../math";
|
|
|
import {
|
|
|
ExcalidrawLinearElement,
|
|
|
NonDeletedExcalidrawElement,
|
|
@@ -27,7 +27,7 @@ export type ResizeArrowFnType = (
|
|
|
deltaY: number,
|
|
|
pointerX: number,
|
|
|
pointerY: number,
|
|
|
- perfect: boolean,
|
|
|
+ sidesWithSameLength: boolean,
|
|
|
) => void;
|
|
|
|
|
|
const arrowResizeOrigin: ResizeArrowFnType = (
|
|
@@ -37,7 +37,7 @@ const arrowResizeOrigin: ResizeArrowFnType = (
|
|
|
deltaY,
|
|
|
pointerX,
|
|
|
pointerY,
|
|
|
- perfect,
|
|
|
+ sidesWithSameLength,
|
|
|
) => {
|
|
|
const [px, py] = element.points[pointIndex];
|
|
|
let x = element.x + deltaX;
|
|
@@ -45,7 +45,7 @@ const arrowResizeOrigin: ResizeArrowFnType = (
|
|
|
let pointX = px - deltaX;
|
|
|
let pointY = py - deltaY;
|
|
|
|
|
|
- if (perfect) {
|
|
|
+ if (sidesWithSameLength) {
|
|
|
const { width, height } = getPerfectElementSize(
|
|
|
element.type,
|
|
|
px + element.x - pointerX,
|
|
@@ -73,10 +73,10 @@ const arrowResizeEnd: ResizeArrowFnType = (
|
|
|
deltaY,
|
|
|
pointerX,
|
|
|
pointerY,
|
|
|
- perfect,
|
|
|
+ sidesWithSameLength,
|
|
|
) => {
|
|
|
const [px, py] = element.points[pointIndex];
|
|
|
- if (perfect) {
|
|
|
+ if (sidesWithSameLength) {
|
|
|
const { width, height } = getPerfectElementSize(
|
|
|
element.type,
|
|
|
pointerX - element.x,
|
|
@@ -96,7 +96,31 @@ const arrowResizeEnd: ResizeArrowFnType = (
|
|
|
}
|
|
|
};
|
|
|
|
|
|
-export function resizeElements(
|
|
|
+const applyResizeArrowFn = (
|
|
|
+ element: NonDeleted<ExcalidrawLinearElement>,
|
|
|
+ resizeArrowFn: ResizeArrowFnType | null,
|
|
|
+ setResizeArrowFn: (fn: ResizeArrowFnType) => void,
|
|
|
+ isResizeEnd: boolean,
|
|
|
+ sidesWithSameLength: boolean,
|
|
|
+ x: number,
|
|
|
+ y: number,
|
|
|
+ lastX: number,
|
|
|
+ lastY: number,
|
|
|
+) => {
|
|
|
+ const angle = element.angle;
|
|
|
+ const [deltaX, deltaY] = rotate(x - lastX, y - lastY, 0, 0, -angle);
|
|
|
+ if (!resizeArrowFn) {
|
|
|
+ if (isResizeEnd) {
|
|
|
+ resizeArrowFn = arrowResizeEnd;
|
|
|
+ } else {
|
|
|
+ resizeArrowFn = arrowResizeOrigin;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ resizeArrowFn(element, 1, deltaX, deltaY, x, y, sidesWithSameLength);
|
|
|
+ setResizeArrowFn(resizeArrowFn);
|
|
|
+};
|
|
|
+
|
|
|
+export const resizeElements = (
|
|
|
resizeHandle: ResizeTestType,
|
|
|
setResizeHandle: (nextResizeHandle: ResizeTestType) => void,
|
|
|
appState: AppState,
|
|
@@ -104,11 +128,11 @@ export function resizeElements(
|
|
|
resizeArrowFn: ResizeArrowFnType | null,
|
|
|
setResizeArrowFn: (fn: ResizeArrowFnType) => void,
|
|
|
event: PointerEvent,
|
|
|
- x: number,
|
|
|
- y: number,
|
|
|
+ xPointer: number,
|
|
|
+ yPointer: number,
|
|
|
lastX: number,
|
|
|
lastY: number,
|
|
|
-) {
|
|
|
+) => {
|
|
|
setAppState({
|
|
|
isResizing: resizeHandle !== "rotation",
|
|
|
isRotating: resizeHandle === "rotation",
|
|
@@ -117,224 +141,79 @@ export function resizeElements(
|
|
|
globalSceneState.getElements(),
|
|
|
appState,
|
|
|
);
|
|
|
+ const handleOffset = 4 / appState.zoom;
|
|
|
+ const dashedLinePadding = 4 / appState.zoom;
|
|
|
+ const offsetPointer = handleOffset + dashedLinePadding;
|
|
|
+ const minSize = handleOffset * 4;
|
|
|
if (selectedElements.length === 1) {
|
|
|
- const element = selectedElements[0];
|
|
|
- const angle = element.angle;
|
|
|
-
|
|
|
- const [deltaX, deltaY] = rotate(x - lastX, y - lastY, 0, 0, -angle);
|
|
|
- switch (resizeHandle) {
|
|
|
- case "nw":
|
|
|
- if (isLinearElement(element) && element.points.length === 2) {
|
|
|
- const [, p1] = element.points;
|
|
|
-
|
|
|
- if (!resizeArrowFn) {
|
|
|
- if (p1[0] < 0 || p1[1] < 0) {
|
|
|
- resizeArrowFn = arrowResizeEnd;
|
|
|
- } else {
|
|
|
- resizeArrowFn = arrowResizeOrigin;
|
|
|
- }
|
|
|
- }
|
|
|
- resizeArrowFn(element, 1, deltaX, deltaY, x, y, event.shiftKey);
|
|
|
- setResizeArrowFn(resizeArrowFn);
|
|
|
- } else {
|
|
|
- const width = element.width - deltaX;
|
|
|
- const height = event.shiftKey ? width : element.height - deltaY;
|
|
|
- const dY = element.height - height;
|
|
|
- mutateElement(element, {
|
|
|
- width,
|
|
|
- height,
|
|
|
- ...adjustXYWithRotation("nw", element, deltaX, dY, angle),
|
|
|
- ...(isLinearElement(element) && width !== 0 && height !== 0
|
|
|
- ? {
|
|
|
- points: rescalePoints(
|
|
|
- 0,
|
|
|
- width,
|
|
|
- rescalePoints(1, height, element.points),
|
|
|
- ),
|
|
|
- }
|
|
|
- : {}),
|
|
|
- });
|
|
|
- }
|
|
|
- break;
|
|
|
- case "ne":
|
|
|
- if (isLinearElement(element) && element.points.length === 2) {
|
|
|
- const [, p1] = element.points;
|
|
|
- if (!resizeArrowFn) {
|
|
|
- if (p1[0] >= 0) {
|
|
|
- resizeArrowFn = arrowResizeEnd;
|
|
|
- } else {
|
|
|
- resizeArrowFn = arrowResizeOrigin;
|
|
|
- }
|
|
|
- }
|
|
|
- resizeArrowFn(element, 1, deltaX, deltaY, x, y, event.shiftKey);
|
|
|
- setResizeArrowFn(resizeArrowFn);
|
|
|
- } else {
|
|
|
- const width = element.width + deltaX;
|
|
|
- const height = event.shiftKey ? width : element.height - deltaY;
|
|
|
- const dY = element.height - height;
|
|
|
- mutateElement(element, {
|
|
|
- width,
|
|
|
- height,
|
|
|
- ...adjustXYWithRotation("ne", element, deltaX, dY, angle),
|
|
|
- ...(isLinearElement(element) && width !== 0 && height !== 0
|
|
|
- ? {
|
|
|
- points: rescalePoints(
|
|
|
- 0,
|
|
|
- width,
|
|
|
- rescalePoints(1, height, element.points),
|
|
|
- ),
|
|
|
- }
|
|
|
- : {}),
|
|
|
- });
|
|
|
- }
|
|
|
- break;
|
|
|
- case "sw":
|
|
|
- if (isLinearElement(element) && element.points.length === 2) {
|
|
|
- const [, p1] = element.points;
|
|
|
- if (!resizeArrowFn) {
|
|
|
- if (p1[0] <= 0) {
|
|
|
- resizeArrowFn = arrowResizeEnd;
|
|
|
- } else {
|
|
|
- resizeArrowFn = arrowResizeOrigin;
|
|
|
- }
|
|
|
- }
|
|
|
- resizeArrowFn(element, 1, deltaX, deltaY, x, y, event.shiftKey);
|
|
|
- setResizeArrowFn(resizeArrowFn);
|
|
|
- } else {
|
|
|
- const width = element.width - deltaX;
|
|
|
- const height = event.shiftKey ? width : element.height + deltaY;
|
|
|
- const dY = height - element.height;
|
|
|
- mutateElement(element, {
|
|
|
- width,
|
|
|
- height,
|
|
|
- ...adjustXYWithRotation("sw", element, deltaX, dY, angle),
|
|
|
- ...(isLinearElement(element) && width !== 0 && height !== 0
|
|
|
- ? {
|
|
|
- points: rescalePoints(
|
|
|
- 0,
|
|
|
- width,
|
|
|
- rescalePoints(1, height, element.points),
|
|
|
- ),
|
|
|
- }
|
|
|
- : {}),
|
|
|
- });
|
|
|
- }
|
|
|
- break;
|
|
|
- case "se":
|
|
|
- if (isLinearElement(element) && element.points.length === 2) {
|
|
|
- const [, p1] = element.points;
|
|
|
- if (!resizeArrowFn) {
|
|
|
- if (p1[0] > 0 || p1[1] > 0) {
|
|
|
- resizeArrowFn = arrowResizeEnd;
|
|
|
- } else {
|
|
|
- resizeArrowFn = arrowResizeOrigin;
|
|
|
- }
|
|
|
- }
|
|
|
- resizeArrowFn(element, 1, deltaX, deltaY, x, y, event.shiftKey);
|
|
|
- setResizeArrowFn(resizeArrowFn);
|
|
|
- } else {
|
|
|
- const width = element.width + deltaX;
|
|
|
- const height = event.shiftKey ? width : element.height + deltaY;
|
|
|
- const dY = height - element.height;
|
|
|
- mutateElement(element, {
|
|
|
- width,
|
|
|
- height,
|
|
|
- ...adjustXYWithRotation("se", element, deltaX, dY, angle),
|
|
|
- ...(isLinearElement(element) && width !== 0 && height !== 0
|
|
|
- ? {
|
|
|
- points: rescalePoints(
|
|
|
- 0,
|
|
|
- width,
|
|
|
- rescalePoints(1, height, element.points),
|
|
|
- ),
|
|
|
- }
|
|
|
- : {}),
|
|
|
- });
|
|
|
- }
|
|
|
- break;
|
|
|
- case "n": {
|
|
|
- const height = element.height - deltaY;
|
|
|
-
|
|
|
- if (isLinearElement(element) && height !== 0) {
|
|
|
- mutateElement(element, {
|
|
|
- height,
|
|
|
- ...adjustXYWithRotation("n", element, 0, deltaY, angle),
|
|
|
- points: rescalePoints(1, height, element.points),
|
|
|
- });
|
|
|
- } else {
|
|
|
- mutateElement(element, {
|
|
|
- height,
|
|
|
- ...adjustXYWithRotation("n", element, 0, deltaY, angle),
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- break;
|
|
|
- }
|
|
|
- case "w": {
|
|
|
- const width = element.width - deltaX;
|
|
|
-
|
|
|
- if (isLinearElement(element) && width !== 0) {
|
|
|
- mutateElement(element, {
|
|
|
- width,
|
|
|
- ...adjustXYWithRotation("w", element, deltaX, 0, angle),
|
|
|
- points: rescalePoints(0, width, element.points),
|
|
|
- });
|
|
|
- } else {
|
|
|
- mutateElement(element, {
|
|
|
- width,
|
|
|
- ...adjustXYWithRotation("w", element, deltaX, 0, angle),
|
|
|
- });
|
|
|
- }
|
|
|
- break;
|
|
|
- }
|
|
|
- case "s": {
|
|
|
- const height = element.height + deltaY;
|
|
|
-
|
|
|
- if (isLinearElement(element) && height !== 0) {
|
|
|
- mutateElement(element, {
|
|
|
- height,
|
|
|
- ...adjustXYWithRotation("s", element, 0, deltaY, angle),
|
|
|
- points: rescalePoints(1, height, element.points),
|
|
|
- });
|
|
|
- } else {
|
|
|
- mutateElement(element, {
|
|
|
- height,
|
|
|
- ...adjustXYWithRotation("s", element, 0, deltaY, angle),
|
|
|
- });
|
|
|
- }
|
|
|
- break;
|
|
|
+ const [element] = selectedElements;
|
|
|
+ if (resizeHandle === "rotation") {
|
|
|
+ const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
|
|
+ const cx = (x1 + x2) / 2;
|
|
|
+ const cy = (y1 + y2) / 2;
|
|
|
+ let angle = (5 * Math.PI) / 2 + Math.atan2(yPointer - cy, xPointer - cx);
|
|
|
+ if (event.shiftKey) {
|
|
|
+ angle += SHIFT_LOCKING_ANGLE / 2;
|
|
|
+ angle -= angle % SHIFT_LOCKING_ANGLE;
|
|
|
}
|
|
|
- case "e": {
|
|
|
- const width = element.width + deltaX;
|
|
|
-
|
|
|
- if (isLinearElement(element) && width !== 0) {
|
|
|
- mutateElement(element, {
|
|
|
- width,
|
|
|
- ...adjustXYWithRotation("e", element, deltaX, 0, angle),
|
|
|
- points: rescalePoints(0, width, element.points),
|
|
|
- });
|
|
|
- } else {
|
|
|
- mutateElement(element, {
|
|
|
- width,
|
|
|
- ...adjustXYWithRotation("e", element, deltaX, 0, angle),
|
|
|
- });
|
|
|
- }
|
|
|
- break;
|
|
|
+ if (angle >= 2 * Math.PI) {
|
|
|
+ angle -= 2 * Math.PI;
|
|
|
}
|
|
|
- case "rotation": {
|
|
|
- const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
|
|
- const cx = (x1 + x2) / 2;
|
|
|
- const cy = (y1 + y2) / 2;
|
|
|
- let angle = (5 * Math.PI) / 2 + Math.atan2(y - cy, x - cx);
|
|
|
- if (event.shiftKey) {
|
|
|
- angle += SHIFT_LOCKING_ANGLE / 2;
|
|
|
- angle -= angle % SHIFT_LOCKING_ANGLE;
|
|
|
- }
|
|
|
- if (angle >= 2 * Math.PI) {
|
|
|
- angle -= 2 * Math.PI;
|
|
|
- }
|
|
|
- mutateElement(element, { angle });
|
|
|
- break;
|
|
|
+ mutateElement(element, { angle });
|
|
|
+ } else if (
|
|
|
+ isLinearElement(element) &&
|
|
|
+ element.points.length === 2 &&
|
|
|
+ (resizeHandle === "nw" ||
|
|
|
+ resizeHandle === "ne" ||
|
|
|
+ resizeHandle === "sw" ||
|
|
|
+ resizeHandle === "se")
|
|
|
+ ) {
|
|
|
+ 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));
|
|
|
+ applyResizeArrowFn(
|
|
|
+ element,
|
|
|
+ resizeArrowFn,
|
|
|
+ setResizeArrowFn,
|
|
|
+ isResizeEnd,
|
|
|
+ event.shiftKey,
|
|
|
+ xPointer,
|
|
|
+ yPointer,
|
|
|
+ lastX,
|
|
|
+ lastY,
|
|
|
+ );
|
|
|
+ } else if (resizeHandle) {
|
|
|
+ const [x1, y1] = getElementAbsoluteCoords(element);
|
|
|
+ const resized = resizeXYWidthHightWithRotation(
|
|
|
+ resizeHandle,
|
|
|
+ x1,
|
|
|
+ y1,
|
|
|
+ element.width,
|
|
|
+ element.height,
|
|
|
+ x1 - element.x,
|
|
|
+ y1 - element.y,
|
|
|
+ element.angle,
|
|
|
+ xPointer,
|
|
|
+ yPointer,
|
|
|
+ offsetPointer,
|
|
|
+ event.shiftKey,
|
|
|
+ );
|
|
|
+ if (resized.width !== 0 && resized.height !== 0) {
|
|
|
+ mutateElement(element, {
|
|
|
+ ...resized,
|
|
|
+ ...(isLinearElement(element)
|
|
|
+ ? {
|
|
|
+ points: rescalePoints(
|
|
|
+ 0,
|
|
|
+ resized.width,
|
|
|
+ rescalePoints(1, resized.height, element.points),
|
|
|
+ ),
|
|
|
+ }
|
|
|
+ : {}),
|
|
|
+ });
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -359,15 +238,12 @@ export function resizeElements(
|
|
|
return true;
|
|
|
} else if (selectedElements.length > 1) {
|
|
|
const [x1, y1, x2, y2] = getCommonBounds(selectedElements);
|
|
|
- const handleOffset = 4 / appState.zoom;
|
|
|
- const dashedLinePadding = 4 / appState.zoom;
|
|
|
- const minSize = handleOffset * 4;
|
|
|
const minScale = Math.max(minSize / (x2 - x1), minSize / (y2 - y1));
|
|
|
switch (resizeHandle) {
|
|
|
case "se": {
|
|
|
const scale = Math.max(
|
|
|
- (x - handleOffset - dashedLinePadding - x1) / (x2 - x1),
|
|
|
- (y - handleOffset - dashedLinePadding - y1) / (y2 - y1),
|
|
|
+ (xPointer - offsetPointer - x1) / (x2 - x1),
|
|
|
+ (yPointer - offsetPointer - y1) / (y2 - y1),
|
|
|
);
|
|
|
if (scale > minScale) {
|
|
|
selectedElements.forEach((element) => {
|
|
@@ -382,8 +258,8 @@ export function resizeElements(
|
|
|
}
|
|
|
case "nw": {
|
|
|
const scale = Math.max(
|
|
|
- (x2 - handleOffset - dashedLinePadding - x) / (x2 - x1),
|
|
|
- (y2 - handleOffset - dashedLinePadding - y) / (y2 - y1),
|
|
|
+ (x2 - offsetPointer - xPointer) / (x2 - x1),
|
|
|
+ (y2 - offsetPointer - yPointer) / (y2 - y1),
|
|
|
);
|
|
|
if (scale > minScale) {
|
|
|
selectedElements.forEach((element) => {
|
|
@@ -398,8 +274,8 @@ export function resizeElements(
|
|
|
}
|
|
|
case "ne": {
|
|
|
const scale = Math.max(
|
|
|
- (x - handleOffset - dashedLinePadding - x1) / (x2 - x1),
|
|
|
- (y2 - handleOffset - dashedLinePadding - y) / (y2 - y1),
|
|
|
+ (xPointer - offsetPointer - x1) / (x2 - x1),
|
|
|
+ (y2 - offsetPointer - yPointer) / (y2 - y1),
|
|
|
);
|
|
|
if (scale > minScale) {
|
|
|
selectedElements.forEach((element) => {
|
|
@@ -414,8 +290,8 @@ export function resizeElements(
|
|
|
}
|
|
|
case "sw": {
|
|
|
const scale = Math.max(
|
|
|
- (x2 - handleOffset - dashedLinePadding - x) / (x2 - x1),
|
|
|
- (y - handleOffset - dashedLinePadding - y1) / (y2 - y1),
|
|
|
+ (x2 - offsetPointer - xPointer) / (x2 - x1),
|
|
|
+ (yPointer - offsetPointer - y1) / (y2 - y1),
|
|
|
);
|
|
|
if (scale > minScale) {
|
|
|
selectedElements.forEach((element) => {
|
|
@@ -431,12 +307,12 @@ export function resizeElements(
|
|
|
}
|
|
|
}
|
|
|
return false;
|
|
|
-}
|
|
|
+};
|
|
|
|
|
|
-export function canResizeMutlipleElements(
|
|
|
+export const canResizeMutlipleElements = (
|
|
|
elements: readonly NonDeletedExcalidrawElement[],
|
|
|
-) {
|
|
|
+) => {
|
|
|
return elements.every((element) =>
|
|
|
["rectangle", "diamond", "ellipse"].includes(element.type),
|
|
|
);
|
|
|
-}
|
|
|
+};
|