feat: Center point with more precise highlight outlines

This commit is contained in:
Mark Tolmacs
2025-09-13 14:41:47 +02:00
parent c4874e9dd9
commit 737f6e08c1
2 changed files with 129 additions and 57 deletions

View File

@@ -31,7 +31,6 @@ import {
getHoveredElementForBinding, getHoveredElementForBinding,
intersectElementWithLineSegment, intersectElementWithLineSegment,
isBindableElementInsideOtherBindable, isBindableElementInsideOtherBindable,
isPointInElement,
} from "./collision"; } from "./collision";
import { distanceToElement } from "./distance"; import { distanceToElement } from "./distance";
import { import {

View File

@@ -1,4 +1,3 @@
import rough from "roughjs/bin/rough";
import { import {
pointFrom, pointFrom,
pointsEqual, pointsEqual,
@@ -17,7 +16,12 @@ import {
throttleRAF, throttleRAF,
} from "@excalidraw/common"; } from "@excalidraw/common";
import { LinearElementEditor, renderElement } from "@excalidraw/element"; import {
deconstructDiamondElement,
deconstructRectanguloidElement,
elementCenterPoint,
LinearElementEditor,
} from "@excalidraw/element";
import { import {
getOmitSidesForDevice, getOmitSidesForDevice,
getTransformHandles, getTransformHandles,
@@ -219,9 +223,17 @@ const renderBindingHighlightForBindableElement = (
context.restore(); context.restore();
break; break;
case "image": default:
case "text":
context.save(); context.save();
const center = elementCenterPoint(element, allElementsMap);
const cx = center[0] + appState.scrollX;
const cy = center[1] + appState.scrollY;
context.translate(cx, cy);
context.rotate(element.angle as Radians);
context.translate(-cx, -cy);
context.translate( context.translate(
element.x + appState.scrollX, element.x + appState.scrollX,
element.y + appState.scrollY, element.y + appState.scrollY,
@@ -231,63 +243,124 @@ const renderBindingHighlightForBindableElement = (
context.strokeStyle = context.strokeStyle =
appState.theme === THEME.DARK ? "#035da1" : "#6abdfc"; appState.theme === THEME.DARK ? "#035da1" : "#6abdfc";
context.strokeRect(0, 0, element.width, element.height); switch (element.type) {
context.restore(); case "ellipse":
break; context.beginPath();
default: context.ellipse(
renderElement( element.width / 2,
{ element.height / 2,
...element, element.width / 2,
strokeColor: appState.theme === THEME.DARK ? "#035da1" : "#6abdfc", element.height / 2,
fillStyle: "solid", 0,
backgroundColor: "transparent", 0,
}, 2 * Math.PI,
elementsMap, );
allElementsMap, context.closePath();
rough.canvas(context.canvas), context.stroke();
context, break;
{ case "diamond":
canvasBackgroundColor: "transparent",
imageCache: new Map(),
renderGrid: false,
isExporting: false,
embedsValidationStatus: new Map(),
elementsPendingErasure: new Set(),
pendingFlowchartNodes: null,
},
appState,
);
// Overdraw the arrow if exists (if there is a suggestedBinding it's an arrow)
if (appState.selectedLinearElement) {
const arrow = LinearElementEditor.getElement(
appState.selectedLinearElement.elementId,
allElementsMap,
);
invariant(arrow, "arrow not found during highlight element binding");
renderElement(
arrow,
elementsMap,
allElementsMap,
rough.canvas(context.canvas),
context,
{ {
canvasBackgroundColor: "transparent", const [segments, curves] = deconstructDiamondElement(element);
imageCache: new Map(),
renderGrid: false, // Draw each line segment individually
isExporting: false, segments.forEach((segment) => {
embedsValidationStatus: new Map(), context.beginPath();
elementsPendingErasure: new Set(), context.moveTo(
pendingFlowchartNodes: null, segment[0][0] - element.x,
}, segment[0][1] - element.y,
appState, );
); context.lineTo(
segment[1][0] - element.x,
segment[1][1] - element.y,
);
context.stroke();
});
// Draw each curve individually (for rounded corners)
curves.forEach((curve) => {
const [start, control1, control2, end] = curve;
context.beginPath();
context.moveTo(start[0] - element.x, start[1] - element.y);
context.bezierCurveTo(
control1[0] - element.x,
control1[1] - element.y,
control2[0] - element.x,
control2[1] - element.y,
end[0] - element.x,
end[1] - element.y,
);
context.stroke();
});
}
break;
default:
{
const [segments, curves] = deconstructRectanguloidElement(element);
// Draw each line segment individually
segments.forEach((segment) => {
context.beginPath();
context.moveTo(
segment[0][0] - element.x,
segment[0][1] - element.y,
);
context.lineTo(
segment[1][0] - element.x,
segment[1][1] - element.y,
);
context.stroke();
});
// Draw each curve individually (for rounded corners)
curves.forEach((curve) => {
const [start, control1, control2, end] = curve;
context.beginPath();
context.moveTo(start[0] - element.x, start[1] - element.y);
context.bezierCurveTo(
control1[0] - element.x,
control1[1] - element.y,
control2[0] - element.x,
control2[1] - element.y,
end[0] - element.x,
end[1] - element.y,
);
context.stroke();
});
}
break;
} }
context.restore();
break; break;
} }
// Draw center snap area
context.save();
context.translate(element.x + appState.scrollX, element.y + appState.scrollY);
context.strokeStyle = "rgba(0, 0, 0, 0.1)";
context.lineWidth = 1 / appState.zoom.value;
context.setLineDash([4 / appState.zoom.value, 4 / appState.zoom.value]);
context.lineDashOffset = 0;
context.fillStyle = "rgba(0, 0, 0, 0.01)";
const radius = 0.5 * (Math.min(element.width, element.height) / 2);
context.beginPath();
context.ellipse(
element.width / 2,
element.height / 2,
radius,
radius,
0,
0,
2 * Math.PI,
);
context.stroke();
context.fill();
context.restore();
}; };
type ElementSelectionBorder = { type ElementSelectionBorder = {