ocornut / imgui

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

Position inside child window when border width changes #7887

Open ElectroidDes opened 2 months ago

ElectroidDes commented 2 months ago

Version/Branch of Dear ImGui:

1.90.8

Back-ends:

Raylib

Compiler, OS:

Windows10

Full config/build information:

No response

Details:

I draw a child window and inside this child window another child window at coordinates {1,1} and everything is fine - the child is adjacent directly to the sides of the outer child window.

However, when I set the frame width of the outer child window to 2 or more, then I can no longer draw the inner child window directly adjacent to the sides of the outer child window - now there is always a gap of 1 pixel. I took a screenshot.

Why is that? Is this correct?

        ImGui::Begin("Main Window");

        //---------------------------------------------------------------------------
        float border_thickness1 = 1;
        ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, border_thickness1);
        ImGui::BeginChild("Child Window 1", ImVec2(180, 180), true);

        ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, 1.0f);
        ImGui::PushStyleColor(ImGuiCol_Border, IM_COL32(255,0,0,255));
        ImGui::SetCursorPos({ 1,1 });
        ImGui::BeginChild("SUBChild Window 1", ImVec2(100, 100), true);
        ImGui::EndChild();

        ImGui::PopStyleColor();
        ImGui::PopStyleVar();

        ImGui::EndChild();
        ImGui::PopStyleVar();
        //---------------------------------------------------------------------------

        ImGui::SameLine();

        //---------------------------------------------------------------------------
        float border_thickness2 = 2;
        ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, border_thickness2);
        ImGui::BeginChild("Child Window 2", ImVec2(180, 180), true);

        ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, 1.0f);
        ImGui::PushStyleColor(ImGuiCol_Border, IM_COL32(255, 0, 0, 255));
        ImGui::SetCursorPos({ 1,1 });
        ImGui::BeginChild("SUBChild Window 2", ImVec2(100, 100), true);
        ImGui::EndChild();

        ImGui::PopStyleColor();
        ImGui::PopStyleVar();

        ImGui::EndChild();
        ImGui::PopStyleVar();
        //---------------------------------------------------------------------------

        ImGui::SameLine();

        //---------------------------------------------------------------------------
        float border_thickness3 = 2;
        ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, border_thickness3);
        ImGui::BeginChild("Child Window 3", ImVec2(180, 180), true);

        ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, 1.0f);
        ImGui::PushStyleColor(ImGuiCol_Border, IM_COL32(255, 0, 0, 255));
        ImGui::SetCursorPos({ 2,2 });
        ImGui::BeginChild("SUBChild Window 3", ImVec2(100, 100), true);
        ImGui::EndChild();

        ImGui::PopStyleColor();
        ImGui::PopStyleVar();

        ImGui::EndChild();
        ImGui::PopStyleVar();
        //---------------------------------------------------------------------------

        ImGui::SameLine();

        //---------------------------------------------------------------------------
        float border_thickness4 = 3;
        ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, border_thickness4);
        ImGui::BeginChild("Child Window 4", ImVec2(180, 180), true);

        ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, 1.0f);
        ImGui::PushStyleColor(ImGuiCol_Border, IM_COL32(255, 0, 0, 255));
        ImGui::SetCursorPos({ 1,1 });
        ImGui::BeginChild("SUBChild Window 4", ImVec2(100, 100), true);
        ImGui::EndChild();

        ImGui::PopStyleColor();
        ImGui::PopStyleVar();

        ImGui::EndChild();
        ImGui::PopStyleVar();
        //---------------------------------------------------------------------------

        ImGui::SameLine();

        //---------------------------------------------------------------------------
        float border_thickness5 = 3;
        ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, border_thickness5);
        ImGui::BeginChild("Child Window 5", ImVec2(180, 180), true);

        ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, 1.0f);
        ImGui::PushStyleColor(ImGuiCol_Border, IM_COL32(255, 0, 0, 255));
        ImGui::SetCursorPos({ 2,2 });
        ImGui::BeginChild("SUBChild Window 5", ImVec2(100, 100), true);
        ImGui::EndChild();

        ImGui::PopStyleColor();
        ImGui::PopStyleVar();

        ImGui::EndChild();
        ImGui::PopStyleVar();
        //---------------------------------------------------------------------------

        ImGui::SameLine();

        //---------------------------------------------------------------------------
        float border_thickness6 = 3;
        ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, border_thickness6);
        ImGui::BeginChild("Child Window 6", ImVec2(180, 180), true);

        ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, 1.0f);
        ImGui::PushStyleColor(ImGuiCol_Border, IM_COL32(255, 0, 0, 255));
        ImGui::SetCursorPos({ 3,3 });
        ImGui::BeginChild("SUBChild Window 6", ImVec2(100, 100), true);
        ImGui::EndChild();

        ImGui::PopStyleColor();
        ImGui::PopStyleVar();

        ImGui::EndChild();
        ImGui::PopStyleVar();
        //---------------------------------------------------------------------------

        ImGui::End();

