Fix ImGui hover dead zones at negative canvas coordinates

Port upstream imgui-node-editor's IMGUI_HAS_VIEWPORT support into the
vendored canvas: docking-branch ImGui's IsMouseHoveringRect requires
rects to overlap the mouse viewport's screen-space rect, so items at
canvas coordinates outside the screen range (e.g. negative) never
hovered or clicked. Transform the viewport into canvas-local space in
EnterLocalSpace() and restore it in LeaveLocalSpace(), like the mouse
position. Fixes node hover highlight and in-node widget interaction
left of / above the canvas origin.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-10 23:02:44 +08:00
parent d9826b7bbb
commit 22406fbe63
2 changed files with 99 additions and 0 deletions
+74
View File
@@ -94,6 +94,7 @@ bool ImGuiEx::Canvas::Begin(ImGuiID id, const ImVec2& size)
# endif
SaveInputState();
SaveViewportState();
EnterLocalSpace();
@@ -319,6 +320,46 @@ void ImGuiEx::Canvas::RestoreInputState()
ImGui::GetCurrentWindow()->DC.CursorMaxPos = m_WindowCursorMaxBackup;
}
// UsdLayerManager local patch, ported from upstream imgui-node-editor
// (see the declaration in imgui_canvas.h for why).
void ImGuiEx::Canvas::SaveViewportState()
{
# if defined(IMGUI_HAS_VIEWPORT)
auto window = ImGui::GetCurrentWindow();
auto viewport = ImGui::GetWindowViewport();
m_WindowPosBackup = window->Pos;
m_ViewportPosBackup = viewport->Pos;
m_ViewportSizeBackup = viewport->Size;
# if IMGUI_VERSION_NUM > 18002
m_ViewportWorkPosBackup = viewport->WorkPos;
m_ViewportWorkSizeBackup = viewport->WorkSize;
# else
m_ViewportWorkOffsetMinBackup = viewport->WorkOffsetMin;
m_ViewportWorkOffsetMaxBackup = viewport->WorkOffsetMax;
# endif
# endif
}
void ImGuiEx::Canvas::RestoreViewportState()
{
# if defined(IMGUI_HAS_VIEWPORT)
auto window = ImGui::GetCurrentWindow();
auto viewport = ImGui::GetWindowViewport();
window->Pos = m_WindowPosBackup;
viewport->Pos = m_ViewportPosBackup;
viewport->Size = m_ViewportSizeBackup;
# if IMGUI_VERSION_NUM > 18002
viewport->WorkPos = m_ViewportWorkPosBackup;
viewport->WorkSize = m_ViewportWorkSizeBackup;
# else
viewport->WorkOffsetMin = m_ViewportWorkOffsetMinBackup;
viewport->WorkOffsetMax = m_ViewportWorkOffsetMaxBackup;
# endif
# endif
}
void ImGuiEx::Canvas::EnterLocalSpace()
{
// Prepare ImDrawList for drawing in local coordinate system:
@@ -374,6 +415,38 @@ void ImGuiEx::Canvas::EnterLocalSpace()
for (auto i = 0; i < IM_ARRAYSIZE(m_MouseClickedPosBackup); ++i)
io.MouseClickedPos[i] = (m_MouseClickedPosBackup[i] - m_ViewTransformPosition) * m_View.InvScale;
// UsdLayerManager local patch, ported from upstream imgui-node-editor:
// move the viewport rect into local space too, so IsMouseHoveringRect's
// screen-space viewport overlap check (docking-branch ImGui) passes for
// items at canvas coordinates outside the screen range (e.g. negative).
// See SaveViewportState() declaration for the full story.
# if defined(IMGUI_HAS_VIEWPORT)
{
auto window = ImGui::GetCurrentWindow();
window->Pos = ImVec2(0.0f, 0.0f);
auto viewport_min = m_ViewportPosBackup;
auto viewport_max = m_ViewportPosBackup + m_ViewportSizeBackup;
viewport_min.x = (viewport_min.x - m_ViewTransformPosition.x) * m_View.InvScale;
viewport_min.y = (viewport_min.y - m_ViewTransformPosition.y) * m_View.InvScale;
viewport_max.x = (viewport_max.x - m_ViewTransformPosition.x) * m_View.InvScale;
viewport_max.y = (viewport_max.y - m_ViewTransformPosition.y) * m_View.InvScale;
auto viewport = ImGui::GetWindowViewport();
viewport->Pos = viewport_min;
viewport->Size = viewport_max - viewport_min;
# if IMGUI_VERSION_NUM > 18002
viewport->WorkPos = m_ViewportWorkPosBackup * m_View.InvScale;
viewport->WorkSize = m_ViewportWorkSizeBackup * m_View.InvScale;
# else
viewport->WorkOffsetMin = m_ViewportWorkOffsetMinBackup * m_View.InvScale;
viewport->WorkOffsetMax = m_ViewportWorkOffsetMaxBackup * m_View.InvScale;
# endif
}
# endif
m_ViewRect = CalcViewRect(m_View);;
auto& fringeScale = ImFringeScaleRef(m_DrawList);
@@ -449,4 +522,5 @@ void ImGuiEx::Canvas::LeaveLocalSpace()
ImGui::PopClipRect();
RestoreInputState();
RestoreViewportState();
}
+25
View File
@@ -210,6 +210,18 @@ private:
void SaveInputState();
void RestoreInputState();
// UsdLayerManager local patch, ported from upstream imgui-node-editor:
// with a viewport-enabled ImGui (IMGUI_HAS_VIEWPORT, i.e. the docking
// branch), ImGui::IsMouseHoveringRect() — and therefore every item's
// hover test — requires the rect to overlap the mouse viewport's rect,
// which is in *screen* space. Inside the canvas, rects are in canvas
// space, so anything at canvas coordinates outside the screen-space
// range (e.g. negative) went hover/click-dead. Fix: transform the
// viewport rect into canvas-local space while inside EnterLocalSpace()
// and restore it on LeaveLocalSpace(), exactly like the mouse position.
void SaveViewportState();
void RestoreViewportState();
void EnterLocalSpace();
void LeaveLocalSpace();
@@ -243,6 +255,19 @@ private:
ImVec2 m_MousePosPrevBackup;
ImVec2 m_MouseClickedPosBackup[IM_ARRAYSIZE(ImGuiIO::MouseClickedPos)];
ImVec2 m_WindowCursorMaxBackup;
# if defined(IMGUI_HAS_VIEWPORT)
ImVec2 m_WindowPosBackup;
ImVec2 m_ViewportPosBackup;
ImVec2 m_ViewportSizeBackup;
# if IMGUI_VERSION_NUM > 18002
ImVec2 m_ViewportWorkPosBackup;
ImVec2 m_ViewportWorkSizeBackup;
# else
ImVec2 m_ViewportWorkOffsetMinBackup;
ImVec2 m_ViewportWorkOffsetMaxBackup;
# endif
# endif
};
} // namespace ImGuiEx