mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-10-12 10:40:12 +02:00
FEAT: No binding to frame cutout
This commit is contained in:
@@ -25,6 +25,7 @@ import {
|
|||||||
doBoundsIntersect,
|
doBoundsIntersect,
|
||||||
getCenterForBounds,
|
getCenterForBounds,
|
||||||
getElementBounds,
|
getElementBounds,
|
||||||
|
pointInsideBounds,
|
||||||
} from "./bounds";
|
} from "./bounds";
|
||||||
import {
|
import {
|
||||||
getAllHoveredElementAtPoint,
|
getAllHoveredElementAtPoint,
|
||||||
@@ -47,6 +48,7 @@ import {
|
|||||||
isBindableElement,
|
isBindableElement,
|
||||||
isBoundToContainer,
|
isBoundToContainer,
|
||||||
isElbowArrow,
|
isElbowArrow,
|
||||||
|
isFrameLikeElement,
|
||||||
isRectanguloidElement,
|
isRectanguloidElement,
|
||||||
isTextElement,
|
isTextElement,
|
||||||
} from "./typeChecks";
|
} from "./typeChecks";
|
||||||
@@ -553,6 +555,65 @@ export const getBindingStrategyForDraggingBindingElementEndpoints = (
|
|||||||
return { start, end };
|
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
|
// Handle simpler elbow arrow binding
|
||||||
if (isElbowArrow(arrow)) {
|
if (isElbowArrow(arrow)) {
|
||||||
return bindingStrategyForElbowArrowEndpointDragging(
|
return bindingStrategyForElbowArrowEndpointDragging(
|
||||||
@@ -901,6 +962,7 @@ export const bindPointToSnapToElementOutline = (
|
|||||||
startOrEnd: "start" | "end",
|
startOrEnd: "start" | "end",
|
||||||
elementsMap: ElementsMap,
|
elementsMap: ElementsMap,
|
||||||
customIntersector?: LineSegment<GlobalPoint>,
|
customIntersector?: LineSegment<GlobalPoint>,
|
||||||
|
ignoreFrameCutouts?: boolean,
|
||||||
): GlobalPoint => {
|
): GlobalPoint => {
|
||||||
const aabb = aabbForElement(bindableElement, elementsMap);
|
const aabb = aabbForElement(bindableElement, elementsMap);
|
||||||
const localPoint =
|
const localPoint =
|
||||||
@@ -1020,6 +1082,21 @@ export const bindPointToSnapToElementOutline = (
|
|||||||
return edgePoint;
|
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;
|
return intersection;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -36,6 +36,7 @@ import {
|
|||||||
getCubicBezierCurveBound,
|
getCubicBezierCurveBound,
|
||||||
getDiamondPoints,
|
getDiamondPoints,
|
||||||
getElementBounds,
|
getElementBounds,
|
||||||
|
pointInsideBounds,
|
||||||
} from "./bounds";
|
} from "./bounds";
|
||||||
import {
|
import {
|
||||||
hasBoundTextElement,
|
hasBoundTextElement,
|
||||||
@@ -226,6 +227,20 @@ const bindingBorderTest = (
|
|||||||
return false;
|
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
|
// Do the intersection test against the element since it's close enough
|
||||||
const intersections = intersectElementWithLineSegment(
|
const intersections = intersectElementWithLineSegment(
|
||||||
element,
|
element,
|
||||||
|
@@ -2147,7 +2147,7 @@ const pointDraggingUpdates = (
|
|||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (endIsDragged) {
|
if (endIsDragged && updates.endBinding.mode === "orbit") {
|
||||||
updates.suggestedBinding = end.element;
|
updates.suggestedBinding = end.element;
|
||||||
}
|
}
|
||||||
} else if (endIsDragged) {
|
} else if (endIsDragged) {
|
||||||
|
@@ -209,6 +209,35 @@ const renderBindingHighlightForBindableElement = (
|
|||||||
const opacity = clamp((1 / BIND_MODE_TIMEOUT) * remainingTime, 0.0001, 1);
|
const opacity = clamp((1 / BIND_MODE_TIMEOUT) * remainingTime, 0.0001, 1);
|
||||||
const offset = element.strokeWidth / 2;
|
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) {
|
switch (element.type) {
|
||||||
case "magicframe":
|
case "magicframe":
|
||||||
case "frame":
|
case "frame":
|
||||||
|
Reference in New Issue
Block a user