Answer a hovered node with the links and reach it has - #377
Merged
Conversation
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
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
|
This was referenced Sep 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



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.AllowsMultipleConnectionsdefaults to many for an output and one for an input.The highlight colour follows the theme (ImNodes'
LinkHovered) unlessHighlightColorsays 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
EndNodeEditorand 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, soPushColorStylearound 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:[OutputPin(AllowMultipleConnections = false)]and[InputPin(AllowMultipleConnections = true)]are carried onto the created pins byAttributeBasedNodeFactoryinstead of being read and dropped, andNodeEditorEngine.SetPinAllowsMultipleConnectionssets it directly;Existing behaviour is unchanged for anything that did not declare a capacity.
Also added
NodeEditorEngine.GetOutgoingLinks,GetIncomingLinksandGetDownstream(a cycle-safe forward walk returning aGraphReachof node and link ids), andNodeEditorRenderer.TryGetNodeScreenRect/TryGetPinScreenPositionfor 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 aHoverHighlightTestssuite 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.UITestscould not run in this environment: the demo loadsicon.pngat 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