imgui icon indicating copy to clipboard operation
imgui copied to clipboard

Strange `SameLine(offset)` behavior inside table

Open achabense opened this issue 4 months ago • 4 comments

Version/Branch of Dear ImGui:

Version 1.92.2b

Back-ends:

Any

Compiler, OS:

Any

Full config/build information:

No response

Details:

Normally, SameLine(offset) works with local window pos. However, it doesn't work if inside table cells. I think this may suggest bugs.

Screenshots/Video:

Image

Minimal, Complete and Verifiable Example code:

static void issue() {
    if (ImGui::Begin("Issue")) {
        static int spacing = 20;
        ImGui::SetNextItemWidth(300);
        ImGui::SliderInt("Spacing", &spacing, 0, 100, "%d", ImGuiSliderFlags_AlwaysClamp);
        // Normally, SameLine(offset_from_start_x) works with GetCursorPosX():
        {
            ImGui::Button("Button1");
            ImGui::SameLine(0, spacing);
            const float local_x = ImGui::GetCursorPosX();
            ImGui::Button("Button2");

            ImGui::Button("Button3");
            ImGui::SameLine(local_x);
            ImGui::Button("Button4");

        }
        // But if inside a table cell, neither local pos nor screen pos can work.
        if (ImGui::BeginTable("Table", 2, ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_Resizable)) {
            ImGui::TableNextRow();
            ImGui::TableNextColumn();
            ImGui::Button("Left", ImGui::GetContentRegionAvail());

            ImGui::TableNextColumn();
            ImGui::Button("Button1");
            ImGui::SameLine(0, spacing);
            const float local_x = ImGui::GetCursorPosX();
            const float screen_x = ImGui::GetCursorScreenPos().x;
            // This works by chance (12 is adjusted manually, IDK how to account for it...)
            const float table_x = screen_x - GImGui->CurrentWindow->WorkRect.Min.x + 12;
            ImGui::Button("Button2");

            ImGui::Button("Button3");
            ImGui::SameLine(local_x);
            ImGui::Button("Button4");

            ImGui::Button("Button5");
            ImGui::SameLine(screen_x);
            ImGui::Button("Button6");

            ImGui::Button("Button7");
            ImGui::SameLine(table_x);
            ImGui::Button("Button8");

            ImGui::EndTable();
        }
    }
    ImGui::End();
}

achabense avatar Nov 06 '25 17:11 achabense

SetCursorPosX is not affected, and SameLine(0, 0) + SetCursorPosX is able to produce correct behavior.

Image
static void workaround() {
    if (ImGui::Begin("Workaround")) {
        static int spacing = 20;
        ImGui::SetNextItemWidth(300);
        ImGui::SliderInt("Spacing", &spacing, 0, 100, "%d", ImGuiSliderFlags_AlwaysClamp);
        if (ImGui::BeginTable("Table", 2, ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_Resizable)) {
            ImGui::TableNextRow();
            ImGui::TableNextColumn();
            ImGui::Button("Left", ImGui::GetContentRegionAvail());

            ImGui::TableNextColumn();
            ImGui::Button("Button1");
            ImGui::SameLine(0, spacing);
            const float local_x = ImGui::GetCursorPosX();
            ImGui::Button("Button2");

            ImGui::Button("Button3");
            // Workaround: use SameLine + SetCursorPosX instead.
            ImGui::SameLine(0, 0);
            ImGui::SetCursorPosX(local_x);
            ImGui::Button("Button4");

            ImGui::EndTable();
        }
    }
    ImGui::End();
}

achabense avatar Nov 07 '25 09:11 achabense

I think it's confusing what SameLine's offset means the table context. When the offset is very small, the second button will be moved into the first column.

static void issue() {
    if (ImGui::Begin("Issue")) {
        // (As tested, when offset == 12, the two buttons overlap.)
        static int offset = 12;
        ImGui::SetNextItemWidth(300);
        ImGui::SliderInt("Spacing", &offset, 0, 20, "%d", ImGuiSliderFlags_AlwaysClamp);
        if (ImGui::BeginTable("Table", 2, ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_Resizable)) {
            ImGui::TableNextRow();
            ImGui::TableNextColumn();
            ImGui::Button("Left", ImGui::GetContentRegionAvail());

            ImGui::TableNextColumn();
            ImGui::Button("Button1");
            ImGui::SameLine(offset);
            ImGui::Button("Button2");

            ImGui::EndTable();
        }
    }
    ImGui::End();
}
Image Image

achabense avatar Nov 07 '25 10:11 achabense

General comment:

  • You should do everything possible to avoid using local coordinates.
  • Everything about local coordinates are legacy thing than I am trying to find ways to remove (but it's a bit of a puzzle).
  • Honestly the first parameter of SameLine() was a big design mistake that I am still needing to solve.

Comment on your first post:

If you were to do:

ImGui::Button("ButtonA");
ImGui::SameLine(200.0f);
ImGui::Button("ButtonB");

It works both outside and inside the table.

(A) This doesn't work: (Your code)

ImGui::Button("Button1");
ImGui::SameLine(0, (float)spacing);
const float local_x = ImGui::GetCursorPosX();
ImGui::Button("Button2");

ImGui::Button("Button3");
ImGui::SameLine(local_x);
ImGui::Button("Button4");

Indeed because GetCursorPosX() is window-local vs SameLine() is work-rect local which I agree is a design issue and could need a redesign.

(B) This is incorrect: (Your code)

ImGui::Button("Button1");
ImGui::SameLine(0, (float)spacing);
const float screen_x = ImGui::GetCursorScreenPos().x;
ImGui::Button("Button5");
ImGui::SameLine(screen_x);
ImGui::Button("Button6");

Because screen_x is absolute position vs SameLine() is work-rect local.


Initially (~2014) the intent was that you could do "SameLine(100)" aka absolute offset as a "mini-column" thing and then this got carried into supporting SameLine(100.0f) in columns.

Outside of tables this work:

ImGui::Button("ButtonA");
ImGui::SameLine(200.0f);
ImGui::Button("ButtonB");

ImGui::Button("ButtonC");
ImGui::SameLine();
ImGui::SetCursorPosX(200.0f);
ImGui::Button("ButtonD");

Inside table it doesn't: Image

Again, because SetCursorPosX() is window-local not work-rect local.


(C)

I think it's confusing what SameLine's offset means the table context. When the offset is very small, the second button will be moved into the first column.

That's interesting and arguably seems like a bug, and might be simpler to solve than (A).

SameLine() use its own computation mess:

    if (offset_from_start_x != 0.0f)
    {
        if (spacing_w < 0.0f)
            spacing_w = 0.0f;
        window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + offset_from_start_x + spacing_w + window->DC.GroupOffset.x + window->DC.ColumnsOffset.x;

I suspect this code is incorrect.

ocornut avatar Nov 07 '25 13:11 ocornut

Presently, the only correct way to achieve what you were aiming to you is to not use the SameLine() offset but SameLine(0) + SetCursorPosX().

I will want to address this as part of a bigger upcoming layout refactor but I’ll investigate the calculation in SameLine() sooner.

ocornut avatar Nov 07 '25 13:11 ocornut