diff --git a/src/lib/litegraph/src/LGraphCanvas.ts b/src/lib/litegraph/src/LGraphCanvas.ts index d2df8939a4..9a5affe3c2 100644 --- a/src/lib/litegraph/src/LGraphCanvas.ts +++ b/src/lib/litegraph/src/LGraphCanvas.ts @@ -2540,8 +2540,15 @@ export class LGraphCanvas implements CustomEventDispatcher this.ctx.lineWidth = this.connections_width + 7 const dpi = this.dpr - // Try layout store for segment hit testing first (more precise) - const hitSegment = layoutStore.queryLinkSegmentAtPoint({ x, y }, this.ctx) + // Try layout store for segment hit testing first (more precise). + // Pass this.dpr so the layout-store hit-test uses the same DPR as the + // isPointInStroke fallback below; otherwise the two paths can disagree + // on low-DPR displays (e.g. chromium-0.5x). + const hitSegment = layoutStore.queryLinkSegmentAtPoint( + { x, y }, + this.ctx, + this.dpr + ) for (const linkSegment of this.renderedPaths) { const centre = linkSegment._pos diff --git a/src/renderer/core/layout/store/layoutStore.ts b/src/renderer/core/layout/store/layoutStore.ts index 9ce5ee5892..fb5f7ba557 100644 --- a/src/renderer/core/layout/store/layoutStore.ts +++ b/src/renderer/core/layout/store/layoutStore.ts @@ -661,10 +661,17 @@ class LayoutStoreImpl implements LayoutStore { /** * Query link segment at point (returns structured data) + * + * @param dpr Device pixel ratio used to map the CSS-space point into the + * canvas's device-pixel-scaled stroke space. Pass the active + * `LGraphCanvas.dpr` so this hit-test agrees with `processMouseDown`'s + * `isPointInStroke` fallback. Falls back to `window.devicePixelRatio` + * for legacy callers without a canvas reference. */ queryLinkSegmentAtPoint( point: Point, - ctx?: CanvasRenderingContext2D + ctx?: CanvasRenderingContext2D, + dpr?: number ): { linkId: LinkId; rerouteId: RerouteId | null } | null { // Determine tolerance from current canvas state (if available) // - Use the caller-provided ctx.lineWidth (LGraphCanvas sets this to connections_width + padding) @@ -695,9 +702,12 @@ class LayoutStoreImpl implements LayoutStore { if (!segmentLayout) continue if (ctx && segmentLayout.path) { - // TODO: Migrate to canvas.dpr once layoutStore has access to the active viewport + // Prefer the caller-supplied DPR (the active LGraphCanvas.dpr) so + // this hit-test stays in lockstep with processMouseDown's fallback + // path; fall back to window.devicePixelRatio for legacy callers. const dpi = - (typeof window !== 'undefined' && window?.devicePixelRatio) || 1 + dpr ?? + ((typeof window !== 'undefined' && window?.devicePixelRatio) || 1) const hit = ctx.isPointInStroke( segmentLayout.path, point.x * dpi, @@ -732,10 +742,11 @@ class LayoutStoreImpl implements LayoutStore { */ queryLinkAtPoint( point: Point, - ctx?: CanvasRenderingContext2D + ctx?: CanvasRenderingContext2D, + dpr?: number ): LinkId | null { // Invoke segment query and return just the linkId - const segment = this.queryLinkSegmentAtPoint(point, ctx) + const segment = this.queryLinkSegmentAtPoint(point, ctx, dpr) return segment ? segment.linkId : null } diff --git a/src/renderer/core/layout/types.ts b/src/renderer/core/layout/types.ts index 42df159cc0..e832f6906c 100644 --- a/src/renderer/core/layout/types.ts +++ b/src/renderer/core/layout/types.ts @@ -274,10 +274,15 @@ export interface LayoutStore { queryNodesInBounds(bounds: Bounds): NodeId[] // Hit testing queries for links, slots, and reroutes - queryLinkAtPoint(point: Point, ctx?: CanvasRenderingContext2D): LinkId | null + queryLinkAtPoint( + point: Point, + ctx?: CanvasRenderingContext2D, + dpr?: number + ): LinkId | null queryLinkSegmentAtPoint( point: Point, - ctx?: CanvasRenderingContext2D + ctx?: CanvasRenderingContext2D, + dpr?: number ): { linkId: LinkId; rerouteId: RerouteId | null } | null querySlotAtPoint(point: Point): SlotLayout | null queryRerouteAtPoint(point: Point): RerouteLayout | null