ocornut / imgui

Dear ImGui: Bloat-free Graphical User interface for C++ with minimal dependencies
MIT License
59.42k stars 10.12k forks source link

How to check if scrollbar is visible in the current window #7818

Closed MayitaMayoso closed 1 month ago

MayitaMayoso commented 1 month ago

Version/Branch of Dear ImGui:

Version 1.89.7, Branch: docking (master/docking/etc.)

Back-ends:

imgui_impl_glfw.cpp + imgui_impl_opengl3.cpp

Compiler, OS:

Debian 10 + GNUC

Question:

Is there a way to know if the scrollbar is visible other than pre-calculating if the size of the window / child window / frame exceeds the available region? Style has ScrollBarSize which I use for drawing some stuff but since the scrollbar only appears if there is something to scroll I was wondering if there is maybe any Getter I have missed when searching for an answer.

ocornut commented 1 month ago

Use ImGui::GetScrollMaxY() > 0.0f is there's scrolling, and window->ScrollbarY (imgui_internal.h) if there's a scrollbar. You can have a scrolling range without a scrollbar.

But it is also very likely that you may be facing a XY Problem because you normally should not need to know about this. Can you clarify/explain why do you want to know if a scrollbar is visible?

MayitaMayoso commented 1 month ago

First of all thanks for the quick response as always. It worked perfectly!

I am doing some custom drawing within a child window. I have used the ScrollBarSize in order for the symbols to not go under the ScrollBar. But when there is no Scrollar you can see an empty space. I want to know if there is Scrollbar to decide if I deduce the size or not of the total size for my own calculations for the drawings. image

ocornut commented 1 month ago

I want to know if there is Scrollbar to decide if I deduce the size or not of the total size for my own calculations for the drawings.

This seems to be a XY Problem indeed. You should not need to query scrollbar visibility. You should call GetContentRegionAvail() instead to obtain the drawable size.

ImVec2 pos_min = ImGui::GetCursorScreenPos();
ImVec2 pos_max = pos_min + ImGui::GetContentRegionAvail();

If you are not sure what some positions/values are, draw them on screen:

ImGui::GetForegroundDrawList()->AddRect(pos_min, pos_max, IM_COL32(255, 0, 0, 255));
MayitaMayoso commented 1 month ago

The problem is that I need the size of the scroll before ImGui knows there is a scroll(?). I begin with an empty child window and I get the available size. Next I use the DrawList functionalities to draw my elements. These calls are followed by moving the cursor in order to let ImGui know the space has been occupied and if exceeded the height of the window make the scrollbar appear.

void SymbolColorComponent::RenderSymbols()
{
    // Window parameters
    auto Style = ImGui::State(ImGuiCol_ChildBg, Color4::Grey(.5f, .1f));
    Vec2 WindowStart = ImGui::GetWindowPos() + ImGui::GetCursorPos();
    Vec2 WindowSize = ImGui::GetContentRegionAvail();
    ImGui::BeginChild(ImGui::GetID("Symbols"), WindowSize);
    if (ImGui::GetScrollMaxY())
    {
        WindowSize -= Vec2(ImGui::GetStyle().ScrollbarSize, 0.0f);
    }

    // Symbol parameters
    Vec2 SymbolStart = WindowStart - Vec2(0, ImGui::GetScrollY());
    Vec2 SymbolPos = SymbolStart;
    Vec2 SymbolSize(100);

    // Fixing symbol width to fit window span
    auto SymbolsHorizontalCount = std::floor(WindowSize.x / SymbolSize.x);
    SymbolSize.x += (WindowSize.x - SymbolSize.x * SymbolsHorizontalCount) / SymbolsHorizontalCount;

    ImGui::GetWindowDrawList()->PushClipRect(WindowStart, WindowStart + WindowSize);
    for (const auto& [Symbol, _] : mColors)
    {
        if (SymbolMatchFilter(Symbol))
        {
            RenderSymbol(Symbol, SymbolPos, SymbolSize);

            UpdateSymbol(Symbol, SymbolPos, SymbolSize);

            SymbolPos += SymbolSize * Vec2(1, 0);
            if (SymbolPos.x + SymbolSize.x > WindowStart.x + WindowSize.x + 1)
            {
                SymbolPos = Vec2(SymbolStart.x, SymbolPos.y + SymbolSize.y);
                ImGui::SetCursorPos(ImGui::GetCursorPos() + SymbolSize * Vec2(0, 1));
            }

            if (SymbolPos.y > WindowStart.y + WindowSize.y)
            {
                break;
            }
        }
    }
    ImGui::SetCursorPos(ImGui::GetCursorPos() + SymbolSize * Vec2(0, 1));
    ImGui::GetWindowDrawList()->PopClipRect();
    ImGui::EndChild();
}
ocornut commented 1 month ago

Some suggestions

(1)

Vec2 WindowStart = ImGui::GetWindowPos() + ImGui::GetCursorPos();

Use

Vec2 WindowStart = ImGui::GetCursorScreenPos()`

Avoid all functions returning local coordinates. They make everything more complicated.

Vec2 SymbolStart = WindowStart - Vec2(0, ImGui::GetScrollY());

This also simply:

Vec2 SymbolStart = ImGui::GetCursorScreenPos();

(2) I don't understand your explanation above and I don't think you need to do most of this. You only need to use GetCursorScreenPos() and GetContentRegionAvail().

(3)

These calls are followed by moving the cursor in order

This is illegal since 1.89: (#5548)

ImGui::SetCursorPos(ImGui::GetCursorPos() + SymbolSize * Vec2(0, 1));

You should do

ImGui::SetCursorPos(ImGui::GetCursorPos() + SymbolSize * Vec2(0, 1));
ImGui::Dummy({0,0});

or

ImGui::Dummy(SymbolSize * Vec2(0, 1));

See v1.89 release notes.

ocornut commented 1 month ago

In spite of multiple warnings in the comments, it has been such a recurrent issue that people are using GetWindowPos(), GetCursorPos() etc. they tend to be source of confusion and comlexity.

I have increased and boldened the warnings in comments. I have also moved next step forward by obsoleting GetContentRegionMax(), GetWindowContentRegionMin(), GetWindowContentRegionMax().

I have also reorganized headers a little bit to move GetContentRegionAvail() need to GetCursorScreenPos(). Those two functions are everyone's best friend and you really cater 99% of needs. It will be my new crusade :)