diff --git a/src/actions/actionFlip.ts b/src/actions/actionFlip.ts index ff6bfe4b7e..7a82fa85e2 100644 --- a/src/actions/actionFlip.ts +++ b/src/actions/actionFlip.ts @@ -1,41 +1,26 @@ import { register } from "./register"; import { getSelectedElements } from "../scene"; import { getNonDeletedElements } from "../element"; -import { mutateElement } from "../element/mutateElement"; import { ExcalidrawElement, NonDeleted } from "../element/types"; -import { normalizeAngle, resizeSingleElement } from "../element/resizeElements"; -import { AppState } from "../types"; -import { getTransformHandles } from "../element/transformHandles"; +import { resizeMultipleElements } from "../element/resizeElements"; +import { AppState, PointerDownState } from "../types"; import { updateBoundElements } from "../element/binding"; import { arrayToMap } from "../utils"; -import { - getElementAbsoluteCoords, - getElementPointsCoords, -} from "../element/bounds"; -import { isLinearElement } from "../element/typeChecks"; -import { LinearElementEditor } from "../element/linearElementEditor"; import { KEYS } from "../keys"; +import { getCommonBoundingBox } from "../element/bounds"; const enableActionFlipHorizontal = ( elements: readonly ExcalidrawElement[], appState: AppState, ) => { - const eligibleElements = getSelectedElements( - getNonDeletedElements(elements), - appState, - ); - return eligibleElements.length === 1 && eligibleElements[0].type !== "text"; + return true; }; const enableActionFlipVertical = ( elements: readonly ExcalidrawElement[], appState: AppState, ) => { - const eligibleElements = getSelectedElements( - getNonDeletedElements(elements), - appState, - ); - return eligibleElements.length === 1; + return true; }; export const actionFlipHorizontal = register({ @@ -81,11 +66,6 @@ const flipSelectedElements = ( appState, ); - // remove once we allow for groups of elements to be flipped - if (selectedElements.length > 1) { - return elements; - } - const updatedElements = flipElements( selectedElements, appState, @@ -104,144 +84,18 @@ const flipElements = ( appState: AppState, flipDirection: "horizontal" | "vertical", ): ExcalidrawElement[] => { - elements.forEach((element) => { - flipElement(element, appState); - // If vertical flip, rotate an extra 180 - if (flipDirection === "vertical") { - rotateElement(element, Math.PI); - } - }); + const { minX, minY, maxX, maxY } = getCommonBoundingBox(elements); + + resizeMultipleElements( + { originalElements: arrayToMap(elements) } as PointerDownState, + elements, + "nw", + true, + flipDirection === "horizontal" ? maxX : minX, + flipDirection === "horizontal" ? minY : maxY, + ); + + elements.forEach((element) => updateBoundElements(element)); + return elements; }; - -const flipElement = ( - element: NonDeleted, - appState: AppState, -) => { - const originalX = element.x; - const originalY = element.y; - const width = element.width; - const height = element.height; - const originalAngle = normalizeAngle(element.angle); - - // Rotate back to zero, if necessary - mutateElement(element, { - angle: normalizeAngle(0), - }); - // Flip unrotated by pulling TransformHandle to opposite side - const transformHandles = getTransformHandles(element, appState.zoom); - let usingNWHandle = true; - let nHandle = transformHandles.nw; - if (!nHandle) { - // Use ne handle instead - usingNWHandle = false; - nHandle = transformHandles.ne; - if (!nHandle) { - mutateElement(element, { - angle: originalAngle, - }); - return; - } - } - - let finalOffsetX = 0; - if (isLinearElement(element) && element.points.length < 3) { - finalOffsetX = - element.points.reduce((max, point) => Math.max(max, point[0]), 0) * 2 - - element.width; - } - - let initialPointsCoords; - if (isLinearElement(element)) { - initialPointsCoords = getElementPointsCoords(element, element.points); - } - const initialElementAbsoluteCoords = getElementAbsoluteCoords(element); - - if (isLinearElement(element) && element.points.length < 3) { - for (let index = 1; index < element.points.length; index++) { - LinearElementEditor.movePoints(element, [ - { - index, - point: [-element.points[index][0], element.points[index][1]], - }, - ]); - } - LinearElementEditor.normalizePoints(element); - } else { - const elWidth = initialPointsCoords - ? initialPointsCoords[2] - initialPointsCoords[0] - : initialElementAbsoluteCoords[2] - initialElementAbsoluteCoords[0]; - - const startPoint = initialPointsCoords - ? [initialPointsCoords[0], initialPointsCoords[1]] - : [initialElementAbsoluteCoords[0], initialElementAbsoluteCoords[1]]; - - resizeSingleElement( - new Map().set(element.id, element), - false, - element, - usingNWHandle ? "nw" : "ne", - true, - usingNWHandle ? startPoint[0] + elWidth : startPoint[0] - elWidth, - startPoint[1], - ); - } - - // Rotate by (360 degrees - original angle) - let angle = normalizeAngle(2 * Math.PI - originalAngle); - if (angle < 0) { - // check, probably unnecessary - angle = normalizeAngle(angle + 2 * Math.PI); - } - mutateElement(element, { - angle, - }); - - // Move back to original spot to appear "flipped in place" - mutateElement(element, { - x: originalX + finalOffsetX, - y: originalY, - width, - height, - }); - - updateBoundElements(element); - - if (initialPointsCoords && isLinearElement(element)) { - // Adjusting origin because when a beizer curve path exceeds min/max points it offsets the origin. - // There's still room for improvement since when the line roughness is > 1 - // we still have a small offset of the origin when fliipping the element. - const finalPointsCoords = getElementPointsCoords(element, element.points); - - const topLeftCoordsDiff = initialPointsCoords[0] - finalPointsCoords[0]; - const topRightCoordDiff = initialPointsCoords[2] - finalPointsCoords[2]; - - const coordsDiff = topLeftCoordsDiff + topRightCoordDiff; - - mutateElement(element, { - x: element.x + coordsDiff * 0.5, - y: element.y, - width, - height, - }); - } -}; - -const rotateElement = (element: ExcalidrawElement, rotationAngle: number) => { - const originalX = element.x; - const originalY = element.y; - let angle = normalizeAngle(element.angle + rotationAngle); - if (angle < 0) { - // check, probably unnecessary - angle = normalizeAngle(2 * Math.PI + angle); - } - mutateElement(element, { - angle, - }); - - // Move back to original spot - mutateElement(element, { - x: originalX, - y: originalY, - }); -}; diff --git a/src/element/resizeElements.ts b/src/element/resizeElements.ts index 24cbb10f40..99753aade3 100644 --- a/src/element/resizeElements.ts +++ b/src/element/resizeElements.ts @@ -587,7 +587,7 @@ export const resizeSingleElement = ( } }; -const resizeMultipleElements = ( +export const resizeMultipleElements = ( pointerDownState: PointerDownState, selectedElements: readonly NonDeletedExcalidrawElement[], transformHandleType: "nw" | "ne" | "sw" | "se", @@ -684,6 +684,7 @@ const resizeMultipleElements = ( const x = anchorX + flipFactorX * (offsetX * scale + flipAdjustX); const y = anchorY + flipFactorY * (offsetY * scale + flipAdjustY); + // TODO curved lines adjustment // readjust points for linear & free draw elements const rescaledPoints = rescalePointsInElement( element.orig,