From 22406fbe638b83d03860c86c0d54f695fcc6aa3c Mon Sep 17 00:00:00 2001 From: indigo Date: Fri, 10 Jul 2026 23:02:44 +0800 Subject: [PATCH] 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 --- src/ui/NodeEditor/Source/imgui_canvas.cpp | 74 +++++++++++++++++++++++ src/ui/NodeEditor/Source/imgui_canvas.h | 25 ++++++++ 2 files changed, 99 insertions(+) diff --git a/src/ui/NodeEditor/Source/imgui_canvas.cpp b/src/ui/NodeEditor/Source/imgui_canvas.cpp index 6845d73..d23a12e 100644 --- a/src/ui/NodeEditor/Source/imgui_canvas.cpp +++ b/src/ui/NodeEditor/Source/imgui_canvas.cpp @@ -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(); } diff --git a/src/ui/NodeEditor/Source/imgui_canvas.h b/src/ui/NodeEditor/Source/imgui_canvas.h index f6b1a85..0c3bd27 100644 --- a/src/ui/NodeEditor/Source/imgui_canvas.h +++ b/src/ui/NodeEditor/Source/imgui_canvas.h @@ -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