mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-10-12 02:29:57 +02:00
FEAT: No binding to frame cutout
This commit is contained in:
@@ -25,6 +25,7 @@ import {
|
||||
doBoundsIntersect,
|
||||
getCenterForBounds,
|
||||
getElementBounds,
|
||||
pointInsideBounds,
|
||||
} from "./bounds";
|
||||
import {
|
||||
getAllHoveredElementAtPoint,
|
||||
@@ -47,6 +48,7 @@ import {
|
||||
isBindableElement,
|
||||
isBoundToContainer,
|
||||
isElbowArrow,
|
||||
isFrameLikeElement,
|
||||
isRectanguloidElement,
|
||||
isTextElement,
|
||||
} from "./typeChecks";
|
||||
@@ -553,6 +555,65 @@ export const getBindingStrategyForDraggingBindingElementEndpoints = (
|
||||
return { start, end };
|
||||
}
|
||||
|
||||
// Handle binding to shapes where the frame cuts out a part of the shape
|
||||
{
|
||||
const globalPoint = LinearElementEditor.getPointGlobalCoordinates(
|
||||
arrow,
|
||||
draggingPoints.get(startDragged ? startIdx : endIdx)!.point,
|
||||
elementsMap,
|
||||
);
|
||||
const hoveredElement = getHoveredElementForBinding(
|
||||
globalPoint,
|
||||
elements,
|
||||
elementsMap,
|
||||
);
|
||||
const intersectionPoint =
|
||||
hoveredElement &&
|
||||
hoveredElement.frameId &&
|
||||
bindPointToSnapToElementOutline(
|
||||
arrow,
|
||||
hoveredElement,
|
||||
startDragged ? "start" : "end",
|
||||
elementsMap,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
if (intersectionPoint) {
|
||||
const enclosingFrame = elementsMap.get(hoveredElement.frameId);
|
||||
if (enclosingFrame && isFrameLikeElement(enclosingFrame)) {
|
||||
const enclosingFrameBounds = getElementBounds(
|
||||
enclosingFrame,
|
||||
elementsMap,
|
||||
);
|
||||
if (!pointInsideBounds(intersectionPoint, enclosingFrameBounds)) {
|
||||
if (isElbowArrow(arrow)) {
|
||||
return {
|
||||
start: { mode: startDragged ? null : start.mode },
|
||||
end: { mode: endDragged ? null : end.mode },
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
start: startDragged
|
||||
? {
|
||||
mode: "inside",
|
||||
element: hoveredElement,
|
||||
focusPoint: globalPoint,
|
||||
}
|
||||
: start,
|
||||
end: endDragged
|
||||
? {
|
||||
mode: "inside",
|
||||
element: hoveredElement,
|
||||
focusPoint: globalPoint,
|
||||
}
|
||||
: end,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle simpler elbow arrow binding
|
||||
if (isElbowArrow(arrow)) {
|
||||
return bindingStrategyForElbowArrowEndpointDragging(
|
||||
@@ -901,6 +962,7 @@ export const bindPointToSnapToElementOutline = (
|
||||
startOrEnd: "start" | "end",
|
||||
elementsMap: ElementsMap,
|
||||
customIntersector?: LineSegment<GlobalPoint>,
|
||||
ignoreFrameCutouts?: boolean,
|
||||
): GlobalPoint => {
|
||||
const aabb = aabbForElement(bindableElement, elementsMap);
|
||||
const localPoint =
|
||||
@@ -1020,6 +1082,21 @@ export const bindPointToSnapToElementOutline = (
|
||||
return edgePoint;
|
||||
}
|
||||
|
||||
// Frames can cut out bindables, so ignore the intersection if
|
||||
// it isn't in the frame
|
||||
if (!ignoreFrameCutouts && bindableElement.frameId) {
|
||||
const enclosingFrame = elementsMap.get(bindableElement.frameId);
|
||||
if (enclosingFrame && isFrameLikeElement(enclosingFrame)) {
|
||||
const enclosingFrameBounds = getElementBounds(
|
||||
enclosingFrame,
|
||||
elementsMap,
|
||||
);
|
||||
if (!pointInsideBounds(intersection, enclosingFrameBounds)) {
|
||||
return edgePoint;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return intersection;
|
||||
};
|
||||
|
||||
|
@@ -36,6 +36,7 @@ import {
|
||||
getCubicBezierCurveBound,
|
||||
getDiamondPoints,
|
||||
getElementBounds,
|
||||
pointInsideBounds,
|
||||
} from "./bounds";
|
||||
import {
|
||||
hasBoundTextElement,
|
||||
@@ -226,6 +227,20 @@ const bindingBorderTest = (
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the element is inside a frame, we should clip the element
|
||||
if (element.frameId) {
|
||||
const enclosingFrame = elementsMap.get(element.frameId);
|
||||
if (enclosingFrame && isFrameLikeElement(enclosingFrame)) {
|
||||
const enclosingFrameBounds = getElementBounds(
|
||||
enclosingFrame,
|
||||
elementsMap,
|
||||
);
|
||||
if (!pointInsideBounds(p, enclosingFrameBounds)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Do the intersection test against the element since it's close enough
|
||||
const intersections = intersectElementWithLineSegment(
|
||||
element,
|
||||
|
@@ -2147,7 +2147,7 @@ const pointDraggingUpdates = (
|
||||
),
|
||||
};
|
||||
|
||||
if (endIsDragged) {
|
||||
if (endIsDragged && updates.endBinding.mode === "orbit") {
|
||||
updates.suggestedBinding = end.element;
|
||||
}
|
||||
} else if (endIsDragged) {
|
||||
|
@@ -209,6 +209,35 @@ const renderBindingHighlightForBindableElement = (
|
||||
const opacity = clamp((1 / BIND_MODE_TIMEOUT) * remainingTime, 0.0001, 1);
|
||||
const offset = element.strokeWidth / 2;
|
||||
|
||||
const enclosingFrame = element.frameId && allElementsMap.get(element.frameId);
|
||||
if (enclosingFrame && isFrameLikeElement(enclosingFrame)) {
|
||||
context.translate(
|
||||
enclosingFrame.x + appState.scrollX,
|
||||
enclosingFrame.y + appState.scrollY,
|
||||
);
|
||||
|
||||
context.beginPath();
|
||||
|
||||
if (FRAME_STYLE.radius && context.roundRect) {
|
||||
context.roundRect(
|
||||
-1,
|
||||
-1,
|
||||
enclosingFrame.width + 1,
|
||||
enclosingFrame.height + 1,
|
||||
FRAME_STYLE.radius / appState.zoom.value,
|
||||
);
|
||||
} else {
|
||||
context.rect(-1, -1, enclosingFrame.width + 1, enclosingFrame.height + 1);
|
||||
}
|
||||
|
||||
context.clip();
|
||||
|
||||
context.translate(
|
||||
-(enclosingFrame.x + appState.scrollX),
|
||||
-(enclosingFrame.y + appState.scrollY),
|
||||
);
|
||||
}
|
||||
|
||||
switch (element.type) {
|
||||
case "magicframe":
|
||||
case "frame":
|
||||
|
Reference in New Issue
Block a user