Refactor: resize two-point lines/arrows (#1568)

This commit is contained in:
Daishi Kato 2020-05-11 00:41:36 +09:00 committed by GitHub
parent cdb483b895
commit 394237728f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 100 additions and 185 deletions

View File

@ -25,6 +25,7 @@ import {
getElementWithResizeHandler,
canResizeMutlipleElements,
getResizeOffsetXY,
getResizeArrowDirection,
getResizeHandlerFromCoords,
isNonDeletedElement,
} from "../element";
@ -53,11 +54,7 @@ import Portal from "./Portal";
import { renderScene } from "../renderer";
import { AppState, GestureEvent, Gesture } from "../types";
import {
ExcalidrawElement,
ExcalidrawTextElement,
ResizeArrowFnType,
} from "../element/types";
import { ExcalidrawElement, ExcalidrawTextElement } from "../element/types";
import { distance2d, isPathALoop } from "../math";
@ -1847,6 +1844,7 @@ class App extends React.Component<any, AppState> {
resizeHandle = nextResizeHandle;
};
let resizeOffsetXY: [number, number] = [0, 0];
let resizeArrowDirection: "origin" | "end" = "origin";
let isResizingElements = false;
let draggingOccurred = false;
let hitElement: ExcalidrawElement | null = null;
@ -1900,6 +1898,16 @@ class App extends React.Component<any, AppState> {
x,
y,
);
if (
selectedElements.length === 1 &&
isLinearElement(selectedElements[0]) &&
selectedElements[0].points.length === 2
) {
resizeArrowDirection = getResizeArrowDirection(
resizeHandle,
selectedElements[0],
);
}
}
if (!isResizingElements) {
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;
const onPointerMove = withBatchedUpdates((event: PointerEvent) => {
@ -2142,23 +2145,17 @@ class App extends React.Component<any, AppState> {
isResizing: resizeHandle && resizeHandle !== "rotation",
isRotating: resizeHandle === "rotation",
});
const resized = resizeElements(
resizeHandle,
setResizeHandle,
selectedElements,
resizeArrowFn,
setResizeArrowFn,
event,
x,
y,
resizeOffsetXY[0],
resizeOffsetXY[1],
lastX,
lastY,
);
if (resized) {
lastX = x;
lastY = y;
if (
resizeElements(
resizeHandle,
setResizeHandle,
selectedElements,
resizeArrowDirection,
event,
x - resizeOffsetXY[0],
y - resizeOffsetXY[1],
)
) {
return;
}
}
@ -2328,8 +2325,6 @@ class App extends React.Component<any, AppState> {
this.savePointer(childEvent.clientX, childEvent.clientY, "up");
resizeArrowFn = null;
resizeOffsetXY = [0, 0];
lastPointerUp = null;
window.removeEventListener(EVENT.POINTER_MOVE, onPointerMove);

View File

@ -36,6 +36,7 @@ export {
resizeElements,
canResizeMutlipleElements,
getResizeOffsetXY,
getResizeArrowDirection,
} from "./resizeElements";
export { isTextElement, isExcalidrawElement } from "./typeChecks";
export { textWysiwyg } from "./textWysiwyg";

View File

@ -6,7 +6,6 @@ import {
ExcalidrawLinearElement,
NonDeletedExcalidrawElement,
NonDeleted,
ResizeArrowFnType,
} from "./types";
import {
getElementAbsoluteCoords,
@ -32,15 +31,10 @@ export const resizeElements = (
resizeHandle: ResizeTestType,
setResizeHandle: (nextResizeHandle: ResizeTestType) => void,
selectedElements: NonDeletedExcalidrawElement[],
resizeArrowFn: ResizeArrowFnType | null, // XXX eliminate in #1339
setResizeArrowFn: (fn: ResizeArrowFnType) => void, // XXX eliminate in #1339
resizeArrowDirection: "origin" | "end",
event: PointerEvent, // XXX we want to make it independent?
pointerX: number,
pointerY: number,
offsetX: number,
offsetY: number,
lastX: number, // XXX eliminate in #1339
lastY: number, // XXX eliminate in #1339
) => {
if (selectedElements.length === 1) {
const [element] = selectedElements;
@ -56,14 +50,10 @@ export const resizeElements = (
) {
resizeSingleTwoPointElement(
element,
resizeHandle,
resizeArrowFn,
setResizeArrowFn,
resizeArrowDirection,
event.shiftKey,
pointerX,
pointerY,
lastX,
lastY,
);
} else if (resizeHandle) {
resizeSingleElement(
@ -73,8 +63,6 @@ export const resizeElements = (
getResizeCenterPointKey(event),
pointerX,
pointerY,
offsetX,
offsetY,
);
setResizeHandle(normalizeResizeHandle(element, resizeHandle));
if (element.width < 0) {
@ -100,14 +88,7 @@ export const resizeElements = (
resizeHandle === "sw" ||
resizeHandle === "se")
) {
resizeMultipleElements(
selectedElements,
resizeHandle,
pointerX,
pointerY,
offsetX,
offsetY,
);
resizeMultipleElements(selectedElements, resizeHandle, pointerX, pointerY);
return true;
}
return false;
@ -135,122 +116,61 @@ const rotateSingleElement = (
const resizeSingleTwoPointElement = (
element: NonDeleted<ExcalidrawLinearElement>,
resizeHandle: "nw" | "ne" | "sw" | "se",
resizeArrowFn: ResizeArrowFnType | null,
setResizeArrowFn: (fn: ResizeArrowFnType) => void,
sidesWithSameLength: boolean,
resizeArrowDirection: "origin" | "end",
isAngleLocking: boolean,
pointerX: number,
pointerY: number,
lastX: number,
lastY: number,
) => {
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,
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;
const pointOrigin = element.points[0]; // can assume always [0, 0]?
const pointEnd = element.points[1];
if (resizeArrowDirection === "end") {
if (isAngleLocking) {
const { width, height } = getPerfectElementSize(
element.type,
pointerX - element.x,
pointerY - element.y,
);
mutateElement(element, {
points: [pointOrigin, [width, height]],
});
} 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 = (
@ -260,16 +180,14 @@ const resizeSingleElement = (
isResizeFromCenter: boolean,
pointerX: number,
pointerY: number,
offsetX: number,
offsetY: number,
) => {
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
const cx = (x1 + x2) / 2;
const cy = (y1 + y2) / 2;
// rotation pointer with reverse angle
const [rotatedX, rotatedY] = rotate(
pointerX - offsetX,
pointerY - offsetY,
pointerX,
pointerY,
cx,
cy,
-element.angle,
@ -366,15 +284,13 @@ const resizeMultipleElements = (
resizeHandle: "nw" | "ne" | "sw" | "se",
pointerX: number,
pointerY: number,
offsetX: number,
offsetY: number,
) => {
const [x1, y1, x2, y2] = getCommonBounds(elements);
switch (resizeHandle) {
case "se": {
const scale = Math.max(
(pointerX - offsetX - x1) / (x2 - x1),
(pointerY - offsetY - y1) / (y2 - y1),
(pointerX - x1) / (x2 - x1),
(pointerY - y1) / (y2 - y1),
);
if (scale > 0) {
elements.forEach((element) => {
@ -389,8 +305,8 @@ const resizeMultipleElements = (
}
case "nw": {
const scale = Math.max(
(x2 - (pointerX - offsetX)) / (x2 - x1),
(y2 - (pointerY - offsetY)) / (y2 - y1),
(x2 - pointerX) / (x2 - x1),
(y2 - pointerY) / (y2 - y1),
);
if (scale > 0) {
elements.forEach((element) => {
@ -405,8 +321,8 @@ const resizeMultipleElements = (
}
case "ne": {
const scale = Math.max(
(pointerX - offsetX - x1) / (x2 - x1),
(y2 - (pointerY - offsetY)) / (y2 - y1),
(pointerX - x1) / (x2 - x1),
(y2 - pointerY) / (y2 - y1),
);
if (scale > 0) {
elements.forEach((element) => {
@ -421,8 +337,8 @@ const resizeMultipleElements = (
}
case "sw": {
const scale = Math.max(
(x2 - (pointerX - offsetX)) / (x2 - x1),
(pointerY - offsetY - y1) / (y2 - y1),
(x2 - pointerX) / (x2 - x1),
(pointerY - y1) / (y2 - y1),
);
if (scale > 0) {
elements.forEach((element) => {
@ -481,3 +397,16 @@ export const getResizeOffsetXY = (
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";
};

View File

@ -58,13 +58,3 @@ export type ExcalidrawLinearElement = _ExcalidrawElementBase &
export type PointerType = "mouse" | "pen" | "touch";
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;