diff --git a/src/components/App.tsx b/src/components/App.tsx index c7786afa..4de9d602 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -9,7 +9,6 @@ import { newElement, newTextElement, duplicateElement, - resizeTest, isInvisiblySmallElement, isTextElement, textWysiwyg, @@ -22,10 +21,10 @@ import { getSyncableElements, newLinearElement, resizeElements, - getElementWithResizeHandler, + getElementWithTransformHandleType, getResizeOffsetXY, getResizeArrowDirection, - getResizeHandlerFromCoords, + getTransformHandleTypeFromCoords, isNonDeletedElement, updateTextElement, dragSelectedElements, @@ -176,6 +175,7 @@ import { isLinearElementSimpleAndAlreadyBound, isBindingEnabled, } from "../element/binding"; +import { MaybeTransformHandleType } from "../element/transformHandles"; /** * @param func handler taking at most single parameter (event). @@ -221,7 +221,7 @@ type PointerDownState = Readonly<{ lastCoords: { x: number; y: number }; resize: { // Handle when resizing, might change during the pointer interaction - handle: ReturnType; + handleType: MaybeTransformHandleType; // This is determined on the initial pointer down event isResizing: boolean; // This is determined on the initial pointer down event @@ -2057,7 +2057,7 @@ class App extends React.Component { !isOverScrollBar && !this.state.editingLinearElement ) { - const elementWithResizeHandler = getElementWithResizeHandler( + const elementWithTransformHandleType = getElementWithTransformHandleType( elements, this.state, scenePointerX, @@ -2065,23 +2065,26 @@ class App extends React.Component { this.state.zoom, event.pointerType, ); - if (elementWithResizeHandler && elementWithResizeHandler.resizeHandle) { + if ( + elementWithTransformHandleType && + elementWithTransformHandleType.transformHandleType + ) { document.documentElement.style.cursor = getCursorForResizingElement( - elementWithResizeHandler, + elementWithTransformHandleType, ); return; } } else if (selectedElements.length > 1 && !isOverScrollBar) { - const resizeHandle = getResizeHandlerFromCoords( + const transformHandleType = getTransformHandleTypeFromCoords( getCommonBounds(selectedElements), scenePointerX, scenePointerY, this.state.zoom, event.pointerType, ); - if (resizeHandle) { + if (transformHandleType) { document.documentElement.style.cursor = getCursorForResizingElement({ - resizeHandle, + transformHandleType, }); return; } @@ -2363,7 +2366,7 @@ class App extends React.Component { // we need to duplicate because we'll be updating this state lastCoords: { ...origin }, resize: { - handle: false as ReturnType, + handleType: false, isResizing: false, offset: { x: 0, y: 0 }, arrowDirection: "origin", @@ -2446,7 +2449,7 @@ class App extends React.Component { const elements = this.scene.getElements(); const selectedElements = getSelectedElements(elements, this.state); if (selectedElements.length === 1 && !this.state.editingLinearElement) { - const elementWithResizeHandler = getElementWithResizeHandler( + const elementWithTransformHandleType = getElementWithTransformHandleType( elements, this.state, pointerDownState.origin.x, @@ -2454,15 +2457,15 @@ class App extends React.Component { this.state.zoom, event.pointerType, ); - if (elementWithResizeHandler != null) { + if (elementWithTransformHandleType != null) { this.setState({ - resizingElement: elementWithResizeHandler.element, + resizingElement: elementWithTransformHandleType.element, }); - pointerDownState.resize.handle = - elementWithResizeHandler.resizeHandle; + pointerDownState.resize.handleType = + elementWithTransformHandleType.transformHandleType; } } else if (selectedElements.length > 1) { - pointerDownState.resize.handle = getResizeHandlerFromCoords( + pointerDownState.resize.handleType = getTransformHandleTypeFromCoords( getCommonBounds(selectedElements), pointerDownState.origin.x, pointerDownState.origin.y, @@ -2470,14 +2473,14 @@ class App extends React.Component { event.pointerType, ); } - if (pointerDownState.resize.handle) { + if (pointerDownState.resize.handleType) { document.documentElement.style.cursor = getCursorForResizingElement({ - resizeHandle: pointerDownState.resize.handle, + transformHandleType: pointerDownState.resize.handleType, }); pointerDownState.resize.isResizing = true; pointerDownState.resize.offset = tupleToCoors( getResizeOffsetXY( - pointerDownState.resize.handle, + pointerDownState.resize.handleType, selectedElements, pointerDownState.origin.x, pointerDownState.origin.y, @@ -2489,7 +2492,7 @@ class App extends React.Component { selectedElements[0].points.length === 2 ) { pointerDownState.resize.arrowDirection = getResizeArrowDirection( - pointerDownState.resize.handle, + pointerDownState.resize.handleType, selectedElements[0], ); } @@ -2794,13 +2797,13 @@ class App extends React.Component { this.scene.getElements(), this.state, ); - const resizeHandle = pointerDownState.resize.handle; + const transformHandleType = pointerDownState.resize.handleType; this.setState({ // TODO: rename this state field to "isScaling" to distinguish // it from the generic "isResizing" which includes scaling and // rotating - isResizing: resizeHandle && resizeHandle !== "rotation", - isRotating: resizeHandle === "rotation", + isResizing: transformHandleType && transformHandleType !== "rotation", + isRotating: transformHandleType === "rotation", }); const [resizeX, resizeY] = getGridPoint( pointerCoords.x - pointerDownState.resize.offset.x, @@ -2809,9 +2812,9 @@ class App extends React.Component { ); if ( resizeElements( - resizeHandle, - (newResizeHandle) => { - pointerDownState.resize.handle = newResizeHandle; + transformHandleType, + (newTransformHandle) => { + pointerDownState.resize.handleType = newTransformHandle; }, selectedElements, pointerDownState.resize.arrowDirection, diff --git a/src/element/index.ts b/src/element/index.ts index ec3c5df4..adf40178 100644 --- a/src/element/index.ts +++ b/src/element/index.ts @@ -23,16 +23,16 @@ export { export { OMIT_SIDES_FOR_MULTIPLE_ELEMENTS, - handlerRectanglesFromCoords, - handlerRectangles, -} from "./handlerRectangles"; + getTransformHandlesFromCoords, + getTransformHandles, +} from "./transformHandles"; export { hitTest } from "./collision"; export { resizeTest, getCursorForResizingElement, - normalizeResizeHandle, - getElementWithResizeHandler, - getResizeHandlerFromCoords, + normalizeTransformHandleType, + getElementWithTransformHandleType, + getTransformHandleTypeFromCoords, } from "./resizeTest"; export { resizeElements, diff --git a/src/element/resizeElements.ts b/src/element/resizeElements.ts index 2bc5ff3c..98b0177a 100644 --- a/src/element/resizeElements.ts +++ b/src/element/resizeElements.ts @@ -17,12 +17,15 @@ import { isLinearElement } from "./typeChecks"; import { mutateElement } from "./mutateElement"; import { getPerfectElementSize } from "./sizeHelpers"; import { - resizeTest, getCursorForResizingElement, - normalizeResizeHandle, + normalizeTransformHandleType, } from "./resizeTest"; import { measureText, getFontString } from "../utils"; import { updateBoundElements } from "./binding"; +import { + TransformHandleType, + MaybeTransformHandleType, +} from "./transformHandles"; const normalizeAngle = (angle: number): number => { if (angle >= 2 * Math.PI) { @@ -31,12 +34,10 @@ const normalizeAngle = (angle: number): number => { return angle; }; -type ResizeTestType = ReturnType; - // Returns true when a resize (scaling/rotation) happened export const resizeElements = ( - resizeHandle: ResizeTestType, - setResizeHandle: (nextResizeHandle: ResizeTestType) => void, + transformHandleType: MaybeTransformHandleType, + setTransformHandle: (nextTransformHandle: MaybeTransformHandleType) => void, selectedElements: readonly NonDeletedExcalidrawElement[], resizeArrowDirection: "origin" | "end", isRotateWithDiscreteAngle: boolean, @@ -50,7 +51,7 @@ export const resizeElements = ( ) => { if (selectedElements.length === 1) { const [element] = selectedElements; - if (resizeHandle === "rotation") { + if (transformHandleType === "rotation") { rotateSingleElement( element, pointerX, @@ -61,10 +62,10 @@ export const resizeElements = ( } else if ( isLinearElement(element) && element.points.length === 2 && - (resizeHandle === "nw" || - resizeHandle === "ne" || - resizeHandle === "sw" || - resizeHandle === "se") + (transformHandleType === "nw" || + transformHandleType === "ne" || + transformHandleType === "sw" || + transformHandleType === "se") ) { resizeSingleTwoPointElement( element, @@ -75,28 +76,30 @@ export const resizeElements = ( ); } else if ( element.type === "text" && - (resizeHandle === "nw" || - resizeHandle === "ne" || - resizeHandle === "sw" || - resizeHandle === "se") + (transformHandleType === "nw" || + transformHandleType === "ne" || + transformHandleType === "sw" || + transformHandleType === "se") ) { resizeSingleTextElement( element, - resizeHandle, + transformHandleType, isResizeCenterPoint, pointerX, pointerY, ); - } else if (resizeHandle) { + } else if (transformHandleType) { resizeSingleElement( element, - resizeHandle, + transformHandleType, isResizeWithSidesSameLength, isResizeCenterPoint, pointerX, pointerY, ); - setResizeHandle(normalizeResizeHandle(element, resizeHandle)); + setTransformHandle( + normalizeTransformHandleType(element, transformHandleType), + ); if (element.width < 0) { mutateElement(element, { width: -element.width }); } @@ -109,12 +112,12 @@ export const resizeElements = ( // FIXME it is not very nice to have this here document.documentElement.style.cursor = getCursorForResizingElement({ element, - resizeHandle, + transformHandleType, }); return true; } else if (selectedElements.length > 1) { - if (resizeHandle === "rotation") { + if (transformHandleType === "rotation") { rotateMultipleElements( selectedElements, pointerX, @@ -126,14 +129,14 @@ export const resizeElements = ( ); return true; } else if ( - resizeHandle === "nw" || - resizeHandle === "ne" || - resizeHandle === "sw" || - resizeHandle === "se" + transformHandleType === "nw" || + transformHandleType === "ne" || + transformHandleType === "sw" || + transformHandleType === "se" ) { resizeMultipleElements( selectedElements, - resizeHandle, + transformHandleType, pointerX, pointerY, ); @@ -257,29 +260,29 @@ const measureFontSizeFromWH = ( }; }; -const getSidesForResizeHandle = ( - resizeHandle: "n" | "s" | "w" | "e" | "nw" | "ne" | "sw" | "se", +const getSidesForTransformHandle = ( + transformHandleType: TransformHandleType, isResizeFromCenter: boolean, ) => { return { n: - /^(n|ne|nw)$/.test(resizeHandle) || - (isResizeFromCenter && /^(s|se|sw)$/.test(resizeHandle)), + /^(n|ne|nw)$/.test(transformHandleType) || + (isResizeFromCenter && /^(s|se|sw)$/.test(transformHandleType)), s: - /^(s|se|sw)$/.test(resizeHandle) || - (isResizeFromCenter && /^(n|ne|nw)$/.test(resizeHandle)), + /^(s|se|sw)$/.test(transformHandleType) || + (isResizeFromCenter && /^(n|ne|nw)$/.test(transformHandleType)), w: - /^(w|nw|sw)$/.test(resizeHandle) || - (isResizeFromCenter && /^(e|ne|se)$/.test(resizeHandle)), + /^(w|nw|sw)$/.test(transformHandleType) || + (isResizeFromCenter && /^(e|ne|se)$/.test(transformHandleType)), e: - /^(e|ne|se)$/.test(resizeHandle) || - (isResizeFromCenter && /^(w|nw|sw)$/.test(resizeHandle)), + /^(e|ne|se)$/.test(transformHandleType) || + (isResizeFromCenter && /^(w|nw|sw)$/.test(transformHandleType)), }; }; const resizeSingleTextElement = ( element: NonDeleted, - resizeHandle: "nw" | "ne" | "sw" | "se", + transformHandleType: "nw" | "ne" | "sw" | "se", isResizeFromCenter: boolean, pointerX: number, pointerY: number, @@ -296,7 +299,7 @@ const resizeSingleTextElement = ( -element.angle, ); let scale; - switch (resizeHandle) { + switch (transformHandleType) { case "se": scale = Math.max( (rotatedX - x1) / (x2 - x1), @@ -339,7 +342,7 @@ const resizeSingleTextElement = ( const deltaX2 = (x2 - nextX2) / 2; const deltaY2 = (y2 - nextY2) / 2; const [nextElementX, nextElementY] = adjustXYWithRotation( - getSidesForResizeHandle(resizeHandle, isResizeFromCenter), + getSidesForTransformHandle(transformHandleType, isResizeFromCenter), element.x, element.y, element.angle, @@ -361,7 +364,7 @@ const resizeSingleTextElement = ( const resizeSingleElement = ( element: NonDeletedExcalidrawElement, - resizeHandle: "n" | "s" | "w" | "e" | "nw" | "ne" | "sw" | "se", + transformHandleType: "n" | "s" | "w" | "e" | "nw" | "ne" | "sw" | "se", sidesWithSameLength: boolean, isResizeFromCenter: boolean, pointerX: number, @@ -380,16 +383,32 @@ const resizeSingleElement = ( ); let scaleX = 1; let scaleY = 1; - if (resizeHandle === "e" || resizeHandle === "ne" || resizeHandle === "se") { + if ( + transformHandleType === "e" || + transformHandleType === "ne" || + transformHandleType === "se" + ) { scaleX = (rotatedX - x1) / (x2 - x1); } - if (resizeHandle === "s" || resizeHandle === "sw" || resizeHandle === "se") { + if ( + transformHandleType === "s" || + transformHandleType === "sw" || + transformHandleType === "se" + ) { scaleY = (rotatedY - y1) / (y2 - y1); } - if (resizeHandle === "w" || resizeHandle === "nw" || resizeHandle === "sw") { + if ( + transformHandleType === "w" || + transformHandleType === "nw" || + transformHandleType === "sw" + ) { scaleX = (x2 - rotatedX) / (x2 - x1); } - if (resizeHandle === "n" || resizeHandle === "nw" || resizeHandle === "ne") { + if ( + transformHandleType === "n" || + transformHandleType === "nw" || + transformHandleType === "ne" + ) { scaleY = (y2 - rotatedY) / (y2 - y1); } let nextWidth = element.width * scaleX; @@ -419,7 +438,7 @@ const resizeSingleElement = ( Math.abs(nextHeight), ); const [flipDiffX, flipDiffY] = getFlipAdjustment( - resizeHandle, + transformHandleType, nextWidth, nextHeight, nextX1, @@ -434,7 +453,7 @@ const resizeSingleElement = ( element.angle, ); const [nextElementX, nextElementY] = adjustXYWithRotation( - getSidesForResizeHandle(resizeHandle, isResizeFromCenter), + getSidesForTransformHandle(transformHandleType, isResizeFromCenter), element.x - flipDiffX, element.y - flipDiffY, element.angle, @@ -461,7 +480,7 @@ const resizeSingleElement = ( const resizeMultipleElements = ( elements: readonly NonDeletedExcalidrawElement[], - resizeHandle: "nw" | "ne" | "sw" | "se", + transformHandleType: "nw" | "ne" | "sw" | "se", pointerX: number, pointerY: number, ) => { @@ -472,7 +491,7 @@ const resizeMultipleElements = ( origCoords: readonly [number, number, number, number], finalCoords: readonly [number, number, number, number], ) => { x: number; y: number }; - switch (resizeHandle) { + switch (transformHandleType) { case "se": scale = Math.max( (pointerX - x1) / (x2 - x1), @@ -651,7 +670,7 @@ const rotateMultipleElements = ( }; export const getResizeOffsetXY = ( - resizeHandle: ResizeTestType, + transformHandleType: MaybeTransformHandleType, selectedElements: NonDeletedExcalidrawElement[], x: number, y: number, @@ -664,7 +683,7 @@ export const getResizeOffsetXY = ( const cy = (y1 + y2) / 2; const angle = selectedElements.length === 1 ? selectedElements[0].angle : 0; [x, y] = rotate(x, y, cx, cy, -angle); - switch (resizeHandle) { + switch (transformHandleType) { case "n": return rotate(x - (x1 + x2) / 2, y - y1, 0, 0, angle); case "s": @@ -687,14 +706,14 @@ export const getResizeOffsetXY = ( }; export const getResizeArrowDirection = ( - resizeHandle: ResizeTestType, + transformHandleType: MaybeTransformHandleType, element: NonDeleted, ): "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)); + (transformHandleType === "nw" && (px < 0 || py < 0)) || + (transformHandleType === "ne" && px >= 0) || + (transformHandleType === "sw" && px <= 0) || + (transformHandleType === "se" && (px > 0 || py > 0)); return isResizeEnd ? "end" : "origin"; }; diff --git a/src/element/resizeTest.ts b/src/element/resizeTest.ts index 46185512..4e0e6d2c 100644 --- a/src/element/resizeTest.ts +++ b/src/element/resizeTest.ts @@ -6,22 +6,23 @@ import { import { OMIT_SIDES_FOR_MULTIPLE_ELEMENTS, - handlerRectanglesFromCoords, - handlerRectangles, -} from "./handlerRectangles"; + getTransformHandlesFromCoords, + getTransformHandles, + TransformHandleType, + TransformHandle, + MaybeTransformHandleType, +} from "./transformHandles"; import { AppState } from "../types"; -type HandlerRectanglesRet = keyof ReturnType; - -const isInHandlerRect = ( - handler: [number, number, number, number], +const isInsideTransformHandle = ( + transformHandle: TransformHandle, x: number, y: number, ) => - x >= handler[0] && - x <= handler[0] + handler[2] && - y >= handler[1] && - y <= handler[1] + handler[3]; + x >= transformHandle[0] && + x <= transformHandle[0] + transformHandle[2] && + y >= transformHandle[1] && + y <= transformHandle[1] + transformHandle[3]; export const resizeTest = ( element: NonDeletedExcalidrawElement, @@ -30,37 +31,41 @@ export const resizeTest = ( y: number, zoom: number, pointerType: PointerType, -): HandlerRectanglesRet | false => { +): MaybeTransformHandleType => { if (!appState.selectedElementIds[element.id]) { return false; } - const { rotation: rotationHandler, ...handlers } = handlerRectangles( - element, - zoom, - pointerType, - ); + const { + rotation: rotationTransformHandle, + ...transformHandles + } = getTransformHandles(element, zoom, pointerType); - if (rotationHandler && isInHandlerRect(rotationHandler, x, y)) { - return "rotation" as HandlerRectanglesRet; + if ( + rotationTransformHandle && + isInsideTransformHandle(rotationTransformHandle, x, y) + ) { + return "rotation" as TransformHandleType; } - const filter = Object.keys(handlers).filter((key) => { - const handler = handlers[key as Exclude]!; - if (!handler) { + const filter = Object.keys(transformHandles).filter((key) => { + const transformHandle = transformHandles[ + key as Exclude + ]!; + if (!transformHandle) { return false; } - return isInHandlerRect(handler, x, y); + return isInsideTransformHandle(transformHandle, x, y); }); if (filter.length > 0) { - return filter[0] as HandlerRectanglesRet; + return filter[0] as TransformHandleType; } return false; }; -export const getElementWithResizeHandler = ( +export const getElementWithTransformHandleType = ( elements: readonly NonDeletedExcalidrawElement[], appState: AppState, scenePointerX: number, @@ -72,7 +77,7 @@ export const getElementWithResizeHandler = ( if (result) { return result; } - const resizeHandle = resizeTest( + const transformHandleType = resizeTest( element, appState, scenePointerX, @@ -80,18 +85,18 @@ export const getElementWithResizeHandler = ( zoom, pointerType, ); - return resizeHandle ? { element, resizeHandle } : null; - }, null as { element: NonDeletedExcalidrawElement; resizeHandle: HandlerRectanglesRet } | null); + return transformHandleType ? { element, transformHandleType } : null; + }, null as { element: NonDeletedExcalidrawElement; transformHandleType: MaybeTransformHandleType } | null); }; -export const getResizeHandlerFromCoords = ( +export const getTransformHandleTypeFromCoords = ( [x1, y1, x2, y2]: readonly [number, number, number, number], scenePointerX: number, scenePointerY: number, zoom: number, pointerType: PointerType, -) => { - const handlers = handlerRectanglesFromCoords( +): MaybeTransformHandleType => { + const transformHandles = getTransformHandlesFromCoords( [x1, y1, x2, y2], 0, zoom, @@ -99,11 +104,16 @@ export const getResizeHandlerFromCoords = ( OMIT_SIDES_FOR_MULTIPLE_ELEMENTS, ); - const found = Object.keys(handlers).find((key) => { - const handler = handlers[key as Exclude]!; - return handler && isInHandlerRect(handler, scenePointerX, scenePointerY); + const found = Object.keys(transformHandles).find((key) => { + const transformHandle = transformHandles[ + key as Exclude + ]!; + return ( + transformHandle && + isInsideTransformHandle(transformHandle, scenePointerX, scenePointerY) + ); }); - return (found || false) as HandlerRectanglesRet; + return (found || false) as MaybeTransformHandleType; }; const RESIZE_CURSORS = ["ns", "nesw", "ew", "nwse"]; @@ -121,14 +131,14 @@ const rotateResizeCursor = (cursor: string, angle: number) => { */ export const getCursorForResizingElement = (resizingElement: { element?: ExcalidrawElement; - resizeHandle: ReturnType; + transformHandleType: MaybeTransformHandleType; }): string => { - const { element, resizeHandle } = resizingElement; + const { element, transformHandleType } = resizingElement; const shouldSwapCursors = element && Math.sign(element.height) * Math.sign(element.width) === -1; let cursor = null; - switch (resizeHandle) { + switch (transformHandleType) { case "n": case "s": cursor = "ns"; @@ -164,16 +174,16 @@ export const getCursorForResizingElement = (resizingElement: { return cursor ? `${cursor}-resize` : ""; }; -export const normalizeResizeHandle = ( +export const normalizeTransformHandleType = ( element: ExcalidrawElement, - resizeHandle: HandlerRectanglesRet, -): HandlerRectanglesRet => { + transformHandleType: TransformHandleType, +): TransformHandleType => { if (element.width >= 0 && element.height >= 0) { - return resizeHandle; + return transformHandleType; } if (element.width < 0 && element.height < 0) { - switch (resizeHandle) { + switch (transformHandleType) { case "nw": return "se"; case "ne": @@ -184,7 +194,7 @@ export const normalizeResizeHandle = ( return "ne"; } } else if (element.width < 0) { - switch (resizeHandle) { + switch (transformHandleType) { case "nw": return "ne"; case "ne": @@ -199,7 +209,7 @@ export const normalizeResizeHandle = ( return "e"; } } else { - switch (resizeHandle) { + switch (transformHandleType) { case "nw": return "sw"; case "ne": @@ -215,5 +225,5 @@ export const normalizeResizeHandle = ( } } - return resizeHandle; + return transformHandleType; }; diff --git a/src/element/handlerRectangles.ts b/src/element/transformHandles.ts similarity index 53% rename from src/element/handlerRectangles.ts rename to src/element/transformHandles.ts index 227d267d..683bc753 100644 --- a/src/element/handlerRectangles.ts +++ b/src/element/transformHandles.ts @@ -3,19 +3,30 @@ import { ExcalidrawElement, PointerType } from "./types"; import { getElementAbsoluteCoords, Bounds } from "./bounds"; import { rotate } from "../math"; -type Sides = "n" | "s" | "w" | "e" | "nw" | "ne" | "sw" | "se" | "rotation"; +export type TransformHandleType = + | "n" + | "s" + | "w" + | "e" + | "nw" + | "ne" + | "sw" + | "se" + | "rotation"; -export type Handlers = Partial< - { [T in Sides]: [number, number, number, number] } +export type TransformHandle = [number, number, number, number]; +export type TransformHandles = Partial< + { [T in TransformHandleType]: TransformHandle } >; +export type MaybeTransformHandleType = TransformHandleType | false; -const handleSizes: { [k in PointerType]: number } = { +const transformHandleSizes: { [k in PointerType]: number } = { mouse: 8, pen: 16, touch: 28, }; -const ROTATION_HANDLER_GAP = 16; +const ROTATION_RESIZE_HANDLE_GAP = 16; export const OMIT_SIDES_FOR_MULTIPLE_ELEMENTS = { e: true, @@ -51,7 +62,7 @@ const OMIT_SIDES_FOR_LINE_BACKSLASH = { rotation: true, }; -const generateHandler = ( +const generateTransformHandle = ( x: number, y: number, width: number, @@ -59,24 +70,24 @@ const generateHandler = ( cx: number, cy: number, angle: number, -): [number, number, number, number] => { +): TransformHandle => { const [xx, yy] = rotate(x + width / 2, y + height / 2, cx, cy, angle); return [xx - width / 2, yy - height / 2, width, height]; }; -export const handlerRectanglesFromCoords = ( +export const getTransformHandlesFromCoords = ( [x1, y1, x2, y2]: Bounds, angle: number, zoom: number, - pointerType: PointerType = "mouse", - omitSides: { [T in Sides]?: boolean } = {}, -): Handlers => { - const size = handleSizes[pointerType]; - const handlerWidth = size / zoom; - const handlerHeight = size / zoom; + pointerType: PointerType = "touch", + omitSides: { [T in TransformHandleType]?: boolean } = {}, +): TransformHandles => { + const size = transformHandleSizes[pointerType]; + const handleWidth = size / zoom; + const handleHeight = size / zoom; - const handlerMarginX = size / zoom; - const handlerMarginY = size / zoom; + const handleMarginX = size / zoom; + const handleMarginY = size / zoom; const width = x2 - x1; const height = y2 - y1; @@ -85,116 +96,114 @@ export const handlerRectanglesFromCoords = ( const dashedLineMargin = 4 / zoom; - const centeringOffset = (size - 8) / (2 * zoom); + const centeringOffset = 0; - const handlers: Partial< - { [T in Sides]: [number, number, number, number] } - > = { + const transformHandles: TransformHandles = { nw: omitSides["nw"] ? undefined - : generateHandler( - x1 - dashedLineMargin - handlerMarginX + centeringOffset, - y1 - dashedLineMargin - handlerMarginY + centeringOffset, - handlerWidth, - handlerHeight, + : generateTransformHandle( + x1 - dashedLineMargin - handleMarginX + centeringOffset, + y1 - dashedLineMargin - handleMarginY + centeringOffset, + handleWidth, + handleHeight, cx, cy, angle, ), ne: omitSides["ne"] ? undefined - : generateHandler( + : generateTransformHandle( x2 + dashedLineMargin - centeringOffset, - y1 - dashedLineMargin - handlerMarginY + centeringOffset, - handlerWidth, - handlerHeight, + y1 - dashedLineMargin - handleMarginY + centeringOffset, + handleWidth, + handleHeight, cx, cy, angle, ), sw: omitSides["sw"] ? undefined - : generateHandler( - x1 - dashedLineMargin - handlerMarginX + centeringOffset, + : generateTransformHandle( + x1 - dashedLineMargin - handleMarginX + centeringOffset, y2 + dashedLineMargin - centeringOffset, - handlerWidth, - handlerHeight, + handleWidth, + handleHeight, cx, cy, angle, ), se: omitSides["se"] ? undefined - : generateHandler( + : generateTransformHandle( x2 + dashedLineMargin - centeringOffset, y2 + dashedLineMargin - centeringOffset, - handlerWidth, - handlerHeight, + handleWidth, + handleHeight, cx, cy, angle, ), rotation: omitSides["rotation"] ? undefined - : generateHandler( - x1 + width / 2 - handlerWidth / 2, + : generateTransformHandle( + x1 + width / 2 - handleWidth / 2, y1 - dashedLineMargin - - handlerMarginY + + handleMarginY + centeringOffset - - ROTATION_HANDLER_GAP / zoom, - handlerWidth, - handlerHeight, + ROTATION_RESIZE_HANDLE_GAP / zoom, + handleWidth, + handleHeight, cx, cy, angle, ), }; - // We only want to show height handlers (all cardinal directions) above a certain size - const minimumSizeForEightHandlers = (5 * size) / zoom; - if (Math.abs(width) > minimumSizeForEightHandlers) { + // We only want to show height handles (all cardinal directions) above a certain size + const minimumSizeForEightHandles = (5 * size) / zoom; + if (Math.abs(width) > minimumSizeForEightHandles) { if (!omitSides["n"]) { - handlers["n"] = generateHandler( - x1 + width / 2 - handlerWidth / 2, - y1 - dashedLineMargin - handlerMarginY + centeringOffset, - handlerWidth, - handlerHeight, + transformHandles["n"] = generateTransformHandle( + x1 + width / 2 - handleWidth / 2, + y1 - dashedLineMargin - handleMarginY + centeringOffset, + handleWidth, + handleHeight, cx, cy, angle, ); } if (!omitSides["s"]) { - handlers["s"] = generateHandler( - x1 + width / 2 - handlerWidth / 2, + transformHandles["s"] = generateTransformHandle( + x1 + width / 2 - handleWidth / 2, y2 + dashedLineMargin - centeringOffset, - handlerWidth, - handlerHeight, + handleWidth, + handleHeight, cx, cy, angle, ); } } - if (Math.abs(height) > minimumSizeForEightHandlers) { + if (Math.abs(height) > minimumSizeForEightHandles) { if (!omitSides["w"]) { - handlers["w"] = generateHandler( - x1 - dashedLineMargin - handlerMarginX + centeringOffset, - y1 + height / 2 - handlerHeight / 2, - handlerWidth, - handlerHeight, + transformHandles["w"] = generateTransformHandle( + x1 - dashedLineMargin - handleMarginX + centeringOffset, + y1 + height / 2 - handleHeight / 2, + handleWidth, + handleHeight, cx, cy, angle, ); } if (!omitSides["e"]) { - handlers["e"] = generateHandler( + transformHandles["e"] = generateTransformHandle( x2 + dashedLineMargin - centeringOffset, - y1 + height / 2 - handlerHeight / 2, - handlerWidth, - handlerHeight, + y1 + height / 2 - handleHeight / 2, + handleWidth, + handleHeight, cx, cy, angle, @@ -202,15 +211,15 @@ export const handlerRectanglesFromCoords = ( } } - return handlers; + return transformHandles; }; -export const handlerRectangles = ( +export const getTransformHandles = ( element: ExcalidrawElement, zoom: number, - pointerType: PointerType = "mouse", -) => { - let omitSides: { [T in Sides]?: boolean } = {}; + pointerType: PointerType = "touch", +): TransformHandles => { + let omitSides: { [T in TransformHandleType]?: boolean } = {}; if ( element.type === "arrow" || element.type === "line" || @@ -235,7 +244,7 @@ export const handlerRectangles = ( omitSides = OMIT_SIDES_FOR_TEXT_ELEMENT; } - return handlerRectanglesFromCoords( + return getTransformHandlesFromCoords( getElementAbsoluteCoords(element), element.angle, zoom, diff --git a/src/renderer/renderScene.ts b/src/renderer/renderScene.ts index ea5d8c14..9d4d78c6 100644 --- a/src/renderer/renderScene.ts +++ b/src/renderer/renderScene.ts @@ -14,8 +14,8 @@ import { import { getElementAbsoluteCoords, OMIT_SIDES_FOR_MULTIPLE_ELEMENTS, - handlerRectanglesFromCoords, - handlerRectangles, + getTransformHandlesFromCoords, + getTransformHandles, getElementBounds, getCommonBounds, } from "../element"; @@ -43,9 +43,10 @@ import { SuggestedPointBinding, isBindingEnabled, } from "../element/binding"; -import { Handlers } from "../element/handlerRectangles"; - -type HandlerRectanglesRet = keyof ReturnType; +import { + TransformHandles, + TransformHandleType, +} from "../element/transformHandles"; const strokeRectWithRotation = ( context: CanvasRenderingContext2D, @@ -362,18 +363,18 @@ export const renderScene = ( const locallySelectedElements = getSelectedElements(elements, appState); - // Paint resize handlers + // Paint resize transformHandles context.translate(sceneState.scrollX, sceneState.scrollY); if (locallySelectedElements.length === 1) { context.fillStyle = oc.white; - const handlers = handlerRectangles( + const transformHandles = getTransformHandles( locallySelectedElements[0], sceneState.zoom, ); - renderHandlers( + renderTransformHandles( context, sceneState, - handlers, + transformHandles, locallySelectedElements[0].angle, ); } else if (locallySelectedElements.length > 1 && !appState.isRotating) { @@ -396,14 +397,14 @@ export const renderScene = ( ); context.lineWidth = lineWidth; context.setLineDash(initialLineDash); - const handlers = handlerRectanglesFromCoords( + const transformHandles = getTransformHandlesFromCoords( [x1, y1, x2, y2], 0, sceneState.zoom, undefined, OMIT_SIDES_FOR_MULTIPLE_ELEMENTS, ); - renderHandlers(context, sceneState, handlers, 0); + renderTransformHandles(context, sceneState, transformHandles, 0); } context.translate(-sceneState.scrollX, -sceneState.scrollY); } @@ -545,33 +546,33 @@ export const renderScene = ( return { atLeastOneVisibleElement: visibleElements.length > 0, scrollBars }; }; -const renderHandlers = ( +const renderTransformHandles = ( context: CanvasRenderingContext2D, sceneState: SceneState, - handlers: Handlers, + transformHandles: TransformHandles, angle: number, ): void => { - Object.keys(handlers).forEach((key) => { - const handler = handlers[key as HandlerRectanglesRet]; - if (handler !== undefined) { + Object.keys(transformHandles).forEach((key) => { + const transformHandle = transformHandles[key as TransformHandleType]; + if (transformHandle !== undefined) { const lineWidth = context.lineWidth; context.lineWidth = 1 / sceneState.zoom; if (key === "rotation") { fillCircle( context, - handler[0] + handler[2] / 2, - handler[1] + handler[3] / 2, - handler[2] / 2, + transformHandle[0] + transformHandle[2] / 2, + transformHandle[1] + transformHandle[3] / 2, + transformHandle[2] / 2, ); } else { strokeRectWithRotation( context, - handler[0], - handler[1], - handler[2], - handler[3], - handler[0] + handler[2] / 2, - handler[1] + handler[3] / 2, + transformHandle[0], + transformHandle[1], + transformHandle[2], + transformHandle[3], + transformHandle[0] + transformHandle[2] / 2, + transformHandle[1] + transformHandle[3] / 2, angle, true, // fill before stroke ); diff --git a/src/tests/regressionTests.test.tsx b/src/tests/regressionTests.test.tsx index 3d49dca5..d159fdf6 100644 --- a/src/tests/regressionTests.test.tsx +++ b/src/tests/regressionTests.test.tsx @@ -9,7 +9,7 @@ import { ToolName } from "./queries/toolQueries"; import { KEYS, Key } from "../keys"; import { setDateTimeForTests } from "../utils"; import { ExcalidrawElement } from "../element/types"; -import { handlerRectangles } from "../element"; +import { getTransformHandles as _getTransformHandles } from "../element"; import { queryByText } from "@testing-library/react"; import { copiedStyles } from "../actions/actionStyles"; @@ -192,9 +192,9 @@ function getStateHistory() { return h.history.stateHistory; } -type HandlerRectanglesRet = keyof ReturnType; -const getResizeHandles = (pointerType: "mouse" | "touch" | "pen") => { - const rects = handlerRectangles( +type HandlerRectanglesRet = keyof ReturnType; +const getTransformHandles = (pointerType: "mouse" | "touch" | "pen") => { + const rects = _getTransformHandles( getSelectedElement(), h.state.zoom, pointerType, @@ -362,10 +362,12 @@ describe("regression tests", () => { mouse.down(10, 10); mouse.up(10, 10); - const resizeHandles = getResizeHandles("mouse"); - delete resizeHandles.rotation; // exclude rotation handle - for (const handlePos in resizeHandles) { - const [x, y] = resizeHandles[handlePos as keyof typeof resizeHandles]; + const transformHandles = getTransformHandles("mouse"); + delete transformHandles.rotation; // exclude rotation handle + for (const handlePos in transformHandles) { + const [x, y] = transformHandles[ + handlePos as keyof typeof transformHandles + ]; const { width: prevWidth, height: prevHeight } = getSelectedElement(); mouse.restorePosition(x, y); mouse.down();