feat: Support labels for arrow 🔥 (#5723)

* feat: support arrow with text

* render arrow -> clear rect-> render text

* move bound text when linear elements move

* fix centering cursor when linear element rotated

* fix y coord when new line added and container has 3 points

* update text position when 2nd point moved

* support adding label on top of 2nd point when 3 points are present

* change linear element editor shortcut to cmd+enter and fix tests

* scale bound text points when resizing via bounding box

* ohh yeah rotation works :)

* fix coords when updating text properties

* calculate new position after rotation always from original position

* rotate the bound text by same angle as parent

* don't rotate text and make sure dimensions and coords are always calculated from original point

* hardcoding the text width for now

* Move the linear element when bound text hit

* Rotation working yaay

* consider text element angle when editing

* refactor

* update x2 coords if needed when text updated

* simplify

* consider bound text to be part of bounding box when hit

* show bounding box correctly when multiple element selected

* fix typo

* support rotating multiple elements

* support multiple element resizing

* shift bound text to mid point when odd points

* Always render linear element handles inside editor after element rendered so point is visible for bound text

* Delete bound text when point attached to it deleted

* move bound to mid segement mid point when points are even

* shift bound text when points nearby deleted and handle segment deletion

* Resize working :)

* more resize fixes

* don't update cache-its breaking delete points, look for better soln

* update mid point cache for bound elements when updated

* introduce wrapping when resizing

* wrap when resize for 2 pointer linear elements

* support adding text for linear elements with more than 3 points

* export to svg  working :)

* clip from nearest enclosing element with non transparent color if present when exporting and fill with correct color in canvas

* fix snap

* use visible elements

* Make export to svg work with Mask :)

* remove id

* mask canvas linear element area where label is added

* decide the position of bound text during render

* fix coords when editing

* fix multiple resize

* update cache when bound text version changes

* fix masking when rotated

* render text in correct position in preview

* remove unnecessary code

* fix masking when rotating linear element

* fix masking with zoom

* fix mask in preview for export

* fix offsets in export view

* fix coords on svg export

* fix mask when element rotated in svg

* enable double-click to enter text

* fix hint

* Position cursor correctly and text dimensiosn when height of element is negative

* don't allow 2 pointer linear element with bound text width to go beyond min width

* code cleanup

* fix freedraw

* Add padding

* don't show vertical align action for linear element containers

* Add specs for getBoundTextElementPosition

* more specs

* move some utils to linearElementEditor.ts

* remove only :p

* check absoulte coods in test

* Add test to hide vertical align for linear eleemnt with bound text

* improve export preview

* support labels only for arrows

* spec

* fix large texts

* fix tests

* fix zooming

* enter line editor with cmd+double click

* Allow points to move beyond min width/height for 2 pointer arrow with bound text

* fix hint for line editing

* attempt to fix arrow getting deselected

* fix hint and shortcut

* Add padding of 5px when creating bound text and add spec

* Wrap bound text when arrow binding containers moved

* Add spec

* remove

* set boundTextElementVersion to null if not present

* dont use cache when version mismatch

* Add a padding of 5px vertically when creating text

* Add box sizing content box

* Set bound elements when text element created to fix the padding

* fix zooming in editor

* fix zoom in export

* remove globalCompositeOperation and use clearRect instead of fillRect
This commit is contained in:
Aakansha Doshi 2022-12-05 21:03:13 +05:30 committed by GitHub
parent 1933116261
commit 760fd7b3a6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 1668 additions and 363 deletions

View file

