feat: support segment midpoints in line editor (#5641)

* feat: support segment midpoints in line editor

* fix tests

* midpoints working in bezier curve

* midpoint working with non zero roughness

* calculate beizer curve control points for points >2

* unnecessary rerender

* don't show phantom points inside editor for short segments

* don't show phantom points for small curves

* improve the algo for plotting midpoints on bezier curve by taking arc lengths and doing binary search

* fix tests finally

* fix naming

* cache editor midpoints

* clear midpoint cache when undo

* fix caching

* calculate index properly when not all segments have midpoints

* make sure correct element version is fetched from cache

* chore

* fix

* direct comparison for equal points

* create arePointsEqual util

* upate name

* don't update cache except inside getter

* don't compute midpoints outside editor unless 2pointer lines

* update cache to object and burst when Zoom updated as well

* early return if midpoints not present outside editor

* don't early return

* cleanup

* Add specs

* fix
This commit is contained in:
Aakansha Doshi 2022-09-14 19:55:54 +05:30 committed by GitHub
parent c5869979c8
commit 0d1058a596
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 666 additions and 113 deletions

View file

@ -0,0 +1,60 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[` Test Linear Elements Inside editor should allow dragging line from midpoint in 2 pointer lines 1`] = `
Array [
Array [
0,
0,
],
Array [
70,
50,
],
Array [
40,
0,
],
]
`;
exports[` Test Linear Elements Inside editor should allow dragging lines from midpoints in between segments 1`] = `
Array [
Array [
0,
0,
],
Array [
85,
75,
],
Array [
70,
50,
],
Array [
105,
75,
],
Array [
40,
0,
],
]
`;
exports[` Test Linear Elements should allow dragging line from midpoint in 2 pointer lines outside editor 1`] = `
Array [
Array [
0,
0,
],
Array [
70,
50,
],
Array [
40,
0,
],
]
`;

View file

@ -10982,7 +10982,6 @@ Object {
"hoverPointIndex": -1,
"isDragging": false,
"lastUncommittedPoint": null,
"midPointHovered": false,
"pointerDownState": Object {
"lastClickedPoint": -1,
"prevSelectedPointsIndices": null,
@ -10991,6 +10990,7 @@ Object {
"x": 0,
"y": 0,
},
"segmentMidPointHoveredCoords": null,
"selectedPointsIndices": null,
"startBindingElement": "keep",
},
@ -11208,7 +11208,6 @@ Object {
"hoverPointIndex": -1,
"isDragging": false,
"lastUncommittedPoint": null,
"midPointHovered": false,
"pointerDownState": Object {
"lastClickedPoint": -1,
"prevSelectedPointsIndices": null,
@ -11217,6 +11216,7 @@ Object {
"x": 0,
"y": 0,
},
"segmentMidPointHoveredCoords": null,
"selectedPointsIndices": null,
"startBindingElement": "keep",
},
@ -11661,7 +11661,6 @@ Object {
"hoverPointIndex": -1,
"isDragging": false,
"lastUncommittedPoint": null,
"midPointHovered": false,
"pointerDownState": Object {
"lastClickedPoint": -1,
"prevSelectedPointsIndices": null,
@ -11670,6 +11669,7 @@ Object {
"x": 0,
"y": 0,
},
"segmentMidPointHoveredCoords": null,
"selectedPointsIndices": null,
"startBindingElement": "keep",
},
@ -12066,7 +12066,6 @@ Object {
"hoverPointIndex": -1,
"isDragging": false,
"lastUncommittedPoint": null,
"midPointHovered": false,
"pointerDownState": Object {
"lastClickedPoint": -1,
"prevSelectedPointsIndices": null,
@ -12075,6 +12074,7 @@ Object {
"x": 0,
"y": 0,
},
"segmentMidPointHoveredCoords": null,
"selectedPointsIndices": null,
"startBindingElement": "keep",
},

View file

@ -0,0 +1,146 @@
import ReactDOM from "react-dom";
import { ExcalidrawLinearElement } from "../element/types";
import ExcalidrawApp from "../excalidraw-app";
import { centerPoint } from "../math";
import { reseed } from "../random";
import * as Renderer from "../renderer/renderScene";
import { Keyboard } from "./helpers/ui";
import { screen } from "./test-utils";
import { render, fireEvent } from "./test-utils";
import { Point } from "../types";
import { KEYS } from "../keys";
import { LinearElementEditor } from "../element/linearElementEditor";
const renderScene = jest.spyOn(Renderer, "renderScene");
const { h } = window;
describe(" Test Linear Elements", () => {
let getByToolName: (...args: string[]) => HTMLElement;
let container: HTMLElement;
let canvas: HTMLCanvasElement;
beforeEach(async () => {
// Unmount ReactDOM from root
ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
localStorage.clear();
renderScene.mockClear();
reseed(7);
const comp = await render(<ExcalidrawApp />);
getByToolName = comp.getByToolName;
container = comp.container;
canvas = container.querySelector("canvas")!;
});
const p1: Point = [20, 20];
const p2: Point = [60, 20];
const midpoint = centerPoint(p1, p2);
const createTwoPointerLinearElement = (
type: ExcalidrawLinearElement["type"],
edge: "Sharp" | "Round" = "Sharp",
roughness: "Architect" | "Cartoonist" | "Artist" = "Architect",
) => {
const tool = getByToolName(type);
fireEvent.click(tool);
fireEvent.click(screen.getByTitle(edge));
fireEvent.click(screen.getByTitle(roughness));
fireEvent.pointerDown(canvas, { clientX: p1[0], clientY: p1[1] });
fireEvent.pointerMove(canvas, { clientX: p2[0], clientY: p2[1] });
fireEvent.pointerUp(canvas, { clientX: p2[0], clientY: p2[1] });
};
const createThreePointerLinearElement = (
type: ExcalidrawLinearElement["type"],
edge: "Sharp" | "Round" = "Sharp",
) => {
createTwoPointerLinearElement("line");
// Extending line via midpoint
fireEvent.pointerDown(canvas, {
clientX: midpoint[0],
clientY: midpoint[1],
});
fireEvent.pointerMove(canvas, {
clientX: midpoint[0] + 50,
clientY: midpoint[1] + 50,
});
fireEvent.pointerUp(canvas, {
clientX: midpoint[0] + 50,
clientY: midpoint[1] + 50,
});
};
const dragLinearElementFromPoint = (point: Point) => {
fireEvent.pointerDown(canvas, {
clientX: point[0],
clientY: point[1],
});
fireEvent.pointerMove(canvas, {
clientX: point[0] + 50,
clientY: point[1] + 50,
});
fireEvent.pointerUp(canvas, {
clientX: point[0] + 50,
clientY: point[1] + 50,
});
};
it("should allow dragging line from midpoint in 2 pointer lines outside editor", async () => {
createTwoPointerLinearElement("line");
const line = h.elements[0] as ExcalidrawLinearElement;
expect(renderScene).toHaveBeenCalledTimes(10);
expect((h.elements[0] as ExcalidrawLinearElement).points.length).toEqual(2);
// drag line from midpoint
dragLinearElementFromPoint(midpoint);
expect(renderScene).toHaveBeenCalledTimes(13);
expect(line.points.length).toEqual(3);
expect(line.points).toMatchSnapshot();
});
describe("Inside editor", () => {
it("should allow dragging line from midpoint in 2 pointer lines", async () => {
createTwoPointerLinearElement("line");
const line = h.elements[0] as ExcalidrawLinearElement;
fireEvent.click(canvas, { clientX: p1[0], clientY: p1[1] });
Keyboard.keyPress(KEYS.ENTER);
expect(h.state.editingLinearElement?.elementId).toEqual(h.elements[0].id);
// drag line from midpoint
dragLinearElementFromPoint(midpoint);
expect(line.points.length).toEqual(3);
expect(line.points).toMatchSnapshot();
});
it("should allow dragging lines from midpoints in between segments", async () => {
createThreePointerLinearElement("line");
const line = h.elements[0] as ExcalidrawLinearElement;
expect(line.points.length).toEqual(3);
fireEvent.click(canvas, { clientX: p1[0], clientY: p1[1] });
Keyboard.keyPress(KEYS.ENTER);
expect(h.state.editingLinearElement?.elementId).toEqual(h.elements[0].id);
let points = LinearElementEditor.getPointsGlobalCoordinates(line);
const firstSegmentMidpoint = centerPoint(points[0], points[1]);
// drag line via first segment midpoint
dragLinearElementFromPoint(firstSegmentMidpoint);
expect(line.points.length).toEqual(4);
// drag line from last segment midpoint
points = LinearElementEditor.getPointsGlobalCoordinates(line);
const lastSegmentMidpoint = centerPoint(points.at(-2)!, points.at(-1)!);
dragLinearElementFromPoint(lastSegmentMidpoint);
expect(line.points.length).toEqual(5);
expect(
(h.elements[0] as ExcalidrawLinearElement).points,
).toMatchSnapshot();
});
});
});