Keyboard navigation scoring logic has a bias to minimize horizontal distance #9580

Open
opened 2026-10-12 16:13:56 +00:00 by amuTBKT · 1 comment
amuTBKT commented 2026-10-12 16:13:56 +00:00 (Migrated from github.com)

Version/Branch of Dear ImGui:

Version 1.93.0 WIP, Branch: docking

Back-ends:

imgui_impl_win32.cpp + imgui_impl_dx11.cpp

Compiler, OS:

Windows 10 + MSVC 2026

Full config/build information:

Dear ImGui 1.93.0 WIP (19298)
--------------------------------
sizeof(size_t): 8, sizeof(ImDrawIdx): 2, sizeof(ImDrawVert): 20
define: __cplusplus=199711
define: _WIN32
define: _WIN64
define: _MSC_VER=1951
define: _MSVC_LANG=201402
define: IMGUI_HAS_VIEWPORT
define: IMGUI_HAS_DOCK
IM_ASSERT: runs expression: OK. expand size: OK
--------------------------------
io.BackendPlatformName: imgui_impl_win32
io.BackendRendererName: imgui_impl_dx11
io.ConfigFlags: 0x00000483
 NavEnableKeyboard
 NavEnableGamepad
 DockingEnable
 ViewportsEnable
io.ConfigDpiScaleFonts
io.ConfigDpiScaleViewports
io.ConfigViewportsNoDecoration
io.ConfigViewportsNoDefaultParent
io.ConfigNavCaptureKeyboard
io.ConfigInputTextCursorBlink
io.ConfigWindowsResizeFromEdges
io.ConfigMemoryCompactTimer = 60.0
io.BackendFlags: 0x00003C1E
 HasMouseCursors
 HasSetMousePos
 PlatformHasViewports
 HasMouseHoveredViewport
 HasParentViewport
 RendererHasVtxOffset
 RendererHasTextures
 RendererHasViewports
--------------------------------
io.Fonts: 1 fonts, Flags: 0x00000000, TexSize: 512,128
io.Fonts->FontLoaderName: stb_truetype
io.DisplaySize: 1264.00,761.00
io.DisplayFramebufferScale: 1.00,1.00
--------------------------------
style.WindowPadding: 8.00,8.00
style.WindowBorderSize: 1.00
style.FramePadding: 4.00,3.00
style.FrameRounding: 0.00
style.FrameBorderSize: 0.00
style.ItemSpacing: 8.00,4.00
style.ItemInnerSpacing: 4.00,4.00

Details:

Hello, I am working on a asset picker/popup for my unreal plugin and running into some weird issues when using keyboard navigations.
The way the interaction is supposed to work is that

  1. User opens the popup and it auto focues on search/filter widget
  2. User should then be able to press down to navigate to the list of items for selection.
    However due to how the Nav scoring works it is picking the wrong widget for navigation.

https://github.com/user-attachments/assets/97b98d18-208b-4424-a450-a19656664ec3

Since the setup currently compares distances b/w rect centers it tends to penalize bigger sized items.
It prioritizes the side buttons over the list view (minimal repro shows it better, without any Unreal theme/style)

Any chance we could try to compare the edge distances instead of center?
I tried with a very HACKY setup and it seems to work better but I am sure there would be other edge cases

//inside ImGui::NavScoreItem

float dbx = NavScoreItemDistInterval(cand.Min.x, cand.Max.x, curr.Min.x, curr.Max.x);
float dby = NavScoreItemDistInterval(ImLerp(cand.Min.y, cand.Max.y, 0.2f), ImLerp(cand.Min.y, cand.Max.y, 0.8f), ImLerp(curr.Min.y, curr.Max.y, 0.2f), ImLerp(curr.Min.y, curr.Max.y, 0.8f)); // Scale down on Y to keep using box-distance for vertically touching items

