Support rotating two-point lines (angle can be non-zero) (#2090)

Co-authored-by: David Luzar <luzar.david@gmail.com>
This commit is contained in:
Daishi Kato 2020-08-28 17:20:06 +09:00 committed by GitHub
parent 8b9e2a540d
commit 84c49ebaa1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 98 additions and 118 deletions

View File

@ -20,7 +20,7 @@ import {
getDrawingVersion, getDrawingVersion,
getSyncableElements, getSyncableElements,
newLinearElement, newLinearElement,
resizeElements, transformElements,
getElementWithTransformHandleType, getElementWithTransformHandleType,
getResizeOffsetXY, getResizeOffsetXY,
getResizeArrowDirection, getResizeArrowDirection,
@ -2911,7 +2911,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
this.state.gridSize, this.state.gridSize,
); );
if ( if (
resizeElements( transformElements(
transformHandleType, transformHandleType,
(newTransformHandle) => { (newTransformHandle) => {
pointerDownState.resize.handleType = newTransformHandle; pointerDownState.resize.handleType = newTransformHandle;

View File

@ -38,7 +38,7 @@ export {
getTransformHandleTypeFromCoords, getTransformHandleTypeFromCoords,
} from "./resizeTest"; } from "./resizeTest";
export { export {
resizeElements, transformElements,
getResizeOffsetXY, getResizeOffsetXY,
getResizeArrowDirection, getResizeArrowDirection,
} from "./resizeElements"; } from "./resizeElements";

View File

@ -34,8 +34,8 @@ const normalizeAngle = (angle: number): number => {
return angle; return angle;
}; };
// Returns true when a resize (scaling/rotation) happened // Returns true when transform (resizing/rotation) happened
export const resizeElements = ( export const transformElements = (
transformHandleType: MaybeTransformHandleType, transformHandleType: MaybeTransformHandleType,
setTransformHandle: (nextTransformHandle: MaybeTransformHandleType) => void, setTransformHandle: (nextTransformHandle: MaybeTransformHandleType) => void,
selectedElements: readonly NonDeletedExcalidrawElement[], selectedElements: readonly NonDeletedExcalidrawElement[],
@ -67,7 +67,7 @@ export const resizeElements = (
transformHandleType === "sw" || transformHandleType === "sw" ||
transformHandleType === "se") transformHandleType === "se")
) { ) {
resizeSingleTwoPointElement( reshapeSingleTwoPointElement(
element, element,
resizeArrowDirection, resizeArrowDirection,
isRotateWithDiscreteAngle, isRotateWithDiscreteAngle,
@ -164,63 +164,90 @@ const rotateSingleElement = (
mutateElement(element, { angle }); mutateElement(element, { angle });
}; };
const resizeSingleTwoPointElement = ( // used in DEV only
const validateTwoPointElementNormalized = (
element: NonDeleted<ExcalidrawLinearElement>,
) => {
if (
element.points.length !== 2 ||
element.points[0][0] !== 0 ||
element.points[0][1] !== 0 ||
Math.abs(element.points[1][0]) !== element.width ||
Math.abs(element.points[1][1]) !== element.height
) {
throw new Error("Two-point element is not normalized");
}
};
const getPerfectElementSizeWithRotation = (
elementType: string,
width: number,
height: number,
angle: number,
): [number, number] => {
const size = getPerfectElementSize(
elementType,
...rotate(width, height, 0, 0, angle),
);
return rotate(size.width, size.height, 0, 0, -angle);
};
const reshapeSingleTwoPointElement = (
element: NonDeleted<ExcalidrawLinearElement>, element: NonDeleted<ExcalidrawLinearElement>,
resizeArrowDirection: "origin" | "end", resizeArrowDirection: "origin" | "end",
isRotateWithDiscreteAngle: boolean, isRotateWithDiscreteAngle: boolean,
pointerX: number, pointerX: number,
pointerY: number, pointerY: number,
) => { ) => {
const pointOrigin = element.points[0]; // can assume always [0, 0]? if (process.env.NODE_ENV !== "production") {
const pointEnd = element.points[1]; validateTwoPointElementNormalized(element);
if (resizeArrowDirection === "end") {
if (isRotateWithDiscreteAngle) {
const { width, height } = getPerfectElementSize(
element.type,
pointerX - element.x,
pointerY - element.y,
);
mutateElement(element, {
points: [pointOrigin, [width, height]],
});
} else {
mutateElement(element, {
points: [
pointOrigin,
[
pointerX - pointOrigin[0] - element.x,
pointerY - pointOrigin[1] - element.y,
],
],
});
}
} else {
// resizeArrowDirection === "origin"
if (isRotateWithDiscreteAngle) {
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),
],
],
});
}
} }
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,
pointerY,
cx,
cy,
-element.angle,
);
let [width, height] =
resizeArrowDirection === "end"
? [rotatedX - element.x, rotatedY - element.y]
: [
element.x + element.points[1][0] - rotatedX,
element.y + element.points[1][1] - rotatedY,
];
if (isRotateWithDiscreteAngle) {
[width, height] = getPerfectElementSizeWithRotation(
element.type,
width,
height,
element.angle,
);
}
const [nextElementX, nextElementY] = adjustXYWithRotation(
resizeArrowDirection === "end"
? { s: true, e: true }
: { n: true, w: true },
element.x,
element.y,
element.angle,
0,
0,
(element.points[1][0] - width) / 2,
(element.points[1][1] - height) / 2,
);
mutateElement(element, {
x: nextElementX,
y: nextElementY,
points: [
[0, 0],
[width, height],
],
});
}; };
const rescalePointsInElement = ( const rescalePointsInElement = (
@ -606,66 +633,21 @@ const rotateMultipleElements = (
centerAngle -= centerAngle % SHIFT_LOCKING_ANGLE; centerAngle -= centerAngle % SHIFT_LOCKING_ANGLE;
} }
elements.forEach((element, index) => { elements.forEach((element, index) => {
if (isLinearElement(element) && element.points.length === 2) { const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
// FIXME this is a bit tricky (how can we make this more readable?) const cx = (x1 + x2) / 2;
const originalElement = originalElements[index]; const cy = (y1 + y2) / 2;
if ( const [rotatedCX, rotatedCY] = rotate(
!isLinearElement(originalElement) || cx,
originalElement.points.length !== 2 cy,
) { centerX,
throw new Error("original element not compatible"); // should not happen centerY,
} centerAngle + originalElements[index].angle - element.angle,
const [x1, y1, x2, y2] = getElementAbsoluteCoords(originalElement); );
const cx = (x1 + x2) / 2; mutateElement(element, {
const cy = (y1 + y2) / 2; x: element.x + (rotatedCX - cx),
const [rotatedCX, rotatedCY] = rotate( y: element.y + (rotatedCY - cy),
cx, angle: normalizeAngle(centerAngle + originalElements[index].angle),
cy, });
centerX,
centerY,
centerAngle,
);
const { points } = originalElement;
const [rotatedX, rotatedY] = rotate(
points[1][0],
points[1][1],
points[0][0],
points[0][1],
centerAngle,
);
mutateElement(element, {
x:
originalElement.x +
(rotatedCX - cx) +
((originalElement.points[0][0] + originalElement.points[1][0]) / 2 -
(points[0][0] + rotatedX) / 2),
y:
originalElement.y +
(rotatedCY - cy) +
((originalElement.points[0][1] + originalElement.points[1][1]) / 2 -
(points[0][1] + rotatedY) / 2),
points: [
[points[0][0], points[0][1]],
[rotatedX, rotatedY],
],
});
} else {
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
const cx = (x1 + x2) / 2;
const cy = (y1 + y2) / 2;
const [rotatedCX, rotatedCY] = rotate(
cx,
cy,
centerX,
centerY,
centerAngle + originalElements[index].angle - element.angle,
);
mutateElement(element, {
x: element.x + (rotatedCX - cx),
y: element.y + (rotatedCY - cy),
angle: normalizeAngle(centerAngle + originalElements[index].angle),
});
}
}); });
}; };

View File

@ -49,7 +49,6 @@ const OMIT_SIDES_FOR_LINE_SLASH = {
w: true, w: true,
nw: true, nw: true,
se: true, se: true,
rotation: true,
}; };
const OMIT_SIDES_FOR_LINE_BACKSLASH = { const OMIT_SIDES_FOR_LINE_BACKSLASH = {
@ -59,7 +58,6 @@ const OMIT_SIDES_FOR_LINE_BACKSLASH = {
w: true, w: true,
ne: true, ne: true,
sw: true, sw: true,
rotation: true,
}; };
const generateTransformHandle = ( const generateTransformHandle = (