ocornut/imgui

Incorrect appearing size caused by child windows

Closed this issue · 3 comments

Version/Branch of Dear ImGui:

Version 1.92.2b

Back-ends:

imgui_impl_sdl3.cpp + imgui_impl_sdlrenderer3.cpp

Compiler, OS:

MSVC

Full config/build information:

No response

Details:

If an auto-resized window contains another child window with ImGuiChildFlags_AutoResize.. flags, the window will have wrong size at the first visible frame. (See the example code for more context.)

Workarounds: 1. Calculate content size manually (but that beats the purpose of auto-resize). 2. Hide the window for one extra frame.

Screenshots/Video:

Image

Minimal, Complete and Verifiable Example code:

static void issue() {
    if (ImGui::Begin("Issue")) {
        static bool slow = false;
        static int number = 0;
        ImGui::Checkbox("Slow", &slow);
        if (slow) {
            Sleep(100);
        }
        if (ImGui::Button("Popup")) {
            ImGui::OpenPopup("Example");
            number = rand() % 5; // 0 ~ 4
        }
        if (ImGui::BeginPopup("Example")) {
            ImGui::Button("##other contents", { 200,60 });
            // I want to achieve the following effects:
            // 1. The actual required width is not known in advance (e.g. list of files), and can be calculated automatically.
            // 2. The height is dependent on number of items, but I can easily specify height limit.
            // 3. If the contents reach the height limit, the window will show a scrollbar.

            // The following combination works well, but will have wrong size (only) at first visible frame, causing a small flick.
            // Intuitively, this can be avoided as the popup has already hidden itself for one frame for size calculation.
            // (Turn on 'Slow' to see the effect.)
            if (number != 0) {
                ImGui::SetNextWindowSizeConstraints({ 0,0 }, { 300,100 });
                if (ImGui::BeginChild("List", { 0,0 }, ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY)) {
                    for (int i = 0; i < number; ++i) {
                        ImGui::PushID(i);
                        ImGui::Button("##Item", { 220,30 });
                        ImGui::PopID();
                    }
                }
                ImGui::EndChild();
            }

            ImGui::EndPopup();
        }
    }
    ImGui::End();
}

This is known and has always been the case. Any auto-resizing requires one frame to proceed.
There's no way around it.

If you wish to hide the parent you can do, e.g.

                [....]
                ImGuiWindow* child_window = ImGui::GetCurrentWindow();
                child_hidden = child_window->Hidden;
                ImGui::EndChild();
            }
            if (child_hidden)
            {
                ImGuiWindow* window = ImGui::GetCurrentWindow();
                window->HiddenFramesForRenderOnly = 2;
            }

But note that isn't specific to child windows, and it's not even generally "correct" to use "child_window->Hidden" as a source of information. It's just an acceptable proxy here since you know that child size is locked at each opening (since you compute number = rand() % 5; // 0 ~ 4 next to OpenPopup()). If you were to resize contents in the parent after the appearing frame it would be one frame late as well.

Thanks! Glad to know about HiddenFramesForRenderOnly.

Closing this as I don't think there's much more to add to this now.