Bläddra i källkod

Creating a text near the center of a shape should put it in the center (#270)

* Snap to element center

* Fixed typo

* Added comment

* Reduced threshold to 30

* Skip snapping if alt key is pressed

* Fixed creating text with shape tool
Timur Khazamov 5 år sedan
förälder
incheckning
1739540f00
4 ändrade filer med 90 tillägg och 12 borttagningar
  1. 66 10
      src/index.tsx
  2. 0 1
      src/renderer/renderElement.ts
  3. 18 0
      src/scene/comparisons.ts
  4. 6 1
      src/scene/index.ts

+ 66 - 10
src/index.tsx

@@ -26,7 +26,8 @@ import {
   hasBackground,
   hasStroke,
   getElementAtPosition,
-  createScene
+  createScene,
+  getElementContainingPosition
 } from "./scene";
 
 import { renderScene } from "./renderer";
@@ -110,6 +111,7 @@ function addTextElement(
 
 const ELEMENT_SHIFT_TRANSLATE_AMOUNT = 5;
 const ELEMENT_TRANSLATE_AMOUNT = 1;
+const TEXT_TO_CENTER_SNAP_THRESHOLD = 30;
 
 let lastCanvasWidth = -1;
 let lastCanvasHeight = -1;
@@ -702,10 +704,25 @@ class App extends React.Component<{}, AppState> {
             }
 
             if (isTextElement(element)) {
+              let textX = e.clientX;
+              let textY = e.clientY;
+              if (!e.altKey) {
+                const snappedToCenterPosition = this.getTextWysiwygSnappedToCenterPosition(
+                  x,
+                  y
+                );
+                if (snappedToCenterPosition) {
+                  element.x = snappedToCenterPosition.elementCenterX;
+                  element.y = snappedToCenterPosition.elementCenterY;
+                  textX = snappedToCenterPosition.wysiwygX;
+                  textY = snappedToCenterPosition.wysiwygY;
+                }
+              }
+
               textWysiwyg({
                 initText: "",
-                x: e.clientX,
-                y: e.clientY,
+                x: textX,
+                y: textY,
                 strokeColor: this.state.currentItemStrokeColor,
                 font: this.state.currentItemFont,
                 onSubmit: text => {
@@ -932,12 +949,6 @@ class App extends React.Component<{}, AppState> {
               e.clientX - CANVAS_WINDOW_OFFSET_LEFT - this.state.scrollX;
             const y = e.clientY - CANVAS_WINDOW_OFFSET_TOP - this.state.scrollY;
             const elementAtPosition = getElementAtPosition(elements, x, y);
-            if (elementAtPosition && !isTextElement(elementAtPosition)) {
-              return;
-            } else if (elementAtPosition) {
-              elements.splice(elements.indexOf(elementAtPosition), 1);
-              this.forceUpdate();
-            }
 
             const element = newElement(
               "text",
@@ -954,7 +965,11 @@ class App extends React.Component<{}, AppState> {
             let initText = "";
             let textX = e.clientX;
             let textY = e.clientY;
-            if (elementAtPosition) {
+
+            if (elementAtPosition && isTextElement(elementAtPosition)) {
+              elements.splice(elements.indexOf(elementAtPosition), 1);
+              this.forceUpdate();
+
               Object.assign(element, elementAtPosition);
               // x and y will change after calling addTextElement function
               element.x = elementAtPosition.x + elementAtPosition.width / 2;
@@ -970,6 +985,18 @@ class App extends React.Component<{}, AppState> {
                 elementAtPosition.y +
                 CANVAS_WINDOW_OFFSET_TOP +
                 elementAtPosition.height / 2;
+            } else if (!e.altKey) {
+              const snappedToCenterPosition = this.getTextWysiwygSnappedToCenterPosition(
+                x,
+                y
+              );
+
+              if (snappedToCenterPosition) {
+                element.x = snappedToCenterPosition.elementCenterX;
+                element.y = snappedToCenterPosition.elementCenterY;
+                textX = snappedToCenterPosition.wysiwygX;
+                textY = snappedToCenterPosition.wysiwygY;
+              }
             }
 
             textWysiwyg({
@@ -1036,6 +1063,35 @@ class App extends React.Component<{}, AppState> {
     }
   };
 
+  private getTextWysiwygSnappedToCenterPosition(x: number, y: number) {
+    const elementClickedInside = getElementContainingPosition(elements, x, y);
+    if (elementClickedInside) {
+      const elementCenterX =
+        elementClickedInside.x + elementClickedInside.width / 2;
+      const elementCenterY =
+        elementClickedInside.y + elementClickedInside.height / 2;
+      const distanceToCenter = Math.hypot(
+        x - elementCenterX,
+        y - elementCenterY
+      );
+      const isSnappedToCenter =
+        distanceToCenter < TEXT_TO_CENTER_SNAP_THRESHOLD;
+      if (isSnappedToCenter) {
+        const wysiwygX =
+          this.state.scrollX +
+          elementClickedInside.x +
+          CANVAS_WINDOW_OFFSET_LEFT +
+          elementClickedInside.width / 2;
+        const wysiwygY =
+          this.state.scrollY +
+          elementClickedInside.y +
+          CANVAS_WINDOW_OFFSET_TOP +
+          elementClickedInside.height / 2;
+        return { wysiwygX, wysiwygY, elementCenterX, elementCenterY };
+      }
+    }
+  }
+
   componentDidUpdate() {
     renderScene(elements, rc, canvas, {
       scrollX: this.state.scrollX,

+ 0 - 1
src/renderer/renderElement.ts

@@ -138,7 +138,6 @@ export function renderElement(
     context.fillStyle = fillStyle;
     context.font = font;
     context.globalAlpha = 1;
-    console.log(element);
   } else {
     throw new Error("Unimplemented type " + element.type);
   }

+ 18 - 0
src/scene/comparisons.ts

@@ -1,5 +1,6 @@
 import { ExcalidrawElement } from "../element/types";
 import { hitTest } from "../element/collision";
+import { getElementAbsoluteCoords } from "../element";
 
 export const hasBackground = (elements: ExcalidrawElement[]) =>
   elements.some(
@@ -36,3 +37,20 @@ export function getElementAtPosition(
 
   return hitElement;
 }
+
+export function getElementContainingPosition(
+  elements: ExcalidrawElement[],
+  x: number,
+  y: number
+) {
+  let hitElement = null;
+  // We need to to hit testing from front (end of the array) to back (beginning of the array)
+  for (let i = elements.length - 1; i >= 0; --i) {
+    const [x1, y1, x2, y2] = getElementAbsoluteCoords(elements[i]);
+    if (x1 < x && x < x2 && y1 < y && y < y2) {
+      hitElement = elements[i];
+      break;
+    }
+  }
+  return hitElement;
+}

+ 6 - 1
src/scene/index.ts

@@ -14,5 +14,10 @@ export {
   restoreFromLocalStorage,
   saveToLocalStorage
 } from "./data";
-export { hasBackground, hasStroke, getElementAtPosition } from "./comparisons";
+export {
+  hasBackground,
+  hasStroke,
+  getElementAtPosition,
+  getElementContainingPosition
+} from "./comparisons";
 export { createScene } from "./createScene";