From c1581882a4f2703fa81354fea9173c0f92579873 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 29 Oct 2026 20:14:03 +0100 Subject: [PATCH] Tables: fixed various border overshooting issues, some mostly visible with transparent borders: (#4678, #9415) As a side effect of cleaning up a few things, altering border size becomes closer to being correct. --- docs/CHANGELOG.txt | 11 ++++++---- imgui_internal.h | 1 + imgui_tables.cpp | 53 +++++++++++++++++++--------------------------- 3 files changed, 30 insertions(+), 35 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index d20a74cf0..96ebe7493 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -79,14 +79,17 @@ Other Changes: - Modals: fixed an assert displaying a modal in an environment where `io.DisplayFramebufferScale` is not an integer (regression in 1.93.0). (#9598) - Tables: - - In non-scrolling tables, clamp lower border coordinates to stay within visible - range (no visible changes, but ImDrawCmd AABB becomes more sensible). + - Fixed various border overshooting issues, some mostly visible with transparent + borders: (#4678) + - On non-scrolling tables, inner horizontal borders slightly overlapped + outer vertical ones with certain settings. + - On non-scrolling tables, clamping lower border coordinates to stay within + visible range (no visible change, but it makes ImDrawCmd AABB more sensible). + - Fixed vertical inner borders slightly overlapping bottom outer border. - Fixed various issues with using `ImGuiTableFlags_NoHostExtendY`: - Fixed selectable highlight straying off the Y2 clipping limit. - Fixed parent window work rectangle affecting the Y2 clipping limit. - Fixed the Y2 clipping limit overlapping bottom outer border. - - Fixed vertical inner borders slightly overlapping bottom outer border - (may be visible with transparent borders). - ImVec2: added a check for cases where a custom `IM_VEC2_CLASS_EXTRA` macro adds an extra `operator=` to ImVec2. This isn't supported but should not be needed as you should have a constructor to convert from your type to ImVec2. diff --git a/imgui_internal.h b/imgui_internal.h index da6d68730..001bc687c 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -653,6 +653,7 @@ struct IMGUI_API ImRect void TranslateY(float dy) { Min.y += dy; Max.y += dy; } void ClipWith(const ImRect& r) { Min = ImMax(Min, r.Min); Max = ImMin(Max, r.Max); } // Simple version, may lead to an inverted rectangle, which is fine for Contains/Overlaps test but not for display. void ClipWithFull(const ImRect& r) { Min = ImClamp(Min, r.Min, r.Max); Max = ImClamp(Max, r.Min, r.Max); } // Full version, ensure both points are fully clipped. + void ShrinkClamp(const ImVec2& inset_min, const ImVec2& inset_max) { Min.x = ImMin(Min.x + inset_min.x, Max.x); Min.y = ImMin(Min.y + inset_min.y, Max.y); Max.x = ImMax(Max.x - inset_max.x, Min.x); Max.y = ImMax(Max.y - inset_max.y, Min.y); } bool IsInverted() const { return Min.x > Max.x || Min.y > Max.y; } ImVec4 ToVec4() const { return ImVec4(Min.x, Min.y, Max.x, Max.y); } const ImVec4& AsVec4() const { return *(const ImVec4*)&Min.x; } diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 120c29364..f05c29f73 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -483,27 +483,22 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG const float border_size = table->BorderSize = TABLE_BORDER_SIZE; // Make borders not overlap our contents by offsetting HostClipRect (#6765, #7428, #3752) - // (we normally shouldn't alter HostClipRect as we rely on TableMergeDrawChannels() expanding non-clipped column toward the - // limits of that rectangle, in order for ImDrawListSplitter::Merge() to merge the draw commands. However since the overlap - // problem only affect scrolling tables in this case we can get away with doing it without extra cost). - if (inner_window != outer_window) + // - We normally shouldn't alter HostClipRect as we rely on TableMergeDrawChannels() expanding non-clipped column toward the + // limits of that rectangle, in order for ImDrawListSplitter::Merge() to merge the draw commands. However since the overlap + // problem only affect scrolling tables in this case we can get away with doing it without extra cost). + // - (FIXME: Because inner_window's Scrollbar doesn't know about border size, since it's not encoded in window->WindowBorderSize, + // it already overlaps it and doesn't need an extra offset. Ideally we should be able to pass custom border size with + // different x/y values to BeginChild().) + // - For non-scrolling window, this is applied to table->InnerClipRect below. + ImRect border_size_outer = { - // FIXME: Because inner_window's Scrollbar doesn't know about border size, since it's not encoded in window->WindowBorderSize, - // it already overlaps it and doesn't need an extra offset. Ideally we should be able to pass custom border size with - // different x/y values to BeginChild(). - if (flags & ImGuiTableFlags_BordersOuterV) - { - table->HostClipRect.Min.x = ImMin(table->HostClipRect.Min.x + border_size, table->HostClipRect.Max.x); - if (inner_window->DecoOuterSizeX2 == 0.0f) - table->HostClipRect.Max.x = ImMax(table->HostClipRect.Max.x - border_size, table->HostClipRect.Min.x); - } - if (flags & ImGuiTableFlags_BordersOuterH) - { - table->HostClipRect.Min.y = ImMin(table->HostClipRect.Min.y + border_size, table->HostClipRect.Max.y); - if (inner_window->DecoOuterSizeY2 == 0.0f) - table->HostClipRect.Max.y = ImMax(table->HostClipRect.Max.y - border_size, table->HostClipRect.Min.y); - } - } + (flags & ImGuiTableFlags_BordersOuterV) ? border_size : 0.0f, + (flags & ImGuiTableFlags_BordersOuterH) ? border_size : 0.0f, + ((flags & ImGuiTableFlags_BordersOuterV) && ((inner_window == outer_window) || inner_window->DecoOuterSizeX2 == 0.0f)) ? border_size : 0.0f, + ((flags & ImGuiTableFlags_BordersOuterH) && ((inner_window == outer_window) || inner_window->DecoOuterSizeY2 == 0.0f)) ? border_size : 0.0f, + }; + if (inner_window != outer_window) + table->HostClipRect.ShrinkClamp(border_size_outer.Min, border_size_outer.Max); // Padding and Spacing // - None ........Content..... Pad .....Content........ @@ -528,17 +523,13 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG table->RowBgColorCounter = 0; table->LastRowFlags = ImGuiTableRowFlags_None; table->InnerClipRect = (inner_window == outer_window) ? table->WorkRect : inner_window->ClipRect; + if (inner_window == outer_window) + table->InnerClipRect.ShrinkClamp(border_size_outer.Min, border_size_outer.Max); table->InnerClipRect.ClipWith(table->WorkRect); // We need this to honor inner_width table->InnerClipRect.ClipWithFull(table->HostClipRect); - if (flags & ImGuiTableFlags_NoHostExtendY) - { - if (flags & ImGuiTableFlags_BordersOuterH) - table->InnerClipRect.Max.y = ImClamp(table->OuterRect.Max.y - border_size, table->InnerClipRect.Min.y, table->InnerClipRect.Max.y); - } - else - { + if ((flags & ImGuiTableFlags_NoHostExtendY) == 0) table->InnerClipRect.Max.y = table->HostClipRect.Max.y; - } + table->RowPosY1 = table->RowPosY2 = table->WorkRect.Min.y; // This is needed somehow table->RowTextBaseline = 0.0f; // This will be cleared again by TableBeginRow() table->RowCellPaddingY = 0.0f; @@ -1513,10 +1504,10 @@ void ImGui::EndTable() // but since the later is likely to be impossible to do we'd rather update both axes together. if (table->Flags & ImGuiTableFlags_ScrollX) { - const float outer_padding_for_border = (table->Flags & ImGuiTableFlags_BordersOuterV) ? table->BorderSize : 0.0f; + const float border_size_outer_w = (table->Flags & ImGuiTableFlags_BordersOuterV) ? table->BorderSize : 0.0f; float max_pos_x = inner_window->DC.CursorMaxPos.x; if (table->RightMostEnabledColumn != -1) - max_pos_x = ImMax(max_pos_x, table->Columns[table->RightMostEnabledColumn].WorkMaxX + table->CellPaddingX + table->OuterPaddingX - outer_padding_for_border); + max_pos_x = ImMax(max_pos_x, table->Columns[table->RightMostEnabledColumn].WorkMaxX + table->CellPaddingX + table->OuterPaddingX - border_size_outer_w); if (table->ResizedColumn != -1) max_pos_x = ImMax(max_pos_x, table->ResizeLockMinContentsX2); inner_window->DC.CursorMaxPos.x = max_pos_x + table->TempData->AngledHeadersExtraWidth; @@ -3049,7 +3040,7 @@ void ImGui::TableDrawBorders(ImGuiTable* table) // Draw bottom-most row border between it and above outer border. const float border_y = table->RowPosY2; if (border_y >= table->BgClipRect.Min.y && border_y < table->BgClipRect.Max.y) - inner_drawlist->AddLineH(table->BorderX1 + border_size_outer_w, table->BorderX2 - border_size_outer_w, border_y, table->BorderColorLight, border_size_inner); + inner_drawlist->AddLineH(table->BorderX1, table->BorderX2, border_y, table->BorderColorLight, border_size_inner); } inner_drawlist->PopClipRect();