Screenshots/Video:

image

ElectroidDes commented 2 months ago

image

enlarged screenshot

ocornut commented 2 months ago

Why is that? Is this correct?

Our style editor doesn't allow borders larger than 1.0f partly for this reason.

Right now we made a conscious decision that borders would try to not affect size/layout, which simplifies lots of things both for dear imgui code and user code. It however applies to the initial value of InnerClipRect.

I think we have a small bug in the assignment of InnerClipRect where because it assumes BorderSize is 0.0f or 1.0f, it doesn't account for the fact that larger borders are centered around the outer rectangle, and therefore our offset should be +/- window->WindowBorderSize0.5f rather than window->WindowBorderSize1.0f.

This is what is causing the 1 pixel gap. It's not really formally defined how borders rather than >1.0f are drawn, they are currently centered but it may make sense to make them inner borders.

float top_border_size = (((flags & ImGuiWindowFlags_MenuBar) || !(flags & ImGuiWindowFlags_NoTitleBar)) ? style.FrameBorderSize : window->WindowBorderSize);
window->InnerClipRect.Min.x = ImFloor(0.5f + window->InnerRect.Min.x + window->WindowBorderSize);
window->InnerClipRect.Min.y = ImFloor(0.5f + window->InnerRect.Min.y + top_border_size);
window->InnerClipRect.Max.x = ImFloor(0.5f + window->InnerRect.Max.x - window->WindowBorderSize);
window->InnerClipRect.Max.y = ImFloor(0.5f + window->InnerRect.Max.y - window->WindowBorderSize);
window->InnerClipRect.ClipWithFull(host_rect);

We can fix this but it won't really fix you underlying issue, which is that you should not use ImGui::SetCursorPos({ 1,1 });. It is meaningless because it doesn't take account of window padding + inner clip rect applied by border size.

You are better off adjusting WindowPadding however you like + use GetCursorScreenPos() as a base position.

ocornut commented 2 months ago

I believe maybe the saner direction would be to steer toward making rectangular borders inner-borders rather than centered-around-rect borders, but I a not sure this is a trivial change.

ocornut commented 2 months ago

Here's a simplified test bed which display the values:

ImGui::Begin("Test #7887");

struct TestCase { float BorderSize; float CursorPos; };
const TestCase test_cases[] = { { 1.0f, +1 },  { 2.0f, +1 }, { 2.0f, +2 }, { 3.0f, +1 }, { 3.0f, +2 }, { 3.0f, +3 } };
for (int n = 0; n < IM_ARRAYSIZE(test_cases); n++)
{
    const TestCase& test_case = test_cases[n];
    if (n > 0)
        ImGui::SameLine();

    char buf[128];

    ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, test_case.BorderSize);
    snprintf(buf, IM_ARRAYSIZE(buf), "Child window %d", n);

    ImGui::BeginGroup();
    ImGui::BeginChild(buf, ImVec2(180, 180), true);

    ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, 1.0f);
    ImGui::PushStyleColor(ImGuiCol_Border, IM_COL32(255, 0, 0, 255));
    ImGui::SetCursorPos({ test_case.CursorPos, test_case.CursorPos });
    snprintf(buf, IM_ARRAYSIZE(buf), "SubChild window %d", n);
    ImGui::BeginChild(buf, ImVec2(100, 100), true);
    ImGui::EndChild();

    ImGui::PopStyleColor();
    ImGui::PopStyleVar();

    ImGui::EndChild();

    ImGui::Text("BorderSize = %.1f", test_case.BorderSize);
    ImGui::Text("SetCursorPos(%.1f, %.1f)", test_case.CursorPos, test_case.CursorPos);
    ImGui::EndGroup();

    ImGui::PopStyleVar();
}

ImGui::End();
ocornut commented 2 months ago

As a generally stance, large BorderSize are currently not even supported, e.g.: image

ocornut commented 2 months ago

I tried to make the border move to become an inner border, but recalled what the issue is. The issue is that it affect rounding, as our AddPolyline() function for thick line always has the pivot center in the middle of the line.

inner_thick_border

Approximating a different rounding tends to still leave holes, but it would probably requires reworking polyline to be able to render a thick rounded rectangle with rounding matching the underlying filled shape.

ocornut commented 2 months ago

I've pushed e471206 which should generally minimize the issue where there is a render hole between rendered shape and border. However as the border is centered it is normal that e.g. BorderSize=3, SetCursorPos({3,3}) there will be a gap, as the border reaches to ~+1.5,~+1.5

image