@ -4,6 +4,7 @@ import {
Arrowhead,
ExcalidrawFreeDrawElement,
NonDeleted,
ExcalidrawTextElementWithContainer,
} from "./types";
import { distance2d, rotate } from "../math";
import rough from "roughjs/bin/rough";
@ -13,8 +14,15 @@ import {
getShapeForElement,
generateRoughOptions,
} from "../renderer/renderElement";
import { isFreeDrawElement, isLinearElement } from "./typeChecks";
import {
isArrowElement,
isFreeDrawElement,
isLinearElement,
isTextElement,
} from "./typeChecks";
import { rescalePoints } from "../points";
import { getBoundTextElement, getContainerElement } from "./textElement";
import { LinearElementEditor } from "./linearElementEditor";
// x and y position of top left corner, x and y position of bottom right corner
export type Bounds = readonly [number, number, number, number];
@ -24,17 +32,39 @@ type MaybeQuadraticSolution = [number | null, number | null] | false;
// This set of functions retrieves the absolute position of the 4 points.
export const getElementAbsoluteCoords = (
element: ExcalidrawElement,
): Bounds => {
includeBoundText: boolean = false,
): [number, number, number, number, number, number] => {
if (isFreeDrawElement(element)) {
return getFreeDrawElementAbsoluteCoords(element);
} else if (isLinearElement(element)) {
return getLinearElementAbsoluteCoords(element);
return LinearElementEditor.getElementAbsoluteCoords(
element,
includeBoundText,
);
} else if (isTextElement(element)) {
const container = getContainerElement(element);
if (isArrowElement(container)) {
const coords = LinearElementEditor.getBoundTextElementPosition(
container,
element as ExcalidrawTextElementWithContainer,
);
return [
coords.x,
coords.y,
coords.x + element.width,
coords.y + element.height,
coords.x + element.width / 2,
coords.y + element.height / 2,
];
}
}
return [
element.x,
element.y,
element.x + element.width,
element.y + element.height,
element.x + element.width / 2,
element.y + element.height / 2,
];
};
@ -159,7 +189,7 @@ const getCubicBezierCurveBound = (
return [minX, minY, maxX, maxY];
};
const getMinMaxXYFromCurvePathOps = (
export const getMinMaxXYFromCurvePathOps = (
ops: Op[],
transformXY?: (x: number, y: number) => [number, number],
): [number, number, number, number] => {
@ -230,59 +260,13 @@ const getBoundsFromPoints = (
const getFreeDrawElementAbsoluteCoords = (
element: ExcalidrawFreeDrawElement,
): [number, number, number, number] => {
): [number, number, number, number, number, number] => {
const [minX, minY, maxX, maxY] = getBoundsFromPoints(element.points);
return [
minX + element.x,
minY + element.y,
maxX + element.x,
maxY + element.y,
];
};
const getLinearElementAbsoluteCoords = (
element: ExcalidrawLinearElement,
): [number, number, number, number] => {
let coords: [number, number, number, number];
if (element.points.length < 2 || !getShapeForElement(element)) {
// XXX this is just a poor estimate and not very useful
const { minX, minY, maxX, maxY } = element.points.reduce(
(limits, [x, y]) => {
limits.minY = Math.min(limits.minY, y);
limits.minX = Math.min(limits.minX, x);
limits.maxX = Math.max(limits.maxX, x);
limits.maxY = Math.max(limits.maxY, y);
return limits;
},
{ minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity },
);
coords = [
minX + element.x,
minY + element.y,
maxX + element.x,
maxY + element.y,
];
} else {
const shape = getShapeForElement(element)!;
// first element is always the curve
const ops = getCurvePathOps(shape[0]);
const [minX, minY, maxX, maxY] = getMinMaxXYFromCurvePathOps(ops);
coords = [
minX + element.x,
minY + element.y,
maxX + element.x,
maxY + element.y,
];
}
return coords;
const x1 = minX + element.x;
const y1 = minY + element.y;
const x2 = maxX + element.x;
const y2 = maxY + element.y;
return [x1, y1, x2, y2, (x1 + x2) / 2, (y1 + y2) / 2];
};
export const getArrowheadPoints = (
@ -420,7 +404,23 @@ const getLinearElementRotatedBounds = (
cy,
element.angle,
);
return [x, y, x, y];
let coords: [number, number, number, number] = [x, y, x, y];
const boundTextElement = getBoundTextElement(element);
if (boundTextElement) {
const coordsWithBoundText = LinearElementEditor.getMinMaxXYWithBoundText(
element,
[x, y, x, y],
boundTextElement,
);
coords = [
coordsWithBoundText[0],
coordsWithBoundText[1],
coordsWithBoundText[2],
coordsWithBoundText[3],
];
}
return coords;
}
// first element is always the curve
@ -429,8 +429,28 @@ const getLinearElementRotatedBounds = (
const ops = getCurvePathOps(shape);
const transformXY = (x: number, y: number) =>
rotate(element.x + x, element.y + y, cx, cy, element.angle);
return getMinMaxXYFromCurvePathOps(ops, transformXY);
const res = getMinMaxXYFromCurvePathOps(ops, transformXY);
let coords: [number, number, number, number] = [
res[0],
res[1],
res[2],
res[3],
];
const boundTextElement = getBoundTextElement(element);
if (boundTextElement) {
const coordsWithBoundText = LinearElementEditor.getMinMaxXYWithBoundText(
element,
coords,
boundTextElement,
);
coords = [
coordsWithBoundText[0],
coordsWithBoundText[1],
coordsWithBoundText[2],
coordsWithBoundText[3],
];
}
return coords;
};
// We could cache this stuff
@ -439,9 +459,7 @@ export const getElementBounds = (
): [number, number, number, number] => {
let bounds: [number, number, number, number];
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
const cx = (x1 + x2) / 2;
const cy = (y1 + y2) / 2;
const [x1, y1, x2, y2, cx, cy] = getElementAbsoluteCoords(element);
if (isFreeDrawElement(element)) {
const [minX, minY, maxX, maxY] = getBoundsFromPoints(
element.points.map(([x, y]) =>