From 84c49ebaa16a008ae13fd10d6e0c2cadea87e373 Mon Sep 17 00:00:00 2001 From: Daishi Kato Date: Fri, 28 Aug 2020 17:20:06 +0900 Subject: [PATCH] Support rotating two-point lines (angle can be non-zero) (#2090) Co-authored-by: David Luzar --- src/components/App.tsx | 4 +- src/element/index.ts | 2 +- src/element/resizeElements.ts | 208 +++++++++++++++----------------- src/element/transformHandles.ts | 2 - 4 files changed, 98 insertions(+), 118 deletions(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index 997ad7dc..bc849f11 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -20,7 +20,7 @@ import { getDrawingVersion, getSyncableElements, newLinearElement, - resizeElements, + transformElements, getElementWithTransformHandleType, getResizeOffsetXY, getResizeArrowDirection, @@ -2911,7 +2911,7 @@ class App extends React.Component { this.state.gridSize, ); if ( - resizeElements( + transformElements( transformHandleType, (newTransformHandle) => { pointerDownState.resize.handleType = newTransformHandle; diff --git a/src/element/index.ts b/src/element/index.ts index 3cc3ba35..0947d42c 100644 --- a/src/element/index.ts +++ b/src/element/index.ts @@ -38,7 +38,7 @@ export { getTransformHandleTypeFromCoords, } from "./resizeTest"; export { - resizeElements, + transformElements, getResizeOffsetXY, getResizeArrowDirection, } from "./resizeElements"; diff --git a/src/element/resizeElements.ts b/src/element/resizeElements.ts index 98b0177a..660932ab 100644 --- a/src/element/resizeElements.ts +++ b/src/element/resizeElements.ts @@ -34,8 +34,8 @@ const normalizeAngle = (angle: number): number => { return angle; }; -// Returns true when a resize (scaling/rotation) happened -export const resizeElements = ( +// Returns true when transform (resizing/rotation) happened +export const transformElements = ( transformHandleType: MaybeTransformHandleType, setTransformHandle: (nextTransformHandle: MaybeTransformHandleType) => void, selectedElements: readonly NonDeletedExcalidrawElement[], @@ -67,7 +67,7 @@ export const resizeElements = ( transformHandleType === "sw" || transformHandleType === "se") ) { - resizeSingleTwoPointElement( + reshapeSingleTwoPointElement( element, resizeArrowDirection, isRotateWithDiscreteAngle, @@ -164,63 +164,90 @@ const rotateSingleElement = ( mutateElement(element, { angle }); }; -const resizeSingleTwoPointElement = ( +// used in DEV only +const validateTwoPointElementNormalized = ( + element: NonDeleted, +) => { + 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, resizeArrowDirection: "origin" | "end", isRotateWithDiscreteAngle: boolean, pointerX: number, pointerY: number, ) => { - const pointOrigin = element.points[0]; // can assume always [0, 0]? - const pointEnd = element.points[1]; - 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), - ], - ], - }); - } + if (process.env.NODE_ENV !== "production") { + validateTwoPointElementNormalized(element); } + 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 = ( @@ -606,66 +633,21 @@ const rotateMultipleElements = ( centerAngle -= centerAngle % SHIFT_LOCKING_ANGLE; } elements.forEach((element, index) => { - if (isLinearElement(element) && element.points.length === 2) { - // FIXME this is a bit tricky (how can we make this more readable?) - const originalElement = originalElements[index]; - if ( - !isLinearElement(originalElement) || - originalElement.points.length !== 2 - ) { - throw new Error("original element not compatible"); // should not happen - } - const [x1, y1, x2, y2] = getElementAbsoluteCoords(originalElement); - const cx = (x1 + x2) / 2; - const cy = (y1 + y2) / 2; - const [rotatedCX, rotatedCY] = rotate( - cx, - 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), - }); - } + 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), + }); }); }; diff --git a/src/element/transformHandles.ts b/src/element/transformHandles.ts index 40894b44..3f9f0b2f 100644 --- a/src/element/transformHandles.ts +++ b/src/element/transformHandles.ts @@ -49,7 +49,6 @@ const OMIT_SIDES_FOR_LINE_SLASH = { w: true, nw: true, se: true, - rotation: true, }; const OMIT_SIDES_FOR_LINE_BACKSLASH = { @@ -59,7 +58,6 @@ const OMIT_SIDES_FOR_LINE_BACKSLASH = { w: true, ne: true, sw: true, - rotation: true, }; const generateTransformHandle = (