Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ImGuiListClipper doesn't obey scroll position in certain conditions, seemingly unrelated #3073

Closed
wolfpld opened this issue Mar 24, 2020 · 4 comments

Comments

@wolfpld
Copy link
Contributor

wolfpld commented Mar 24, 2020

Version/Branch of Dear ImGui:

Version: 1.75
Branch: master

Issue

Consider the following code:

    static bool useClipper = false;
    int target = -1;
    ImGui::SetNextWindowSize( ImVec2( 1000, 1000 ) );
    ImGui::Begin( "clipper test" );
    if( ImGui::Button( "10" ) ) target = 10;
    ImGui::SameLine();
    if( ImGui::Button( "100" ) ) target = 100;
    ImGui::SameLine();
    if( ImGui::Button( "1000" ) ) target = 1000;
    ImGui::TextUnformatted( "bleble" );
    ImGui::BeginChild( "##cliptest", ImVec2( 0, 0 ), true );
    if( target >= 0 ) useClipper = false;
    if( useClipper )
    {
        ImGuiListClipper clipper( 2000 );
        while( clipper.Step() )
        {
            for( auto i=clipper.DisplayStart; i<clipper.DisplayEnd; i++ )
            {
                ImGui::Text( "%i", i );
            }
        }

    }
    else
    {
        for( int i=0; i<2000; i++ )
        {
            if( target == i )
            {
                target = -1;
                useClipper = true;
                ImGui::SetScrollHereY();
            }
            ImGui::Text( "%i", i );
        }
    }
    ImGui::EndChild();
    ImGui::End();

This creates a list of 2000 items, which is clipped by ImGuiListClipper during normal usage, unless target position is selected using one of the buttons, in which case whole list is rendered for a single frame, so that ImGui::SetScrollHereY() can be called to center the view on the selected entry.

What should happen

When the '1000' button is pressed, the list of items should be centered on the '1000' item.

What happens instead

When the aforementioned button is pressed, the list of items is centered on the '1000' item for a single frame, then, when the clipper is used to skip rendering of unseen elements, the '1000' item is placed outside the view. Notice that the same thing happens when the '100' button is pressed, but this time scroll mis-movement is smaller.

The WTF moment

Remove the ImGui::TextUnformatted( "bleble" ); line to reduce number of printed lines in header to 1, or add another line (doesn't matter what's in it) to increase number of printed lines in header to 3. This will fix the issue. It appears that when number of header lines is even, this issue manifests itself, and when number of header lines is odd, everything works as intended.

@ocornut
Copy link
Owner

ocornut commented Mar 24, 2020

Wow yeah that's weird. I feel this may be an issue related to incorrect floating point rounding somewhere, will look at it !

(Not your bug but as for the clipper API: it's obviously lacking. We should rewrite it to at least 1) have a flag to disable clipping while preserving same looping API, 2) allow to manually insert items to not clip, both will allow you to avoid duplicating your code.)

@ocornut ocornut added the bug label Mar 24, 2020
ocornut added a commit that referenced this issue Mar 24, 2020
…ling values and initial cursor position. (#3073)

This would often get fixed after the fix item submission, but using the ImGuiListClipper as the first thing after Begin() could largely break size calculations. (#3073)
@ocornut
Copy link
Owner

ocornut commented Mar 24, 2020

Hello,

Fixed with 4986dba. Haven't merged in Docking yet but you may cherry-pick it without conflict.

The issue that was Y position are meant to be aligned to integer, but that centering code would lead to misaligned scrolling leading to misaligned starting cursor position. This would generally be fixed after any item submission, but using the clipper right after Begin() the clipper would grab an unrounded position, then miscalculate item height (e.g. it would calculate line height as 13.5 instead of 13).

Passing our tests.

Thanks a lot!

@ocornut ocornut closed this as completed Mar 24, 2020
@wolfpld
Copy link
Contributor Author

wolfpld commented Mar 24, 2020

Thanks :)

sergeyn pushed a commit to sergeyn/imgui that referenced this issue Mar 30, 2020
…ling values and initial cursor position. (ocornut#3073)

This would often get fixed after the fix item submission, but using the ImGuiListClipper as the first thing after Begin() could largely break size calculations. (ocornut#3073)
@ocornut ocornut added the clipper label Apr 7, 2021
@ForrestFeng
Copy link

@wolfpld I comes to the same issue and resolved it this way. Just put it as reference for anyone who comes to the same problem

This solution can scroll to target row in one frame. with ImGui::SetScrollY().
In this example, the item_height will be calculate by the clipper automatically for us see item_height = clipper.ItemsHeight
The good thing is that it has no performance penalty with large amout of items to display.

                static bool useClipper = false;
                static int target = -1;
                static float item_height = -1;

                ImGui::SetNextWindowPos(ImVec2(0.f, 0.f));
                ImGui::SetNextWindowSize(ImVec2(400, 1000));
                ImGui::Begin("clipper test");
                if (ImGui::Button("10")) target = 10;
                ImGui::SameLine();
                if (ImGui::Button("100")) target = 100;
                ImGui::SameLine();
                if (ImGui::Button("1000")) target = 1000;
                ImGui::TextUnformatted("bleble");
                ImGui::BeginChild("##cliptest", ImVec2(0, 0), true);

                if (target >= 0 && item_height > 0)
                {
                    if (true)
                    {
                        // let item appear at the top
                        ImGui::SetScrollY(item_height * target);
                        target = -1;
                    }
                    else
                    {
                        // let item appear at the middle
                        float h = ImGui::GetCurrentContext()->CurrentWindow->InnerRect.GetSize().y;
                        int cnt = (int)(h / item_height); // lines can be seen for the item in the child window
                        ImGui::SetScrollY(item_height * ImMax(0, (target - cnt / 2)));
                        target = -1;
                    }
                }

                ImGuiListClipper clipper(2000);
                while (clipper.Step())
                {
                    for (auto i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
                    {
                        ImGui::Text("%i", i);
                    }
                    if (clipper.ItemsHeight > 0)
                    {
                        item_height = clipper.ItemsHeight;
                    }

                }

                ImGui::EndChild();
                ImGui::End();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants