tldraw
Version:
A tiny little drawing editor.
163 lines (162 loc) • 5.85 kB
JavaScript
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