Explorar o código

resize elements from center point (#1225)

* add hint & support multi-line hints

* resize from center point using the new resize maths

* resize with origin element when lifting alt key

* add readonly to elementOriginPosition

* add setResizeWithCenterKeyLifted

* isResizeFromCenter logic

* offsetX and offsetY

* simplify equations

* creating element from center point

* lint

* lint

* lint

* remove revert on key up logic

Co-authored-by: dwelle <luzar.david@gmail.com>
Co-authored-by: daishi <daishi@axlight.com>
José Quinto %!s(int64=5) %!d(string=hai) anos
pai
achega
ed6fb60337
Modificáronse 6 ficheiros con 69 adicións e 22 borrados
  1. 22 6
      src/components/App.tsx
  2. 2 1
      src/components/HintViewer.scss
  3. 11 6
      src/element/resizeElements.ts
  4. 6 0
      src/keys.ts
  5. 1 1
      src/locales/en.json
  6. 27 8
      src/math.ts

+ 22 - 6
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<any, AppState> {
     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<any, AppState> {
     }
 
     let resizeArrowFn: ResizeArrowFnType | null = null;
-    const setResizeArrrowFn = (fn: ResizeArrowFnType) => {
+    const setResizeArrowFn = (fn: ResizeArrowFnType) => {
       resizeArrowFn = fn;
     };
 
@@ -2082,7 +2088,7 @@ class App extends React.Component<any, AppState> {
           this.state,
           this.setAppState,
           resizeArrowFn,
-          setResizeArrrowFn,
+          setResizeArrowFn,
           event,
           x,
           y,
@@ -2189,7 +2195,7 @@ class App extends React.Component<any, AppState> {
           });
         }
       } else {
-        if (event.shiftKey) {
+        if (getResizeWithSidesSameLengthKey(event)) {
           ({ width, height } = getPerfectElementSize(
             this.state.elementType,
             width,
@@ -2201,9 +2207,19 @@ class App extends React.Component<any, AppState> {
           }
         }
 
+        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,
         });

+ 2 - 1
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 {

+ 11 - 6
src/element/resizeElements.ts

@@ -19,6 +19,10 @@ import {
   getCursorForResizingElement,
   normalizeResizeHandle,
 } from "./resizeTest";
+import {
+  getResizeCenterPointKey,
+  getResizeWithSidesSameLengthKey,
+} from "../keys";
 
 type ResizeTestType = ReturnType<typeof resizeTest>;
 
@@ -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, {

+ 6 - 0
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;

+ 1 - 1
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": {

+ 27 - 8
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,
     ),
   };
 };