Преглед изворни кода

account for rotation when moving the crop region

Ryan Di пре 11 месеци
родитељ
комит
e30fd9960d
2 измењених фајлова са 87 додато и 7 уклоњено
  1. 80 7
      packages/excalidraw/components/App.tsx
  2. 7 0
      packages/math/vector.ts

+ 80 - 7
packages/excalidraw/components/App.tsx

@@ -445,7 +445,18 @@ import {
 } from "../element/flowchart";
 import { searchItemInFocusAtom } from "./SearchMenu";
 import type { LocalPoint, Radians } from "../../math";
-import { clamp, pointFrom, pointDistance, vector } from "../../math";
+import {
+  clamp,
+  pointFrom,
+  pointDistance,
+  vector,
+  pointRotateRads,
+  vectorScale,
+  vectorFromPoint,
+  vectorSubtract,
+  vectorDot,
+  vectorMagnitude,
+} from "../../math";
 import { cropElement } from "../element/cropElement";
 
 const AppContext = React.createContext<AppClassProperties>(null!);
@@ -7921,22 +7932,84 @@ class App extends React.Component<AppProps, AppState> {
               this.imageCache.get(croppingElement.fileId)?.image;
 
             if (image && !(image instanceof Promise)) {
-              const instantDragOffset = {
-                x: pointerCoords.x - lastPointerCoords.x,
-                y: pointerCoords.y - lastPointerCoords.y,
-              };
+              // scale the offset by at least a factor of 2 to improve ux
+              let instantDragOffset = vectorScale(
+                vector(
+                  pointerCoords.x - lastPointerCoords.x,
+                  pointerCoords.y - lastPointerCoords.y,
+                ),
+                Math.max(this.state.zoom.value, 2),
+              );
+
+              if (croppingElement.angle !== 0) {
+                const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(
+                  croppingElement,
+                  elementsMap,
+                );
+
+                const topLeft = vectorFromPoint(
+                  pointRotateRads(
+                    pointFrom(x1, y1),
+                    pointFrom(cx, cy),
+                    croppingElement.angle,
+                  ),
+                );
+                const topRight = vectorFromPoint(
+                  pointRotateRads(
+                    pointFrom(x2, y1),
+                    pointFrom(cx, cy),
+                    croppingElement.angle,
+                  ),
+                );
+                const bottomLeft = vectorFromPoint(
+                  pointRotateRads(
+                    pointFrom(x1, y2),
+                    pointFrom(cx, cy),
+                    croppingElement.angle,
+                  ),
+                );
+                const topEdge = vectorSubtract(topRight, topLeft);
+                const leftEdge = vectorSubtract(bottomLeft, topLeft);
+
+                /**
+                 * project instantDrafOffset onto leftEdge to find out the y scalar
+                 *                                 topEdge to find out the x scalar
+                 */
+
+                const scaleY =
+                  vectorDot(instantDragOffset, leftEdge) /
+                  vectorDot(leftEdge, leftEdge);
+                const scaleX =
+                  vectorDot(instantDragOffset, topEdge) /
+                  vectorDot(topEdge, topEdge);
+
+                instantDragOffset = vectorScale(
+                  vector(scaleX, scaleY),
+                  /**
+                   * projection results in small x and y scalars
+                   * scale to account for this
+                   */
+                  Math.min(
+                    Math.max(
+                      vectorMagnitude(topEdge),
+                      vectorMagnitude(leftEdge),
+                    ),
+                    100,
+                  ),
+                );
+              }
 
               const nextCrop = {
                 ...crop,
                 x: clamp(
                   crop.x -
-                    instantDragOffset.x * Math.sign(croppingElement.scale[0]),
+                    instantDragOffset[0] * Math.sign(croppingElement.scale[0]),
                   0,
                   image.naturalWidth - crop.width,
                 ),
                 y: clamp(
                   crop.y -
-                    instantDragOffset.y * Math.sign(croppingElement.scale[1]),
+                    instantDragOffset[1] * Math.sign(croppingElement.scale[1]),
                   0,
                   image.naturalHeight - crop.height,
                 ),

+ 7 - 0
packages/math/vector.ts

@@ -139,3 +139,10 @@ export const vectorNormalize = (v: Vector): Vector => {
 
   return vector(v[0] / m, v[1] / m);
 };
+
+/**
+ * Project the first vector onto the second vector
+ */
+export const vectorProjection = (a: Vector, b: Vector) => {
+  return vectorScale(b, vectorDot(a, b) / vectorDot(b, b));
+};