Skip to content

Answer a hovered node with the links and reach it has - #377

Merged
matt-edmondson merged 4 commits into
mainfrom
claude/dreamy-lovelace-1jjp93
Sep 9, 2026
Merged

Answer a hovered node with the links and reach it has#377
matt-edmondson merged 4 commits into
mainfrom
claude/dreamy-lovelace-1jjp93

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Closes #372, closes #373, closes #374, closes #376.

What changed

Hovering a node highlights the links that meet it (#372). NodeEditorRenderer.HighlightLinksOnNodeHover, on by default.

The link under the pointer is drawn over the nodes it passes behind (#373). DrawHoveredLinkOnTop, on by default.

An option highlights everything downstream of the hovered node (#374). HighlightDownstreamOnNodeHover, off by default — the reach of a node in a large graph is most of the graph, so it is a question worth asking rather than a permanent state of the view. Downstream links take the highlight colour and downstream nodes are outlined in it.

How many links a pin accepts is now the pin's own business (#376). Pin.AllowsMultipleConnections defaults to many for an output and one for an input.

The highlight colour follows the theme (ImNodes' LinkHovered) unless HighlightColor says otherwise. The demo's Clean ImNodes tab gets a checkbox for each of the three options.

Why it is built this way

ImNodes only answers what the pointer is over once the editor has ended, so the renderer asks after EndNodeEditor and highlights on the next frame. A frame's lag on a pointer that has to rest on a node to mean anything is not one anybody sees, and it is what makes the colouring possible at all: ImNodes copies a link's three colours, and a node's outline colour and border thickness, out of the style as each is submitted, so PushColorStyle around one submission colours that one thing and no other.

The hovered link is redrawn on the foreground draw list, as the same cubic bezier ImNodes draws — between the same two pin positions, control points a quarter of the straight-line distance to either side — clipped to the editor. It has to be the foreground list: the editor is a child window, and a child window's drawing is composited over its parent's, so an overlay on the window draw list ends up underneath the nodes. A test asserts exactly this, and it fails with the window list.

That same discovery applies to RenderDebugOverlays, which drew on the window list while claiming to be on top: its force and velocity vectors start at node centres, so they were hidden under the nodes they belong to. It now draws on the foreground list too, keeping its existing clip rect.

On #376

Output fan-out already worked, but only through the absence of a check — the engine hardcoded "one link per input, any number per output" and discarded every declared AllowMultipleConnections. Now:

  • fan-out is the stated, tested default for an output pin;
  • [OutputPin(AllowMultipleConnections = false)] and [InputPin(AllowMultipleConnections = true)] are carried onto the created pins by AttributeBasedNodeFactory instead of being read and dropped, and NodeEditorEngine.SetPinAllowsMultipleConnections sets it directly;
  • the same pair of pins can no longer be linked twice, which fan-out otherwise makes easy to do by accident.

Existing behaviour is unchanged for anything that did not declare a capacity.

Also added

NodeEditorEngine.GetOutgoingLinks, GetIncomingLinks and GetDownstream (a cycle-safe forward walk returning a GraphReach of node and link ids), and NodeEditorRenderer.TryGetNodeScreenRect / TryGetPinScreenPosition for consumers — and tests — that need to know where something was drawn.

Testing

dotnet test tests/ImGui.NodeEditor.Tests — 93 passed, Debug and Release. New coverage: fan-out, duplicate-pair refusal, narrowed and widened pins, the factory carrying declared capacities, downstream traversal (chain, branch, upstream excluded, cycle), and a HoverHighlightTests suite that drives real frames — hovering a node, moving off it, the options on and off, and a pixel comparison over a node the hovered link passes behind.

tests/NodeGraph.Tests — 106 passed. dotnet build ImGui.sln -c Release — clean.

tests/ImGuiAppDemo.UITests could not run in this environment: the demo loads icon.png at start-up and the repository's Git LFS assets are pointer files here, so the harness fails before any test body. That is independent of this change (no image or texture code is touched) and CI runs those suites with LFS fetched.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KkR28NfEw1iVW73A9A6q7u


Generated by Claude Code

Hovering a node now colours the links that meet it, an option colours
everything downstream of it as well, and the link under the pointer is
drawn again over the nodes it passes behind. How many links a pin accepts
becomes the pin's own business rather than a rule the engine hardcodes.

ImNodes only answers what the pointer is over once the editor has ended,
so the renderer asks after EndNodeEditor and highlights on the next frame.
That is also what makes the colouring possible: ImNodes copies a link's
three colours, and a node's outline colour and border thickness, out of the
style as each is submitted, so pushing them around one submission colours
that one thing.

The hovered link is redrawn on the foreground draw list rather than the
window's. The editor is a child window and a child's drawing is composited
over its parent's, so an overlay on the window list ends up underneath the
very nodes it is meant to clear - which is why the debug overlays, drawn
from node centres, moved to the foreground list too.

Pins carry AllowsMultipleConnections: many for an output, one for an input,
either overridable. Output fan-out already worked, but only by the absence
of a check; now it is the stated default, a declared
[OutputPin(AllowMultipleConnections = false)] is carried onto the pin by
the factory instead of being read and dropped, and the same pair of pins
cannot be linked twice.

Closes #372
Closes #373
Closes #374
Closes #376

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkR28NfEw1iVW73A9A6q7u
Comment thread ImGui.NodeEditor/NodeEditorEngine.cs Fixed
The loop searched each node's pins twice over: once to find out whether the
node owned the pin, and again to set it. A pin id is unique across the
graph, so there is one node to find.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkR28NfEw1iVW73A9A6q7u
GetUpstream is the mirror of GetDownstream: what a node's value comes from
rather than what it reaches. Both are the same walk with the direction as
an argument, so a cycle terminates and the far end of a link is found the
same way in either direction.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkR28NfEw1iVW73A9A6q7u
MSTEST0068 asks for Assert.AreSequenceEqual over CollectionAssert.AreEquivalent,
which on its own would turn these into order-dependent assertions over a set
that promises no order. Sorting the actual first satisfies the analyzer and
says what the test means.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkR28NfEw1iVW73A9A6q7u
@sonarqubecloud

sonarqubecloud Bot commented Sep 9, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants