소스 검색

Normalize dimensions (#527)

* normalize dimensions of non-linear elements

* fix element type check regression
David Luzar 5 년 전
부모
커밋
afb1d6725f
6개의 변경된 파일141개의 추가작업 그리고 39개의 파일을 삭제
  1. 6 1
      src/element/index.ts
  2. 58 0
      src/element/resizeTest.ts
  3. 30 0
      src/element/sizeHelpers.ts
  4. 42 34
      src/index.tsx
  5. 1 4
      src/scene/getExportCanvasPreview.ts
  6. 4 0
      src/utils.ts

+ 6 - 1
src/element/index.ts

@@ -8,7 +8,11 @@ export {
 
 export { handlerRectangles } from "./handlerRectangles";
 export { hitTest } from "./collision";
-export { resizeTest, getCursorForResizingElement } from "./resizeTest";
+export {
+  resizeTest,
+  getCursorForResizingElement,
+  normalizeResizeHandle,
+} from "./resizeTest";
 export { isTextElement } from "./typeChecks";
 export { textWysiwyg } from "./textWysiwyg";
 export { redrawTextBoundingBox } from "./textElement";
@@ -16,4 +20,5 @@ export {
   getPerfectElementSize,
   isInvisiblySmallElement,
   resizePerfectLineForNWHandler,
+  normalizeDimensions,
 } from "./sizeHelpers";

+ 58 - 0
src/element/resizeTest.ts

@@ -91,3 +91,61 @@ export function getCursorForResizingElement(resizingElement: {
 
   return cursor ? `${cursor}-resize` : "";
 }
+
+export function normalizeResizeHandle(
+  element: ExcalidrawElement,
+  resizeHandle: HandlerRectanglesRet,
+): HandlerRectanglesRet {
+  if (
+    (element.width >= 0 && element.height >= 0) ||
+    element.type === "line" ||
+    element.type === "arrow"
+  ) {
+    return resizeHandle;
+  }
+
+  if (element.width < 0 && element.height < 0) {
+    switch (resizeHandle) {
+      case "nw":
+        return "se";
+      case "ne":
+        return "sw";
+      case "se":
+        return "nw";
+      case "sw":
+        return "ne";
+    }
+  } else if (element.width < 0) {
+    switch (resizeHandle) {
+      case "nw":
+        return "ne";
+      case "ne":
+        return "nw";
+      case "se":
+        return "sw";
+      case "sw":
+        return "se";
+      case "e":
+        return "w";
+      case "w":
+        return "e";
+    }
+  } else {
+    switch (resizeHandle) {
+      case "nw":
+        return "sw";
+      case "ne":
+        return "se";
+      case "se":
+        return "ne";
+      case "sw":
+        return "nw";
+      case "n":
+        return "s";
+      case "s":
+        return "n";
+    }
+  }
+
+  return resizeHandle;
+}

+ 30 - 0
src/element/sizeHelpers.ts

@@ -57,3 +57,33 @@ export function resizePerfectLineForNWHandler(
     element.y = anchorY - element.height;
   }
 }
+
+/**
+ * @returns {boolean} whether element was normalized
+ */
+export function normalizeDimensions(
+  element: ExcalidrawElement | null,
+): element is ExcalidrawElement {
+  if (
+    !element ||
+    (element.width >= 0 && element.height >= 0) ||
+    element.type === "line" ||
+    element.type === "arrow"
+  ) {
+    return false;
+  }
+
+  if (element.width < 0) {
+    element.width = Math.abs(element.width);
+    element.x -= element.width;
+  }
+
+  if (element.height < 0) {
+    element.height = Math.abs(element.height);
+    element.y -= element.height;
+  }
+
+  element.shape = null;
+
+  return true;
+}

+ 42 - 34
src/index.tsx

@@ -9,6 +9,7 @@ import {
   newTextElement,
   duplicateElement,
   resizeTest,
+  normalizeResizeHandle,
   isInvisiblySmallElement,
   isTextElement,
   textWysiwyg,
@@ -16,6 +17,7 @@ import {
   getCursorForResizingElement,
   getPerfectElementSize,
   resizePerfectLineForNWHandler,
+  normalizeDimensions,
 } from "./element";
 import {
   clearSelection,
@@ -38,7 +40,7 @@ import { renderScene } from "./renderer";
 import { AppState } from "./types";
 import { ExcalidrawElement } from "./element/types";
 
-import { isInputLike, debounce, capitalizeString } from "./utils";
+import { isInputLike, debounce, capitalizeString, distance } from "./utils";
 import { KEYS, isArrowKey } from "./keys";
 
 import { findShapeByKey, shapesShortcutKeys, SHAPES } from "./shapes";
@@ -776,6 +778,9 @@ export class App extends React.Component<any, AppState> {
 
             const { x, y } = viewportCoordsToSceneCoords(e, this.state);
 
+            const originX = x;
+            const originY = y;
+
             let element = newElement(
               this.state.elementType,
               x,
@@ -945,16 +950,15 @@ export class App extends React.Component<any, AppState> {
                   const deltaX = x - lastX;
                   const deltaY = y - lastY;
                   const element = selectedElements[0];
+                  const isLinear =
+                    element.type === "line" || element.type === "arrow";
                   switch (resizeHandle) {
                     case "nw":
                       element.width -= deltaX;
                       element.x += deltaX;
 
                       if (e.shiftKey) {
-                        if (
-                          element.type === "arrow" ||
-                          element.type === "line"
-                        ) {
+                        if (isLinear) {
                           resizePerfectLineForNWHandler(element, x, y);
                         } else {
                           element.y += element.height - element.width;
@@ -986,10 +990,7 @@ export class App extends React.Component<any, AppState> {
                       break;
                     case "se":
                       if (e.shiftKey) {
-                        if (
-                          element.type === "arrow" ||
-                          element.type === "line"
-                        ) {
+                        if (isLinear) {
                           const { width, height } = getPerfectElementSize(
                             element.type,
                             x - element.x,
@@ -1022,6 +1023,11 @@ export class App extends React.Component<any, AppState> {
                       break;
                   }
 
+                  if (resizeHandle) {
+                    resizeHandle = normalizeResizeHandle(element, resizeHandle);
+                  }
+                  normalizeDimensions(element);
+
                   document.documentElement.style.cursor = getCursorForResizingElement(
                     { element, resizeHandle },
                   );
@@ -1064,33 +1070,35 @@ export class App extends React.Component<any, AppState> {
               const draggingElement = this.state.draggingElement;
               if (!draggingElement) return;
 
-              let width =
-                e.clientX -
-                CANVAS_WINDOW_OFFSET_LEFT -
-                draggingElement.x -
-                this.state.scrollX;
-              let height =
-                e.clientY -
-                CANVAS_WINDOW_OFFSET_TOP -
-                draggingElement.y -
-                this.state.scrollY;
+              const { x, y } = viewportCoordsToSceneCoords(e, this.state);
+
+              let width = distance(originX, x);
+              let height = distance(originY, y);
+
+              const isLinear =
+                this.state.elementType === "line" ||
+                this.state.elementType === "arrow";
+
+              if (isLinear && x < originX) width = -width;
+              if (isLinear && y < originY) height = -height;
 
               if (e.shiftKey) {
-                let {
-                  width: newWidth,
-                  height: newHeight,
-                } = getPerfectElementSize(
+                ({ width, height } = getPerfectElementSize(
                   this.state.elementType,
                   width,
-                  height,
-                );
-                draggingElement.width = newWidth;
-                draggingElement.height = newHeight;
-              } else {
-                draggingElement.width = width;
-                draggingElement.height = height;
+                  !isLinear && y < originY ? -height : height,
+                ));
+
+                if (!isLinear && height < 0) height = -height;
+              }
+
+              if (!isLinear) {
+                draggingElement.x = x < originX ? originX - width : originX;
+                draggingElement.y = y < originY ? originY - height : originY;
               }
 
+              draggingElement.width = width;
+              draggingElement.height = height;
               draggingElement.shape = null;
 
               if (this.state.elementType === "selection") {
@@ -1136,6 +1144,10 @@ export class App extends React.Component<any, AppState> {
                 return;
               }
 
+              if (normalizeDimensions(draggingElement)) {
+                this.forceUpdate();
+              }
+
               if (resizingElement && isInvisiblySmallElement(resizingElement)) {
                 elements = elements.filter(el => el.id !== resizingElement.id);
               }
@@ -1349,10 +1361,6 @@ export class App extends React.Component<any, AppState> {
       const minX = Math.min(...parsedElements.map(element => element.x));
       const minY = Math.min(...parsedElements.map(element => element.y));
 
-      const distance = (x: number, y: number) => {
-        return Math.abs(x > y ? x - y : y - x);
-      };
-
       parsedElements.forEach(parsedElement => {
         const [x1, y1, x2, y2] = getElementAbsoluteCoords(parsedElement);
         subCanvasX1 = Math.min(subCanvasX1, x1);

+ 1 - 4
src/scene/getExportCanvasPreview.ts

@@ -2,6 +2,7 @@ import rough from "roughjs/bin/rough";
 import { ExcalidrawElement } from "../element/types";
 import { getElementAbsoluteCoords } from "../element/bounds";
 import { renderScene } from "../renderer/renderScene";
+import { distance } from "../utils";
 
 export function getExportCanvasPreview(
   elements: readonly ExcalidrawElement[],
@@ -40,10 +41,6 @@ export function getExportCanvasPreview(
     subCanvasY2 = Math.max(subCanvasY2, y2);
   });
 
-  function distance(x: number, y: number) {
-    return Math.abs(x > y ? x - y : y - x);
-  }
-
   const width = distance(subCanvasX1, subCanvasX2) + exportPadding * 2;
   const height = distance(subCanvasY1, subCanvasY2) + exportPadding * 2;
   const tempCanvas: any = createCanvas(width, height);

+ 4 - 0
src/utils.ts

@@ -86,3 +86,7 @@ export function removeSelection() {
     selection.removeAllRanges();
   }
 }
+
+export function distance(x: number, y: number) {
+  return Math.abs(x > y ? x - y : y - x);
+}