Feature: Multi Point Arrows (#338)

* Add points to arrow on double click

* Use line generator instead of path to generate line segments

* Switch color of the circle when it is on an existing point in the segment

* Check point against both ends of the line segment to find collinearity

* Keep drawing the arrow based on mouse position until shape is changed

* Always select the arrow when in multi element mode

* Use curves instead of lines when drawing arrow points

* Add basic collision detection with some debug points

* Use roughjs shape when performing hit testing

* Draw proper handler rectangles for arrows

* Add argument to renderScene in export

* Globally resize all points on the arrow when bounds are resized

* Hide handler rectangles if an arrow has no size

- Allow continuing adding arrows when selected element is deleted

* Add dragging functionality to arrows

* Add SHIFT functionality to two point arrows

- Fix arrow positions when scrolling
- Revert the element back to selection when not in multi select mode

* Clean app state for export (JSON)

* Set curve options manually instead of using global options

- For some reason, this fixed the flickering issue in all shapes when arrows are rendered

* Set proper options for the arrow

* Increaase accuracy of hit testing arrows

- Additionally, skip testing if point is outside the domain of arrow and each curve

* Calculate bounding box of arrow based on roughjs curves

- Remove domain check per curve

* Change bounding box threshold to 10 and remove unnecessary code

* Fix handler rectangles for 2 and multi point arrows

- Fix margins of handler rectangles when using arrows
- Show handler rectangles in endpoints of 2-point arrows

* Remove unnecessary values from app state for export

* Use `resetTransform` instead of "retranslating" canvas space after each element rendering

* Allow resizing 2-point arrows

- Fix position of one of the handler rectangles

* refactor variable initialization

* Refactored to extract out mult-point generation to the abstracted function

* prevent dragging on arrow creation if under threshold

* Finalize selection during multi element mode when ENTER or ESC is clicked

* Set dragging element to null when finalizing

* Remove pathSegmentCircle from code

* Check if element is any "non-value" instead of NULL

* Show two points on any two point arrow and fix visibility of arrows during scroll

* Resume recording when done with drawing

- When deleting a multi select element, revert back to selection element type

* Resize arrow starting points perfectly

* Fix direction of arrow resize based for NW

* Resume recording history when there is more than one arrow

* Set dragging element to NULL when element is not locked

* Blur active element when finalizing

* Disable undo/redo for multielement, editingelement, and resizing element

- Allow undoing parts of the arrow

* Disable element visibility for arrow

* Use points array for arrow bounds when bezier curve shape is not available

Co-authored-by: David Luzar <luzar.david@gmail.com>
Co-authored-by: Preet <833927+pshihn@users.noreply.github.com>
This commit is contained in:
Gasim Gasimzada 2020-01-31 21:16:33 +04:00 committed by GitHub
parent 9a17abcb34
commit 16263e942b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 769 additions and 110 deletions

View file

@ -7,6 +7,7 @@ import {
} from "../element/bounds";
import { RoughCanvas } from "roughjs/bin/canvas";
import { Drawable } from "roughjs/bin/core";
import { Point } from "roughjs/bin/geometry";
import { RoughSVG } from "roughjs/bin/svg";
import { RoughGenerator } from "roughjs/bin/generator";
import { SVG_NS } from "../utils";
@ -89,18 +90,23 @@ function generateElement(
);
break;
case "arrow": {
const [x1, y1, x2, y2, x3, y3, x4, y4] = getArrowPoints(element);
const [x2, y2, x3, y3, x4, y4] = getArrowPoints(element);
const options = {
stroke: element.strokeColor,
strokeWidth: element.strokeWidth,
roughness: element.roughness,
seed: element.seed,
};
// points array can be empty in the beginning, so it is important to add
// initial position to it
const points: Point[] = element.points.length
? element.points
: [[0, 0]];
element.shape = [
// \
generator.line(x3, y3, x2, y2, options),
// -----
generator.line(x1, y1, x2, y2, options),
generator.curve(points, options),
// /
generator.line(x4, y4, x2, y2, options),
];
@ -169,7 +175,6 @@ export function renderElement(
context.fillStyle = fillStyle;
context.font = font;
context.globalAlpha = 1;
break;
} else {
throw new Error("Unimplemented type " + element.type);
}

View file

@ -76,10 +76,7 @@ export function renderScene(
element.y + sceneState.scrollY,
);
renderElement(element, rc, context);
context.translate(
-element.x - sceneState.scrollX,
-element.y - sceneState.scrollY,
);
context.resetTransform();
});
if (renderSelection) {
@ -107,9 +104,11 @@ export function renderScene(
if (selectedElements.length === 1 && selectedElements[0].type !== "text") {
const handlers = handlerRectangles(selectedElements[0], sceneState);
Object.values(handlers).forEach(handler => {
context.strokeRect(handler[0], handler[1], handler[2], handler[3]);
});
Object.values(handlers)
.filter(handler => handler !== undefined)
.forEach(handler => {
context.strokeRect(handler[0], handler[1], handler[2], handler[3]);
});
}
}
@ -149,11 +148,20 @@ function isVisibleElement(
canvasHeight: number,
) {
let [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
x1 += scrollX;
y1 += scrollY;
x2 += scrollX;
y2 += scrollY;
return x2 >= 0 && x1 <= canvasWidth && y2 >= 0 && y1 <= canvasHeight;
if (element.type !== "arrow") {
x1 += scrollX;
y1 += scrollY;
x2 += scrollX;
y2 += scrollY;
return x2 >= 0 && x1 <= canvasWidth && y2 >= 0 && y1 <= canvasHeight;
} else {
return (
x2 + scrollX >= 0 &&
x1 + scrollX <= canvasWidth &&
y2 + scrollY >= 0 &&
y1 + scrollY <= canvasHeight
);
}
}
// This should be only called for exporting purposes