Normalize dimensions (#527)

* normalize dimensions of non-linear elements

* fix element type check regression
This commit is contained in:
David Luzar 2020-01-24 20:45:52 +01:00 committed by GitHub
parent d65e90209c
commit afb1d6725f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 141 additions and 39 deletions

View File

@ -8,7 +8,11 @@ export {
export { handlerRectangles } from "./handlerRectangles"; export { handlerRectangles } from "./handlerRectangles";
export { hitTest } from "./collision"; export { hitTest } from "./collision";
export { resizeTest, getCursorForResizingElement } from "./resizeTest"; export {
resizeTest,
getCursorForResizingElement,
normalizeResizeHandle,
} from "./resizeTest";
export { isTextElement } from "./typeChecks"; export { isTextElement } from "./typeChecks";
export { textWysiwyg } from "./textWysiwyg"; export { textWysiwyg } from "./textWysiwyg";
export { redrawTextBoundingBox } from "./textElement"; export { redrawTextBoundingBox } from "./textElement";
@ -16,4 +20,5 @@ export {
getPerfectElementSize, getPerfectElementSize,
isInvisiblySmallElement, isInvisiblySmallElement,
resizePerfectLineForNWHandler, resizePerfectLineForNWHandler,
normalizeDimensions,
} from "./sizeHelpers"; } from "./sizeHelpers";

View File

@ -91,3 +91,61 @@ export function getCursorForResizingElement(resizingElement: {
return cursor ? `${cursor}-resize` : ""; 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;
}

View File

@ -57,3 +57,33 @@ export function resizePerfectLineForNWHandler(
element.y = anchorY - element.height; 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;
}

View File

@ -9,6 +9,7 @@ import {
newTextElement, newTextElement,
duplicateElement, duplicateElement,
resizeTest, resizeTest,
normalizeResizeHandle,
isInvisiblySmallElement, isInvisiblySmallElement,
isTextElement, isTextElement,
textWysiwyg, textWysiwyg,
@ -16,6 +17,7 @@ import {
getCursorForResizingElement, getCursorForResizingElement,
getPerfectElementSize, getPerfectElementSize,
resizePerfectLineForNWHandler, resizePerfectLineForNWHandler,
normalizeDimensions,
} from "./element"; } from "./element";
import { import {
clearSelection, clearSelection,
@ -38,7 +40,7 @@ import { renderScene } from "./renderer";
import { AppState } from "./types"; import { AppState } from "./types";
import { ExcalidrawElement } from "./element/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 { KEYS, isArrowKey } from "./keys";
import { findShapeByKey, shapesShortcutKeys, SHAPES } from "./shapes"; import { findShapeByKey, shapesShortcutKeys, SHAPES } from "./shapes";
@ -776,6 +778,9 @@ export class App extends React.Component<any, AppState> {
const { x, y } = viewportCoordsToSceneCoords(e, this.state); const { x, y } = viewportCoordsToSceneCoords(e, this.state);
const originX = x;
const originY = y;
let element = newElement( let element = newElement(
this.state.elementType, this.state.elementType,
x, x,
@ -945,16 +950,15 @@ export class App extends React.Component<any, AppState> {
const deltaX = x - lastX; const deltaX = x - lastX;
const deltaY = y - lastY; const deltaY = y - lastY;
const element = selectedElements[0]; const element = selectedElements[0];
const isLinear =
element.type === "line" || element.type === "arrow";
switch (resizeHandle) { switch (resizeHandle) {
case "nw": case "nw":
element.width -= deltaX; element.width -= deltaX;
element.x += deltaX; element.x += deltaX;
if (e.shiftKey) { if (e.shiftKey) {
if ( if (isLinear) {
element.type === "arrow" ||
element.type === "line"
) {
resizePerfectLineForNWHandler(element, x, y); resizePerfectLineForNWHandler(element, x, y);
} else { } else {
element.y += element.height - element.width; element.y += element.height - element.width;
@ -986,10 +990,7 @@ export class App extends React.Component<any, AppState> {
break; break;
case "se": case "se":
if (e.shiftKey) { if (e.shiftKey) {
if ( if (isLinear) {
element.type === "arrow" ||
element.type === "line"
) {
const { width, height } = getPerfectElementSize( const { width, height } = getPerfectElementSize(
element.type, element.type,
x - element.x, x - element.x,
@ -1022,6 +1023,11 @@ export class App extends React.Component<any, AppState> {
break; break;
} }
if (resizeHandle) {
resizeHandle = normalizeResizeHandle(element, resizeHandle);
}
normalizeDimensions(element);
document.documentElement.style.cursor = getCursorForResizingElement( document.documentElement.style.cursor = getCursorForResizingElement(
{ element, resizeHandle }, { element, resizeHandle },
); );
@ -1064,33 +1070,35 @@ export class App extends React.Component<any, AppState> {
const draggingElement = this.state.draggingElement; const draggingElement = this.state.draggingElement;
if (!draggingElement) return; if (!draggingElement) return;
let width = const { x, y } = viewportCoordsToSceneCoords(e, this.state);
e.clientX -
CANVAS_WINDOW_OFFSET_LEFT - let width = distance(originX, x);
draggingElement.x - let height = distance(originY, y);
this.state.scrollX;
let height = const isLinear =
e.clientY - this.state.elementType === "line" ||
CANVAS_WINDOW_OFFSET_TOP - this.state.elementType === "arrow";
draggingElement.y -
this.state.scrollY; if (isLinear && x < originX) width = -width;
if (isLinear && y < originY) height = -height;
if (e.shiftKey) { if (e.shiftKey) {
let { ({ width, height } = getPerfectElementSize(
width: newWidth,
height: newHeight,
} = getPerfectElementSize(
this.state.elementType, this.state.elementType,
width, width,
height, !isLinear && y < originY ? -height : height,
); ));
draggingElement.width = newWidth;
draggingElement.height = newHeight; if (!isLinear && height < 0) height = -height;
} else {
draggingElement.width = width;
draggingElement.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; draggingElement.shape = null;
if (this.state.elementType === "selection") { if (this.state.elementType === "selection") {
@ -1136,6 +1144,10 @@ export class App extends React.Component<any, AppState> {
return; return;
} }
if (normalizeDimensions(draggingElement)) {
this.forceUpdate();
}
if (resizingElement && isInvisiblySmallElement(resizingElement)) { if (resizingElement && isInvisiblySmallElement(resizingElement)) {
elements = elements.filter(el => el.id !== resizingElement.id); elements = elements.filter(el => el.id !== resizingElement.id);
} }
@ -1349,10 +1361,6 @@ export class App extends React.Component<any, AppState> {
const minX = Math.min(...parsedElements.map(element => element.x)); const minX = Math.min(...parsedElements.map(element => element.x));
const minY = Math.min(...parsedElements.map(element => element.y)); 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 => { parsedElements.forEach(parsedElement => {
const [x1, y1, x2, y2] = getElementAbsoluteCoords(parsedElement); const [x1, y1, x2, y2] = getElementAbsoluteCoords(parsedElement);
subCanvasX1 = Math.min(subCanvasX1, x1); subCanvasX1 = Math.min(subCanvasX1, x1);

View File

@ -2,6 +2,7 @@ import rough from "roughjs/bin/rough";
import { ExcalidrawElement } from "../element/types"; import { ExcalidrawElement } from "../element/types";
import { getElementAbsoluteCoords } from "../element/bounds"; import { getElementAbsoluteCoords } from "../element/bounds";
import { renderScene } from "../renderer/renderScene"; import { renderScene } from "../renderer/renderScene";
import { distance } from "../utils";
export function getExportCanvasPreview( export function getExportCanvasPreview(
elements: readonly ExcalidrawElement[], elements: readonly ExcalidrawElement[],
@ -40,10 +41,6 @@ export function getExportCanvasPreview(
subCanvasY2 = Math.max(subCanvasY2, y2); 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 width = distance(subCanvasX1, subCanvasX2) + exportPadding * 2;
const height = distance(subCanvasY1, subCanvasY2) + exportPadding * 2; const height = distance(subCanvasY1, subCanvasY2) + exportPadding * 2;
const tempCanvas: any = createCanvas(width, height); const tempCanvas: any = createCanvas(width, height);

View File

@ -86,3 +86,7 @@ export function removeSelection() {
selection.removeAllRanges(); selection.removeAllRanges();
} }
} }
export function distance(x: number, y: number) {
return Math.abs(x > y ? x - y : y - x);
}