UNPKG

tldraw

Version:

A tiny little drawing editor.

163 lines (162 loc) 5.85 kB
import { Mat, Vec, intersectLineSegmentPolygon, intersectLineSegmentPolyline } from "@tldraw/editor"; import { BOUND_ARROW_OFFSET, MIN_ARROW_LENGTH, STROKE_SIZES, getArrowTerminalsInArrowSpace, getBoundShapeInfoForTerminal, getBoundShapeRelationships } from "./shared.mjs"; function getStraightArrowInfo(editor, shape, bindings) { const { arrowheadStart, arrowheadEnd } = shape.props; const terminalsInArrowSpace = getArrowTerminalsInArrowSpace(editor, shape, bindings); const a = terminalsInArrowSpace.start.clone(); const b = terminalsInArrowSpace.end.clone(); const c = Vec.Med(a, b); if (Vec.Equals(a, b)) { return { bindings, isStraight: true, start: { handle: a, point: a, arrowhead: shape.props.arrowheadStart }, end: { handle: b, point: b, arrowhead: shape.props.arrowheadEnd }, middle: c, isValid: false, length: 0 }; } const uAB = Vec.Sub(b, a).uni(); const startShapeInfo = getBoundShapeInfoForTerminal(editor, shape, "start"); const endShapeInfo = getBoundShapeInfoForTerminal(editor, shape, "end"); const arrowPageTransform = editor.getShapePageTransform(shape); updateArrowheadPointWithBoundShape( b, // <-- will be mutated terminalsInArrowSpace.start, arrowPageTransform, endShapeInfo ); updateArrowheadPointWithBoundShape( a, // <-- will be mutated terminalsInArrowSpace.end, arrowPageTransform, startShapeInfo ); let offsetA = 0; let offsetB = 0; let strokeOffsetA = 0; let strokeOffsetB = 0; let minLength = MIN_ARROW_LENGTH * shape.props.scale; const isSelfIntersection = startShapeInfo && endShapeInfo && startShapeInfo.shape === endShapeInfo.shape; const relationship = startShapeInfo && endShapeInfo ? getBoundShapeRelationships(editor, startShapeInfo.shape.id, endShapeInfo.shape.id) : "safe"; if (relationship === "safe" && startShapeInfo && endShapeInfo && !isSelfIntersection && !startShapeInfo.isExact && !endShapeInfo.isExact) { if (endShapeInfo.didIntersect && !startShapeInfo.didIntersect) { if (startShapeInfo.isClosed) { a.setTo(b.clone().add(uAB.clone().mul(MIN_ARROW_LENGTH * shape.props.scale))); } } else if (!endShapeInfo.didIntersect) { if (endShapeInfo.isClosed) { b.setTo(a.clone().sub(uAB.clone().mul(MIN_ARROW_LENGTH * shape.props.scale))); } } } const distance = Vec.Sub(b, a); const u = Vec.Len(distance) ? distance.uni() : Vec.From(distance); const didFlip = !Vec.Equals(u, uAB); if (!isSelfIntersection) { if (relationship !== "start-contains-end" && startShapeInfo && arrowheadStart !== "none" && !startShapeInfo.isExact) { strokeOffsetA = STROKE_SIZES[shape.props.size] / 2 + ("size" in startShapeInfo.shape.props ? STROKE_SIZES[startShapeInfo.shape.props.size] / 2 : 0); offsetA = (BOUND_ARROW_OFFSET + strokeOffsetA) * shape.props.scale; minLength += strokeOffsetA * shape.props.scale; } if (relationship !== "end-contains-start" && endShapeInfo && arrowheadEnd !== "none" && !endShapeInfo.isExact) { strokeOffsetB = STROKE_SIZES[shape.props.size] / 2 + ("size" in endShapeInfo.shape.props ? STROKE_SIZES[endShapeInfo.shape.props.size] / 2 : 0); offsetB = (BOUND_ARROW_OFFSET + strokeOffsetB) * shape.props.scale; minLength += strokeOffsetB * shape.props.scale; } } const tA = a.clone().add(u.clone().mul(offsetA * (didFlip ? -1 : 1))); const tB = b.clone().sub(u.clone().mul(offsetB * (didFlip ? -1 : 1))); if (Vec.DistMin(tA, tB, minLength)) { if (offsetA !== 0 && offsetB !== 0) { offsetA *= -1.5; offsetB *= -1.5; } else if (offsetA !== 0) { offsetA *= -1; } else if (offsetB !== 0) { offsetB *= -1; } else { } } a.add(u.clone().mul(offsetA * (didFlip ? -1 : 1))); b.sub(u.clone().mul(offsetB * (didFlip ? -1 : 1))); if (didFlip) { if (startShapeInfo && endShapeInfo) { b.setTo(Vec.Add(a, u.clone().mul(-MIN_ARROW_LENGTH * shape.props.scale))); } c.setTo(Vec.Med(terminalsInArrowSpace.start, terminalsInArrowSpace.end)); } else { c.setTo(Vec.Med(a, b)); } const length = Vec.Dist(a, b); return { bindings, isStraight: true, start: { handle: terminalsInArrowSpace.start, point: a, arrowhead: shape.props.arrowheadStart }, end: { handle: terminalsInArrowSpace.end, point: b, arrowhead: shape.props.arrowheadEnd }, middle: c, isValid: length > 0, length }; } function updateArrowheadPointWithBoundShape(point, opposite, arrowPageTransform, targetShapeInfo) { if (targetShapeInfo === void 0) { return; } if (targetShapeInfo.isExact) { return; } const pageFrom = Mat.applyToPoint(arrowPageTransform, opposite); const pageTo = Mat.applyToPoint(arrowPageTransform, point); const targetFrom = Mat.applyToPoint(Mat.Inverse(targetShapeInfo.transform), pageFrom); const targetTo = Mat.applyToPoint(Mat.Inverse(targetShapeInfo.transform), pageTo); const isClosed = targetShapeInfo.isClosed; const fn = isClosed ? intersectLineSegmentPolygon : intersectLineSegmentPolyline; const intersection = fn(targetFrom, targetTo, targetShapeInfo.outline); let targetInt; if (intersection !== null) { targetInt = intersection.sort((p1, p2) => Vec.Dist2(p1, targetFrom) - Vec.Dist2(p2, targetFrom))[0] ?? (isClosed ? void 0 : targetTo); } if (targetInt === void 0) { return; } const pageInt = Mat.applyToPoint(targetShapeInfo.transform, targetInt); const arrowInt = Mat.applyToPoint(Mat.Inverse(arrowPageTransform), pageInt); point.setTo(arrowInt); targetShapeInfo.didIntersect = true; } export { getStraightArrowInfo }; //# sourceMappingURL=straight-arrow.mjs.map