소스 검색

Show move and resize cursors on hover (#280)

* Change to move cursor on hover

* Show resize handlers on hover
Guillermo Peralta Scura 5 년 전
부모
커밋
4a044d3ace
4개의 변경된 파일104개의 추가작업 그리고 57개의 파일을 삭제
  1. 18 18
      src/element/handlerRectangles.ts
  2. 24 7
      src/element/resizeTest.ts
  3. 57 32
      src/index.tsx
  4. 5 0
      src/scene/types.ts

+ 18 - 18
src/element/handlerRectangles.ts

@@ -1,11 +1,11 @@
-import { SceneState } from "../scene/types";
 import { ExcalidrawElement } from "./types";
+import { SceneScroll } from "../scene/types";
 
 type Sides = "n" | "s" | "w" | "e" | "nw" | "ne" | "sw" | "se";
 
 export function handlerRectangles(
   element: ExcalidrawElement,
-  sceneState: SceneState
+  { scrollX, scrollY }: SceneScroll
 ) {
   const elementX1 = element.x;
   const elementX2 = element.x + element.width;
@@ -21,15 +21,15 @@ export function handlerRectangles(
 
   if (Math.abs(elementX2 - elementX1) > minimumSize) {
     handlers["n"] = [
-      elementX1 + (elementX2 - elementX1) / 2 + sceneState.scrollX - 4,
-      elementY1 - margin + sceneState.scrollY + marginY,
+      elementX1 + (elementX2 - elementX1) / 2 + scrollX - 4,
+      elementY1 - margin + scrollY + marginY,
       8,
       8
     ];
 
     handlers["s"] = [
-      elementX1 + (elementX2 - elementX1) / 2 + sceneState.scrollX - 4,
-      elementY2 - margin + sceneState.scrollY - marginY,
+      elementX1 + (elementX2 - elementX1) / 2 + scrollX - 4,
+      elementY2 - margin + scrollY - marginY,
       8,
       8
     ];
@@ -37,41 +37,41 @@ export function handlerRectangles(
 
   if (Math.abs(elementY2 - elementY1) > minimumSize) {
     handlers["w"] = [
-      elementX1 - margin + sceneState.scrollX + marginX,
-      elementY1 + (elementY2 - elementY1) / 2 + sceneState.scrollY - 4,
+      elementX1 - margin + scrollX + marginX,
+      elementY1 + (elementY2 - elementY1) / 2 + scrollY - 4,
       8,
       8
     ];
 
     handlers["e"] = [
-      elementX2 - margin + sceneState.scrollX - marginX,
-      elementY1 + (elementY2 - elementY1) / 2 + sceneState.scrollY - 4,
+      elementX2 - margin + scrollX - marginX,
+      elementY1 + (elementY2 - elementY1) / 2 + scrollY - 4,
       8,
       8
     ];
   }
 
   handlers["nw"] = [
-    elementX1 - margin + sceneState.scrollX + marginX,
-    elementY1 - margin + sceneState.scrollY + marginY,
+    elementX1 - margin + scrollX + marginX,
+    elementY1 - margin + scrollY + marginY,
     8,
     8
   ]; // nw
   handlers["ne"] = [
-    elementX2 - margin + sceneState.scrollX - marginX,
-    elementY1 - margin + sceneState.scrollY + marginY,
+    elementX2 - margin + scrollX - marginX,
+    elementY1 - margin + scrollY + marginY,
     8,
     8
   ]; // ne
   handlers["sw"] = [
-    elementX1 - margin + sceneState.scrollX + marginX,
-    elementY2 - margin + sceneState.scrollY - marginY,
+    elementX1 - margin + scrollX + marginX,
+    elementY2 - margin + scrollY - marginY,
     8,
     8
   ]; // sw
   handlers["se"] = [
-    elementX2 - margin + sceneState.scrollX - marginX,
-    elementY2 - margin + sceneState.scrollY - marginY,
+    elementX2 - margin + scrollX - marginX,
+    elementY2 - margin + scrollY - marginY,
     8,
     8
   ]; // se

+ 24 - 7
src/element/resizeTest.ts

@@ -1,7 +1,7 @@
 import { ExcalidrawElement } from "./types";
-import { SceneState } from "../scene/types";
 
 import { handlerRectangles } from "./handlerRectangles";
+import { SceneScroll } from "../scene/types";
 
 type HandlerRectanglesRet = keyof ReturnType<typeof handlerRectangles>;
 
@@ -9,20 +9,20 @@ export function resizeTest(
   element: ExcalidrawElement,
   x: number,
   y: number,
-  sceneState: SceneState
+  { scrollX, scrollY }: SceneScroll
 ): HandlerRectanglesRet | false {
   if (!element.isSelected || element.type === "text") return false;
 
-  const handlers = handlerRectangles(element, sceneState);
+  const handlers = handlerRectangles(element, { scrollX, scrollY });
 
   const filter = Object.keys(handlers).filter(key => {
     const handler = handlers[key as HandlerRectanglesRet]!;
 
     return (
-      x + sceneState.scrollX >= handler[0] &&
-      x + sceneState.scrollX <= handler[0] + handler[2] &&
-      y + sceneState.scrollY >= handler[1] &&
-      y + sceneState.scrollY <= handler[1] + handler[3]
+      x + scrollX >= handler[0] &&
+      x + scrollX <= handler[0] + handler[2] &&
+      y + scrollY >= handler[1] &&
+      y + scrollY <= handler[1] + handler[3]
     );
   });
 
@@ -32,3 +32,20 @@ export function resizeTest(
 
   return false;
 }
+
+export function getElementWithResizeHandler(
+  elements: ExcalidrawElement[],
+  { x, y }: { x: number; y: number },
+  { scrollX, scrollY }: SceneScroll
+) {
+  return elements.reduce((result, element) => {
+    if (result) {
+      return result;
+    }
+    const resizeHandle = resizeTest(element, x, y, {
+      scrollX,
+      scrollY
+    });
+    return resizeHandle ? { element, resizeHandle } : null;
+  }, null as { element: ExcalidrawElement; resizeHandle: ReturnType<typeof resizeTest> } | null);
+}

+ 57 - 32
src/index.tsx

@@ -53,6 +53,7 @@ import { PanelCanvas } from "./components/panels/PanelCanvas";
 import { Panel } from "./components/Panel";
 
 import "./styles.scss";
+import { getElementWithResizeHandler } from "./element/resizeTest";
 
 const { elements } = createScene();
 const { history } = createHistory();
@@ -99,6 +100,15 @@ let lastCanvasHeight = -1;
 
 let lastMouseUp: ((e: any) => void) | null = null;
 
+export function viewportCoordsToSceneCoords(
+  { clientX, clientY }: { clientX: number; clientY: number },
+  { scrollX, scrollY }: { scrollX: number; scrollY: number }
+) {
+  const x = clientX - CANVAS_WINDOW_OFFSET_LEFT - scrollX;
+  const y = clientY - CANVAS_WINDOW_OFFSET_TOP - scrollY;
+  return { x, y };
+}
+
 export class App extends React.Component<{}, AppState> {
   canvas: HTMLCanvasElement | null = null;
   rc: RoughCanvas | null = null;
@@ -591,9 +601,7 @@ export class App extends React.Component<{}, AppState> {
           onContextMenu={e => {
             e.preventDefault();
 
-            const x =
-              e.clientX - CANVAS_WINDOW_OFFSET_LEFT - this.state.scrollX;
-            const y = e.clientY - CANVAS_WINDOW_OFFSET_TOP - this.state.scrollY;
+            const { x, y } = viewportCoordsToSceneCoords(e, this.state);
 
             const element = getElementAtPosition(elements, x, y);
             if (!element) {
@@ -670,9 +678,8 @@ export class App extends React.Component<{}, AppState> {
               this.state.scrollY
             );
 
-            const x =
-              e.clientX - CANVAS_WINDOW_OFFSET_LEFT - this.state.scrollX;
-            const y = e.clientY - CANVAS_WINDOW_OFFSET_TOP - this.state.scrollY;
+            const { x, y } = viewportCoordsToSceneCoords(e, this.state);
+
             const element = newElement(
               this.state.elementType,
               x,
@@ -684,28 +691,23 @@ export class App extends React.Component<{}, AppState> {
               1,
               100
             );
-            let resizeHandle: ReturnType<typeof resizeTest> = false;
+            type ResizeTestType = ReturnType<typeof resizeTest>;
+            let resizeHandle: ResizeTestType = false;
             let isDraggingElements = false;
             let isResizingElements = false;
             if (this.state.elementType === "selection") {
-              const resizeElement = elements.find(element => {
-                return resizeTest(element, x, y, {
-                  scrollX: this.state.scrollX,
-                  scrollY: this.state.scrollY,
-                  viewBackgroundColor: this.state.viewBackgroundColor
-                });
-              });
+              const resizeElement = getElementWithResizeHandler(
+                elements,
+                { x, y },
+                this.state
+              );
 
               this.setState({
-                resizingElement: resizeElement ? resizeElement : null
+                resizingElement: resizeElement ? resizeElement.element : null
               });
 
               if (resizeElement) {
-                resizeHandle = resizeTest(resizeElement, x, y, {
-                  scrollX: this.state.scrollX,
-                  scrollY: this.state.scrollY,
-                  viewBackgroundColor: this.state.viewBackgroundColor
-                });
+                resizeHandle = resizeElement.resizeHandle;
                 document.documentElement.style.cursor = `${resizeHandle}-resize`;
                 isResizingElements = true;
               } else {
@@ -714,7 +716,7 @@ export class App extends React.Component<{}, AppState> {
                 // If we click on something
                 if (hitElement) {
                   if (hitElement.isSelected) {
-                    // If that element is not already selected, do nothing,
+                    // If that element is already selected, do nothing,
                     // we're likely going to drag it
                   } else {
                     // We unselect every other elements unless shift is pressed
@@ -829,10 +831,8 @@ export class App extends React.Component<{}, AppState> {
                 const el = this.state.resizingElement;
                 const selectedElements = elements.filter(el => el.isSelected);
                 if (selectedElements.length === 1) {
-                  const x =
-                    e.clientX - CANVAS_WINDOW_OFFSET_LEFT - this.state.scrollX;
-                  const y =
-                    e.clientY - CANVAS_WINDOW_OFFSET_TOP - this.state.scrollY;
+                  const { x, y } = viewportCoordsToSceneCoords(e, this.state);
+
                   selectedElements.forEach(element => {
                     switch (resizeHandle) {
                       case "nw":
@@ -904,10 +904,8 @@ export class App extends React.Component<{}, AppState> {
               if (isDraggingElements) {
                 const selectedElements = elements.filter(el => el.isSelected);
                 if (selectedElements.length) {
-                  const x =
-                    e.clientX - CANVAS_WINDOW_OFFSET_LEFT - this.state.scrollX;
-                  const y =
-                    e.clientY - CANVAS_WINDOW_OFFSET_TOP - this.state.scrollY;
+                  const { x, y } = viewportCoordsToSceneCoords(e, this.state);
+
                   selectedElements.forEach(element => {
                     element.x += x - lastX;
                     element.y += y - lastY;
@@ -991,9 +989,8 @@ export class App extends React.Component<{}, AppState> {
             this.forceUpdate();
           }}
           onDoubleClick={e => {
-            const x =
-              e.clientX - CANVAS_WINDOW_OFFSET_LEFT - this.state.scrollX;
-            const y = e.clientY - CANVAS_WINDOW_OFFSET_TOP - this.state.scrollY;
+            const { x, y } = viewportCoordsToSceneCoords(e, this.state);
+
             const elementAtPosition = getElementAtPosition(elements, x, y);
 
             const element = newElement(
@@ -1066,6 +1063,34 @@ export class App extends React.Component<{}, AppState> {
               }
             });
           }}
+          onMouseMove={e => {
+            const hasDeselectedButton = Boolean(e.buttons);
+            if (hasDeselectedButton || this.state.elementType !== "selection") {
+              return;
+            }
+            const { x, y } = viewportCoordsToSceneCoords(e, this.state);
+            const resizeElement = getElementWithResizeHandler(
+              elements,
+              { x, y },
+              this.state
+            );
+            if (resizeElement && resizeElement.resizeHandle) {
+              document.documentElement.style.cursor = `${resizeElement.resizeHandle}-resize`;
+              return;
+            }
+            const hitElement = getElementAtPosition(elements, x, y);
+            if (hitElement) {
+              const resizeHandle = resizeTest(hitElement, x, y, {
+                scrollX: this.state.scrollX,
+                scrollY: this.state.scrollY
+              });
+              document.documentElement.style.cursor = resizeHandle
+                ? `${resizeHandle}-resize`
+                : `move`;
+            } else {
+              document.documentElement.style.cursor = ``;
+            }
+          }}
         />
       </div>
     );

+ 5 - 0
src/scene/types.ts

@@ -7,6 +7,11 @@ export type SceneState = {
   viewBackgroundColor: string | null;
 };
 
+export type SceneScroll = {
+  scrollX: number;
+  scrollY: number;
+};
+
 export interface Scene {
   elements: ExcalidrawTextElement[];
 }