From afb1d6725f93982ce856f563794554267388f745 Mon Sep 17 00:00:00 2001 From: David Luzar Date: Fri, 24 Jan 2020 20:45:52 +0100 Subject: [PATCH] Normalize dimensions (#527) * normalize dimensions of non-linear elements * fix element type check regression --- src/element/index.ts | 7 ++- src/element/resizeTest.ts | 58 ++++++++++++++++++++++ src/element/sizeHelpers.ts | 30 ++++++++++++ src/index.tsx | 76 ++++++++++++++++------------- src/scene/getExportCanvasPreview.ts | 5 +- src/utils.ts | 4 ++ 6 files changed, 141 insertions(+), 39 deletions(-) diff --git a/src/element/index.ts b/src/element/index.ts index dbe16c286..a31a0e4b4 100644 --- a/src/element/index.ts +++ b/src/element/index.ts @@ -8,7 +8,11 @@ export { export { handlerRectangles } from "./handlerRectangles"; export { hitTest } from "./collision"; -export { resizeTest, getCursorForResizingElement } from "./resizeTest"; +export { + resizeTest, + getCursorForResizingElement, + normalizeResizeHandle, +} from "./resizeTest"; export { isTextElement } from "./typeChecks"; export { textWysiwyg } from "./textWysiwyg"; export { redrawTextBoundingBox } from "./textElement"; @@ -16,4 +20,5 @@ export { getPerfectElementSize, isInvisiblySmallElement, resizePerfectLineForNWHandler, + normalizeDimensions, } from "./sizeHelpers"; diff --git a/src/element/resizeTest.ts b/src/element/resizeTest.ts index 2c3d8a8cc..0e8c98a2d 100644 --- a/src/element/resizeTest.ts +++ b/src/element/resizeTest.ts @@ -91,3 +91,61 @@ export function getCursorForResizingElement(resizingElement: { return cursor ? `${cursor}-resize` : ""; } + +export function normalizeResizeHandle( + element: ExcalidrawElement, + resizeHandle: HandlerRectanglesRet, +): HandlerRectanglesRet { + if ( + (element.width >= 0 && element.height >= 0) || + element.type === "line" || + element.type === "arrow" + ) { + return resizeHandle; + } + + if (element.width < 0 && element.height < 0) { + switch (resizeHandle) { + case "nw": + return "se"; + case "ne": + return "sw"; + case "se": + return "nw"; + case "sw": + return "ne"; + } + } else if (element.width < 0) { + switch (resizeHandle) { + case "nw": + return "ne"; + case "ne": + return "nw"; + case "se": + return "sw"; + case "sw": + return "se"; + case "e": + return "w"; + case "w": + return "e"; + } + } else { + switch (resizeHandle) { + case "nw": + return "sw"; + case "ne": + return "se"; + case "se": + return "ne"; + case "sw": + return "nw"; + case "n": + return "s"; + case "s": + return "n"; + } + } + + return resizeHandle; +} diff --git a/src/element/sizeHelpers.ts b/src/element/sizeHelpers.ts index 273a6fd21..34c1a1848 100644 --- a/src/element/sizeHelpers.ts +++ b/src/element/sizeHelpers.ts @@ -57,3 +57,33 @@ export function resizePerfectLineForNWHandler( element.y = anchorY - element.height; } } + +/** + * @returns {boolean} whether element was normalized + */ +export function normalizeDimensions( + element: ExcalidrawElement | null, +): element is ExcalidrawElement { + if ( + !element || + (element.width >= 0 && element.height >= 0) || + element.type === "line" || + element.type === "arrow" + ) { + return false; + } + + if (element.width < 0) { + element.width = Math.abs(element.width); + element.x -= element.width; + } + + if (element.height < 0) { + element.height = Math.abs(element.height); + element.y -= element.height; + } + + element.shape = null; + + return true; +} diff --git a/src/index.tsx b/src/index.tsx index 56f14628a..8305b28e4 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -9,6 +9,7 @@ import { newTextElement, duplicateElement, resizeTest, + normalizeResizeHandle, isInvisiblySmallElement, isTextElement, textWysiwyg, @@ -16,6 +17,7 @@ import { getCursorForResizingElement, getPerfectElementSize, resizePerfectLineForNWHandler, + normalizeDimensions, } from "./element"; import { clearSelection, @@ -38,7 +40,7 @@ import { renderScene } from "./renderer"; import { AppState } from "./types"; import { ExcalidrawElement } from "./element/types"; -import { isInputLike, debounce, capitalizeString } from "./utils"; +import { isInputLike, debounce, capitalizeString, distance } from "./utils"; import { KEYS, isArrowKey } from "./keys"; import { findShapeByKey, shapesShortcutKeys, SHAPES } from "./shapes"; @@ -776,6 +778,9 @@ export class App extends React.Component { const { x, y } = viewportCoordsToSceneCoords(e, this.state); + const originX = x; + const originY = y; + let element = newElement( this.state.elementType, x, @@ -945,16 +950,15 @@ export class App extends React.Component { const deltaX = x - lastX; const deltaY = y - lastY; const element = selectedElements[0]; + const isLinear = + element.type === "line" || element.type === "arrow"; switch (resizeHandle) { case "nw": element.width -= deltaX; element.x += deltaX; if (e.shiftKey) { - if ( - element.type === "arrow" || - element.type === "line" - ) { + if (isLinear) { resizePerfectLineForNWHandler(element, x, y); } else { element.y += element.height - element.width; @@ -986,10 +990,7 @@ export class App extends React.Component { break; case "se": if (e.shiftKey) { - if ( - element.type === "arrow" || - element.type === "line" - ) { + if (isLinear) { const { width, height } = getPerfectElementSize( element.type, x - element.x, @@ -1022,6 +1023,11 @@ export class App extends React.Component { break; } + if (resizeHandle) { + resizeHandle = normalizeResizeHandle(element, resizeHandle); + } + normalizeDimensions(element); + document.documentElement.style.cursor = getCursorForResizingElement( { element, resizeHandle }, ); @@ -1064,33 +1070,35 @@ export class App extends React.Component { const draggingElement = this.state.draggingElement; if (!draggingElement) return; - let width = - e.clientX - - CANVAS_WINDOW_OFFSET_LEFT - - draggingElement.x - - this.state.scrollX; - let height = - e.clientY - - CANVAS_WINDOW_OFFSET_TOP - - draggingElement.y - - this.state.scrollY; + const { x, y } = viewportCoordsToSceneCoords(e, this.state); + + let width = distance(originX, x); + let height = distance(originY, y); + + const isLinear = + this.state.elementType === "line" || + this.state.elementType === "arrow"; + + if (isLinear && x < originX) width = -width; + if (isLinear && y < originY) height = -height; if (e.shiftKey) { - let { - width: newWidth, - height: newHeight, - } = getPerfectElementSize( + ({ width, height } = getPerfectElementSize( this.state.elementType, width, - height, - ); - draggingElement.width = newWidth; - draggingElement.height = newHeight; - } else { - draggingElement.width = width; - draggingElement.height = height; + !isLinear && y < originY ? -height : height, + )); + + if (!isLinear && height < 0) height = -height; } + if (!isLinear) { + draggingElement.x = x < originX ? originX - width : originX; + draggingElement.y = y < originY ? originY - height : originY; + } + + draggingElement.width = width; + draggingElement.height = height; draggingElement.shape = null; if (this.state.elementType === "selection") { @@ -1136,6 +1144,10 @@ export class App extends React.Component { return; } + if (normalizeDimensions(draggingElement)) { + this.forceUpdate(); + } + if (resizingElement && isInvisiblySmallElement(resizingElement)) { elements = elements.filter(el => el.id !== resizingElement.id); } @@ -1349,10 +1361,6 @@ export class App extends React.Component { const minX = Math.min(...parsedElements.map(element => element.x)); const minY = Math.min(...parsedElements.map(element => element.y)); - const distance = (x: number, y: number) => { - return Math.abs(x > y ? x - y : y - x); - }; - parsedElements.forEach(parsedElement => { const [x1, y1, x2, y2] = getElementAbsoluteCoords(parsedElement); subCanvasX1 = Math.min(subCanvasX1, x1); diff --git a/src/scene/getExportCanvasPreview.ts b/src/scene/getExportCanvasPreview.ts index e6ab59c4f..fef30c119 100644 --- a/src/scene/getExportCanvasPreview.ts +++ b/src/scene/getExportCanvasPreview.ts @@ -2,6 +2,7 @@ import rough from "roughjs/bin/rough"; import { ExcalidrawElement } from "../element/types"; import { getElementAbsoluteCoords } from "../element/bounds"; import { renderScene } from "../renderer/renderScene"; +import { distance } from "../utils"; export function getExportCanvasPreview( elements: readonly ExcalidrawElement[], @@ -40,10 +41,6 @@ export function getExportCanvasPreview( subCanvasY2 = Math.max(subCanvasY2, y2); }); - function distance(x: number, y: number) { - return Math.abs(x > y ? x - y : y - x); - } - const width = distance(subCanvasX1, subCanvasX2) + exportPadding * 2; const height = distance(subCanvasY1, subCanvasY2) + exportPadding * 2; const tempCanvas: any = createCanvas(width, height); diff --git a/src/utils.ts b/src/utils.ts index b99fd646a..d473cb47f 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -86,3 +86,7 @@ export function removeSelection() { selection.removeAllRanges(); } } + +export function distance(x: number, y: number) { + return Math.abs(x > y ? x - y : y - x); +}