mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
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:
parent
1933116261
commit
760fd7b3a6
25 changed files with 1668 additions and 363 deletions
|
@ -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]) =>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue