diff --git a/src/element/handlerRectangles.ts b/src/element/handlerRectangles.ts index 6e01c9d8..98575110 100644 --- a/src/element/handlerRectangles.ts +++ b/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 diff --git a/src/element/resizeTest.ts b/src/element/resizeTest.ts index a74a2fec..51ecf7ef 100644 --- a/src/element/resizeTest.ts +++ b/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; @@ -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 } | null); +} diff --git a/src/index.tsx b/src/index.tsx index 2ff0dc08..9e503b4a 100644 --- a/src/index.tsx +++ b/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 = false; + type ResizeTestType = ReturnType; + 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 = ``; + } + }} /> ); diff --git a/src/scene/types.ts b/src/scene/types.ts index 41a2f1b2..c8904dca 100644 --- a/src/scene/types.ts +++ b/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[]; }