diff --git a/src/components/App.tsx b/src/components/App.tsx index 026b816c..0aafef4f 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -71,7 +71,12 @@ import { sceneCoordsToViewportCoords, setCursorForShape, } from "../utils"; -import { KEYS, isArrowKey } from "../keys"; +import { + KEYS, + isArrowKey, + getResizeCenterPointKey, + getResizeWithSidesSameLengthKey, +} from "../keys"; import { findShapeByKey, shapesShortcutKeys } from "../shapes"; import { createHistory, SceneHistory } from "../history"; @@ -1801,6 +1806,7 @@ class App extends React.Component { let draggingOccurred = false; let hitElement: ExcalidrawElement | null = null; let hitElementWasAddedToSelection = false; + if (this.state.elementType === "selection") { const elements = globalSceneState.getElements(); const selectedElements = getSelectedElements(elements, this.state); @@ -2021,7 +2027,7 @@ class App extends React.Component { } let resizeArrowFn: ResizeArrowFnType | null = null; - const setResizeArrrowFn = (fn: ResizeArrowFnType) => { + const setResizeArrowFn = (fn: ResizeArrowFnType) => { resizeArrowFn = fn; }; @@ -2082,7 +2088,7 @@ class App extends React.Component { this.state, this.setAppState, resizeArrowFn, - setResizeArrrowFn, + setResizeArrowFn, event, x, y, @@ -2189,7 +2195,7 @@ class App extends React.Component { }); } } else { - if (event.shiftKey) { + if (getResizeWithSidesSameLengthKey(event)) { ({ width, height } = getPerfectElementSize( this.state.elementType, width, @@ -2201,9 +2207,19 @@ class App extends React.Component { } } + let newX = x < originX ? originX - width : originX; + let newY = y < originY ? originY - height : originY; + + if (getResizeCenterPointKey(event)) { + width += width; + height += height; + newX = originX - width / 2; + newY = originY - height / 2; + } + mutateElement(draggingElement, { - x: x < originX ? originX - width : originX, - y: y < originY ? originY - height : originY, + x: newX, + y: newY, width: width, height: height, }); diff --git a/src/components/HintViewer.scss b/src/components/HintViewer.scss index 75df4c0a..0b95cf7e 100644 --- a/src/components/HintViewer.scss +++ b/src/components/HintViewer.scss @@ -8,11 +8,12 @@ position: absolute; top: 54px; transform: translateX(calc(-50% - 16px)); /* 16px is half of lock icon */ + white-space: pre; + text-align: center; @media #{$media-query} { position: static; transform: none; margin-top: 0.5rem; - text-align: center; } > span { diff --git a/src/element/resizeElements.ts b/src/element/resizeElements.ts index 7dad5eec..0e35e162 100644 --- a/src/element/resizeElements.ts +++ b/src/element/resizeElements.ts @@ -19,6 +19,10 @@ import { getCursorForResizingElement, normalizeResizeHandle, } from "./resizeTest"; +import { + getResizeCenterPointKey, + getResizeWithSidesSameLengthKey, +} from "../keys"; type ResizeTestType = ReturnType; @@ -117,13 +121,13 @@ export const resizeElements = ( setResizeHandle: (nextResizeHandle: ResizeTestType) => void, appState: AppState, setAppState: (obj: any) => void, - resizeArrowFn: ResizeArrowFnType | null, - setResizeArrowFn: (fn: ResizeArrowFnType) => void, - event: PointerEvent, + resizeArrowFn: ResizeArrowFnType | null, // XXX eliminate in #1339 + setResizeArrowFn: (fn: ResizeArrowFnType) => void, // XXX eliminate in #1339 + event: PointerEvent, // XXX we want to make it independent? xPointer: number, yPointer: number, - lastX: number, - lastY: number, + lastX: number, // XXX eliminate in #1339 + lastY: number, // XXX eliminate in #1339 ) => { setAppState({ isResizing: resizeHandle !== "rotation", @@ -191,7 +195,8 @@ export const resizeElements = ( xPointer, yPointer, offsetPointer, - event.shiftKey, + getResizeWithSidesSameLengthKey(event), + getResizeCenterPointKey(event), ); if (resized.width !== 0 && resized.height !== 0) { mutateElement(element, { diff --git a/src/keys.ts b/src/keys.ts index dd2c6bed..a8e701d2 100644 --- a/src/keys.ts +++ b/src/keys.ts @@ -14,6 +14,7 @@ export const KEYS = { SPACE: " ", QUESTION_MARK: "?", F_KEY_CODE: 70, + ALT_KEY_CODE: 18, } as const; export type Key = keyof typeof KEYS; @@ -26,3 +27,8 @@ export function isArrowKey(keyCode: string) { keyCode === KEYS.ARROW_UP ); } + +export const getResizeCenterPointKey = (event: MouseEvent | KeyboardEvent) => + event.altKey || event.which === KEYS.ALT_KEY_CODE; +export const getResizeWithSidesSameLengthKey = (event: MouseEvent) => + event.shiftKey; diff --git a/src/locales/en.json b/src/locales/en.json index a557096d..7b2f9582 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -109,7 +109,7 @@ "hints": { "linearElement": "Click to start multiple points, drag for single line", "linearElementMulti": "Click on last point or press Escape or Enter to finish", - "resize": "You can constrain proportions by holding SHIFT while resizing", + "resize": "You can constrain proportions by holding SHIFT while resizing,\nhold ALT to resize from the center", "rotate": "You can constrain angles by holding SHIFT while rotating" }, "errorSplash": { diff --git a/src/math.ts b/src/math.ts index 6e4a36a4..f7ebe800 100644 --- a/src/math.ts +++ b/src/math.ts @@ -63,26 +63,43 @@ const adjustXYWithRotation = ( angle: number, deltaX: number, deltaY: number, + isResizeFromCenter: boolean, ) => { const cos = Math.cos(angle); const sin = Math.sin(angle); deltaX /= 2; deltaY /= 2; if (side === "e" || side === "ne" || side === "se") { - x += deltaX * (1 - cos); - y += deltaX * -sin; + if (isResizeFromCenter) { + x += deltaX; + } else { + x += deltaX * (1 - cos); + y += deltaX * -sin; + } } if (side === "s" || side === "sw" || side === "se") { - x += deltaY * sin; - y += deltaY * (1 - cos); + if (isResizeFromCenter) { + y += deltaY; + } else { + x += deltaY * sin; + y += deltaY * (1 - cos); + } } if (side === "w" || side === "nw" || side === "sw") { - x += deltaX * (1 + cos); - y += deltaX * sin; + if (isResizeFromCenter) { + x += deltaX; + } else { + x += deltaX * (1 + cos); + y += deltaX * sin; + } } if (side === "n" || side === "nw" || side === "ne") { - x += deltaY * -sin; - y += deltaY * (1 + cos); + if (isResizeFromCenter) { + y += deltaY; + } else { + x += deltaY * -sin; + y += deltaY * (1 + cos); + } } return { x, y }; }; @@ -100,6 +117,7 @@ export const resizeXYWidthHightWithRotation = ( yPointer: number, offsetPointer: number, sidesWithSameLength: boolean, + isResizeFromCenter: boolean, ) => { // center point for rotation const cx = x + width / 2; @@ -139,6 +157,7 @@ export const resizeXYWidthHightWithRotation = ( angle, width - nextWidth, height - nextHeight, + isResizeFromCenter, ), }; };