Multi Point Lines (based on Multi Point Arrows) (#660)

* Enable multi points in lines

* Stop retrieving arrow points for lines

* Migrate lines to new spec during load

* Clean up and refactor some code

- Normalize shape dimensions during load
- Rename getArrowAbsoluteBounds

* Fix linter issues
This commit is contained in:
Gasim Gasimzada 2020-02-04 13:45:22 +04:00 committed by GitHub
parent f70bd0081c
commit dab35c9033
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 102 additions and 178 deletions

View file

@ -17,51 +17,27 @@ const _ce = ({ x, y, w, h }: { x: number; y: number; w: number; h: number }) =>
} as ExcalidrawElement);
describe("getElementAbsoluteCoords", () => {
it("test x1 coordinate if width is positive or zero", () => {
it("test x1 coordinate", () => {
const [x1] = getElementAbsoluteCoords(_ce({ x: 10, y: 0, w: 10, h: 0 }));
expect(x1).toEqual(10);
});
it("test x1 coordinate if width is negative", () => {
const [x1] = getElementAbsoluteCoords(_ce({ x: 20, y: 0, w: -10, h: 0 }));
expect(x1).toEqual(10);
});
it("test x2 coordinate if width is positive or zero", () => {
it("test x2 coordinate", () => {
const [, , x2] = getElementAbsoluteCoords(
_ce({ x: 10, y: 0, w: 10, h: 0 }),
);
expect(x2).toEqual(20);
});
it("test x2 coordinate if width is negative", () => {
const [, , x2] = getElementAbsoluteCoords(
_ce({ x: 10, y: 0, w: -10, h: 0 }),
);
expect(x2).toEqual(10);
});
it("test y1 coordinate if height is positive or zero", () => {
it("test y1 coordinate", () => {
const [, y1] = getElementAbsoluteCoords(_ce({ x: 0, y: 10, w: 0, h: 10 }));
expect(y1).toEqual(10);
});
it("test y1 coordinate if height is negative", () => {
const [, y1] = getElementAbsoluteCoords(_ce({ x: 0, y: 20, w: 0, h: -10 }));
expect(y1).toEqual(10);
});
it("test y2 coordinate if height is positive or zero", () => {
it("test y2 coordinate", () => {
const [, , , y2] = getElementAbsoluteCoords(
_ce({ x: 0, y: 10, w: 0, h: 10 }),
);
expect(y2).toEqual(20);
});
it("test y2 coordinate if height is negative", () => {
const [, , , y2] = getElementAbsoluteCoords(
_ce({ x: 0, y: 10, w: 0, h: -10 }),
);
expect(y2).toEqual(10);
});
});

View file

@ -5,17 +5,15 @@ import { Point } from "roughjs/bin/geometry";
// If the element is created from right to left, the width is going to be negative
// This set of functions retrieves the absolute position of the 4 points.
// We can't just always normalize it since we need to remember the fact that an arrow
// is pointing left or right.
export function getElementAbsoluteCoords(element: ExcalidrawElement) {
if (element.type === "arrow") {
return getArrowAbsoluteBounds(element);
if (element.type === "arrow" || element.type === "line") {
return getLinearElementAbsoluteBounds(element);
}
return [
element.width >= 0 ? element.x : element.x + element.width, // x1
element.height >= 0 ? element.y : element.y + element.height, // y1
element.width >= 0 ? element.x + element.width : element.x, // x2
element.height >= 0 ? element.y + element.height : element.y, // y2
element.x,
element.y,
element.x + element.width,
element.y + element.height,
];
}
@ -34,7 +32,7 @@ export function getDiamondPoints(element: ExcalidrawElement) {
return [topX, topY, rightX, rightY, bottomX, bottomY, leftX, leftY];
}
export function getArrowAbsoluteBounds(element: ExcalidrawElement) {
export function getLinearElementAbsoluteBounds(element: ExcalidrawElement) {
if (element.points.length < 2 || !element.shape) {
const { minX, minY, maxX, maxY } = element.points.reduce(
(limits, [x, y]) => {
@ -58,7 +56,8 @@ export function getArrowAbsoluteBounds(element: ExcalidrawElement) {
const shape = element.shape as Drawable[];
const ops = shape[1].sets[0].ops;
// first element is always the curve
const ops = shape[0].sets[0].ops;
let currentP: Point = [0, 0];
@ -138,15 +137,6 @@ export function getArrowPoints(element: ExcalidrawElement) {
return [x2, y2, x3, y3, x4, y4];
}
export function getLinePoints(element: ExcalidrawElement) {
const x1 = 0;
const y1 = 0;
const x2 = element.width;
const y2 = element.height;
return [x1, y1, x2, y2];
}
export function getCommonBounds(elements: readonly ExcalidrawElement[]) {
let minX = Infinity;
let maxX = -Infinity;

View file

@ -4,8 +4,7 @@ import { ExcalidrawElement } from "./types";
import {
getDiamondPoints,
getElementAbsoluteCoords,
getLinePoints,
getArrowAbsoluteBounds,
getLinearElementAbsoluteBounds,
} from "./bounds";
import { Point } from "roughjs/bin/geometry";
import { Drawable, OpSet } from "roughjs/bin/core";
@ -148,18 +147,13 @@ export function hitTest(
distanceBetweenPointAndSegment(x, y, leftX, leftY, topX, topY) <
lineThreshold
);
} else if (element.type === "arrow") {
} else if (element.type === "arrow" || element.type === "line") {
if (!element.shape) {
return false;
}
const shape = element.shape as Drawable[];
// If shape does not consist of curve and two line segments
// for arrow shape, return false
if (shape.length < 3) {
return false;
}
const [x1, y1, x2, y2] = getArrowAbsoluteBounds(element);
const [x1, y1, x2, y2] = getLinearElementAbsoluteBounds(element);
if (x < x1 || y < y1 - 10 || x > x2 || y > y2 + 10) {
return false;
}
@ -167,19 +161,8 @@ export function hitTest(
const relX = x - element.x;
const relY = y - element.y;
// hit test curve and lien segments for arrow
return (
hitTestRoughShape(shape[0].sets, relX, relY) ||
hitTestRoughShape(shape[1].sets, relX, relY) ||
hitTestRoughShape(shape[2].sets, relX, relY)
);
} else if (element.type === "line") {
const [x1, y1, x2, y2] = getLinePoints(element);
// The computation is done at the origin, we need to add a translation
x -= element.x;
y -= element.y;
return distanceBetweenPointAndSegment(x, y, x1, y1, x2, y2) < lineThreshold;
// hit thest all "subshapes" of the linear element
return shape.some(s => hitTestRoughShape(s.sets, relX, relY));
} else if (element.type === "text") {
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);

View file

@ -1,6 +1,6 @@
import { ExcalidrawElement } from "./types";
import { SceneScroll } from "../scene/types";
import { getArrowAbsoluteBounds } from "./bounds";
import { getLinearElementAbsoluteBounds } from "./bounds";
type Sides = "n" | "s" | "w" | "e" | "nw" | "ne" | "sw" | "se";
@ -16,10 +16,13 @@ export function handlerRectangles(
let marginY = -8;
const minimumSize = 40;
if (element.type === "arrow") {
[elementX1, elementY1, elementX2, elementY2] = getArrowAbsoluteBounds(
element,
);
if (element.type === "arrow" || element.type === "line") {
[
elementX1,
elementY1,
elementX2,
elementY2,
] = getLinearElementAbsoluteBounds(element);
} else {
elementX1 = element.x;
elementX2 = element.x + element.width;
@ -90,12 +93,7 @@ export function handlerRectangles(
8,
]; // se
if (element.type === "line") {
return {
nw: handlers.nw,
se: handlers.se,
} as typeof handlers;
} else if (element.type === "arrow") {
if (element.type === "arrow" || element.type === "line") {
if (element.points.length === 2) {
// only check the last point because starting point is always (0,0)
const [, p1] = element.points;

View file

@ -4,8 +4,7 @@ export {
getCommonBounds,
getDiamondPoints,
getArrowPoints,
getLinePoints,
getArrowAbsoluteBounds,
getLinearElementAbsoluteBounds,
} from "./bounds";
export { handlerRectangles } from "./handlerRectangles";

View file

@ -1,6 +1,9 @@
import { ExcalidrawElement } from "./types";
export function isInvisiblySmallElement(element: ExcalidrawElement): boolean {
if (element.type === "arrow" || element.type === "line") {
return element.points.length === 0;
}
return element.width === 0 && element.height === 0;
}