Refactor: resize two-point lines/arrows (#1568)
This commit is contained in:
parent
cdb483b895
commit
394237728f
@ -25,6 +25,7 @@ import {
|
|||||||
getElementWithResizeHandler,
|
getElementWithResizeHandler,
|
||||||
canResizeMutlipleElements,
|
canResizeMutlipleElements,
|
||||||
getResizeOffsetXY,
|
getResizeOffsetXY,
|
||||||
|
getResizeArrowDirection,
|
||||||
getResizeHandlerFromCoords,
|
getResizeHandlerFromCoords,
|
||||||
isNonDeletedElement,
|
isNonDeletedElement,
|
||||||
} from "../element";
|
} from "../element";
|
||||||
@ -53,11 +54,7 @@ import Portal from "./Portal";
|
|||||||
|
|
||||||
import { renderScene } from "../renderer";
|
import { renderScene } from "../renderer";
|
||||||
import { AppState, GestureEvent, Gesture } from "../types";
|
import { AppState, GestureEvent, Gesture } from "../types";
|
||||||
import {
|
import { ExcalidrawElement, ExcalidrawTextElement } from "../element/types";
|
||||||
ExcalidrawElement,
|
|
||||||
ExcalidrawTextElement,
|
|
||||||
ResizeArrowFnType,
|
|
||||||
} from "../element/types";
|
|
||||||
|
|
||||||
import { distance2d, isPathALoop } from "../math";
|
import { distance2d, isPathALoop } from "../math";
|
||||||
|
|
||||||
@ -1847,6 +1844,7 @@ class App extends React.Component<any, AppState> {
|
|||||||
resizeHandle = nextResizeHandle;
|
resizeHandle = nextResizeHandle;
|
||||||
};
|
};
|
||||||
let resizeOffsetXY: [number, number] = [0, 0];
|
let resizeOffsetXY: [number, number] = [0, 0];
|
||||||
|
let resizeArrowDirection: "origin" | "end" = "origin";
|
||||||
let isResizingElements = false;
|
let isResizingElements = false;
|
||||||
let draggingOccurred = false;
|
let draggingOccurred = false;
|
||||||
let hitElement: ExcalidrawElement | null = null;
|
let hitElement: ExcalidrawElement | null = null;
|
||||||
@ -1900,6 +1898,16 @@ class App extends React.Component<any, AppState> {
|
|||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
);
|
);
|
||||||
|
if (
|
||||||
|
selectedElements.length === 1 &&
|
||||||
|
isLinearElement(selectedElements[0]) &&
|
||||||
|
selectedElements[0].points.length === 2
|
||||||
|
) {
|
||||||
|
resizeArrowDirection = getResizeArrowDirection(
|
||||||
|
resizeHandle,
|
||||||
|
selectedElements[0],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!isResizingElements) {
|
if (!isResizingElements) {
|
||||||
hitElement = getElementAtPosition(
|
hitElement = getElementAtPosition(
|
||||||
@ -2079,11 +2087,6 @@ class App extends React.Component<any, AppState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let resizeArrowFn: ResizeArrowFnType | null = null;
|
|
||||||
const setResizeArrowFn = (fn: ResizeArrowFnType) => {
|
|
||||||
resizeArrowFn = fn;
|
|
||||||
};
|
|
||||||
|
|
||||||
let selectedElementWasDuplicated = false;
|
let selectedElementWasDuplicated = false;
|
||||||
|
|
||||||
const onPointerMove = withBatchedUpdates((event: PointerEvent) => {
|
const onPointerMove = withBatchedUpdates((event: PointerEvent) => {
|
||||||
@ -2142,23 +2145,17 @@ class App extends React.Component<any, AppState> {
|
|||||||
isResizing: resizeHandle && resizeHandle !== "rotation",
|
isResizing: resizeHandle && resizeHandle !== "rotation",
|
||||||
isRotating: resizeHandle === "rotation",
|
isRotating: resizeHandle === "rotation",
|
||||||
});
|
});
|
||||||
const resized = resizeElements(
|
if (
|
||||||
resizeHandle,
|
resizeElements(
|
||||||
setResizeHandle,
|
resizeHandle,
|
||||||
selectedElements,
|
setResizeHandle,
|
||||||
resizeArrowFn,
|
selectedElements,
|
||||||
setResizeArrowFn,
|
resizeArrowDirection,
|
||||||
event,
|
event,
|
||||||
x,
|
x - resizeOffsetXY[0],
|
||||||
y,
|
y - resizeOffsetXY[1],
|
||||||
resizeOffsetXY[0],
|
)
|
||||||
resizeOffsetXY[1],
|
) {
|
||||||
lastX,
|
|
||||||
lastY,
|
|
||||||
);
|
|
||||||
if (resized) {
|
|
||||||
lastX = x;
|
|
||||||
lastY = y;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2328,8 +2325,6 @@ class App extends React.Component<any, AppState> {
|
|||||||
|
|
||||||
this.savePointer(childEvent.clientX, childEvent.clientY, "up");
|
this.savePointer(childEvent.clientX, childEvent.clientY, "up");
|
||||||
|
|
||||||
resizeArrowFn = null;
|
|
||||||
resizeOffsetXY = [0, 0];
|
|
||||||
lastPointerUp = null;
|
lastPointerUp = null;
|
||||||
|
|
||||||
window.removeEventListener(EVENT.POINTER_MOVE, onPointerMove);
|
window.removeEventListener(EVENT.POINTER_MOVE, onPointerMove);
|
||||||
|
@ -36,6 +36,7 @@ export {
|
|||||||
resizeElements,
|
resizeElements,
|
||||||
canResizeMutlipleElements,
|
canResizeMutlipleElements,
|
||||||
getResizeOffsetXY,
|
getResizeOffsetXY,
|
||||||
|
getResizeArrowDirection,
|
||||||
} from "./resizeElements";
|
} from "./resizeElements";
|
||||||
export { isTextElement, isExcalidrawElement } from "./typeChecks";
|
export { isTextElement, isExcalidrawElement } from "./typeChecks";
|
||||||
export { textWysiwyg } from "./textWysiwyg";
|
export { textWysiwyg } from "./textWysiwyg";
|
||||||
|
@ -6,7 +6,6 @@ import {
|
|||||||
ExcalidrawLinearElement,
|
ExcalidrawLinearElement,
|
||||||
NonDeletedExcalidrawElement,
|
NonDeletedExcalidrawElement,
|
||||||
NonDeleted,
|
NonDeleted,
|
||||||
ResizeArrowFnType,
|
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import {
|
import {
|
||||||
getElementAbsoluteCoords,
|
getElementAbsoluteCoords,
|
||||||
@ -32,15 +31,10 @@ export const resizeElements = (
|
|||||||
resizeHandle: ResizeTestType,
|
resizeHandle: ResizeTestType,
|
||||||
setResizeHandle: (nextResizeHandle: ResizeTestType) => void,
|
setResizeHandle: (nextResizeHandle: ResizeTestType) => void,
|
||||||
selectedElements: NonDeletedExcalidrawElement[],
|
selectedElements: NonDeletedExcalidrawElement[],
|
||||||
resizeArrowFn: ResizeArrowFnType | null, // XXX eliminate in #1339
|
resizeArrowDirection: "origin" | "end",
|
||||||
setResizeArrowFn: (fn: ResizeArrowFnType) => void, // XXX eliminate in #1339
|
|
||||||
event: PointerEvent, // XXX we want to make it independent?
|
event: PointerEvent, // XXX we want to make it independent?
|
||||||
pointerX: number,
|
pointerX: number,
|
||||||
pointerY: number,
|
pointerY: number,
|
||||||
offsetX: number,
|
|
||||||
offsetY: number,
|
|
||||||
lastX: number, // XXX eliminate in #1339
|
|
||||||
lastY: number, // XXX eliminate in #1339
|
|
||||||
) => {
|
) => {
|
||||||
if (selectedElements.length === 1) {
|
if (selectedElements.length === 1) {
|
||||||
const [element] = selectedElements;
|
const [element] = selectedElements;
|
||||||
@ -56,14 +50,10 @@ export const resizeElements = (
|
|||||||
) {
|
) {
|
||||||
resizeSingleTwoPointElement(
|
resizeSingleTwoPointElement(
|
||||||
element,
|
element,
|
||||||
resizeHandle,
|
resizeArrowDirection,
|
||||||
resizeArrowFn,
|
|
||||||
setResizeArrowFn,
|
|
||||||
event.shiftKey,
|
event.shiftKey,
|
||||||
pointerX,
|
pointerX,
|
||||||
pointerY,
|
pointerY,
|
||||||
lastX,
|
|
||||||
lastY,
|
|
||||||
);
|
);
|
||||||
} else if (resizeHandle) {
|
} else if (resizeHandle) {
|
||||||
resizeSingleElement(
|
resizeSingleElement(
|
||||||
@ -73,8 +63,6 @@ export const resizeElements = (
|
|||||||
getResizeCenterPointKey(event),
|
getResizeCenterPointKey(event),
|
||||||
pointerX,
|
pointerX,
|
||||||
pointerY,
|
pointerY,
|
||||||
offsetX,
|
|
||||||
offsetY,
|
|
||||||
);
|
);
|
||||||
setResizeHandle(normalizeResizeHandle(element, resizeHandle));
|
setResizeHandle(normalizeResizeHandle(element, resizeHandle));
|
||||||
if (element.width < 0) {
|
if (element.width < 0) {
|
||||||
@ -100,14 +88,7 @@ export const resizeElements = (
|
|||||||
resizeHandle === "sw" ||
|
resizeHandle === "sw" ||
|
||||||
resizeHandle === "se")
|
resizeHandle === "se")
|
||||||
) {
|
) {
|
||||||
resizeMultipleElements(
|
resizeMultipleElements(selectedElements, resizeHandle, pointerX, pointerY);
|
||||||
selectedElements,
|
|
||||||
resizeHandle,
|
|
||||||
pointerX,
|
|
||||||
pointerY,
|
|
||||||
offsetX,
|
|
||||||
offsetY,
|
|
||||||
);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -135,122 +116,61 @@ const rotateSingleElement = (
|
|||||||
|
|
||||||
const resizeSingleTwoPointElement = (
|
const resizeSingleTwoPointElement = (
|
||||||
element: NonDeleted<ExcalidrawLinearElement>,
|
element: NonDeleted<ExcalidrawLinearElement>,
|
||||||
resizeHandle: "nw" | "ne" | "sw" | "se",
|
resizeArrowDirection: "origin" | "end",
|
||||||
resizeArrowFn: ResizeArrowFnType | null,
|
isAngleLocking: boolean,
|
||||||
setResizeArrowFn: (fn: ResizeArrowFnType) => void,
|
|
||||||
sidesWithSameLength: boolean,
|
|
||||||
pointerX: number,
|
pointerX: number,
|
||||||
pointerY: number,
|
pointerY: number,
|
||||||
lastX: number,
|
|
||||||
lastY: number,
|
|
||||||
) => {
|
) => {
|
||||||
const [, [px, py]] = element.points;
|
const pointOrigin = element.points[0]; // can assume always [0, 0]?
|
||||||
const isResizeEnd =
|
const pointEnd = element.points[1];
|
||||||
(resizeHandle === "nw" && (px < 0 || py < 0)) ||
|
if (resizeArrowDirection === "end") {
|
||||||
(resizeHandle === "ne" && px >= 0) ||
|
if (isAngleLocking) {
|
||||||
(resizeHandle === "sw" && px <= 0) ||
|
const { width, height } = getPerfectElementSize(
|
||||||
(resizeHandle === "se" && (px > 0 || py > 0));
|
element.type,
|
||||||
applyResizeArrowFn(
|
pointerX - element.x,
|
||||||
element,
|
pointerY - element.y,
|
||||||
resizeArrowFn,
|
);
|
||||||
setResizeArrowFn,
|
mutateElement(element, {
|
||||||
isResizeEnd,
|
points: [pointOrigin, [width, height]],
|
||||||
sidesWithSameLength,
|
});
|
||||||
pointerX,
|
|
||||||
pointerY,
|
|
||||||
lastX,
|
|
||||||
lastY,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const arrowResizeOrigin: ResizeArrowFnType = (
|
|
||||||
element,
|
|
||||||
pointIndex,
|
|
||||||
deltaX,
|
|
||||||
deltaY,
|
|
||||||
pointerX,
|
|
||||||
pointerY,
|
|
||||||
sidesWithSameLength,
|
|
||||||
) => {
|
|
||||||
const [px, py] = element.points[pointIndex];
|
|
||||||
let x = element.x + deltaX;
|
|
||||||
let y = element.y + deltaY;
|
|
||||||
let pointX = px - deltaX;
|
|
||||||
let pointY = py - deltaY;
|
|
||||||
|
|
||||||
if (sidesWithSameLength) {
|
|
||||||
const { width, height } = getPerfectElementSize(
|
|
||||||
element.type,
|
|
||||||
px + element.x - pointerX,
|
|
||||||
py + element.y - pointerY,
|
|
||||||
);
|
|
||||||
x = px + element.x - width;
|
|
||||||
y = py + element.y - height;
|
|
||||||
pointX = width;
|
|
||||||
pointY = height;
|
|
||||||
}
|
|
||||||
|
|
||||||
mutateElement(element, {
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
points: element.points.map((point, i) =>
|
|
||||||
i === pointIndex ? ([pointX, pointY] as const) : point,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const arrowResizeEnd: ResizeArrowFnType = (
|
|
||||||
element,
|
|
||||||
pointIndex,
|
|
||||||
deltaX,
|
|
||||||
deltaY,
|
|
||||||
pointerX,
|
|
||||||
pointerY,
|
|
||||||
sidesWithSameLength,
|
|
||||||
) => {
|
|
||||||
const [px, py] = element.points[pointIndex];
|
|
||||||
if (sidesWithSameLength) {
|
|
||||||
const { width, height } = getPerfectElementSize(
|
|
||||||
element.type,
|
|
||||||
pointerX - element.x,
|
|
||||||
pointerY - element.y,
|
|
||||||
);
|
|
||||||
mutateElement(element, {
|
|
||||||
points: element.points.map((point, i) =>
|
|
||||||
i === pointIndex ? ([width, height] as const) : point,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
mutateElement(element, {
|
|
||||||
points: element.points.map((point, i) =>
|
|
||||||
i === pointIndex ? ([px + deltaX, py + deltaY] as const) : point,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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 {
|
} else {
|
||||||
resizeArrowFn = arrowResizeOrigin;
|
mutateElement(element, {
|
||||||
|
points: [
|
||||||
|
pointOrigin,
|
||||||
|
[
|
||||||
|
pointerX - pointOrigin[0] - element.x,
|
||||||
|
pointerY - pointOrigin[1] - element.y,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// resizeArrowDirection === "origin"
|
||||||
|
if (isAngleLocking) {
|
||||||
|
const { width, height } = getPerfectElementSize(
|
||||||
|
element.type,
|
||||||
|
element.x + pointEnd[0] - pointOrigin[0] - pointerX,
|
||||||
|
element.y + pointEnd[1] - pointOrigin[1] - pointerY,
|
||||||
|
);
|
||||||
|
mutateElement(element, {
|
||||||
|
x: element.x + pointEnd[0] - pointOrigin[0] - width,
|
||||||
|
y: element.y + pointEnd[1] - pointOrigin[1] - height,
|
||||||
|
points: [pointOrigin, [width, height]],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
mutateElement(element, {
|
||||||
|
x: pointerX,
|
||||||
|
y: pointerY,
|
||||||
|
points: [
|
||||||
|
pointOrigin,
|
||||||
|
[
|
||||||
|
pointEnd[0] - (pointerX - pointOrigin[0] - element.x),
|
||||||
|
pointEnd[1] - (pointerY - pointOrigin[1] - element.y),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resizeArrowFn(element, 1, deltaX, deltaY, x, y, sidesWithSameLength);
|
|
||||||
setResizeArrowFn(resizeArrowFn);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const resizeSingleElement = (
|
const resizeSingleElement = (
|
||||||
@ -260,16 +180,14 @@ const resizeSingleElement = (
|
|||||||
isResizeFromCenter: boolean,
|
isResizeFromCenter: boolean,
|
||||||
pointerX: number,
|
pointerX: number,
|
||||||
pointerY: number,
|
pointerY: number,
|
||||||
offsetX: number,
|
|
||||||
offsetY: number,
|
|
||||||
) => {
|
) => {
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
||||||
const cx = (x1 + x2) / 2;
|
const cx = (x1 + x2) / 2;
|
||||||
const cy = (y1 + y2) / 2;
|
const cy = (y1 + y2) / 2;
|
||||||
// rotation pointer with reverse angle
|
// rotation pointer with reverse angle
|
||||||
const [rotatedX, rotatedY] = rotate(
|
const [rotatedX, rotatedY] = rotate(
|
||||||
pointerX - offsetX,
|
pointerX,
|
||||||
pointerY - offsetY,
|
pointerY,
|
||||||
cx,
|
cx,
|
||||||
cy,
|
cy,
|
||||||
-element.angle,
|
-element.angle,
|
||||||
@ -366,15 +284,13 @@ const resizeMultipleElements = (
|
|||||||
resizeHandle: "nw" | "ne" | "sw" | "se",
|
resizeHandle: "nw" | "ne" | "sw" | "se",
|
||||||
pointerX: number,
|
pointerX: number,
|
||||||
pointerY: number,
|
pointerY: number,
|
||||||
offsetX: number,
|
|
||||||
offsetY: number,
|
|
||||||
) => {
|
) => {
|
||||||
const [x1, y1, x2, y2] = getCommonBounds(elements);
|
const [x1, y1, x2, y2] = getCommonBounds(elements);
|
||||||
switch (resizeHandle) {
|
switch (resizeHandle) {
|
||||||
case "se": {
|
case "se": {
|
||||||
const scale = Math.max(
|
const scale = Math.max(
|
||||||
(pointerX - offsetX - x1) / (x2 - x1),
|
(pointerX - x1) / (x2 - x1),
|
||||||
(pointerY - offsetY - y1) / (y2 - y1),
|
(pointerY - y1) / (y2 - y1),
|
||||||
);
|
);
|
||||||
if (scale > 0) {
|
if (scale > 0) {
|
||||||
elements.forEach((element) => {
|
elements.forEach((element) => {
|
||||||
@ -389,8 +305,8 @@ const resizeMultipleElements = (
|
|||||||
}
|
}
|
||||||
case "nw": {
|
case "nw": {
|
||||||
const scale = Math.max(
|
const scale = Math.max(
|
||||||
(x2 - (pointerX - offsetX)) / (x2 - x1),
|
(x2 - pointerX) / (x2 - x1),
|
||||||
(y2 - (pointerY - offsetY)) / (y2 - y1),
|
(y2 - pointerY) / (y2 - y1),
|
||||||
);
|
);
|
||||||
if (scale > 0) {
|
if (scale > 0) {
|
||||||
elements.forEach((element) => {
|
elements.forEach((element) => {
|
||||||
@ -405,8 +321,8 @@ const resizeMultipleElements = (
|
|||||||
}
|
}
|
||||||
case "ne": {
|
case "ne": {
|
||||||
const scale = Math.max(
|
const scale = Math.max(
|
||||||
(pointerX - offsetX - x1) / (x2 - x1),
|
(pointerX - x1) / (x2 - x1),
|
||||||
(y2 - (pointerY - offsetY)) / (y2 - y1),
|
(y2 - pointerY) / (y2 - y1),
|
||||||
);
|
);
|
||||||
if (scale > 0) {
|
if (scale > 0) {
|
||||||
elements.forEach((element) => {
|
elements.forEach((element) => {
|
||||||
@ -421,8 +337,8 @@ const resizeMultipleElements = (
|
|||||||
}
|
}
|
||||||
case "sw": {
|
case "sw": {
|
||||||
const scale = Math.max(
|
const scale = Math.max(
|
||||||
(x2 - (pointerX - offsetX)) / (x2 - x1),
|
(x2 - pointerX) / (x2 - x1),
|
||||||
(pointerY - offsetY - y1) / (y2 - y1),
|
(pointerY - y1) / (y2 - y1),
|
||||||
);
|
);
|
||||||
if (scale > 0) {
|
if (scale > 0) {
|
||||||
elements.forEach((element) => {
|
elements.forEach((element) => {
|
||||||
@ -481,3 +397,16 @@ export const getResizeOffsetXY = (
|
|||||||
return [0, 0];
|
return [0, 0];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getResizeArrowDirection = (
|
||||||
|
resizeHandle: ResizeTestType,
|
||||||
|
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));
|
||||||
|
return isResizeEnd ? "end" : "origin";
|
||||||
|
};
|
||||||
|
@ -58,13 +58,3 @@ export type ExcalidrawLinearElement = _ExcalidrawElementBase &
|
|||||||
export type PointerType = "mouse" | "pen" | "touch";
|
export type PointerType = "mouse" | "pen" | "touch";
|
||||||
|
|
||||||
export type TextAlign = "left" | "center" | "right";
|
export type TextAlign = "left" | "center" | "right";
|
||||||
|
|
||||||
export type ResizeArrowFnType = (
|
|
||||||
element: NonDeleted<ExcalidrawLinearElement>,
|
|
||||||
pointIndex: number,
|
|
||||||
deltaX: number,
|
|
||||||
deltaY: number,
|
|
||||||
pointerX: number,
|
|
||||||
pointerY: number,
|
|
||||||
perfect: boolean,
|
|
||||||
) => void;
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user