Skip to content

Fix: widgets in a back node steal hover/click from the front node#337

Open
pthom wants to merge 1 commit into
thedmd:developfrom
pthom:fix_overlap_node
Open

Fix: widgets in a back node steal hover/click from the front node#337
pthom wants to merge 1 commit into
thedmd:developfrom
pthom:fix_overlap_node

Conversation

@pthom
Copy link
Copy Markdown

@pthom pthom commented Apr 29, 2026

Hi @thedmd,

Here is a proposed fix for a bug when nodes overlap: the back node might sometimes steal the focus (if its ui code comes first).

Bug description

When two nodes overlap and both contain interactive widgets, the back node might react to hover instead of the front node.
This happens when the C++ rendering code for the back node is executed before the front node.

With the example below, AAA is executed before BBB

ed::Begin("editor");

ed::BeginNode(ed::NodeId(1));
    if (ImGui::Button("AAA", ImVec2(300, 300)))  // wins clicks even when covered
        printf("AAA\n");
ed::EndNode();

ed::BeginNode(ed::NodeId(2));
    ImGui::Dummy(ImVec2(50, 50)); // empty header area
    if (ImGui::Button("BBB", ImVec2(300, 300)))
        printf("BBB\n");
ed::EndNode();

ed::End();

And AAA will "win" the click, even in the zone where AAA and BBB overlap below:

image

Analysis of the cause:

It seems like ImGui resolves overlapping items with "first hovered wins": the first ItemHoverable() call whose rect contains the cursor sets g.HoveredId, and later calls bail out.

Items inside BeginNode/EndNode are submitted in user code order, regardless of node Z-order, so the node defined first
always wins. This conflicts with the editor's drawing order, which puts later-defined (or higher-Z) nodes on top.

Node-frame interaction in BuildControl already works correctly because it iterates m_Nodes in reverse and submits its own InvisibleButton per node: the front node's button is submitted first and wins. But, the same guarantee does not extend to widgets the user submits between BeginNode and EndNode.

Fix

  1. In NodeBuilder::Begin, check whether any later-drawn node in the Z-sorted m_Nodes covers the cursor. If so, save g.HoveredId and set it to a sentinel value before user code runs, so any ItemHoverable() call inside this node's body bails out.
  2. NodeBuilder::End restores g.HoveredId first thing.

g.ActiveId is intentionally left alone so any interaction already in progress (e.g. a drag started before the front node moved on top) keeps working until mouse-up.

When two overlapping nodes contain interactive widgets, ImGui's
"first hovered wins" rule causes the back node's widgets to claim
hover even when a front node visually covers them.

Detect the case in NodeBuilder::Begin() by checking whether any
later-drawn node in the Z-sorted m_Nodes covers the cursor; if so,
set HoveredId to a sentinel for the duration of the user's node
body so subsequent ItemHoverable() calls bail out. End() restores
the previous HoveredId. ActiveId is left untouched so any
in-progress interaction keeps working.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant