瀏覽代碼

feat: cursor alignment when creating linear elements with shift pressed (#5518)

* feat: cursor alignment when creating linear elements

* feat: apply cursor alignment to multi-point linear elements

* refactor: rename size helper function
Ryan Di 2 年之前
父節點
當前提交
865d29388c
共有 3 個文件被更改,包括 54 次插入9 次删除
  1. 13 9
      src/components/App.tsx
  2. 1 0
      src/element/index.ts
  3. 40 0
      src/element/sizeHelpers.ts

+ 13 - 9
src/components/App.tsx

@@ -87,9 +87,9 @@ import {
   getDragOffsetXY,
   getElementWithTransformHandleType,
   getNormalizedDimensions,
-  getPerfectElementSize,
   getResizeArrowDirection,
   getResizeOffsetXY,
+  getLockedLinearCursorAlignSize,
   getTransformHandleTypeFromCoords,
   hitTest,
   isHittingElementBoundingBoxWithoutHittingElement,
@@ -2768,10 +2768,13 @@ class App extends React.Component<AppProps, AppState> {
 
         if (shouldRotateWithDiscreteAngle(event)) {
           ({ width: dxFromLastCommitted, height: dyFromLastCommitted } =
-            getPerfectElementSize(
-              this.state.activeTool.type,
-              dxFromLastCommitted,
-              dyFromLastCommitted,
+            getLockedLinearCursorAlignSize(
+              // actual coordinate of the last committed point
+              lastCommittedX + rx,
+              lastCommittedY + ry,
+              // cursor-grid coordinate
+              gridX,
+              gridY,
             ));
         }
 
@@ -4241,10 +4244,11 @@ class App extends React.Component<AppProps, AppState> {
         let dy = gridY - draggingElement.y;
 
         if (shouldRotateWithDiscreteAngle(event) && points.length === 2) {
-          ({ width: dx, height: dy } = getPerfectElementSize(
-            this.state.activeTool.type,
-            dx,
-            dy,
+          ({ width: dx, height: dy } = getLockedLinearCursorAlignSize(
+            draggingElement.x,
+            draggingElement.y,
+            pointerCoords.x,
+            pointerCoords.y,
           ));
         }
 

+ 1 - 0
src/element/index.ts

@@ -53,6 +53,7 @@ export { textWysiwyg } from "./textWysiwyg";
 export { redrawTextBoundingBox } from "./textElement";
 export {
   getPerfectElementSize,
+  getLockedLinearCursorAlignSize,
   isInvisiblySmallElement,
   resizePerfectLineForNWHandler,
   getNormalizedDimensions,

+ 40 - 0
src/element/sizeHelpers.ts

@@ -47,6 +47,46 @@ export const getPerfectElementSize = (
   return { width, height };
 };
 
+export const getLockedLinearCursorAlignSize = (
+  originX: number,
+  originY: number,
+  x: number,
+  y: number,
+) => {
+  let width = x - originX;
+  let height = y - originY;
+
+  const lockedAngle =
+    Math.round(Math.atan(height / width) / SHIFT_LOCKING_ANGLE) *
+    SHIFT_LOCKING_ANGLE;
+
+  if (lockedAngle === 0) {
+    height = 0;
+  } else if (lockedAngle === Math.PI / 2) {
+    width = 0;
+  } else {
+    // locked angle line, y = mx + b => mx - y + b = 0
+    const a1 = Math.tan(lockedAngle);
+    const b1 = -1;
+    const c1 = originY - a1 * originX;
+
+    // line through cursor, perpendicular to locked angle line
+    const a2 = -1 / a1;
+    const b2 = -1;
+    const c2 = y - a2 * x;
+
+    // intersection of the two lines above
+    const intersectX = Math.round((b1 * c2 - b2 * c1) / (a1 * b2 - a2 * b1));
+    const intersectY = Math.round((c1 * a2 - c2 * a1) / (a1 * b2 - a2 * b1));
+
+    // delta
+    width = intersectX - originX;
+    height = intersectY - originY;
+  }
+
+  return { width, height };
+};
+
 export const resizePerfectLineForNWHandler = (
   element: ExcalidrawElement,
   x: number,