// added this block to override values along move dir
if (g.NavMoveDir == ImGuiDir_Down)
{
    if (cand.Min.y > curr.Max.y)
    {
        dby = cand.Min.y - curr.Max.y;
    }
}
else if (g.NavMoveDir == ImGuiDir_Up)
{
    if (cand.Max.y < curr.Min.y)
    {
        dby = -curr.Min.y + cand.Max.y;
    }
}
else if (g.NavMoveDir == ImGuiDir_Right)
{
    if (cand.Min.x > curr.Max.x)
    {
        dbx = cand.Min.x - curr.Max.x;
    }
}
else if (g.NavMoveDir == ImGuiDir_Left)
{
    if (cand.Max.x < curr.Min.x)
    {
        dbx = -curr.Min.x + cand.Max.x;
    }
}

I can try using shortcuts API as a workaround but I really don't want to xD
Are there any other ways to solve this? Should I use child windows instead of groups?

Screenshots/Video:

https://github.com/user-attachments/assets/c013da37-83a9-4c02-b393-082e3d727fcd

Here ListBox is getting a higher score due to its height(size).

Minimal, Complete and Verifiable Example code:

if (ImGui::Begin("BugRepro"))
{
    ImGui::BeginGroup();
    {
        char buf[32] = { 0 };
        ImGui::InputText("##InputText", buf, sizeof(buf));

        if (ImGui::BeginListBox("##ListBox"))
        {
            for (int i = 0; i < 32; ++i)
            {
                ImGui::PushID(i);
                ImGui::Selectable("Selectable");
                ImGui::PopID();
            }
            ImGui::EndListBox();
        }
    }
    ImGui::EndGroup();

    ImGui::SameLine();
    ImGui::BeginGroup();
    {
        ImGui::Button("1");
        ImGui::Button("2");
        ImGui::Button("3");
        ImGui::Button("4");
    }
    ImGui::EndGroup();
}
ImGui::End();
### Version/Branch of Dear ImGui: Version 1.93.0 WIP, Branch: docking ### Back-ends: imgui_impl_win32.cpp + imgui_impl_dx11.cpp ### Compiler, OS: Windows 10 + MSVC 2026 ### Full config/build information: ```cpp Dear ImGui 1.93.0 WIP (19298) -------------------------------- sizeof(size_t): 8, sizeof(ImDrawIdx): 2, sizeof(ImDrawVert): 20 define: __cplusplus=199711 define: _WIN32 define: _WIN64 define: _MSC_VER=1951 define: _MSVC_LANG=201402 define: IMGUI_HAS_VIEWPORT define: IMGUI_HAS_DOCK IM_ASSERT: runs expression: OK. expand size: OK -------------------------------- io.BackendPlatformName: imgui_impl_win32 io.BackendRendererName: imgui_impl_dx11 io.ConfigFlags: 0x00000483 NavEnableKeyboard NavEnableGamepad DockingEnable ViewportsEnable io.ConfigDpiScaleFonts io.ConfigDpiScaleViewports io.ConfigViewportsNoDecoration io.ConfigViewportsNoDefaultParent io.ConfigNavCaptureKeyboard io.ConfigInputTextCursorBlink io.ConfigWindowsResizeFromEdges io.ConfigMemoryCompactTimer = 60.0 io.BackendFlags: 0x00003C1E HasMouseCursors HasSetMousePos PlatformHasViewports HasMouseHoveredViewport HasParentViewport RendererHasVtxOffset RendererHasTextures RendererHasViewports -------------------------------- io.Fonts: 1 fonts, Flags: 0x00000000, TexSize: 512,128 io.Fonts->FontLoaderName: stb_truetype io.DisplaySize: 1264.00,761.00 io.DisplayFramebufferScale: 1.00,1.00 -------------------------------- style.WindowPadding: 8.00,8.00 style.WindowBorderSize: 1.00 style.FramePadding: 4.00,3.00 style.FrameRounding: 0.00 style.FrameBorderSize: 0.00 style.ItemSpacing: 8.00,4.00 style.ItemInnerSpacing: 4.00,4.00 ``` ### Details: Hello, I am working on a asset picker/popup for my unreal plugin and running into some weird issues when using keyboard navigations. The way the interaction is supposed to work is that 1. User opens the popup and it auto focues on search/filter widget 2. User should then be able to press down to navigate to the list of items for selection. However due to how the Nav scoring works it is picking the wrong widget for navigation. https://github.com/user-attachments/assets/97b98d18-208b-4424-a450-a19656664ec3 Since the setup currently compares distances b/w rect centers it tends to penalize bigger sized items. It prioritizes the side buttons over the list view (minimal repro shows it better, without any Unreal theme/style) Any chance we could try to compare the edge distances instead of center? I tried with a very HACKY setup and it seems to work better but I am sure there would be other edge cases ```cpp //inside ImGui::NavScoreItem float dbx = NavScoreItemDistInterval(cand.Min.x, cand.Max.x, curr.Min.x, curr.Max.x); float dby = NavScoreItemDistInterval(ImLerp(cand.Min.y, cand.Max.y, 0.2f), ImLerp(cand.Min.y, cand.Max.y, 0.8f), ImLerp(curr.Min.y, curr.Max.y, 0.2f), ImLerp(curr.Min.y, curr.Max.y, 0.8f)); // Scale down on Y to keep using box-distance for vertically touching items // added this block to override values along move dir if (g.NavMoveDir == ImGuiDir_Down) { if (cand.Min.y > curr.Max.y) { dby = cand.Min.y - curr.Max.y; } } else if (g.NavMoveDir == ImGuiDir_Up) { if (cand.Max.y < curr.Min.y) { dby = -curr.Min.y + cand.Max.y; } } else if (g.NavMoveDir == ImGuiDir_Right) { if (cand.Min.x > curr.Max.x) { dbx = cand.Min.x - curr.Max.x; } } else if (g.NavMoveDir == ImGuiDir_Left) { if (cand.Max.x < curr.Min.x) { dbx = -curr.Min.x + cand.Max.x; } } ``` I can try using shortcuts API as a workaround but I really don't want to xD Are there any other ways to solve this? Should I use child windows instead of groups? ### Screenshots/Video: https://github.com/user-attachments/assets/c013da37-83a9-4c02-b393-082e3d727fcd Here ListBox is getting a higher score due to its height(size). ### Minimal, Complete and Verifiable Example code: ```cpp if (ImGui::Begin("BugRepro")) { ImGui::BeginGroup(); { char buf[32] = { 0 }; ImGui::InputText("##InputText", buf, sizeof(buf)); if (ImGui::BeginListBox("##ListBox")) { for (int i = 0; i < 32; ++i) { ImGui::PushID(i); ImGui::Selectable("Selectable"); ImGui::PopID(); } ImGui::EndListBox(); } } ImGui::EndGroup(); ImGui::SameLine(); ImGui::BeginGroup(); { ImGui::Button("1"); ImGui::Button("2"); ImGui::Button("3"); ImGui::Button("4"); } ImGui::EndGroup(); } ImGui::End(); ```
ocornut commented 2026-10-12 21:16:11 +00:00 (Migrated from github.com)

Any chance we could try to compare the edge distances instead of center?

The problem is proving the effect of changes on many cases/setups.
At minimum it would need adding more cases to test_suite before we can tackle scoring issues.
I'm certain there are other open issues related to Nav scoring and the effect of this X bias.

> Any chance we could try to compare the edge distances instead of center? The problem is proving the effect of changes on many cases/setups. At minimum it would need adding more cases to test_suite before we can tackle scoring issues. I'm certain there are other open issues related to Nav scoring and the effect of this X bias.
Sign in to join this conversation.
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ocornut/imgui#9580
No description provided.