POC: highlight center on hover

This commit is contained in:
dwelle
2025-09-13 15:41:17 +02:00
parent ef2bde0d03
commit b789308798
4 changed files with 33 additions and 3 deletions

View File

@@ -2072,6 +2072,7 @@ class App extends React.Component<AppProps, AppState> {
/> />
)} )}
<InteractiveCanvas <InteractiveCanvas
app={this}
containerRef={this.excalidrawContainerRef} containerRef={this.excalidrawContainerRef}
canvas={this.interactiveCanvas} canvas={this.interactiveCanvas}
elementsMap={elementsMap} elementsMap={elementsMap}

View File

@@ -20,7 +20,12 @@ import type {
RenderableElementsMap, RenderableElementsMap,
RenderInteractiveSceneCallback, RenderInteractiveSceneCallback,
} from "../../scene/types"; } from "../../scene/types";
import type { AppState, Device, InteractiveCanvasAppState } from "../../types"; import type {
AppClassProperties,
AppState,
Device,
InteractiveCanvasAppState,
} from "../../types";
import type { DOMAttributes } from "react"; import type { DOMAttributes } from "react";
type InteractiveCanvasProps = { type InteractiveCanvasProps = {
@@ -36,6 +41,7 @@ type InteractiveCanvasProps = {
appState: InteractiveCanvasAppState; appState: InteractiveCanvasAppState;
renderScrollbars: boolean; renderScrollbars: boolean;
device: Device; device: Device;
app: AppClassProperties;
renderInteractiveSceneCallback: ( renderInteractiveSceneCallback: (
data: RenderInteractiveSceneCallback, data: RenderInteractiveSceneCallback,
) => void; ) => void;
@@ -145,6 +151,8 @@ const InteractiveCanvas = (props: InteractiveCanvasProps) => {
remotePointerUserStates, remotePointerUserStates,
selectionColor, selectionColor,
renderScrollbars: props.renderScrollbars, renderScrollbars: props.renderScrollbars,
// NOTE not memoized on so we don't rerender on cursor move
lastViewportPosition: props.app.lastViewportPosition,
}, },
device: props.device, device: props.device,
callback: props.renderInteractiveSceneCallback, callback: props.renderInteractiveSceneCallback,

View File

@@ -1,4 +1,5 @@
import { import {
pointDistance,
pointFrom, pointFrom,
pointsEqual, pointsEqual,
type GlobalPoint, type GlobalPoint,
@@ -14,6 +15,7 @@ import {
invariant, invariant,
THEME, THEME,
throttleRAF, throttleRAF,
viewportCoordsToSceneCoords,
} from "@excalidraw/common"; } from "@excalidraw/common";
import { import {
@@ -192,6 +194,7 @@ const renderBindingHighlightForBindableElement = (
elementsMap: RenderableElementsMap, elementsMap: RenderableElementsMap,
allElementsMap: NonDeletedSceneElementsMap, allElementsMap: NonDeletedSceneElementsMap,
appState: InteractiveCanvasAppState, appState: InteractiveCanvasAppState,
renderConfig: InteractiveCanvasRenderConfig,
) => { ) => {
switch (element.type) { switch (element.type) {
case "magicframe": case "magicframe":
@@ -337,16 +340,32 @@ const renderBindingHighlightForBindableElement = (
break; break;
} }
const { x: cursorX, y: cursorY } = viewportCoordsToSceneCoords(
{
clientX: renderConfig.lastViewportPosition.x,
clientY: renderConfig.lastViewportPosition.y,
},
appState,
);
// Draw center snap area // Draw center snap area
context.save(); context.save();
context.translate(element.x + appState.scrollX, element.y + appState.scrollY); 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.lineWidth = 1 / appState.zoom.value;
context.setLineDash([4 / appState.zoom.value, 4 / appState.zoom.value]); context.setLineDash([4 / appState.zoom.value, 4 / appState.zoom.value]);
context.lineDashOffset = 0; context.lineDashOffset = 0;
context.fillStyle = "rgba(0, 0, 0, 0.01)";
const radius = 0.5 * (Math.min(element.width, element.height) / 2); 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.beginPath();
context.ellipse( context.ellipse(
element.width / 2, element.width / 2,
@@ -899,6 +918,7 @@ const _renderInteractiveScene = ({
elementsMap, elementsMap,
allElementsMap, allElementsMap,
appState, appState,
renderConfig,
); );
} }

View File

@@ -66,6 +66,7 @@ export type InteractiveCanvasRenderConfig = {
remotePointerUsernames: Map<SocketId, string>; remotePointerUsernames: Map<SocketId, string>;
remotePointerButton: Map<SocketId, string | undefined>; remotePointerButton: Map<SocketId, string | undefined>;
selectionColor: string; selectionColor: string;
lastViewportPosition: { x: number; y: number };
// extra options passed to the renderer // extra options passed to the renderer
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
renderScrollbars?: boolean; renderScrollbars?: boolean;