From 2fb554b1c81e6a8efcb4e3c236649a46354aa9ba Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 28 Oct 2026 15:41:13 +0100 Subject: [PATCH 01/11] Modals: fixed an assert displaying a modal in an environment where io.DisplayFramebufferScale is not an integer. (#9598) --- docs/CHANGELOG.txt | 10 +++++++--- imgui.cpp | 3 ++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 05604c48b..ec2cc0763 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -66,14 +66,18 @@ Autumn cleanup of stuff marked obsolete more than two years ago: Other Changes: -- Style: `StyleColorsDark()`: tweaked WindowBg to be darker than in 1.93.0. -- Style: added `style.SymbolArrowSizeFactor`, `style.SymbolArrowThicknessFactor` to tweak - the shape and look of arrows. Made default arrow stroked. (#5233, #2431, #2235, #6730, #8243) +- Style: + - `StyleColorsDark()`: tweaked WindowBg to be darker than in 1.93.0. + - Added `style.SymbolArrowSizeFactor`, `style.SymbolArrowThicknessFactor` to tweak the + shape and look of arrows. Made default arrow stroked. (#5233, #2431, #2235, #6730, #8243) Legacy arrow: size: (0.60f,0.70f), thickness: 0.0f New arrow: size: (0.72f,0.40f), thickness: 0.30f - TreeNode: - TreeNodeGetOpen(): added flags parameter to allow passing `ImGuiTreeNodeFlags_DefaultOpen` or `ImGuiTreeNodeFlags_Leaf`. (#9569, #9592, #3823, #9251) [@adalisk-emikhaylov, @Yaki-Blake] +- Popups: + - Modals: fixed an assert displaying a modal in an environment where + `io.DisplayFramebufferScale` is not an integer (regression in 1.93.0). (#9598) - 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.cpp b/imgui.cpp index f8a2def00..67edfa37b 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6122,7 +6122,8 @@ static void ImGui::RenderDimmedBackgroundBehindWindow(ImGuiWindow* window, ImU32 if (draw_list->CmdBuffer.Size == 0) draw_list->AddDrawCmd(); draw_list->PushClipRect(viewport_rect.Min - ImVec2(1, 1), viewport_rect.Max + ImVec2(1, 1), false); // FIXME: Need to strictly ensure ImDrawCmd are not merged (ElemCount==6 checks below will verify that) - draw_list->AddRectFilled(viewport_rect.Min, viewport_rect.Max, col); + draw_list->PrimReserve(6, 4); + draw_list->PrimRect(viewport_rect.Min, viewport_rect.Max, col); ImDrawCmd cmd = draw_list->CmdBuffer.back(); IM_ASSERT(cmd.ElemCount == 6); draw_list->CmdBuffer.pop_back(); From b87728bd2b2bdaa63efa13f390adb73ed3c40afd Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 29 Oct 2026 13:47:01 +0100 Subject: [PATCH 02/11] Tables: clamp vertical border Y2 position when not using scrolling table, so that ImDrawCmd AABB stays reasonable. This does not fix any screen visible issue, and should not be noticeable if correct. (context: investigated this while looking at #4678, but not actually directly related) --- docs/CHANGELOG.txt | 3 +++ imgui.h | 2 +- imgui_tables.cpp | 18 ++++++++++++------ 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index ec2cc0763..d0704cac1 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -78,6 +78,9 @@ Other Changes: - Popups: - 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). - 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.h b/imgui.h index 04c25c98e..9a2313a59 100644 --- a/imgui.h +++ b/imgui.h @@ -30,7 +30,7 @@ // Library Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345') #define IMGUI_VERSION "1.93.1 WIP" -#define IMGUI_VERSION_NUM 19301 +#define IMGUI_VERSION_NUM 19302 #define IMGUI_HAS_TABLE // Added BeginTable() - from IMGUI_VERSION_NUM >= 18000 #define IMGUI_HAS_TEXTURES // Added ImGuiBackendFlags_RendererHasTextures - from IMGUI_VERSION_NUM >= 19198 diff --git a/imgui_tables.cpp b/imgui_tables.cpp index e82c71dfb..a303760a0 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -2951,19 +2951,24 @@ static ImU32 TableGetColumnBorderCol(ImGuiTable* table, int order_n, int column_ void ImGui::TableDrawBorders(ImGuiTable* table) { ImGuiWindow* inner_window = table->InnerWindow; - if (!table->OuterWindow->ClipRect.Overlaps(table->OuterRect)) + if (!table->OuterWindow->ClipRect.Overlaps(table->OuterRect)) // Note how OuterWindow->ClipRect == table->Bg0ClipRectForDrawCmd return; ImDrawList* inner_drawlist = inner_window->DrawList; table->DrawSplitter->SetCurrentChannel(inner_drawlist, TABLE_DRAW_CHANNEL_BG0); - inner_drawlist->PushClipRect(table->Bg0ClipRectForDrawCmd.Min, table->Bg0ClipRectForDrawCmd.Max, false); + inner_drawlist->PushClipRect(table->OuterWindow->ClipRect.Min, table->OuterWindow->ClipRect.Max, false); + + const float border_size = TABLE_BORDER_SIZE; + + // For e.g. non scrolling table we limit vertex coordinates in order to keep ImDrawCmd AABB roughly within visible range. + const float draw_y2_limit = table->OuterWindow->ClipRect.Max.y + (2.0f * ImMax(border_size, inner_drawlist->_FringeScale)); // Draw inner border and resizing feedback ImGuiTableInstanceData* table_instance = TableGetInstanceData(table, table->InstanceCurrent); - const float border_size = TABLE_BORDER_SIZE; const float draw_y1 = ImMax(table->InnerRect.Min.y, (table->FreezeRowsCount >= 1 ? table->InnerRect.Min.y : table->WorkRect.Min.y) + table->AngledHeadersHeight) + ((table->Flags & ImGuiTableFlags_BordersOuterH) ? border_size : 0.0f); - const float draw_y2_body = table->InnerRect.Max.y; - const float draw_y2_head = table->IsUsingHeaders ? ImMin(table->InnerRect.Max.y, (table->FreezeRowsCount >= 1 ? table->InnerRect.Min.y : table->WorkRect.Min.y) + table_instance->LastTopHeadersRowHeight) : draw_y1; + const float draw_y2_body = ImMin(table->InnerRect.Max.y, draw_y2_limit); + const float draw_y2_head = table->IsUsingHeaders ? ImMin(draw_y2_body, (table->FreezeRowsCount >= 1 ? table->InnerRect.Min.y : table->WorkRect.Min.y) + table_instance->LastTopHeadersRowHeight) : draw_y1; + if (table->Flags & ImGuiTableFlags_BordersInnerV) { for (int order_n = 0; order_n < table->ColumnsCount; order_n++) @@ -3009,7 +3014,8 @@ void ImGui::TableDrawBorders(ImGuiTable* table) // parent. In inner_window, it won't reach out over scrollbars. Another weird solution would be to display part // of it in inner window, and the part that's over scrollbars in the outer window..) // Either solution currently won't allow us to use a larger border size: the border would clipped. - const ImRect outer_border = table->OuterRect; + ImRect outer_border = table->OuterRect; + outer_border.Max.y = ImMin(outer_border.Max.y, draw_y2_limit); // Clamp but keep bottom border non-visible. const ImU32 outer_col = table->BorderColorStrong; if ((table->Flags & ImGuiTableFlags_BordersOuter) == ImGuiTableFlags_BordersOuter) { From d2ba763635a958fc5cbdab6139ffa739a7048722 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 29 Oct 2026 18:29:27 +0100 Subject: [PATCH 03/11] Tables: fixed various issues with using ImGuiTableFlags_NoHostExtendY. --- docs/CHANGELOG.txt | 4 ++++ imgui_demo.cpp | 1 + imgui_tables.cpp | 15 ++++++++++++--- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index d0704cac1..fbd0a60f9 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -81,6 +81,10 @@ Other Changes: - 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 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. - 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_demo.cpp b/imgui_demo.cpp index 550e0d8da..547661f7d 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -6954,6 +6954,7 @@ static void DemoWindowTables() if (ImGui::TreeNode("Outer size")) { IMGUI_DEMO_MARKER("Tables/Outer size"); + // Showcasing use of ImGuiTableFlags_NoHostExtendX and ImGuiTableFlags_NoHostExtendY // Important to that note how the two flags have slightly different behaviors! ImGui::Text("Using NoHostExtendX and NoHostExtendY:"); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index a303760a0..0c0796384 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -482,12 +482,12 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG // (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). + const float border_size = TABLE_BORDER_SIZE; if (inner_window != outer_window) { // 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(). - const float border_size = TABLE_BORDER_SIZE; if (flags & ImGuiTableFlags_BordersOuterV) { table->HostClipRect.Min.x = ImMin(table->HostClipRect.Min.x + border_size, table->HostClipRect.Max.x); @@ -527,8 +527,15 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG table->InnerClipRect = (inner_window == outer_window) ? table->WorkRect : inner_window->ClipRect; table->InnerClipRect.ClipWith(table->WorkRect); // We need this to honor inner_width table->InnerClipRect.ClipWithFull(table->HostClipRect); - table->InnerClipRect.Max.y = (flags & ImGuiTableFlags_NoHostExtendY) ? ImMin(table->InnerClipRect.Max.y, inner_window->WorkRect.Max.y) : table->HostClipRect.Max.y; - + 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 + { + 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; @@ -2734,6 +2741,8 @@ void ImGui::TableSetupDrawChannels(ImGuiTable* table) table->Bg0ClipRectForDrawCmd = table->OuterWindow->ClipRect; table->Bg2ClipRectForDrawCmd = table->HostClipRect; IM_ASSERT(table->BgClipRect.Min.y <= table->BgClipRect.Max.y); + if (table->Flags & ImGuiTableFlags_NoHostExtendY) + table->Bg2ClipRectForDrawCmd.Max.y = ImMin(table->Bg2ClipRectForDrawCmd.Max.y, table->BgClipRect.Max.y); // Don't apply to Bg0: shapes in Bg0 are already CPU clipped and it makes merging fails. } // This function reorder draw channels based on matching clip rectangle, to facilitate merging them. Called by EndTable(). From c39a91bfbecc319847e9d4a9b96f320031fd957b Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 29 Oct 2026 18:40:27 +0100 Subject: [PATCH 04/11] Tables: latch border size once, and pull all use from same place. (#9415) --- imgui_internal.h | 1 + imgui_tables.cpp | 26 ++++++++++++++------------ 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index 16743905c..da6d68730 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3156,6 +3156,7 @@ struct IMGUI_API ImGuiTable ImU32 BorderColorLight; float BorderX1; float BorderX2; + float BorderSize; float HostIndentX; float MinColumnWidth; float OuterPaddingX; diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 0c0796384..4d0ae97d8 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -478,11 +478,14 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG temp_data->HostBackupItemWidthStackSize = outer_window->DC.ItemWidthStack.Size; inner_window->DC.PrevLineSize = inner_window->DC.CurrLineSize = ImVec2(0.0f, 0.0f); + // Border size + // FIXME-WIP: Changing this is not currently simple due to ClipRect requirements. Easier to manage with new 1.93 primitives though. + 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). - const float border_size = TABLE_BORDER_SIZE; if (inner_window != outer_window) { // FIXME: Because inner_window's Scrollbar doesn't know about border size, since it's not encoded in window->WindowBorderSize, @@ -509,14 +512,14 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG // - PadOuter+PadInner | Pad ..Content.. Pad | Pad ..Content.. Pad | const bool pad_outer_x = (flags & ImGuiTableFlags_NoPadOuterX) ? false : (flags & ImGuiTableFlags_PadOuterX) ? true : (flags & ImGuiTableFlags_BordersOuterV) != 0; const bool pad_inner_x = (flags & ImGuiTableFlags_NoPadInnerX) ? false : true; - const float inner_spacing_for_border = (flags & ImGuiTableFlags_BordersInnerV) ? TABLE_BORDER_SIZE : 0.0f; + const float inner_spacing_for_border = (flags & ImGuiTableFlags_BordersInnerV) ? border_size : 0.0f; const float inner_spacing_explicit = (pad_inner_x && (flags & ImGuiTableFlags_BordersInnerV) == 0) ? g.Style.CellPadding.x : 0.0f; const float inner_padding_explicit = (pad_inner_x && (flags & ImGuiTableFlags_BordersInnerV) != 0) ? g.Style.CellPadding.x : 0.0f; table->CellSpacingX1 = inner_spacing_explicit + inner_spacing_for_border; table->CellSpacingX2 = inner_spacing_explicit; table->CellPaddingX = inner_padding_explicit; - const float outer_padding_for_border = (flags & ImGuiTableFlags_BordersOuterV) ? TABLE_BORDER_SIZE : 0.0f; + const float outer_padding_for_border = (flags & ImGuiTableFlags_BordersOuterV) ? border_size : 0.0f; const float outer_padding_explicit = pad_outer_x ? g.Style.CellPadding.x : 0.0f; table->OuterPaddingX = (outer_padding_for_border + outer_padding_explicit) - table->CellPaddingX; @@ -1284,7 +1287,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) } if (order_n < table->FreezeColumnsCount) - host_clip_rect.Min.x = ImClamp(column->MaxX + TABLE_BORDER_SIZE, host_clip_rect.Min.x, host_clip_rect.Max.x); + host_clip_rect.Min.x = ImClamp(column->MaxX + table->BorderSize, host_clip_rect.Min.x, host_clip_rect.Max.x); offset_x += column->WidthGiven + table->CellSpacingX1 + table->CellSpacingX2 + table->CellPaddingX * 2.0f; } @@ -1510,7 +1513,7 @@ 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_BORDER_SIZE : 0.0f; + const float outer_padding_for_border = (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); @@ -2175,7 +2178,6 @@ void ImGui::TableEndRow(ImGuiTable* table) // Decide of top border color ImU32 top_border_col = 0; - const float border_size = TABLE_BORDER_SIZE; if (table->CurrentRow > 0 && (table->Flags & ImGuiTableFlags_BordersInnerH)) top_border_col = (table->LastRowFlags & ImGuiTableRowFlags_Headers) ? table->BorderColorStrong : table->BorderColorLight; @@ -2222,11 +2224,11 @@ void ImGui::TableEndRow(ImGuiTable* table) // Draw top border if (top_border_col && bg_y1 >= table->BgClipRect.Min.y && bg_y1 < table->BgClipRect.Max.y) - window->DrawList->AddLineH(table->BorderX1, table->BorderX2, bg_y1, top_border_col, border_size); + window->DrawList->AddLineH(table->BorderX1, table->BorderX2, bg_y1, top_border_col, table->BorderSize); // Draw bottom border at the row unfreezing mark (always strong) if (draw_strong_bottom_border && bg_y2 >= table->BgClipRect.Min.y && bg_y2 < table->BgClipRect.Max.y) - window->DrawList->AddLineH(table->BorderX1, table->BorderX2, bg_y2, table->BorderColorStrong, border_size); + window->DrawList->AddLineH(table->BorderX1, table->BorderX2, bg_y2, table->BorderColorStrong, table->BorderSize); } // End frozen rows (when we are past the last frozen row line, teleport cursor and alter clipping rectangle) @@ -2967,9 +2969,8 @@ void ImGui::TableDrawBorders(ImGuiTable* table) table->DrawSplitter->SetCurrentChannel(inner_drawlist, TABLE_DRAW_CHANNEL_BG0); inner_drawlist->PushClipRect(table->OuterWindow->ClipRect.Min, table->OuterWindow->ClipRect.Max, false); - const float border_size = TABLE_BORDER_SIZE; - // For e.g. non scrolling table we limit vertex coordinates in order to keep ImDrawCmd AABB roughly within visible range. + const float border_size = table->BorderSize; const float draw_y2_limit = table->OuterWindow->ClipRect.Max.y + (2.0f * ImMax(border_size, inner_drawlist->_FringeScale)); // Draw inner border and resizing feedback @@ -3640,8 +3641,9 @@ void ImGui::TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label if (pass == 1) { // Draw border - const float border_size = TABLE_BORDER_SIZE; - draw_list->AddLine(bg_shape[0] + ImVec2(border_size * 0.5f, 0.0f), bg_shape[3] + ImVec2(border_size * 0.5f, 0.0f), TableGetColumnBorderCol(table, order_n, column_n), border_size); + draw_list->AddLine( + bg_shape[0] + ImVec2(table->BorderSize * 0.5f, 0.0f), + bg_shape[3] + ImVec2(table->BorderSize * 0.5f, 0.0f), TableGetColumnBorderCol(table, order_n, column_n), table->BorderSize); } } PopClipRect(); From 0d0cd87dd26fd5cda7858f9c02a811c84487a714 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 29 Oct 2026 18:56:20 +0100 Subject: [PATCH 05/11] Tables: fixed vertical inner borders slightly overlapping bottom outer border. (#4678, #9415) --- docs/CHANGELOG.txt | 2 ++ imgui_tables.cpp | 27 ++++++++++++++------------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index fbd0a60f9..d20a74cf0 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -85,6 +85,8 @@ Other Changes: - 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_tables.cpp b/imgui_tables.cpp index 4d0ae97d8..120c29364 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -2970,13 +2970,15 @@ void ImGui::TableDrawBorders(ImGuiTable* table) inner_drawlist->PushClipRect(table->OuterWindow->ClipRect.Min, table->OuterWindow->ClipRect.Max, false); // For e.g. non scrolling table we limit vertex coordinates in order to keep ImDrawCmd AABB roughly within visible range. - const float border_size = table->BorderSize; - const float draw_y2_limit = table->OuterWindow->ClipRect.Max.y + (2.0f * ImMax(border_size, inner_drawlist->_FringeScale)); + const float border_size_outer_w = (table->Flags & ImGuiTableFlags_BordersOuterV) ? table->BorderSize : 0.0f; + const float border_size_outer_h = (table->Flags & ImGuiTableFlags_BordersOuterH) ? table->BorderSize : 0.0f; + const float border_size_inner = table->BorderSize; + const float draw_y2_limit = table->OuterWindow->ClipRect.Max.y + (2.0f * ImMax(border_size_outer_h, inner_drawlist->_FringeScale)); // Draw inner border and resizing feedback ImGuiTableInstanceData* table_instance = TableGetInstanceData(table, table->InstanceCurrent); - const float draw_y1 = ImMax(table->InnerRect.Min.y, (table->FreezeRowsCount >= 1 ? table->InnerRect.Min.y : table->WorkRect.Min.y) + table->AngledHeadersHeight) + ((table->Flags & ImGuiTableFlags_BordersOuterH) ? border_size : 0.0f); - const float draw_y2_body = ImMin(table->InnerRect.Max.y, draw_y2_limit); + const float draw_y1 = ImMax(table->InnerRect.Min.y, (table->FreezeRowsCount >= 1 ? table->InnerRect.Min.y : table->WorkRect.Min.y) + table->AngledHeadersHeight) + ((table->Flags & ImGuiTableFlags_BordersOuterH) ? border_size_outer_h : 0.0f); + const float draw_y2_body = ImMin(table->InnerRect.Max.y - border_size_outer_h, draw_y2_limit); const float draw_y2_head = table->IsUsingHeaders ? ImMin(draw_y2_body, (table->FreezeRowsCount >= 1 ? table->InnerRect.Min.y : table->WorkRect.Min.y) + table_instance->LastTopHeadersRowHeight) : draw_y1; if (table->Flags & ImGuiTableFlags_BordersInnerV) @@ -3011,7 +3013,7 @@ void ImGui::TableDrawBorders(ImGuiTable* table) else if ((table->Flags & (ImGuiTableFlags_NoBordersInBodyUntilResize | ImGuiTableFlags_NoBordersInBody)) == 0) draw_y2 = draw_y2_body; if (draw_y2 > draw_y1) - inner_drawlist->AddLineV(column->MaxX, draw_y1, draw_y2, TableGetColumnBorderCol(table, order_n, column_n), border_size); + inner_drawlist->AddLineV(column->MaxX, draw_y1, draw_y2, TableGetColumnBorderCol(table, order_n, column_n), border_size_inner); } } @@ -3029,26 +3031,25 @@ void ImGui::TableDrawBorders(ImGuiTable* table) const ImU32 outer_col = table->BorderColorStrong; if ((table->Flags & ImGuiTableFlags_BordersOuter) == ImGuiTableFlags_BordersOuter) { - inner_drawlist->AddRect(outer_border.Min, outer_border.Max, outer_col, 0.0f, border_size); + inner_drawlist->AddRect(outer_border.Min, outer_border.Max, outer_col, 0.0f, border_size_outer_w); } else if (table->Flags & ImGuiTableFlags_BordersOuterV) { - inner_drawlist->AddLineV(outer_border.Min.x, outer_border.Min.y, outer_border.Max.y, outer_col, border_size); - inner_drawlist->AddLineV(outer_border.Max.x - border_size, outer_border.Min.y, outer_border.Max.y, outer_col, border_size); + inner_drawlist->AddLineV(outer_border.Min.x, outer_border.Min.y, outer_border.Max.y, outer_col, border_size_outer_w); + inner_drawlist->AddLineV(outer_border.Max.x - border_size_outer_w, outer_border.Min.y, outer_border.Max.y, outer_col, border_size_outer_w); } else if (table->Flags & ImGuiTableFlags_BordersOuterH) { - inner_drawlist->AddLineH(outer_border.Min.x, outer_border.Max.x, outer_border.Min.y, outer_col, border_size); - inner_drawlist->AddLineH(outer_border.Min.x, outer_border.Max.x, outer_border.Max.y - border_size, outer_col, border_size); + inner_drawlist->AddLineH(outer_border.Min.x, outer_border.Max.x, outer_border.Min.y, outer_col, border_size_outer_h); + inner_drawlist->AddLineH(outer_border.Min.x, outer_border.Max.x, outer_border.Max.y - border_size_outer_h, outer_col, border_size_outer_h); } } if ((table->Flags & ImGuiTableFlags_BordersInnerH) && table->RowPosY2 < table->OuterRect.Max.y) { - // Draw bottom-most row border between it is above outer border. - const float h_inset = (table->Flags & ImGuiTableFlags_BordersOuterV) ? border_size : 0.0f; + // 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 + h_inset, table->BorderX2 - h_inset, border_y, table->BorderColorLight, border_size); + inner_drawlist->AddLineH(table->BorderX1 + border_size_outer_w, table->BorderX2 - border_size_outer_w, border_y, table->BorderColorLight, border_size_inner); } inner_drawlist->PopClipRect(); From c1581882a4f2703fa81354fea9173c0f92579873 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 29 Oct 2026 20:14:03 +0100 Subject: [PATCH 06/11] 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(); From 89845f9db638c998c33d2a6a0d30dd65ff9c9369 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 29 Oct 2026 20:42:01 +0100 Subject: [PATCH 07/11] Tables: fixed various issues with using ImGuiTableFlags_NoHostExtendX. --- docs/CHANGELOG.txt | 6 +++--- imgui_tables.cpp | 11 +++++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 96ebe7493..dddcff7a2 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -80,14 +80,14 @@ Other Changes: `io.DisplayFramebufferScale` is not an integer (regression in 1.93.0). (#9598) - Tables: - Fixed various border overshooting issues, some mostly visible with transparent - borders: (#4678) + borders and under specific conditions: (#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 various issues using `ImGuiTableFlags_NoHostExtendX`/`_NoHostExtendY`: + - Fixed selectable highlight straying off the X2/Y2 clipping limit. - Fixed parent window work rectangle affecting the Y2 clipping limit. - Fixed the Y2 clipping limit overlapping bottom outer border. - ImVec2: added a check for cases where a custom `IM_VEC2_CLASS_EXTRA` macro diff --git a/imgui_tables.cpp b/imgui_tables.cpp index f05c29f73..b46f23b04 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -1310,8 +1310,9 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) table->Flags &= ~ImGuiTableFlags_NoHostExtendX; if (table->Flags & ImGuiTableFlags_NoHostExtendX) { - table->OuterRect.Max.x = table->WorkRect.Max.x = unused_x1; - table->InnerClipRect.Max.x = ImMin(table->InnerClipRect.Max.x, unused_x1); + const float border_size_outer_w = (table->Flags & ImGuiTableFlags_BordersOuterV) ? table->BorderSize : 0.0f; + table->OuterRect.Max.x = table->WorkRect.Max.x = ImMin(table->OuterRect.Max.x, unused_x1 + border_size_outer_w); + table->InnerClipRect.Max.x = ImClamp(table->OuterRect.Max.x - border_size_outer_w, table->InnerClipRect.Min.x, table->InnerClipRect.Max.x); } table->InnerWindow->ParentWorkRect = table->WorkRect; table->BorderX1 = table->InnerClipRect.Min.x; @@ -2734,8 +2735,10 @@ void ImGui::TableSetupDrawChannels(ImGuiTable* table) table->Bg0ClipRectForDrawCmd = table->OuterWindow->ClipRect; table->Bg2ClipRectForDrawCmd = table->HostClipRect; IM_ASSERT(table->BgClipRect.Min.y <= table->BgClipRect.Max.y); + if (table->Flags & ImGuiTableFlags_NoHostExtendX) + table->Bg2ClipRectForDrawCmd.Max.x = ImMin(table->Bg2ClipRectForDrawCmd.Max.x, table->BgClipRect.Max.x); // Don't apply to Bg0: shapes in Bg0 are already CPU clipped and it makes merging fails. if (table->Flags & ImGuiTableFlags_NoHostExtendY) - table->Bg2ClipRectForDrawCmd.Max.y = ImMin(table->Bg2ClipRectForDrawCmd.Max.y, table->BgClipRect.Max.y); // Don't apply to Bg0: shapes in Bg0 are already CPU clipped and it makes merging fails. + table->Bg2ClipRectForDrawCmd.Max.y = ImMin(table->Bg2ClipRectForDrawCmd.Max.y, table->BgClipRect.Max.y); // " } // This function reorder draw channels based on matching clip rectangle, to facilitate merging them. Called by EndTable(). @@ -2893,7 +2896,7 @@ void ImGui::TableMergeDrawChannels(ImGuiTable* table) merge_clip_rect.Min.x = ImMin(merge_clip_rect.Min.x, host_rect.Min.x); if ((merge_group_n & 2) == 0 || !has_freeze_v) merge_clip_rect.Min.y = ImMin(merge_clip_rect.Min.y, host_rect.Min.y); - if ((merge_group_n & 1) != 0) + if ((merge_group_n & 1) != 0 && (table->Flags & ImGuiTableFlags_NoHostExtendX) == 0) merge_clip_rect.Max.x = ImMax(merge_clip_rect.Max.x, host_rect.Max.x); if ((merge_group_n & 2) != 0 && (table->Flags & ImGuiTableFlags_NoHostExtendY) == 0) merge_clip_rect.Max.y = ImMax(merge_clip_rect.Max.y, host_rect.Max.y); From f88ec043afbb800c99d05ad74e348bd70e10da9d Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 29 Oct 2026 20:56:04 +0100 Subject: [PATCH 08/11] Tables: use center-bias for horizontal borders to better support thick borders. (#9415) + Demo: adjust colors in 'Background Color' section to better showcase the blending difference between Bg0 and Bg1. --- imgui_demo.cpp | 10 +++++++--- imgui_tables.cpp | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 547661f7d..a2afd55ad 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -6428,7 +6428,9 @@ static void DemoWindowTables() { static char text_bufs[3 * 5][16]; // Mini text storage for 3x5 cells static bool init = true; - if (!show_widget_frame_bg) + if (show_widget_frame_bg) + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0.0f); + else ImGui::PushStyleColor(ImGuiCol_FrameBg, 0); for (int cell = 0; cell < 3 * 5; cell++) { @@ -6440,7 +6442,9 @@ static void DemoWindowTables() ImGui::InputText("##cell", text_bufs[cell], IM_COUNTOF(text_bufs[cell])); ImGui::PopID(); } - if (!show_widget_frame_bg) + if (show_widget_frame_bg) + ImGui::PopStyleVar(); + else ImGui::PopStyleColor(); init = false; ImGui::EndTable(); @@ -7050,7 +7054,7 @@ static void DemoWindowTables() // We use a transparent color so we can see the one behind in case our target is RowBg1 and RowBg0 was already targeted by the ImGuiTableFlags_RowBg flag. if (row_bg_type != 0) { - ImU32 row_bg_color = ImGui::GetColorU32(row_bg_type == 1 ? ImVec4(0.7f, 0.3f, 0.3f, 0.65f) : ImVec4(0.2f + row * 0.1f, 0.2f, 0.2f, 0.65f)); // Flat or Gradient? + ImU32 row_bg_color = ImGui::GetColorU32(row_bg_type == 1 ? ImVec4(0.7f, 0.3f, 0.3f, 0.40f) : ImVec4(0.2f + row * 0.1f, 0.2f, 0.2f, 0.40f)); // Flat or Gradient? ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0 + row_bg_target, row_bg_color); } diff --git a/imgui_tables.cpp b/imgui_tables.cpp index b46f23b04..483cf3004 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -2216,11 +2216,11 @@ void ImGui::TableEndRow(ImGuiTable* table) // Draw top border if (top_border_col && bg_y1 >= table->BgClipRect.Min.y && bg_y1 < table->BgClipRect.Max.y) - window->DrawList->AddLineH(table->BorderX1, table->BorderX2, bg_y1, top_border_col, table->BorderSize); + window->DrawList->AddLineH(table->BorderX1, table->BorderX2, bg_y1, top_border_col, table->BorderSize, ImDrawFlags_StrokeCenterBiased); // Draw bottom border at the row unfreezing mark (always strong) if (draw_strong_bottom_border && bg_y2 >= table->BgClipRect.Min.y && bg_y2 < table->BgClipRect.Max.y) - window->DrawList->AddLineH(table->BorderX1, table->BorderX2, bg_y2, table->BorderColorStrong, table->BorderSize); + window->DrawList->AddLineH(table->BorderX1, table->BorderX2, bg_y2, table->BorderColorStrong, table->BorderSize, ImDrawFlags_StrokeCenterBiased); } // End frozen rows (when we are past the last frozen row line, teleport cursor and alter clipping rectangle) From a13badd73e3780690208fde799edb1e73c2f8377 Mon Sep 17 00:00:00 2001 From: MilosLord Date: Fri, 30 Oct 2026 11:56:02 +0100 Subject: [PATCH 09/11] MultiSelect: fixed ImGuiSelectionBasicStorage::GetNextSelectedItem() reading past the end of storage when trailing items are unselected (#9604) --- docs/CHANGELOG.txt | 3 +++ imgui_widgets.cpp | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index dddcff7a2..4ba908f4f 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -90,6 +90,9 @@ Other Changes: - Fixed selectable highlight straying off the X2/Y2 clipping limit. - Fixed parent window work rectangle affecting the Y2 clipping limit. - Fixed the Y2 clipping limit overlapping bottom outer border. +- Multi-Select: + - Fixed `ImGuiSelectionBasicStorage::GetNextSelectedItem()` reading one past the + end of storage when trailing items are unselected (#9604) [@MilosLord] - 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_widgets.cpp b/imgui_widgets.cpp index e1a884429..0b862b437 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -8803,7 +8803,7 @@ bool ImGuiSelectionBasicStorage::GetNextSelectedItem(void** opaque_it, ImGuiID* it = _Storage.Data.Data; IM_ASSERT(it >= _Storage.Data.Data && it <= it_end); if (it != it_end) - while (it->val_i == 0 && it < it_end) + while (it < it_end && it->val_i == 0) it++; const bool has_more = (it != it_end); *opaque_it = has_more ? (void**)(it + 1) : (void**)(it); From 1023d3ddaa75072806f6d11c7b434ed031ea14c1 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 30 Oct 2026 17:32:06 +0100 Subject: [PATCH 10/11] InputText: fixed ImGuiInputTextCallbackData::InsertChars() mistakenly over growing internal buffer. (#9603, #7925) Note that the 32-256 ranges may seem misleading because ImVector<> handle its growth policy but it is also the size we request to underlying user impl hence the clamps. --- imgui_widgets.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 0b862b437..f36a888c2 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -4471,7 +4471,7 @@ void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, cons if (new_text_len + BufTextLen + 1 > obj->TextA.Size && is_resizable && !is_readonly) { IM_ASSERT(Buf == obj->TextA.Data); - int new_buf_size = BufTextLen + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1; + int new_buf_size = BufTextLen + ImClamp(new_text_len, 32, ImMax(256, new_text_len)) + 1; obj->TextA.resize(new_buf_size + 1); obj->TextSrc = obj->TextA.Data; Buf = obj->TextA.Data; From 391f714c3736206b896b63d6d5dfbc6a5e7ed4aa Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 30 Oct 2026 19:54:08 +0100 Subject: [PATCH 11/11] RenderArrow doesn't need 64-bits rounding. Added ImRoundPositive() for consistency. --- imgui_draw.cpp | 6 +++--- imgui_internal.h | 12 +++++++----- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 6ab6c6c45..6d8e3f224 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -7582,9 +7582,9 @@ void ImGui::RenderArrowEx(ImDrawList* draw_list, ImVec2 pos, ImU32 col, float co // Pixel align when at a right angle if (is_fill && (cos_a * sin_a >= -FLT_EPSILON) && (cos_a * sin_a <= FLT_EPSILON)) { - p[0] = ImRoundSigned64(p[0]); - p[1] = ImRoundSigned64(p[1]); - p[2] = ImRoundSigned64(p[2]); + p[0] = ImRoundSigned(p[0]); + p[1] = ImRoundSigned(p[1]); + p[2] = ImRoundSigned(p[2]); } if (is_fill) diff --git a/imgui_internal.h b/imgui_internal.h index 001bc687c..145f3f137 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -284,8 +284,8 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer #define IM_MEMALIGN(_OFF,_ALIGN) (((_OFF) + ((_ALIGN) - 1)) & ~((_ALIGN) - 1)) // Memory align e.g. IM_ALIGN(0,4)=0, IM_ALIGN(1,4)=4, IM_ALIGN(4,4)=4, IM_ALIGN(5,4)=8 #define IM_F32_TO_INT8_UNBOUND(_VAL) ((int)((_VAL) * 255.0f + ((_VAL)>=0 ? 0.5f : -0.5f))) // Unsaturated, for display purpose #define IM_F32_TO_INT8_SAT(_VAL) ((int)(ImSaturate(_VAL) * 255.0f + 0.5f)) // Saturated, always output 0..255 -#define IM_TRUNC(_VAL) ((float)(int)(_VAL)) // Positive values only! ImTrunc() is not inlined in MSVC debug builds -#define IM_ROUND(_VAL) ((float)(int)((_VAL) + 0.5f)) // Positive values only! +#define IM_TRUNC(_VAL) ((float)(int)(_VAL)) // Positive values only! == ImTrunc() which is not inlined in MSVC debug builds +#define IM_ROUND(_VAL) ((float)(int)((_VAL) + 0.5f)) // Positive values only! == ImRoundPositive() which is not inlined in MSVC debug builds. //#define IM_FLOOR IM_TRUNC // [OBSOLETE] Renamed in 1.90.0 (Sept 2023) // Hint for branch prediction @@ -542,14 +542,16 @@ inline float ImSaturate(float f) { return inline float ImLengthSqr(const ImVec2& lhs) { return (lhs.x * lhs.x) + (lhs.y * lhs.y); } inline float ImLengthSqr(const ImVec4& lhs) { return (lhs.x * lhs.x) + (lhs.y * lhs.y) + (lhs.z * lhs.z) + (lhs.w * lhs.w); } inline float ImInvLength(const ImVec2& lhs, float fail_value) { float d = (lhs.x * lhs.x) + (lhs.y * lhs.y); if (d > 0.0f) return ImRsqrt(d); return fail_value; } -inline float ImTrunc(float f) { return (float)(int)(f); } +inline float ImTrunc(float f) { return (float)(int)(f); } // == IM_TRUNC() inline ImVec2 ImTrunc(const ImVec2& v) { return ImVec2((float)(int)(v.x), (float)(int)(v.y)); } +inline float ImTrunc64(float f) { return (float)(ImS64)(f); } inline float ImFloor(float f) { return (float)((f >= 0 || (float)(int)f == f) ? (int)f : (int)f - 1); } // Decent replacement for floorf() inline ImVec2 ImFloor(const ImVec2& v) { return ImVec2(ImFloor(v.x), ImFloor(v.y)); } -inline float ImTrunc64(float f) { return (float)(ImS64)(f); } +inline float ImRoundPositive(float f) { return (float)(int)(f + 0.5f); } // == IM_ROUND(). Positive values only. inline float ImRoundPositive64(float f) { return (float)(ImS64)(f + 0.5f); } // Positive values only. +inline float ImRoundSigned(float f) { return (float)(int)(f >= 0.0f ? f + 0.5f : f - 0.5f); } +inline ImVec2 ImRoundSigned(const ImVec2& v) { return ImVec2((float)(int)(v.x >= 0.0f ? v.x + 0.5f : v.x - 0.5f), (float)(int)(v.y >= 0.0f ? v.y + 0.5f : v.y - 0.5f)); } inline float ImRoundSigned64(float f) { return (float)(ImS64)(f >= 0.0f ? f + 0.5f : f - 0.5f); } -inline ImVec2 ImRoundSigned64(const ImVec2& v) { return ImVec2((float)(ImS64)(v.x >= 0.0f ? v.x + 0.5f : v.x - 0.5f), (float)(ImS64)(v.y >= 0.0f ? v.y + 0.5f : v.y - 0.5f)); } inline float ImCeilFast(float f) { int i = (int)f; return (float)(i + (f > (float)i)); } // Consider using the bit-hack version (search for "0x1p120f"). inline int ImModPositive(int a, int b) { return (a + b) % b; } inline float ImDot(const ImVec2& a, const ImVec2& b) { return a.x * b.x + a.y * b.y; }