From 72752636868ba824654d17fb4ebc93f036c490ac Mon Sep 17 00:00:00 2001 From: Mark Tolmacs Date: Fri, 2 May 2025 16:36:15 +0200 Subject: [PATCH] Rectangle highlight Signed-off-by: Mark Tolmacs --- .../excalidraw/renderer/interactiveScene.ts | 213 ++++++++++++++++-- 1 file changed, 190 insertions(+), 23 deletions(-) diff --git a/packages/excalidraw/renderer/interactiveScene.ts b/packages/excalidraw/renderer/interactiveScene.ts index 4388233000..aa811af0f4 100644 --- a/packages/excalidraw/renderer/interactiveScene.ts +++ b/packages/excalidraw/renderer/interactiveScene.ts @@ -17,7 +17,10 @@ import { throttleRAF, } from "@excalidraw/common"; -import { maxBindingGap } from "@excalidraw/element/binding"; +import { + FIXED_BINDING_DISTANCE, + maxBindingGap, +} from "@excalidraw/element/binding"; import { LinearElementEditor } from "@excalidraw/element/linearElementEditor"; import { getOmitSidesForDevice, @@ -69,6 +72,7 @@ import type { ExcalidrawFrameLikeElement, ExcalidrawImageElement, ExcalidrawLinearElement, + ExcalidrawRectanguloidElement, ExcalidrawTextElement, GroupId, NonDeleted, @@ -161,6 +165,186 @@ const highlightPoint = ( ); }; +const drawHighlightForRectWithRotation = ( + context: CanvasRenderingContext2D, + element: ExcalidrawRectanguloidElement, + padding: number, +) => { + const [x, y] = pointRotateRads( + pointFrom(element.x, element.y), + elementCenterPoint(element), + element.angle, + ); + + context.save(); + context.translate(x, y); + context.rotate(element.angle); + + const radius = getCornerRadius( + Math.min(element.width, element.height), + element, + ); + + context.beginPath(); + + // { + // const topLeftApprox = offsetBezier( + // pointFrom(0, 0 + radius), + // pointFrom(0, 0), + // pointFrom(0, 0), + // pointFrom(0 + radius, 0), + // padding, + // ); + // const topRightApprox = offsetBezier( + // pointFrom(element.width - radius, 0), + // pointFrom(element.width, 0), + // pointFrom(element.width, 0), + // pointFrom(element.width, radius), + // padding, + // ); + // const bottomRightApprox = offsetBezier( + // pointFrom(element.width, element.height - radius), + // pointFrom(element.width, element.height), + // pointFrom(element.width, element.height), + // pointFrom(element.width - radius, element.height), + // padding, + // ); + // const bottomLeftApprox = offsetBezier( + // pointFrom(radius, element.height), + // pointFrom(0, element.height), + // pointFrom(0, element.height), + // pointFrom(0, element.height - radius), + // padding, + // ); + + // context.moveTo( + // topLeftApprox[topLeftApprox.length - 1][0], + // topLeftApprox[topLeftApprox.length - 1][1], + // ); + // context.lineTo(topRightApprox[0][0], topRightApprox[0][1]); + // drawCatmullRom(context, topRightApprox); + // context.lineTo(bottomRightApprox[0][0], bottomRightApprox[0][1]); + // drawCatmullRom(context, bottomRightApprox); + // context.lineTo(bottomLeftApprox[0][0], bottomLeftApprox[0][1]); + // drawCatmullRom(context, bottomLeftApprox); + // context.lineTo(topLeftApprox[0][0], topLeftApprox[0][1]); + // drawCatmullRom(context, topLeftApprox); + // } + + context.moveTo(-padding + radius, -padding); + context.lineTo(element.width + padding - radius, -padding); + context.quadraticCurveTo( + element.width + padding, + -padding, + element.width + padding, + -padding + radius, + ); + context.lineTo(element.width + padding, element.height + padding - radius); + context.quadraticCurveTo( + element.width + padding, + element.height + padding, + element.width + padding - radius, + element.height + padding, + ); + context.lineTo(-padding + radius, element.height + padding); + context.quadraticCurveTo( + -padding, + element.height + padding, + -padding, + element.height + padding - radius, + ); + context.lineTo(-padding, -padding + radius); + context.quadraticCurveTo(-padding, -padding, -padding + radius, -padding); + + context.moveTo(-FIXED_BINDING_DISTANCE + radius, -FIXED_BINDING_DISTANCE); + context.quadraticCurveTo( + -FIXED_BINDING_DISTANCE, + -FIXED_BINDING_DISTANCE, + -FIXED_BINDING_DISTANCE, + -FIXED_BINDING_DISTANCE + radius, + ); + context.lineTo( + -FIXED_BINDING_DISTANCE, + element.height + FIXED_BINDING_DISTANCE - radius, + ); + context.quadraticCurveTo( + -FIXED_BINDING_DISTANCE, + element.height + FIXED_BINDING_DISTANCE, + -FIXED_BINDING_DISTANCE + radius, + element.height + FIXED_BINDING_DISTANCE, + ); + context.lineTo( + element.width + FIXED_BINDING_DISTANCE - radius, + element.height + FIXED_BINDING_DISTANCE, + ); + context.quadraticCurveTo( + element.width + FIXED_BINDING_DISTANCE, + element.height + FIXED_BINDING_DISTANCE, + element.width + FIXED_BINDING_DISTANCE, + element.height + FIXED_BINDING_DISTANCE - radius, + ); + context.lineTo( + element.width + FIXED_BINDING_DISTANCE, + -FIXED_BINDING_DISTANCE + radius, + ); + context.quadraticCurveTo( + element.width + FIXED_BINDING_DISTANCE, + -FIXED_BINDING_DISTANCE, + element.width + FIXED_BINDING_DISTANCE - radius, + -FIXED_BINDING_DISTANCE, + ); + context.lineTo(-FIXED_BINDING_DISTANCE + radius, -FIXED_BINDING_DISTANCE); + + // { + // const topLeftApprox = offsetBezier( + // pointFrom(0 + radius, 0), + // pointFrom(0, 0), + // pointFrom(0, 0), + // pointFrom(0, 0 + radius), + // -FIXED_BINDING_DISTANCE, + // ); + // const topRightApprox = offsetBezier( + // pointFrom(element.width, radius), + // pointFrom(element.width, 0), + // pointFrom(element.width, 0), + // pointFrom(element.width - radius, 0), + // -FIXED_BINDING_DISTANCE, + // ); + // const bottomRightApprox = offsetBezier( + // pointFrom(element.width - radius, element.height), + // pointFrom(element.width, element.height), + // pointFrom(element.width, element.height), + // pointFrom(element.width, element.height - radius), + // -FIXED_BINDING_DISTANCE, + // ); + // const bottomLeftApprox = offsetBezier( + // pointFrom(0, element.height - radius), + // pointFrom(0, element.height), + // pointFrom(0, element.height), + // pointFrom(radius, element.height), + // -FIXED_BINDING_DISTANCE, + // ); + + // context.moveTo( + // topLeftApprox[topLeftApprox.length - 1][0], + // topLeftApprox[topLeftApprox.length - 1][1], + // ); + // context.lineTo(bottomLeftApprox[0][0], bottomLeftApprox[0][1]); + // drawCatmullRom(context, bottomLeftApprox); + // context.lineTo(bottomRightApprox[0][0], bottomRightApprox[0][1]); + // drawCatmullRom(context, bottomRightApprox); + // context.lineTo(topRightApprox[0][0], topRightApprox[0][1]); + // drawCatmullRom(context, topRightApprox); + // context.lineTo(topLeftApprox[0][0], topLeftApprox[0][1]); + // drawCatmullRom(context, topLeftApprox); + // } + + context.closePath(); + context.fill(); + + context.restore(); +}; + const strokeRectWithRotation = ( context: CanvasRenderingContext2D, x: number, @@ -276,28 +460,28 @@ const strokeDiamondWithRotation = ( pointFrom(topX, topY), pointFrom(topX, topY), pointFrom(topX - verticalRadius, topY + horizontalRadius), - -5, + -FIXED_BINDING_DISTANCE, ); const rightApprox = offsetBezier( pointFrom(rightX - verticalRadius, rightY + horizontalRadius), pointFrom(rightX, rightY), pointFrom(rightX, rightY), pointFrom(rightX - verticalRadius, rightY - horizontalRadius), - -5, + -FIXED_BINDING_DISTANCE, ); const bottomApprox = offsetBezier( pointFrom(bottomX - verticalRadius, bottomY - horizontalRadius), pointFrom(bottomX, bottomY), pointFrom(bottomX, bottomY), pointFrom(bottomX + verticalRadius, bottomY - horizontalRadius), - -5, + -FIXED_BINDING_DISTANCE, ); const leftApprox = offsetBezier( pointFrom(leftX + verticalRadius, leftY - horizontalRadius), pointFrom(leftX, leftY), pointFrom(leftX, leftY), pointFrom(leftX + verticalRadius, leftY + horizontalRadius), - -5, + -FIXED_BINDING_DISTANCE, ); context.moveTo( @@ -378,12 +562,6 @@ const renderBindingHighlightForBindableElement = ( ); // To ensure the binding highlight doesn't overlap the element itself const padding = maxBindingGap(element, element.width, element.height, zoom); - //context.lineWidth / 2 + BINDING_HIGHLIGHT_OFFSET; - - const radius = getCornerRadius( - Math.min(element.width, element.height), - element, - ); switch (element.type) { case "rectangle": @@ -393,18 +571,7 @@ const renderBindingHighlightForBindableElement = ( case "embeddable": case "frame": case "magicframe": - strokeRectWithRotation( - context, - x1 - padding, - y1 - padding, - width + padding * 2, - height + padding * 2, - x1 + width / 2, - y1 + height / 2, - element.angle, - undefined, - radius, - ); + drawHighlightForRectWithRotation(context, element, padding); break; case "diamond": strokeDiamondWithRotation(context, padding, element);