diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index ba6e847b2..080eb26fc 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -2072,6 +2072,7 @@ class App extends React.Component { /> )} void; @@ -145,6 +151,8 @@ const InteractiveCanvas = (props: InteractiveCanvasProps) => { remotePointerUserStates, selectionColor, renderScrollbars: props.renderScrollbars, + // NOTE not memoized on so we don't rerender on cursor move + lastViewportPosition: props.app.lastViewportPosition, }, device: props.device, callback: props.renderInteractiveSceneCallback, diff --git a/packages/excalidraw/renderer/interactiveScene.ts b/packages/excalidraw/renderer/interactiveScene.ts index 5cd03a8fb..8ac027813 100644 --- a/packages/excalidraw/renderer/interactiveScene.ts +++ b/packages/excalidraw/renderer/interactiveScene.ts @@ -1,4 +1,5 @@ import { + pointDistance, pointFrom, pointsEqual, type GlobalPoint, @@ -14,6 +15,7 @@ import { invariant, THEME, throttleRAF, + viewportCoordsToSceneCoords, } from "@excalidraw/common"; import { @@ -192,6 +194,7 @@ const renderBindingHighlightForBindableElement = ( elementsMap: RenderableElementsMap, allElementsMap: NonDeletedSceneElementsMap, appState: InteractiveCanvasAppState, + renderConfig: InteractiveCanvasRenderConfig, ) => { switch (element.type) { case "magicframe": @@ -337,16 +340,32 @@ const renderBindingHighlightForBindableElement = ( break; } + const { x: cursorX, y: cursorY } = viewportCoordsToSceneCoords( + { + clientX: renderConfig.lastViewportPosition.x, + clientY: renderConfig.lastViewportPosition.y, + }, + appState, + ); + // 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.strokeStyle = "rgba(0, 0, 0, 0.2)"; 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); + const centerX = element.x + element.width / 2; + const centerY = element.y + element.height / 2; + + const isInsideEllipse = + pointDistance(pointFrom(cursorX, cursorY), pointFrom(centerX, centerY)) <= + radius; + + context.fillStyle = isInsideEllipse ? "rgba(0, 0, 0, 0.04)" : "transparent"; + context.beginPath(); context.ellipse( element.width / 2, @@ -899,6 +918,7 @@ const _renderInteractiveScene = ({ elementsMap, allElementsMap, appState, + renderConfig, ); } diff --git a/packages/excalidraw/scene/types.ts b/packages/excalidraw/scene/types.ts index 12a5e27a8..35775f089 100644 --- a/packages/excalidraw/scene/types.ts +++ b/packages/excalidraw/scene/types.ts @@ -66,6 +66,7 @@ export type InteractiveCanvasRenderConfig = { remotePointerUsernames: Map; remotePointerButton: Map; selectionColor: string; + lastViewportPosition: { x: number; y: number }; // extra options passed to the renderer // --------------------------------------------------------------------------- renderScrollbars?: boolean;