ocornut/imgui

Auto-scroll not working when a table has a column with buttons

Opened this issue · 4 comments

Version/Branch of Dear ImGui:

Version 1.94 WIP, Branch: master

Back-ends:

imgui_impl_glfw.cpp + imgui_impl_opengl3.cpp

Compiler, OS:

Window 11 + MSVC 19.43.34809, Windows 11 + MingW-Clang 21.1.0

Full config/build information:

No response

Details:

My Issue/Question:

I have a panel that contains some tab bars, inside each tab bar there is a table. The table is populated with information generated over the time. What is notice is that the auto-scroll is not working, ImGui::GetScrollY() has always a constant value (as long as scroll is not manually done), meanwhile ImGui::GetScrollMaxY() is increasing with each new entry on the table, so the condition is never met. I have tried to set the flag ImGuiTabBarFlags_FittingPolicyScroll but no luck. If I remove the button and replace it with text or SmallButton it works ok. If I let the table be populated, and later I open the tab, the table is scrolled to the bottom, but once is opened, even if I switch tab the auto scroll stops working.

Screenshots/Video:

Image

Minimal, Complete and Verifiable Example code:

// Put this somewhere outside the main loop
std::mutex items_mutex;
int items_count = 0;
int items[100] = {};

auto fnc_puh_items = [&]()
{
    while (items_count < 100)
    {
        {
            std::lock_guard<std::mutex> lock(items_mutex);
            items[items_count++] = items_count + 100;
        }
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
};
std::thread(fnc_puh_items).detach();

// Test code for the bug (inside main loop)
constexpr int TABLE_FLAGS = ImGuiTableFlags_Resizable
    | ImGuiTableFlags_HighlightHoveredColumn
    | ImGuiTableFlags_SizingFixedFit
    | ImGuiTableFlags_Reorderable
    | ImGuiTableFlags_Hideable
    | ImGuiTableFlags_ScrollX
    | ImGuiTableFlags_ScrollY
    | ImGuiTableFlags_Borders
    ;
if (ImGui::Begin("Test panel"))
{
    if (ImGui::BeginTabBar("Test Tab Bar", ImGuiTabBarFlags_Reorderable))
    {
        if (ImGui::BeginTabItem("Test Tab Item"))
        {
            if (ImGui::BeginTable("Test Table", 3, TABLE_FLAGS))
            {
                ImGui::TableSetupColumn("Action");
                ImGui::TableSetupColumn("Count");
                ImGui::TableSetupColumn("Event");

                ImGui::TableSetupScrollFreeze(1, 1);
                ImGui::TableHeadersRow();

                std::lock_guard<std::mutex> lock(items_mutex);
                for (size_t i = 0; i < items_count; ++i)
                {
                    ImGui::TableNextRow();

                    {
                        ImGui::TableNextColumn();
                        ImGui::PushID(i);
                        if (ImGui::Button("View"))
                        {
                        } ImGui::PopID();
                    }
                    {
                        ImGui::TableNextColumn();
                        ImGui::Text("%lld", i + 1);
                    }
                    {
                        ImGui::TableNextColumn();
                        ImGui::Text("%d", items[i]);
                    }
                }

                if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY())
                {
                    ImGui::SetScrollHereY(1.0f);
                }

                ImGui::EndTable();
            }

            ImGui::EndTabItem();
        }
        ImGui::EndTabBar();
    }
}
ImGui::End();

Hello,
Could you simplify your code so it is more standalone and may be pasted anywhere in a dear imgui enabled app?
It probably doesn't need 6 tabs and it is currently relying on type/data we don't have.

Update the main point with a small source code showing the problem. It seems to be related to the table. There are 2 comments, one to put outside the main loop (for initialization) and another one for the main loop.

Here's a simplified repro that exhibits the issue with less unrelated code (no need for tab-bar or threads):

void Issue8984()
{
    // Test code for the bug (inside main loop)
    constexpr int TABLE_FLAGS = ImGuiTableFlags_Resizable
        | ImGuiTableFlags_SizingFixedFit
        | ImGuiTableFlags_ScrollY
        | ImGuiTableFlags_Borders
        ;

    static int items_count = 0;
    static int items[100] = {};
    if (ImGui::IsKeyPressed(ImGuiKey_F1))
        items[items_count++] = items_count + 100;

    if (ImGui::Begin("Test panel"))
    {
        float scroll_y = -1.0f;
        float scroll_max_y = -1.0f;

        if (ImGui::BeginTable("Test Table", 3, TABLE_FLAGS, ImVec2(0.0f, -ImGui::GetTextLineHeightWithSpacing())))
        {
            ImGui::TableSetupColumn("Action");
            ImGui::TableSetupColumn("Count");
            ImGui::TableSetupColumn("Event");

            ImGui::TableSetupScrollFreeze(1, 1);
            ImGui::TableHeadersRow();

            for (size_t i = 0; i < items_count; ++i)
            {
                ImGui::TableNextRow();
                ImGui::PushID((int)i);
                ImGui::TableNextColumn();
                ImGui::Button("View");
                ImGui::TableNextColumn();
                ImGui::Text("%lld", i + 1);
                ImGui::TableNextColumn();
                ImGui::Text("%d", items[i]);
                ImGui::PopID();
            }

            scroll_y = ImGui::GetScrollY();
            scroll_max_y = ImGui::GetScrollMaxY();

            if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY())
                ImGui::SetScrollHereY(1.0f);

            ImGui::EndTable();
        }
        ImGui::Text("Scroll %.3f/%.3f", scroll_y, scroll_max_y);
    }
    ImGui::End();
}

The problem is a combination of two things:

if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY())
    ImGui::SetScrollHereY(1.0f);

The bottom most of the item is not the bottom-most of the scroll region because there's an additional CellPadding.y below it.

As thus, the logic we use in imgui_demo.cpp indeed does not work and should be reworked.

The best workaround is simply to use:

if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY())
    ImGui::SetScrollY(999999.0f);

Another workaround is to use:

if (ImGui::GetScrollY() + ImGui::GetStyle().CellPadding.y >= ImGui::GetScrollMaxY())
    ImGui::SetScrollHereY();

But I will want to investigate this further.

Very disturbingly,

if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY())
    ImGui::SetScrollHereY();

Also works!

Whereas:

if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY())
    ImGui::SetScrollHereY(1.0f);

Doesn't. The parameter is 0.0..1.0 factor between item top and item bottom so in theory 1.0f should be bottom edge of item.

(it might be related to #1114)