Strange `SameLine(offset)` behavior inside table
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:
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();
}
SetCursorPosX is not affected, and SameLine(0, 0) + SetCursorPosX is able to produce correct behavior.
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();
}
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();
}
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:
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.
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.