From 95bd1577d6938f9303096f9ca3cab866fe027f44 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 9 Apr 2026 12:53:20 +0200 Subject: [PATCH 01/23] Windows: Child windows with only ImGuiChildFlags_AutoResizeY flag keep using the proportional default ItemWidth. (#9355) + Removed Tooltip flag check, it's from 8c4fcf1359 (!) where AlwaysAutoReszie was added after Tooltip. Nowadays Tooltips sets ImGuiChildFlags_AlwaysAutoResize. --- docs/CHANGELOG.txt | 2 ++ imgui.cpp | 13 +++++++++---- imgui.h | 2 +- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index f90950541..ace3c72a5 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -55,6 +55,8 @@ Other Changes: - Fixed a single-axis auto-resizing feedback loop issue with nested containers and varying scrollbar visibility. (#9352) - Detect and report error when calling End() instead of EndPopup() on a popup. (#9351) + - Child windows with only ImGuiChildFlags_AutoResizeY flag keep using the proportional + default ItemWidth. (#9355) - Fonts: - imgui_freetype: add FreeType headers & compiled version in 'About Dear ImGui' details. - Clipper: diff --git a/imgui.cpp b/imgui.cpp index 5beb0fbb0..d0823ad38 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6436,7 +6436,7 @@ bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, I window_flags |= ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_NoTitleBar; window_flags |= (parent_window->Flags & ImGuiWindowFlags_NoMove); // Inherit the NoMove flag if (child_flags & (ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize)) - window_flags |= ImGuiWindowFlags_AlwaysAutoResize; + window_flags |= ImGuiWindowFlags_AlwaysAutoResize; // FIXME: Would be sane to not make single-axis flag set this. (#9355) if ((child_flags & (ImGuiChildFlags_ResizeX | ImGuiChildFlags_ResizeY)) == 0) window_flags |= ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings; @@ -8035,9 +8035,14 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->DC.LayoutType = ImGuiLayoutType_Vertical; window->DC.ParentLayoutType = parent_window ? parent_window->DC.LayoutType : ImGuiLayoutType_Vertical; - // Default item width. Make it proportional to window size if window manually resizes - const bool is_resizable_window = (window->Size.x > 0.0f && !(flags & ImGuiWindowFlags_Tooltip) && !(flags & ImGuiWindowFlags_AlwaysAutoResize)); - if (is_resizable_window) + // Default item width. Make it proportional to window size if window can be manually resized. + // (we cannot use AutoFitFramesX/AutoFitFramesY which is a temporary state) + bool is_resizable_width; + if (flags & ImGuiWindowFlags_ChildWindow) + is_resizable_width = (window->Size.x > 0.0f) && !(window->ChildFlags & (ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AlwaysAutoResize)); + else + is_resizable_width = (window->Size.x > 0.0f) && !(flags & ImGuiWindowFlags_AlwaysAutoResize); + if (is_resizable_width) window->DC.ItemWidthDefault = ImTrunc(window->Size.x * 0.65f); else window->DC.ItemWidthDefault = ImTrunc(g.FontSize * 16.0f); diff --git a/imgui.h b/imgui.h index 35adb2dc1..75277db87 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.92.8 WIP" -#define IMGUI_VERSION_NUM 19271 +#define IMGUI_VERSION_NUM 19272 #define IMGUI_HAS_TABLE // Added BeginTable() - from IMGUI_VERSION_NUM >= 18000 #define IMGUI_HAS_TEXTURES // Added ImGuiBackendFlags_RendererHasTextures - from IMGUI_VERSION_NUM >= 19198 From c2565fe64254ed1514feda6b4810e4f5b01f0e75 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 9 Apr 2026 13:59:45 +0200 Subject: [PATCH 02/23] Update ignore list Add .claude --- .gitignore | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 1aedcf779..a8b4e38e3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ -## OSX artifacts +## Misc artifacts .DS_Store +.claude +.vscode ## Dear ImGui artifacts imgui.ini @@ -55,9 +57,6 @@ examples/example_sdl3_wgpu/web/* .idea cmake-build-* -## VS code artifacts -.vscode - ## Unix executables from our example Makefiles examples/example_apple_metal/example_apple_metal examples/example_apple_opengl2/example_apple_opengl2 From 11de9df44ce01d8adbc0b98a73d546c05e8f28e7 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 9 Apr 2026 14:25:34 +0200 Subject: [PATCH 03/23] Minor optimization: reduce redudant label scanning in common widgets. --- docs/CHANGELOG.txt | 2 ++ imgui.cpp | 4 +-- imgui_widgets.cpp | 69 +++++++++++++++++++++++++++------------------- 3 files changed, 45 insertions(+), 30 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index ace3c72a5..fbd663bad 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -64,6 +64,8 @@ Other Changes: reporting the common clipper error over a table sanity check assert). (#9350) - Tweaked assert triggering when first item height measurement fails, and made it a better recoverable error. (#9350) +- Misc: + - Minor optimization: reduce redudant label scanning in common widgets. - Backends: - Metal: avoid redundant vertex buffer bind in SetupRenderState, which leads to validation issue. (#9343) [@Hunam6] diff --git a/imgui.cpp b/imgui.cpp index d0823ad38..0dbd6321a 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -3832,7 +3832,7 @@ void ImGui::RenderText(ImVec2 pos, const char* text, const char* text_end, bool else { if (!text_end) - text_end = text + ImStrlen(text); // FIXME-OPT + text_end = text + ImStrlen(text); // FIXME-OPT (not reached by our internal calls) text_display_end = text_end; } @@ -3850,7 +3850,7 @@ void ImGui::RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end ImGuiWindow* window = g.CurrentWindow; if (!text_end) - text_end = text + ImStrlen(text); // FIXME-OPT + text_end = text + ImStrlen(text); // FIXME-OPT (not reached by our internal calls) if (text != text_end) { diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 2880ed6ba..162605148 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -401,7 +401,8 @@ void ImGui::LabelTextV(const char* label, const char* fmt, va_list args) const char* value_text_begin, *value_text_end; ImFormatStringToTempBufferV(&value_text_begin, &value_text_end, fmt, args); const ImVec2 value_size = CalcTextSize(value_text_begin, value_text_end, false); - const ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + const ImVec2 label_size = CalcTextSize(label, label_end, false); const ImVec2 pos = window->DC.CursorPos; const ImRect value_bb(pos, pos + ImVec2(w, value_size.y + style.FramePadding.y * 2)); @@ -413,7 +414,7 @@ void ImGui::LabelTextV(const char* label, const char* fmt, va_list args) // Render RenderTextClipped(value_bb.Min + style.FramePadding, value_bb.Max, value_text_begin, value_text_end, &value_size, ImVec2(0.0f, 0.0f)); if (label_size.x > 0.0f) - RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label); + RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label, label_end, false); } void ImGui::BulletText(const char* fmt, ...) @@ -788,7 +789,8 @@ bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(label); - const ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + const ImVec2 label_size = CalcTextSize(label, label_end, false); ImVec2 pos = window->DC.CursorPos; if ((flags & ImGuiButtonFlags_AlignTextBaseLine) && style.FramePadding.y < window->DC.CurrLineTextBaseOffset) // Try to vertically align buttons that are smaller/have no padding so that text baseline matches (bit hacky, since it shouldn't be a flag) @@ -810,7 +812,7 @@ bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags if (g.LogEnabled) LogSetNextTextDecoration("[", "]"); - RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb); + RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, label_end, &label_size, style.ButtonTextAlign, &bb); // Automatically close popups //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup)) @@ -1243,7 +1245,8 @@ bool ImGui::Checkbox(const char* label, bool* v) ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(label); - const ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + const ImVec2 label_size = CalcTextSize(label, label_end, false); const float square_sz = GetFrameHeight(); const ImVec2 pos = window->DC.CursorPos; @@ -1303,7 +1306,7 @@ bool ImGui::Checkbox(const char* label, bool* v) if (g.LogEnabled) LogRenderedText(&label_pos, mixed_value ? "[~]" : *v ? "[x]" : "[ ]"); if (is_visible && label_size.x > 0.0f) - RenderText(label_pos, label); + RenderText(label_pos, label, label_end, false); IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0)); return pressed; @@ -1365,7 +1368,8 @@ bool ImGui::RadioButton(const char* label, bool active) ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(label); - const ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + const ImVec2 label_size = CalcTextSize(label, label_end, false); const float square_sz = GetFrameHeight(); const ImVec2 pos = window->DC.CursorPos; @@ -1404,7 +1408,7 @@ bool ImGui::RadioButton(const char* label, bool active) if (g.LogEnabled) LogRenderedText(&label_pos, active ? "(x)" : "( )"); if (label_size.x > 0.0f) - RenderText(label_pos, label); + RenderText(label_pos, label, label_end, false); IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); return pressed; @@ -1944,7 +1948,8 @@ bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboF IM_ASSERT((flags & (ImGuiComboFlags_NoPreview | (ImGuiComboFlags)ImGuiComboFlags_CustomPreview)) == 0); const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight(); - const ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + const ImVec2 label_size = CalcTextSize(label, label_end, false); const float preview_width = ((flags & ImGuiComboFlags_WidthFitPreview) && (preview_value != NULL)) ? CalcTextSize(preview_value, NULL, true).x : 0.0f; const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : ((flags & ImGuiComboFlags_WidthFitPreview) ? (arrow_size + preview_width + style.FramePadding.x * 2.0f) : CalcItemWidth()); const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f)); @@ -1996,7 +2001,7 @@ bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboF RenderTextClipped(bb.Min + style.FramePadding, ImVec2(value_x2, bb.Max.y), preview_value, NULL, NULL); } if (label_size.x > 0) - RenderText(ImVec2(bb.Max.x + style.ItemInnerSpacing.x, bb.Min.y + style.FramePadding.y), label); + RenderText(ImVec2(bb.Max.x + style.ItemInnerSpacing.x, bb.Min.y + style.FramePadding.y), label, label_end, false); if (!popup_open) return false; @@ -2709,7 +2714,8 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, const float w = CalcItemWidth(); const ImU32 color_marker = (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasColorMarker) ? g.NextItemData.ColorMarker : 0; - const ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + const ImVec2 label_size = CalcTextSize(label, label_end, false); const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f)); const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); @@ -2785,7 +2791,7 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f)); if (label_size.x > 0.0f) - RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label, label_end, false); IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (temp_input_allowed ? ImGuiItemStatusFlags_Inputable : 0)); return value_changed; @@ -3310,7 +3316,8 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat const float w = CalcItemWidth(); const ImU32 color_marker = (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasColorMarker) ? g.NextItemData.ColorMarker : 0; - const ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + const ImVec2 label_size = CalcTextSize(label, label_end, false); const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f)); const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); @@ -3382,7 +3389,7 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f)); if (label_size.x > 0.0f) - RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label, label_end, false); IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (temp_input_allowed ? ImGuiItemStatusFlags_Inputable : 0)); return value_changed; @@ -3487,7 +3494,8 @@ bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType d const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(label); - const ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + const ImVec2 label_size = CalcTextSize(label, label_end, false); const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size); const ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); @@ -3532,7 +3540,7 @@ bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType d const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, format); RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.0f)); if (label_size.x > 0.0f) - RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label, label_end, false); return value_changed; } @@ -4695,7 +4703,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (is_multiline) // Open group before calling GetID() because groups tracks id created within their scope (including the scrollbar) BeginGroup(); const ImGuiID id = window->GetID(label); - const ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + const ImVec2 label_size = CalcTextSize(label, label_end, false); const ImVec2 frame_size = CalcItemSize(size_arg, CalcItemWidth(), (is_multiline ? g.FontSize * 8.0f : label_size.y) + style.FramePadding.y * 2.0f); // Arbitrary default of 8 lines high for multi-line const ImVec2 total_size = ImVec2(frame_size.x + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), frame_size.y); @@ -5664,7 +5673,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } if (label_size.x > 0) - RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label, label_end, false); if (value_changed) MarkItemEdited(id); @@ -8667,7 +8676,8 @@ bool ImGui::BeginListBox(const char* label, const ImVec2& size_arg) const ImGuiStyle& style = g.Style; const ImGuiID id = GetID(label); - const ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + const ImVec2 label_size = CalcTextSize(label, label_end, false); // Size default to hold ~7.25 items. // Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar. @@ -8690,7 +8700,7 @@ bool ImGui::BeginListBox(const char* label, const ImVec2& size_arg) if (label_size.x > 0.0f) { ImVec2 label_pos = ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y); - RenderText(label_pos, label); + RenderText(label_pos, label, label_end, false); window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, label_pos + label_size); AlignTextToFramePadding(); } @@ -8786,7 +8796,8 @@ int ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_get const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(label); - const ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + const ImVec2 label_size = CalcTextSize(label, label_end, false); const ImVec2 frame_size = CalcItemSize(size_arg, CalcItemWidth(), label_size.y + style.FramePadding.y * 2.0f); const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size); @@ -8885,7 +8896,7 @@ int ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_get RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, overlay_text, NULL, NULL, ImVec2(0.5f, 0.0f)); if (label_size.x > 0.0f) - RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label); + RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label, label_end, false); // Return hovered index or -1 if none are hovered. // This is currently not exposed in the public API because we need a larger redesign of the whole thing, but in the short-term we are making it available in PlotEx(). @@ -9250,7 +9261,8 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) // Tag menu as used. Next time BeginMenu() with same ID is called it will append to existing menu g.MenusIdSubmittedThisFrame.push_back(id); - ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + ImVec2 label_size = CalcTextSize(label, label_end, false); // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent without always being a Child window) // This is only done for items for the menu set and not the full parent window. @@ -9282,7 +9294,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, pos.y + window->DC.CurrLineTextBaseOffset); pressed = Selectable("", menu_is_open, selectable_flags, label_size); LogSetNextTextDecoration("[", "]"); - RenderText(text_pos, label); + RenderText(text_pos, label, label_end, false); PopStyleVar(); window->DC.CursorPos.x += IM_TRUNC(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). popup_pos = ImVec2(pos.x - 1.0f - IM_TRUNC(style.ItemSpacing.x * 0.5f), text_pos.y - style.FramePadding.y + window->MenuBarHeight); @@ -9299,7 +9311,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) ImVec2 text_pos(window->DC.CursorPos.x, pos.y + window->DC.CurrLineTextBaseOffset); pressed = Selectable("", menu_is_open, selectable_flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, label_size.y)); LogSetNextTextDecoration("", ">"); - RenderText(ImVec2(text_pos.x + offsets->OffsetLabel, text_pos.y), label); + RenderText(ImVec2(text_pos.x + offsets->OffsetLabel, text_pos.y), label, label_end, false); if (icon_w > 0.0f) RenderText(ImVec2(text_pos.x + offsets->OffsetIcon, text_pos.y), icon); RenderArrow(window->DrawList, ImVec2(text_pos.x + offsets->OffsetMark + extra_w + g.FontSize * 0.30f, text_pos.y), GetColorU32(ImGuiCol_Text), ImGuiDir_Right); @@ -9464,7 +9476,8 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut ImGuiContext& g = *GImGui; ImGuiStyle& style = g.Style; ImVec2 pos = window->DC.CursorPos; - ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + ImVec2 label_size = CalcTextSize(label, label_end, false); // See BeginMenuEx() for comments about this. const bool menuset_is_open = IsRootOfOpenMenuSet(); @@ -9491,7 +9504,7 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut pressed = Selectable("", selected, selectable_flags, ImVec2(label_size.x, 0.0f)); PopStyleVar(); if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible) - RenderText(text_pos, label); + RenderText(text_pos, label, label_end, false); window->DC.CursorPos.x += IM_TRUNC(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar(). } else @@ -9508,7 +9521,7 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut pressed = Selectable("", false, selectable_flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, label_size.y)); if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible) { - RenderText(text_pos + ImVec2(offsets->OffsetLabel, 0.0f), label); + RenderText(text_pos + ImVec2(offsets->OffsetLabel, 0.0f), label, label_end, false); if (icon_w > 0.0f) RenderText(text_pos + ImVec2(offsets->OffsetIcon, 0.0f), icon); if (shortcut_w > 0.0f) From f4ed421a88db61ebaf874506c13d1cf1a8a8281f Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 9 Apr 2026 14:52:49 +0200 Subject: [PATCH 04/23] InputText: CharFilter callback event sets CursorPos/SelectionStart/SelectionEnd. (#816) --- docs/CHANGELOG.txt | 1 + docs/TODO.txt | 1 - imgui.h | 8 ++++---- imgui_widgets.cpp | 3 +++ 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index fbd663bad..c6f9baa5d 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -47,6 +47,7 @@ Other Changes: - InputTextMultiline: fixed an issue processing deactivation logic when an active multi-line edit is clipped due to being out of view. - Fixed a crash when toggling ReadOnly while active. (#9354) + - CharFilter callback event sets CursorPos/SelectionStart/SelectionEnd. (#816) - Tables: - Fixed issues reporting ideal size to parent window/container: (#9352, #7651) - When both scrollbars are visible but only one of ScrollX/ScrollY was explicitly requested. diff --git a/docs/TODO.txt b/docs/TODO.txt index 6107e5dc0..30df62e32 100644 --- a/docs/TODO.txt +++ b/docs/TODO.txt @@ -63,7 +63,6 @@ It's mostly a bunch of personal notes, probably incomplete. Feel free to query i - input text: preserve scrolling when unfocused? - input text: reorganize event handling, allow CharFilter to modify buffers, allow multiple events? (#541) - - input text: expose CursorPos in char filter event (#816) - input text: try usage idiom of using InputText with data only exposed through get/set accessors, without extraneous copy/alloc. (#3009) - input text: access public fields via a non-callback API e.g. InputTextGetState("xxx") that may return nullptr if not active (available in internals) - input text: flag to disable live update of the user buffer (also applies to float/int text input) (#701) diff --git a/imgui.h b/imgui.h index 75277db87..e73b5b0d3 100644 --- a/imgui.h +++ b/imgui.h @@ -2642,7 +2642,7 @@ struct ImGuiInputTextCallbackData ImGuiInputTextFlags EventFlag; // One ImGuiInputTextFlags_Callback* // Read-only ImGuiInputTextFlags Flags; // What user passed to InputText() // Read-only void* UserData; // What user passed to InputText() // Read-only - ImGuiID ID; // Widget ID // Read-only + ImGuiID ID; // Widget ID // Read-only // Arguments for the different callback events // - During Resize callback, Buf will be same as your input buffer. @@ -2656,9 +2656,9 @@ struct ImGuiInputTextCallbackData char* Buf; // Text buffer // Read-write // [Resize] Can replace pointer / [Completion,History,Always] Only write to pointed data, don't replace the actual pointer! int BufTextLen; // Text length (in bytes) // Read-write // [Resize,Completion,History,Always] Exclude zero-terminator storage. In C land: == strlen(some_text), in C++ land: string.length() int BufSize; // Buffer size (in bytes) = capacity+1 // Read-only // [Resize,Completion,History,Always] Include zero-terminator storage. In C land: == ARRAYSIZE(my_char_array), in C++ land: string.capacity()+1 - int CursorPos; // // Read-write // [Completion,History,Always] - int SelectionStart; // // Read-write // [Completion,History,Always] == to SelectionEnd when no selection - int SelectionEnd; // // Read-write // [Completion,History,Always] + int CursorPos; // // Read-write // [Completion,History,Always,CharFilter] + int SelectionStart; // // Read-write // [Completion,History,Always,CharFilter] == to SelectionEnd when no selection + int SelectionEnd; // // Read-write // [Completion,History,Always,CharFilter] // Helper functions for text manipulation. // Use those function to benefit from the CallbackResize behaviors. Calling those function reset the selection. diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 162605148..3483acb6c 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -4521,6 +4521,9 @@ static bool InputTextFilterCharacter(ImGuiContext* ctx, ImGuiInputTextState* sta callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter; callback_data.EventChar = (ImWchar)c; callback_data.EventActivated = (g.ActiveId == state->ID && g.ActiveIdIsJustActivated); + callback_data.CursorPos = state->Stb->cursor; + callback_data.SelectionStart = state->Stb->select_start; + callback_data.SelectionEnd = state->Stb->select_end; callback_data.UserData = user_data; if (callback(&callback_data) != 0) return false; From 01a4ad3a50c681a5ff2adbffa55fa31547489f52 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 9 Apr 2026 15:01:40 +0200 Subject: [PATCH 05/23] Docs: update legacy TODO list. --- docs/TODO.txt | 41 +++++++++++++---------------------------- 1 file changed, 13 insertions(+), 28 deletions(-) diff --git a/docs/TODO.txt b/docs/TODO.txt index 30df62e32..0bdf21610 100644 --- a/docs/TODO.txt +++ b/docs/TODO.txt @@ -2,12 +2,14 @@ dear imgui ISSUES & TODO LIST Issue numbers (#) refer to GitHub issues listed at https://github.com/ocornut/imgui/issues/XXXX + THIS LIST IS NOT WELL MAINTAINED. MOST OF THE WORK HAPPENS ON GITHUB NOWADAYS. + The list below consist mostly of ideas noted down before they are requested/discussed by users (at which point they usually exist on the github issue tracker). It's mostly a bunch of personal notes, probably incomplete. Feel free to query if you have any questions. - - doc: add a proper documentation system (maybe relying on automation? #435) - doc: checklist app to verify backends/integration of imgui (test inputs, rendering, callback, etc.). + - doc: add a proper documentation system (maybe relying on automation? #435) - doc/tips: tips of the day: website? applet in imgui_club? - window: preserve/restore relative focus ordering (persistent or not), and e.g. of multiple reappearing windows (#2304) -> also see docking reference to same #. @@ -29,9 +31,9 @@ It's mostly a bunch of personal notes, probably incomplete. Feel free to query i - window/clipping: some form of clipping when DisplaySize (or corresponding viewport) is zero. - window/tabbing: add a way to signify that a window or docked window requires attention (e.g. blinking title bar, trying to click behind a modal). - window/id_stack: add e.g. window->GetIDFromPath() with support for leading / and ../ (#1390, #331) -> model from test engine. - ! scrolling: exposing horizontal scrolling with Shift+Wheel even when scrollbar is disabled expose lots of issues (#2424, #1463) + + ! scrolling: exposing horizontal scrolling with Shift+Wheel even when scrollbar is disabled expose various issues (#2424, #1463) - scrolling: while holding down a scrollbar, try to keep the same contents visible (at least while not moving mouse) - - scrolling: allow immediately effective change of scroll after Begin() if we haven't appended items yet. - scrolling: forward mouse wheel scrolling to parent window when at the edge of scrolling limits? (useful for listbox,tables?) - scrolling/style: shadows on scrollable areas to denote that there is more contents (see e.g. DaVinci Resolve ui) @@ -51,37 +53,32 @@ It's mostly a bunch of personal notes, probably incomplete. Feel free to query i - widgets: display mode: widget-label, label-widget (aligned on column or using fixed size), label-newline-tab-widget etc. (#395) - widgets: clean up widgets internal toward exposing everything and stabilizing imgui_internals.h. - widgets: add always-allow-overlap mode. This should perhaps be the default? one problem is that highlight after mouse-wheel scrolling gets deferred, makes scrolling more flickery. - - widgets: start exposing PushItemFlag() and ImGuiItemFlags - widgets: alignment options in style (e.g. center Selectable, Right-Align within Button, etc.) #1260 - widgets: activate by identifier (trigger button, focus given id) - widgets: custom glyph/shapes replacements for stock shapes. (also #6090 #2431 #2235 #6517) - widgets: coloredit: keep reporting as active when picker is on? - - widgets: group/scalarn functions: expose more per-component information. e.g. store NextItemData.ComponentIdx set by scalarn function, groups can expose them back somehow. + - widgets: group/ScalarN functions: expose more per-component information. e.g. store NextItemData.ComponentIdx set by scalarn function, groups can expose them back somehow. - selectable: using (size.x == 0.0f) and (SelectableTextAlign.x > 0.0f) followed by SameLine() is currently not supported. - selectable: generic BeginSelectable()/EndSelectable() mechanism. (work out alongside range-select branch) - selectable: a way to visualize partial/mixed selection (e.g. parent tree node has children with mixed selection) + - input text: flag to disable live update of the user buffer (also applies to float/int text input) (#701) - input text: preserve scrolling when unfocused? - input text: reorganize event handling, allow CharFilter to modify buffers, allow multiple events? (#541) - - input text: try usage idiom of using InputText with data only exposed through get/set accessors, without extraneous copy/alloc. (#3009) - - input text: access public fields via a non-callback API e.g. InputTextGetState("xxx") that may return nullptr if not active (available in internals) - - input text: flag to disable live update of the user buffer (also applies to float/int text input) (#701) - input text: hover tooltip could show unclamped text - input text: support for INSERT key to toggle overwrite mode. currently disabled because stb_textedit behavior is unsatisfactory on multi-line. (#2863) - input text: option to Tab after an Enter validation. - input text: add ImGuiInputTextFlags_EnterToApply? (off #218) - input text: easier ways to update buffer (from source char*) while owned. preserve some sort of cursor position for multi-line text. - input text: add discard flag (e.g. ImGuiInputTextFlags_DiscardActiveBuffer) or make it easier to clear active focus for text replacement during edition (#725) - - input text: display bug when clicking a drag/slider after an input text in a different window has all-selected text (order dependent). actually a very old bug but no one appears to have noticed it. - input text: allow centering/positioning text so that ctrl+clicking Drag or Slider keeps the textual value at the same pixel position. - input text: decorrelate display layout from inputs with custom template - e.g. what's the easiest way to implement a nice IP/Mac address input editor? - - input text: global callback system so user can plug in an expression evaluator easily. (#1691) + - input text: callback system (global) so user can plug in an expression evaluator easily. (#1691) - input text: force scroll to end or scroll to a given line/contents (so user can implement a log or a search feature) - input text: a way to preview completion (e.g. disabled text completing from the cursor) - input text: a side bar that could e.g. preview where errors are. probably left to the user to draw but we'd need to give them the info there. - input text: a way for the user to provide syntax coloring. - input text: Shift+TAB with ImGuiInputTextFlags_AllowTabInput could eat preceding blanks, up to tab_count. - - input text multi-line: don't directly call AddText() which does an unnecessary vertex reserve for character count prior to clipping. and/or more line-based clipping to AddText(). and/or reorganize TextUnformatted/RenderText for more efficiency for large text (e.g TextUnformatted could clip and log separately, etc). - input text multi-line: support for copy/cut without selection (copy/cut current line?) - input text multi-line: line numbers? status bar? (follow up on #200) - input text multi-line: behave better when user changes input buffer while editing is active (even though it is illegal behavior). namely, the change of buffer can create a scrollbar glitch (#725) @@ -110,11 +107,7 @@ It's mostly a bunch of personal notes, probably incomplete. Feel free to query i !- color: the color conversion helpers/types are a mess and needs sorting out. - color: (api breaking) ImGui::ColorConvertXXX functions should be loose ImColorConvertXX to match imgui_internals.h - - plot: full featured plot/graph api w/ scrolling, zooming etc. --> promote using ImPlot - - (plot: deleted all other todo lines on 2023-06-28) - - clipper: ability to disable the clipping through a simple flag/bool. - - clipper: ability to run without knowing full count in advance. - clipper: horizontal clipping support. (#2580) - separator: expose flags (#759) @@ -140,22 +133,19 @@ It's mostly a bunch of personal notes, probably incomplete. Feel free to query i - knob: rotating knob widget (#942) - drag float: support for reversed drags (min > max) (removed is_locked, also see fdc526e) - drag float: up/down axis - - drag float: power != 0.0f with current value being outside the range keeps the value stuck. - drag float: added leeway on edge (e.g. a few invisible steps past the clamp limits) - combo: a way/helper to customize the combo preview (#1658) -> experimental BeginComboPreview() - combo/listbox: keyboard control. need InputText-like non-active focus + key handling. considering keyboard for custom listbox (pr #203) - - listbox: multiple selection (WIP range-select branch) - listbox: unselect option (#1208) - - listbox: make it easier/more natural to implement range-select (need some sort of info/ref about the last clicked/focused item that user can translate to an index?) (WIP range-select branch) - listbox: user may want to initial scroll to focus on the one selected value? - listbox: disable capturing mouse wheel if the listbox has no scrolling. (#1681) - listbox: scrolling should track modified selection. - listbox: future api should allow to enable horizontal scrolling (#2510) !- popups/menus: clarify usage of popups id, how MenuItem/Selectable closing parent popups affects the ID, etc. this is quite fishy needs improvement! (#331, #402) - - modals: make modal title bar blink when trying to click outside the modal - - modals: technically speaking, we could make Begin() with ImGuiWindowFlags_Modal work without involving popup. May help untangle a few things, as modals are more like regular windows than popups. + - modals: make modal title bar blink when trying to click outside the modal. + - modals: technically speaking, we could make Begin() with ImGuiWindowFlags_Modal work without involving popup. May help untangle a few things, as modals are more like regular windows than popups. (#402) - popups: if the popup functions took explicit ImGuiID it would allow the user to manage the scope of those ID. (#331) - popups: clicking outside (to close popup) and holding shouldn't drag window below. - popups: add variant using global identifier similar to Begin/End (#402) @@ -197,7 +187,7 @@ It's mostly a bunch of personal notes, probably incomplete. Feel free to query i - style: FramePadding could be different for up vs down (#584) - style: WindowPadding needs to be EVEN as the 0.5 multiplier used on this value probably have a subtle effect on clip rectangle - style: have a more global HSV setter (e.g. alter hue on all elements). consider replacing active/hovered by offset in HSV space? (#438, #707, #1223) - - style: gradients fill (#1223) ~ 2 bg colors for each fill? tricky with rounded shapes and using textures for corners. + - style: gradients fill (#4722, #1223) ~ 2 bg colors for each fill? tricky with rounded shapes and using textures for corners. - style editor: color child window height expressed in multiple of line height. - log: improve logging of ArrowButton, ListBox, TabItem @@ -213,7 +203,6 @@ It's mostly a bunch of personal notes, probably incomplete. Feel free to query i - filters: fuzzy matches (may use code at blog.forrestthewoods.com/4cffeed33fdb) - drag and drop: focus drag target window on hold (even without open) - - drag and drop: releasing a drop shows the "..." tooltip for one frame - since e13e598 (#1725) - drag and drop: drag source on a group object (would need e.g. an invisible button covering group in EndGroup) https://twitter.com/paniq/status/1121446364909535233 - drag and drop: have some way to know when a drag begin from BeginDragDropSource() pov. (see 2018/01/11 post in #143) - drag and drop: allow preview tooltip to be submitted from a different place than the drag source. (#1725) @@ -246,7 +235,6 @@ It's mostly a bunch of personal notes, probably incomplete. Feel free to query i - font: make it easier to submit own bitmap font (same texture, another texture?). (#2127, #2575) - font: MemoryTTF taking ownership confusing/not obvious, maybe default should be opposite? - font: storing MinAdvanceX per font would allow us to skip calculating line width (under a threshold of character count) in loops looking for block width - - font/demo: demonstrate use of ImFontGlyphRangesBuilder. - font/draw: vertical and/or rotated text renderer (#705) - vertical is easier clipping wise - font/draw: need to be able to specify wrap start position. - font/draw: better reserve policy for large horizontal block of text (shouldn't reserve for all clipped lines). also see #3349. @@ -272,12 +260,11 @@ It's mostly a bunch of personal notes, probably incomplete. Feel free to query i - nav: NavFlattened: ESC on a flattened child should select something. - nav: NavFlattened: broken: in typical usage scenario, the items of a fully clipped child are currently not considered to enter into a NavFlattened child. - nav: NavFlattened: cannot access menu-bar of a flattened child window with Alt/menu key (not a very common use case..). - - nav: simulate right-click or context activation? (SHIFT+F10, keyboard Menu key?) - nav/popup: esc/enter default behavior for popups, e.g. be able to mark an "ok" or "cancel" button that would get triggered by those keys, default validation button, etc. - nav/treenode: left within a tree node block as a fallback (ImGuiTreeNodeFlags_NavLeftJumpsBackHere by default?) - nav/menus: pressing left-right on a vertically clipped menu bar tends to jump to the collapse/close buttons. - nav/menus: allow pressing Menu to leave a sub-menu. - - nav/menus: a way to access the main menu bar with Alt? (currently needs CTRL+TAB) or last focused window menu bar? + ! nav/menus: a way to access the main menu bar with Alt? (currently needs CTRL+TAB) or last focused window menu bar? - nav/menus: when using the main menu bar, even though we restore focus after, the underlying window loses its title bar highlight during menu manipulation. could we prevent it? - nav/menus: main menu bar currently cannot restore a nullptr focus. Could save NavWindow at the time of being focused, similarly to what popup do? - nav/menus: Alt,Up could open the first menu (e.g. "File") currently it tends to nav into the window/collapse menu. Do do that we would need custom transition? @@ -296,7 +283,6 @@ It's mostly a bunch of personal notes, probably incomplete. Feel free to query i - misc: no way to run a root-most GetID() with ImGui:: api since there's always a Debug window in the stack. (mentioned in #2960) - misc: make the ImGuiCond values linear (non-power-of-two). internal storage for ImGuiWindow can use integers to combine into flags (Why?) - misc: PushItemFlag(): add a flag to disable keyboard capture when used with mouse? (#1682) - - misc: use more size_t in public api? - misc: support for string view/range instead of char* would e.g. facilitate usage with Rust (#683, #3038, WIP string_view branch) - demo: demonstrate using PushStyleVar() in more details. @@ -314,8 +300,7 @@ It's mostly a bunch of personal notes, probably incomplete. Feel free to query i - backends: opengl: could use a single vertex buffer and glBufferSubData for uploads? - backends: opengl: explicitly disable GL_STENCIL_TEST in bindings. - backends: vulkan: viewport: support for synchronized swapping of multiple swap chains. - - backends: bgfx: https://gist.github.com/RichardGale/6e2b74bc42b3005e08397236e4be0fd0 - - backends: emscripten: with refactored examples, we could provide a direct imgui_impl_emscripten platform layer (see eg. https://github.com/floooh/sokol-samples/blob/master/html5/imgui-emsc.cc#L42) + - backends: emscripten: dedicated backend? (#8178, #9120, eg. https://github.com/floooh/sokol-samples/blob/master/html5/imgui-emsc.cc#L42) - bindings: ways to use clang ast dump to generate bindings or helpers for bindings? (e.g. clang++ -Xclang -ast-dump=json imgui.h) (--> use https://github.com/dearimgui/dear_bindings) From 24677c561e6a50c97daa1f6e7ff2b042fddd0ccf Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 9 Apr 2026 21:02:01 +0200 Subject: [PATCH 06/23] Multi-Select: Box-Select: add compile-time debug options. (#7994, #1861, #6518) --- imgui_internal.h | 3 +++ imgui_widgets.cpp | 13 ++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/imgui_internal.h b/imgui_internal.h index bb0b7f3a3..2a51931ef 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -252,6 +252,9 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer #define IMGUI_DEBUG_LOG_FONT(...) do { ImGuiContext* g2 = GImGui; if (g2 && g2->DebugLogFlags & ImGuiDebugLogFlags_EventFont) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) // Called from ImFontAtlas function which may operate without a context. #define IMGUI_DEBUG_LOG_INPUTROUTING(...) do{if (g.DebugLogFlags & ImGuiDebugLogFlags_EventInputRouting)IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) +// Debug options (also see ones on top of imgui.cpp) +//#define IMGUI_DEBUG_BOXSELECT + // Static Asserts #define IM_STATIC_ASSERT(_COND) static_assert(_COND, "") diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 3483acb6c..90484fa1b 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -7505,6 +7505,11 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl if (is_visible) RenderTextClipped(pos, ImVec2(ImMin(pos.x + size.x, window->WorkRect.Max.x), pos.y + size.y), label, NULL, &label_size, style.SelectableTextAlign, &bb); +#ifdef IMGUI_DEBUG_BOXSELECT + if (g.BoxSelectState.UnclipMode) + GetForegroundDrawList()->AddText(pos, IM_COL32(255,255,0,200), label); +#endif + // Automatically close popups if (pressed && !auto_selected && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_NoAutoClosePopups) && (g.LastItemData.ItemFlags & ImGuiItemFlags_AutoClosePopups)) CloseCurrentPopup(); @@ -7841,9 +7846,12 @@ bool ImGui::BeginBoxSelect(const ImRect& scope_rect, ImGuiWindow* window, ImGuiI bs->UnclipRect.Add(bs->BoxSelectRectCurr); } - //GetForegroundDrawList()->AddRect(bs->UnclipRect.Min, bs->UnclipRect.Max, IM_COL32(255,0,0,200), 0.0f, 0, 3.0f); +#ifdef IMGUI_DEBUG_BOXSELECT + if (ms_flags & ImGuiMultiSelectFlags_BoxSelect2d) + GetForegroundDrawList()->AddRect(bs->UnclipRect.Min, bs->UnclipRect.Max, bs->UnclipMode ? IM_COL32(255,255,0,200) : IM_COL32(255,0,0,200), 0.0f, 0, 4.0f); //GetForegroundDrawList()->AddRect(bs->BoxSelectRectPrev.Min, bs->BoxSelectRectPrev.Max, IM_COL32(255,0,0,200), 0.0f, 0, 3.0f); //GetForegroundDrawList()->AddRect(bs->BoxSelectRectCurr.Min, bs->BoxSelectRectCurr.Max, IM_COL32(0,255,0,200), 0.0f, 0, 1.0f); +#endif return true; } @@ -8304,6 +8312,9 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed) { selected = !selected; MultiSelectAddSetRange(ms, selected, +1, item_data, item_data); +#ifdef IMGUI_DEBUG_BOXSELECT + GetForegroundDrawList()->AddRectFilled(g.LastItemData.Rect.Min, g.LastItemData.Rect.Max, selected ? IM_COL32(0, 255, 0, 200) : IM_COL32(255, 0, 0, 200)); +#endif } storage->LastSelectionSize = ImMax(storage->LastSelectionSize + 1, 1); } From b444694b3d16c805200760098040c8a0085fd5e7 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 9 Apr 2026 21:36:50 +0200 Subject: [PATCH 07/23] Multi-Select: Box-Select: fixed an issue using ImGuiMultiSelectFlags_BoxSelect2d mode, where items out of view wouldn't be properly selected. (#7994, #1861, #6518) Because BoxSelectRectPrev and BoxSelectRectCurr were clamped to scope boundaries, dragging mouse outside of the scope would usually keep one axis unchanged. Amend 15391762ddbd4ad03c11dc1746f0603c75036edc --- docs/CHANGELOG.txt | 4 ++++ imgui_widgets.cpp | 16 +++++++++------- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index c6f9baa5d..148728e7b 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -58,6 +58,10 @@ Other Changes: - Detect and report error when calling End() instead of EndPopup() on a popup. (#9351) - Child windows with only ImGuiChildFlags_AutoResizeY flag keep using the proportional default ItemWidth. (#9355) +- Multi-Select: + - Box-Select: fixed an issue using ImGuiMultiSelectFlags_BoxSelect2d mode, where + items out of view wouldn't be properly selected when scrolling while mouse cursor + is hovering outside of selection scope. (#7994, #1861, #6518) - Fonts: - imgui_freetype: add FreeType headers & compiled version in 'About Dear ImGui' details. - Clipper: diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 90484fa1b..bee85438f 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -7836,15 +7836,17 @@ bool ImGui::BeginBoxSelect(const ImRect& scope_rect, ImGuiWindow* window, ImGuiI bs->BoxSelectRectCurr.Min = ImMin(start_pos_abs, curr_end_pos_abs); bs->BoxSelectRectCurr.Max = ImMax(start_pos_abs, curr_end_pos_abs); - // Box-select 2D mode detects horizontal changes (vertical ones are already picked by Clipper) - // Storing an extra rect used by widgets supporting box-select. + // Box-select 2D mode detects change of the rectangle. + // Storing unclip rect used by widgets supporting box-select. if (ms_flags & ImGuiMultiSelectFlags_BoxSelect2d) - if (bs->BoxSelectRectPrev.Min.x != bs->BoxSelectRectCurr.Min.x || bs->BoxSelectRectPrev.Max.x != bs->BoxSelectRectCurr.Max.x) - { + { + if (bs->BoxSelectRectPrev.Min != bs->BoxSelectRectCurr.Min || bs->BoxSelectRectPrev.Max != bs->BoxSelectRectCurr.Max) bs->UnclipMode = true; - bs->UnclipRect = bs->BoxSelectRectPrev; // FIXME-OPT: UnclipRect x coordinates could be intersection of Prev and Curr rect on X axis. - bs->UnclipRect.Add(bs->BoxSelectRectCurr); - } + + // Always update rect even if we don't use it. + bs->UnclipRect = bs->BoxSelectRectPrev; // FIXME-OPT: UnclipRect X coordinates could be intersection of Prev and Curr rect on X axis. + bs->UnclipRect.Add(bs->BoxSelectRectCurr); + } #ifdef IMGUI_DEBUG_BOXSELECT if (ms_flags & ImGuiMultiSelectFlags_BoxSelect2d) From f08b33fd0c9ef438ef2a1d87447a700f01aa7168 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 9 Apr 2026 22:15:32 +0200 Subject: [PATCH 08/23] Multi-Select: Box-Select: fixed an issue where items out of horizontal view would sometimes lead to incorrect merging of sequential selection requests. (#7994, #1861, #6518) SetNextItemSelectionUserData() could use g.NextItemData.SelectionUserData if we could guarantee if would be valid when nesting multi-select blocks. But it doesn't make much of a difference as whole ImGuiMultiSelectTempData fits in 2 cache lines. --- docs/CHANGELOG.txt | 4 +++- imgui_internal.h | 3 ++- imgui_widgets.cpp | 7 ++++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 148728e7b..cf57f093b 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -60,8 +60,10 @@ Other Changes: default ItemWidth. (#9355) - Multi-Select: - Box-Select: fixed an issue using ImGuiMultiSelectFlags_BoxSelect2d mode, where - items out of view wouldn't be properly selected when scrolling while mouse cursor + items out of view wouldn't be properly selected while scrolling while mouse cursor is hovering outside of selection scope. (#7994, #1861, #6518) + - Box-Select: fixed an issue where items out of horizontal view would sometimes lead + to incorrect merging of sequential selection requests. (#7994, #1861, #6518) - Fonts: - imgui_freetype: add FreeType headers & compiled version in 'About Dear ImGui' details. - Clipper: diff --git a/imgui_internal.h b/imgui_internal.h index 2a51931ef..5281c012e 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1915,7 +1915,8 @@ struct IMGUI_API ImGuiMultiSelectTempData ImGuiMultiSelectFlags Flags; ImVec2 ScopeRectMin; ImVec2 BackupCursorMaxPos; - ImGuiSelectionUserData LastSubmittedItem; // Copy of last submitted item data, used to merge output ranges. + ImGuiSelectionUserData CurrSubmittedItem; // Copy of last submitted item data, used to merge output ranges. + ImGuiSelectionUserData PrevSubmittedItem; // Copy of previous submitted item data, used to merge output ranges. ImGuiID BoxSelectId; ImGuiKeyChord KeyMods; ImS8 LoopRequestSetAll; // -1: no operation, 0: clear all, 1: select all. diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index bee85438f..b929589aa 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -8050,7 +8050,7 @@ ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, int sel storage->LastSelectionSize = 0; } ms->LoopRequestSetAll = request_select_all ? 1 : request_clear ? 0 : -1; - ms->LastSubmittedItem = ImGuiSelectionUserData_Invalid; + ms->PrevSubmittedItem = ImGuiSelectionUserData_Invalid; if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection) DebugLogMultiSelectRequests("BeginMultiSelect", &ms->IO); @@ -8153,6 +8153,8 @@ void ImGui::SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_d g.NextItemData.ItemFlags |= ImGuiItemFlags_HasSelectionUserData | ImGuiItemFlags_IsMultiSelect; if (ms->IO.RangeSrcItem == selection_user_data) ms->RangeSrcPassedBy = true; + ms->PrevSubmittedItem = ms->CurrSubmittedItem; // Can't rely on previous g.NextItemData.SelectionUserData because NextItemData is not restored on nested multi-select. + ms->CurrSubmittedItem = selection_user_data; } else { @@ -8430,7 +8432,6 @@ void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed) } if (storage->NavIdItem == item_data) ms->NavIdPassedBy = true; - ms->LastSubmittedItem = item_data; *p_selected = selected; *p_pressed = pressed; @@ -8449,7 +8450,7 @@ void ImGui::MultiSelectAddSetRange(ImGuiMultiSelectTempData* ms, bool selected, if (ms->IO.Requests.Size > 0 && first_item == last_item && (ms->Flags & ImGuiMultiSelectFlags_NoRangeSelect) == 0) { ImGuiSelectionRequest* prev = &ms->IO.Requests.Data[ms->IO.Requests.Size - 1]; - if (prev->Type == ImGuiSelectionRequestType_SetRange && prev->RangeLastItem == ms->LastSubmittedItem && prev->Selected == selected) + if (prev->Type == ImGuiSelectionRequestType_SetRange && prev->RangeLastItem == ms->PrevSubmittedItem && prev->Selected == selected) { prev->RangeLastItem = last_item; return; From 6ce4b44547387c2c968046a8e242eaf4abb0d7cc Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 9 Apr 2026 22:48:52 +0200 Subject: [PATCH 09/23] Mulit-Select: Box-Select: fixed an issue using in a table while relying on the TableNextColumn() return value to perform coarse clipping. (#7994) This is presumably not sufficient as MultiSelectAddSetRange() is also deficient there. --- docs/CHANGELOG.txt | 5 ++++- imgui_tables.cpp | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index cf57f093b..d17a6bee2 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -63,7 +63,10 @@ Other Changes: items out of view wouldn't be properly selected while scrolling while mouse cursor is hovering outside of selection scope. (#7994, #1861, #6518) - Box-Select: fixed an issue where items out of horizontal view would sometimes lead - to incorrect merging of sequential selection requests. (#7994, #1861, #6518) + to incorrect merging of sequential selection requests while also scrolling fast + enough to overlap multiple rows during a frame. (#7994, #1861, #6518) + - Box-Select: fixed an issue using ImGuiMultiSelectFlags_BoxSelect2d mode in a Table + while relying on the TableNextColumn() return value to perform coarse clipping. (#7994) - Fonts: - imgui_freetype: add FreeType headers & compiled version in 'About Dear ImGui' details. - Clipper: diff --git a/imgui_tables.cpp b/imgui_tables.cpp index f8450fbb1..38421af94 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -1152,6 +1152,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) column->ClipRect.Min.y = work_rect.Min.y; column->ClipRect.Max.x = column->MaxX; //column->WorkMaxX; column->ClipRect.Max.y = FLT_MAX; + ImRect clip_rect_unclipped = column->ClipRect; column->ClipRect.ClipWithFull(host_clip_rect); // Mark column as Clipped (not in sight) @@ -1169,6 +1170,9 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // Mark column as requesting output from user. Note that fixed + non-resizable sets are auto-fitting at all times and therefore always request output. column->IsRequestOutput = is_visible || column->AutoFitQueue != 0 || column->CannotSkipItemsQueue != 0; + ImGuiBoxSelectState* bs = &g.BoxSelectState; + if (!column->IsRequestOutput && bs->UnclipMode && bs->UnclipRect.Overlaps(clip_rect_unclipped)) + column->IsRequestOutput = true; // Mark column as SkipItems (ignoring all items/layout) // (table->HostSkipItems is a copy of inner_window->SkipItems before we cleared it above in Part 2) From f678c91617ef55d07399f061d7ff578c888050a0 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 9 Apr 2026 23:07:51 +0200 Subject: [PATCH 10/23] Multi-Select: disable MultiSelectAddSetRange() attempt at merging consecutive requests submitted from box-selection. Essentially reverts 79b77d91c, f904a6646c2 which did the initial span merging. Amend and disable change done for f08b33f. --- docs/CHANGELOG.txt | 5 +++++ imgui_internal.h | 4 ++-- imgui_widgets.cpp | 11 ++++++++--- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index d17a6bee2..e18f5a996 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -67,6 +67,11 @@ Other Changes: enough to overlap multiple rows during a frame. (#7994, #1861, #6518) - Box-Select: fixed an issue using ImGuiMultiSelectFlags_BoxSelect2d mode in a Table while relying on the TableNextColumn() return value to perform coarse clipping. (#7994) + - Box-Select: disabled merging consecutive selection requests as we have no reliable + way of detecting if user has submitted all consecutives items without clipping gaps, + and ImGuiSelectionUserData is technically opaque storage. (#7994, #1861) + (we will probably bring this back as a minor optimization if we have a way to for + user to tell us ImGuiSelectionUserData are indices) - Fonts: - imgui_freetype: add FreeType headers & compiled version in 'About Dear ImGui' details. - Clipper: diff --git a/imgui_internal.h b/imgui_internal.h index 5281c012e..85c9daabf 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1915,8 +1915,8 @@ struct IMGUI_API ImGuiMultiSelectTempData ImGuiMultiSelectFlags Flags; ImVec2 ScopeRectMin; ImVec2 BackupCursorMaxPos; - ImGuiSelectionUserData CurrSubmittedItem; // Copy of last submitted item data, used to merge output ranges. - ImGuiSelectionUserData PrevSubmittedItem; // Copy of previous submitted item data, used to merge output ranges. + //ImGuiSelectionUserData CurrSubmittedItem; // Copy of last submitted item data, used to merge output ranges. + //ImGuiSelectionUserData PrevSubmittedItem; // Copy of previous submitted item data, used to merge output ranges. ImGuiID BoxSelectId; ImGuiKeyChord KeyMods; ImS8 LoopRequestSetAll; // -1: no operation, 0: clear all, 1: select all. diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index b929589aa..ab9b3324e 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -8050,7 +8050,7 @@ ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, int sel storage->LastSelectionSize = 0; } ms->LoopRequestSetAll = request_select_all ? 1 : request_clear ? 0 : -1; - ms->PrevSubmittedItem = ImGuiSelectionUserData_Invalid; + //ms->PrevSubmittedItem = ImGuiSelectionUserData_Invalid; if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection) DebugLogMultiSelectRequests("BeginMultiSelect", &ms->IO); @@ -8153,8 +8153,8 @@ void ImGui::SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_d g.NextItemData.ItemFlags |= ImGuiItemFlags_HasSelectionUserData | ImGuiItemFlags_IsMultiSelect; if (ms->IO.RangeSrcItem == selection_user_data) ms->RangeSrcPassedBy = true; - ms->PrevSubmittedItem = ms->CurrSubmittedItem; // Can't rely on previous g.NextItemData.SelectionUserData because NextItemData is not restored on nested multi-select. - ms->CurrSubmittedItem = selection_user_data; + //ms->PrevSubmittedItem = ms->CurrSubmittedItem; // Can't rely on previous g.NextItemData.SelectionUserData because NextItemData is not restored on nested multi-select. + //ms->CurrSubmittedItem = selection_user_data; } else { @@ -8447,6 +8447,10 @@ void ImGui::MultiSelectAddSetAll(ImGuiMultiSelectTempData* ms, bool selected) void ImGui::MultiSelectAddSetRange(ImGuiMultiSelectTempData* ms, bool selected, int range_dir, ImGuiSelectionUserData first_item, ImGuiSelectionUserData last_item) { // Merge contiguous spans into same request (unless NoRangeSelect is set which guarantees single-item ranges) + // FIXME-OPT: Disabled on 2026/04/09 as this would break with any form of coarse clipping that we don't know about (e.g. TableNextColumn() return value). + // The low-hanging fruit would be to know that ImGuiSelectionUserData are sequential indices, in which case we can trivially compare PrevSubmittedItem + RangeDir == FirstItem. + // User can always perform this merge if required. +#if 0 if (ms->IO.Requests.Size > 0 && first_item == last_item && (ms->Flags & ImGuiMultiSelectFlags_NoRangeSelect) == 0) { ImGuiSelectionRequest* prev = &ms->IO.Requests.Data[ms->IO.Requests.Size - 1]; @@ -8456,6 +8460,7 @@ void ImGui::MultiSelectAddSetRange(ImGuiMultiSelectTempData* ms, bool selected, return; } } +#endif ImGuiSelectionRequest req = { ImGuiSelectionRequestType_SetRange, selected, (ImS8)range_dir, (range_dir > 0) ? first_item : last_item, (range_dir > 0) ? last_item : first_item }; ms->IO.Requests.push_back(req); // Add new request From 5da7eb0a84f637685d9a013ab2953f78930bc1f5 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 10 Apr 2026 17:42:13 +0200 Subject: [PATCH 11/23] DrawList: remove/strip old FixRectCornerFlags() code. Amend 44a6b493e --- imgui_draw.cpp | 41 +++++++++++++++-------------------------- 1 file changed, 15 insertions(+), 26 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 8461c6c4a..97c6f355b 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1432,35 +1432,21 @@ void ImDrawList::PathBezierQuadraticCurveTo(const ImVec2& p2, const ImVec2& p3, } } -static inline ImDrawFlags FixRectCornerFlags(ImDrawFlags flags) -{ - /* - IM_STATIC_ASSERT(ImDrawFlags_RoundCornersTopLeft == (1 << 4)); -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - // Obsoleted in 1.82 (from February 2021). This code was stripped/simplified and mostly commented in 1.90 (from September 2023) - // - Legacy Support for hard coded ~0 (used to be a suggested equivalent to ImDrawCornerFlags_All) - if (flags == ~0) { return ImDrawFlags_RoundCornersAll; } - // - Legacy Support for hard coded 0x01 to 0x0F (matching 15 out of 16 old flags combinations). Read details in older version of this code. - if (flags >= 0x01 && flags <= 0x0F) { return (flags << 4); } - // We cannot support hard coded 0x00 with 'float rounding > 0.0f' --> replace with ImDrawFlags_RoundCornersNone or use 'float rounding = 0.0f' -#endif - */ - // If this assert triggers, please update your code replacing hardcoded values with new ImDrawFlags_RoundCorners* values. - // Note that ImDrawFlags_Closed (== 0x01) is an invalid flag for AddRect(), AddRectFilled(), PathRect() etc. anyway. - // See details in 1.82 Changelog as well as 2021/03/12 and 2023/09/08 entries in "API BREAKING CHANGES" section. - IM_ASSERT((flags & 0x0F) == 0 && "Misuse of legacy hardcoded ImDrawCornerFlags values!"); - - if ((flags & ImDrawFlags_RoundCornersMask_) == 0) - flags |= ImDrawFlags_RoundCornersAll; - - return flags; -} - void ImDrawList::PathRect(const ImVec2& a, const ImVec2& b, float rounding, ImDrawFlags flags) { if (rounding >= 0.5f) { - flags = FixRectCornerFlags(flags); + // If this assert triggers on legacy code, please update your code replacing hardcoded values with ImDrawFlags_RoundCorners* values. + // - See details in 1.82 Changelog as well as 2021/03/12 and 2023/09/08 entries in "API BREAKING CHANGES" section. + // - Note that ImDrawFlags_Closed (== 0x01) is an invalid flag for AddRect(), AddRectFilled(), PathRect() etc. anyway. + // - Marked obsolete in 1.82 and completely removed in 1.90: + // - Hard coded support for ~0 == ImDrawFlags_RoundCornersAll. + // - Hard coded support for values 0x01 to 0x0F (matching 15 out of 16 old flags combinations) --> see FixRectCornerFlags() in <1.90 code. + // - Hard coded 0x00 with 'float rounding > 0.0f' --> replace with ImDrawFlags_RoundCornersNone or use 'float rounding = 0.0f' + IM_ASSERT((flags & 0x0F) == 0 && "Misuse of legacy hardcoded ImDrawCornerFlags values!"); + if ((flags & ImDrawFlags_RoundCornersMask_) == 0) + flags |= ImDrawFlags_RoundCornersAll; + rounding = ImMin(rounding, ImFabs(b.x - a.x) * (((flags & ImDrawFlags_RoundCornersTop) == ImDrawFlags_RoundCornersTop) || ((flags & ImDrawFlags_RoundCornersBottom) == ImDrawFlags_RoundCornersBottom) ? 0.5f : 1.0f) - 1.0f); rounding = ImMin(rounding, ImFabs(b.y - a.y) * (((flags & ImDrawFlags_RoundCornersLeft) == ImDrawFlags_RoundCornersLeft) || ((flags & ImDrawFlags_RoundCornersRight) == ImDrawFlags_RoundCornersRight) ? 0.5f : 1.0f) - 1.0f); } @@ -1776,7 +1762,10 @@ void ImDrawList::AddImageRounded(ImTextureRef tex_ref, const ImVec2& p_min, cons if ((col & IM_COL32_A_MASK) == 0) return; - flags = FixRectCornerFlags(flags); + IM_ASSERT((flags & 0x0F) == 0 && "Misuse of legacy hardcoded ImDrawCornerFlags values!"); // If this assert triggers on legacy code: see comments in ImDrawList::PathRect(). + if ((flags & ImDrawFlags_RoundCornersMask_) == 0) + flags |= ImDrawFlags_RoundCornersAll; + if (rounding < 0.5f || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) { AddImage(tex_ref, p_min, p_max, uv_min, uv_max, col); From 2dc64f99bcb7c16f5a314add49ec5db8905fa940 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 10 Apr 2026 18:37:08 +0200 Subject: [PATCH 12/23] Minor optimization: reduce redudant label scanning in common widgets. Missing stuff from 11de9df. The 6 remaining use of CalcTextSize(...,true) don't need this. --- imgui_tables.cpp | 2 +- imgui_widgets.cpp | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 38421af94..d06fe1e7b 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -3180,7 +3180,7 @@ void ImGui::TableHeader(const char* label) if (label == NULL) label = ""; const char* label_end = FindRenderedTextEnd(label); - ImVec2 label_size = CalcTextSize(label, label_end, true); + ImVec2 label_size = CalcTextSize(label, label_end, false); ImVec2 label_pos = window->DC.CursorPos; // If we already got a row height, there's use that. diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index ab9b3324e..7d0ac8c32 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -1524,7 +1524,7 @@ bool ImGui::TextLink(const char* label) const char* label_end = FindRenderedTextEnd(label); ImVec2 pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset); - ImVec2 size = CalcTextSize(label, label_end, true); + ImVec2 size = CalcTextSize(label, label_end, false); ImRect bb(pos, pos + size); ItemSize(size, 0.0f); if (!ItemAdd(bb, id)) @@ -7348,7 +7348,8 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl // Submit label or explicit size to ItemSize(), whereas ItemAdd() will submit a larger/spanning rectangle. ImGuiID id = window->GetID(label); - ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + ImVec2 label_size = CalcTextSize(label, label_end, false); ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y); ImVec2 pos = window->DC.CursorPos; pos.y += window->DC.CurrLineTextBaseOffset; @@ -7503,7 +7504,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl // Text stays at the submission position. Alignment/clipping extents ignore SpanAllColumns. if (is_visible) - RenderTextClipped(pos, ImVec2(ImMin(pos.x + size.x, window->WorkRect.Max.x), pos.y + size.y), label, NULL, &label_size, style.SelectableTextAlign, &bb); + RenderTextClipped(pos, ImVec2(ImMin(pos.x + size.x, window->WorkRect.Max.x), pos.y + size.y), label, label_end, &label_size, style.SelectableTextAlign, &bb); #ifdef IMGUI_DEBUG_BOXSELECT if (g.BoxSelectState.UnclipMode) @@ -10782,7 +10783,8 @@ void ImGui::TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabI void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImVec2 frame_padding, const char* label, ImGuiID tab_id, ImGuiID close_button_id, bool is_contents_visible, bool* out_just_closed, bool* out_text_clipped) { ImGuiContext& g = *GImGui; - ImVec2 label_size = CalcTextSize(label, NULL, true); + const char* label_end = FindRenderedTextEnd(label); + ImVec2 label_size = CalcTextSize(label, label_end, false); if (out_just_closed) *out_just_closed = false; @@ -10867,7 +10869,7 @@ void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, } } LogSetNextTextDecoration("/", "\\"); - RenderTextEllipsis(draw_list, text_ellipsis_clip_bb.Min, text_ellipsis_clip_bb.Max, ellipsis_max_x, label, NULL, &label_size); + RenderTextEllipsis(draw_list, text_ellipsis_clip_bb.Min, text_ellipsis_clip_bb.Max, ellipsis_max_x, label, label_end, &label_size); #if 0 if (!is_contents_visible) From 84b4b35902b40cff54649d6d2148947a26380014 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Sun, 12 Apr 2026 15:50:54 +0200 Subject: [PATCH 13/23] Fonts: rework UpdateCurrentFontSize() check. (#9364) Consistent with line above. --- imgui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui.cpp b/imgui.cpp index 0dbd6321a..79e9a7808 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -9107,7 +9107,7 @@ void ImGui::UpdateCurrentFontSize(float restore_font_size_after_scaling) } g.FontBaked = (g.Font != NULL && window != NULL) ? g.Font->GetFontBaked(final_size) : NULL; - g.FontBakedScale = (g.Font != NULL && window != NULL) ? (g.FontSize / g.FontBaked->Size) : 0.0f; + g.FontBakedScale = (g.FontBaked != NULL) ? (g.FontSize / g.FontBaked->Size) : 0.0f; g.DrawListSharedData.FontScale = g.FontBakedScale; } From f83a378d623a9fa8f55b749883f811fe4cec3742 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 13 Apr 2026 14:11:19 +0200 Subject: [PATCH 14/23] TabBar: changed edge's TabItem ClipRect to not pass an inverted PushClipRect(). While not currently a problem, it would be if ImRect::Overlaps() is changed to use <= instead of < (cc: #3976 which deal with Contains but sort of similar topic). Changing ImDrawList::PushClipRect()'s intersect_with_current_clip_rect path to use ClipRectFull() would also fix this, but it may ambiguous there which behavior would be correct. Amend 1ec464eb9. --- imgui_draw.cpp | 2 +- imgui_widgets.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 97c6f355b..657e1c512 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -662,7 +662,7 @@ void ImDrawList::PushClipRect(const ImVec2& cr_min, const ImVec2& cr_max, bool i if (intersect_with_current_clip_rect) { ImVec4 current = _CmdHeader.ClipRect; - if (cr.x < current.x) cr.x = current.x; + if (cr.x < current.x) cr.x = current.x; // = ClipWith(). Note that passing inverted range wouldn't be fixed here. if (cr.y < current.y) cr.y = current.y; if (cr.z > current.z) cr.z = current.z; if (cr.w > current.w) cr.w = current.w; diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 7d0ac8c32..e94dd3e87 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -10598,7 +10598,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, // We don't have CPU clipping primitives to clip the CloseButton (until it becomes a texture), so need to add an extra draw call (temporary in the case of vertical animation) const bool want_clip_rect = is_central_section && (bb.Min.x < tab_bar->ScrollingRectMinX || bb.Max.x > tab_bar->ScrollingRectMaxX); if (want_clip_rect) - PushClipRect(ImVec2(ImMax(bb.Min.x, tab_bar->ScrollingRectMinX), bb.Min.y - 1), ImVec2(tab_bar->ScrollingRectMaxX, bb.Max.y), true); + PushClipRect(ImVec2(ImClamp(bb.Min.x, tab_bar->ScrollingRectMinX, tab_bar->ScrollingRectMaxX), bb.Min.y - 1), ImVec2(tab_bar->ScrollingRectMaxX, bb.Max.y), true); ImVec2 backup_cursor_max_pos = window->DC.CursorMaxPos; ItemSize(bb.GetSize(), style.FramePadding.y); From bbd0af72565c12322ce4b3864d030991dc0e698f Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 9 Apr 2026 19:38:15 +0200 Subject: [PATCH 15/23] Multi-Select: Box-Select+Tables: fixed using BeginMultiSelect() before table layout is locked. (#8250) --- docs/CHANGELOG.txt | 2 ++ imgui_tables.cpp | 2 +- imgui_widgets.cpp | 6 +++++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index e18f5a996..ab05e516a 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -72,6 +72,8 @@ Other Changes: and ImGuiSelectionUserData is technically opaque storage. (#7994, #1861) (we will probably bring this back as a minor optimization if we have a way to for user to tell us ImGuiSelectionUserData are indices) + - Box-Select, Tables: fixed an issue when calling `BeginMultiSelect()` in a table + before layout has been locked (first row or headers row submitted). (#8250) - Fonts: - imgui_freetype: add FreeType headers & compiled version in 'About Dear ImGui' details. - Clipper: diff --git a/imgui_tables.cpp b/imgui_tables.cpp index d06fe1e7b..c311c9040 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -1647,7 +1647,7 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, flo ImGuiTable* table = g.CurrentTable; IM_ASSERT_USER_ERROR_RET(table != NULL, "Call should only be done while in BeginTable() scope!"); IM_ASSERT_USER_ERROR_RET(table->DeclColumnsCount < table->ColumnsCount, "TableSetupColumn(): called too many times!"); - IM_ASSERT_USER_ERROR_RET(table->IsLayoutLocked == false, "TableSetupColumn(): need to call before first row!"); + IM_ASSERT_USER_ERROR_RET(table->IsLayoutLocked == false, "TableSetupColumn(): need to call before first row!"); // Table layout is locked when submitting a row or when calling BeginMultiSelect() with box-select. IM_ASSERT((flags & ImGuiTableColumnFlags_StatusMask_) == 0 && "Illegal to pass StatusMask values to TableSetupColumn()"); ImGuiTableColumn* column = &table->Columns[table->DeclColumnsCount]; diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index e94dd3e87..0d1a178c5 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -7958,8 +7958,12 @@ ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, int sel // FIXME: Workaround to the fact we override CursorMaxPos, meaning size measurement are lost. (#8250) // They should perhaps be stacked properly? if (ImGuiTable* table = g.CurrentTable) - if (table->CurrentColumn != -1) + { + if (!table->IsLayoutLocked) + TableUpdateLayout(table); + else if (table->CurrentColumn != -1) TableEndCell(table); // This is currently safe to call multiple time. If that properly is lost we can extract the "save measurement" part of it. + } // FIXME: BeginFocusScope() const ImGuiID id = window->IDStack.back(); From 3cd86830613e5a48f0334bc58f26757590d1d501 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 13 Apr 2026 16:45:18 +0200 Subject: [PATCH 16/23] Fonts: assert when using MergeMode with an explicit size over a target font with an implicit size, as scale factor are likely erroneous. (#9361) --- docs/CHANGELOG.txt | 2 ++ imgui.h | 1 + imgui_draw.cpp | 13 ++++++++++++- misc/freetype/imgui_freetype.cpp | 3 ++- 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index ab05e516a..739a19250 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -76,6 +76,8 @@ Other Changes: before layout has been locked (first row or headers row submitted). (#8250) - Fonts: - imgui_freetype: add FreeType headers & compiled version in 'About Dear ImGui' details. + - Assert when using MergeMode with an explicit size over a target font with an implicit + size, as scale factor are likely erroneous. (#9361) - Clipper: - Improved error reporting when misusing the clipper inside a table (prioritize reporting the common clipper error over a table sanity check assert). (#9350) diff --git a/imgui.h b/imgui.h index e73b5b0d3..fb30f3624 100644 --- a/imgui.h +++ b/imgui.h @@ -3861,6 +3861,7 @@ enum ImFontFlags_ ImFontFlags_NoLoadError = 1 << 1, // Disable throwing an error/assert when calling AddFontXXX() with missing file/data. Calling code is expected to check AddFontXXX() return value. ImFontFlags_NoLoadGlyphs = 1 << 2, // [Internal] Disable loading new glyphs. ImFontFlags_LockBakedSizes = 1 << 3, // [Internal] Disable loading new baked sizes, disable garbage collecting current ones. e.g. if you want to lock a font to a single size. Important: if you use this to preload given sizes, consider the possibility of multiple font density used on Retina display. + ImFontFlags_ImplicitRefSize = 1 << 4, // [Internal] Reference size was not set explicitly. }; // Font runtime data and rendering diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 657e1c512..21e368a43 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3039,7 +3039,7 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg_in) } else { - IM_ASSERT(Fonts.Size > 0 && "Cannot use MergeMode for the first font"); // When using MergeMode make sure that a font has already been added before. + IM_ASSERT(Fonts.Size > 0 && "Cannot use MergeMode for the first font!"); // When using MergeMode make sure that a font has already been added before. font = font_cfg_in->DstFont ? font_cfg_in->DstFont : Fonts.back(); ImFontAtlasFontDiscardBakes(this, font, 0); // Need to discard bakes if the font was already used, because baked->FontLoaderDatas[] will change size. (#9162) } @@ -3067,6 +3067,11 @@ ImFont* ImFontAtlas::AddFont(const ImFontConfig* font_cfg_in) IM_ASSERT(font_cfg->FontLoader->FontBakedLoadGlyph != NULL); IM_ASSERT(font_cfg->FontLoader->LoaderInit == NULL && font_cfg->FontLoader->LoaderShutdown == NULL); // FIXME-NEWATLAS: Unsupported yet. } + // | Target w/ Implicit RefSize | Target w/ Explicit RefSize | + // Adding w/ Implicit RefSize: | OK (same scale) | OK (same scale) | + // Adding w/ Explicit RefSize: | KO | OK (custom scale) | + if (font_cfg_in->MergeMode && font_cfg_in->SizePixels > 0) + IM_ASSERT((font->Flags & ImFontFlags_ImplicitRefSize) == 0 && "Cannot use MergeMode with an explicit reference size when the destination font used an implicit reference size!"); IM_ASSERT(font_cfg->FontLoaderData == NULL); if (!ImFontAtlasFontSourceInit(this, font_cfg)) @@ -3134,7 +3139,10 @@ ImFont* ImFontAtlas::AddFontDefaultBitmap(const ImFontConfig* font_cfg_template) if (!font_cfg_template) font_cfg.PixelSnapH = true; // Prevents sub-integer scaling factors at lower-level layers. if (font_cfg.SizePixels <= 0.0f) + { font_cfg.SizePixels = 13.0f; // This only serves (1) as a reference for GlyphOffset.y setting and (2) as a default for pre-1.92 backend. + font_cfg.Flags |= ImFontFlags_ImplicitRefSize; + } if (font_cfg.Name[0] == '\0') ImFormatString(font_cfg.Name, IM_COUNTOF(font_cfg.Name), "ProggyClean.ttf"); font_cfg.EllipsisChar = (ImWchar)0x0085; @@ -3159,7 +3167,10 @@ ImFont* ImFontAtlas::AddFontDefaultVector(const ImFontConfig* font_cfg_template) if (!font_cfg_template) font_cfg.PixelSnapH = true; // Precisely match ProggyClean, but prevents sub-integer scaling factors at lower-level layers. if (font_cfg.SizePixels <= 0.0f) + { font_cfg.SizePixels = 13.0f; + font_cfg.Flags |= ImFontFlags_ImplicitRefSize; + } if (font_cfg.Name[0] == '\0') ImFormatString(font_cfg.Name, IM_COUNTOF(font_cfg.Name), "ProggyForever.ttf"); font_cfg.ExtraSizeScale *= 1.015f; // Match ProggyClean diff --git a/misc/freetype/imgui_freetype.cpp b/misc/freetype/imgui_freetype.cpp index 76c0ac9bc..3c817d20f 100644 --- a/misc/freetype/imgui_freetype.cpp +++ b/misc/freetype/imgui_freetype.cpp @@ -428,8 +428,9 @@ static bool ImGui_ImplFreeType_FontBakedInit(ImFontAtlas* atlas, ImFontConfig* s { IM_UNUSED(atlas); float size = baked->Size; + const float ref_size = baked->OwnerFont->Sources[0]->SizePixels; if (src->MergeMode && src->SizePixels != 0.0f) - size *= (src->SizePixels / baked->OwnerFont->Sources[0]->SizePixels); + size *= (src->SizePixels / ref_size); size *= src->ExtraSizeScale; ImGui_ImplFreeType_FontSrcData* bd_font_data = (ImGui_ImplFreeType_FontSrcData*)src->FontLoaderData; From ed4dd679f1a103f01094a7f864ae74b7758de88a Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 14 Apr 2026 16:39:08 +0200 Subject: [PATCH 17/23] Fixed vertical scrollbar top coordinates when using thick borders on windows with no title bar and no menu bar. (#9366) --- 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 739a19250..5a3051551 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -74,6 +74,9 @@ Other Changes: user to tell us ImGuiSelectionUserData are indices) - Box-Select, Tables: fixed an issue when calling `BeginMultiSelect()` in a table before layout has been locked (first row or headers row submitted). (#8250) +- Style: + - Fixed vertical scrollbar top coordinates when using thick borders on windows + with no title bar and no menu bar. (#9366) - Fonts: - imgui_freetype: add FreeType headers & compiled version in 'About Dear ImGui' details. - Assert when using MergeMode with an explicit size over a target font with an implicit diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 0d1a178c5..9356e268b 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -977,7 +977,7 @@ ImRect ImGui::GetWindowScrollbarRect(ImGuiWindow* window, ImGuiAxis axis) const float scrollbar_size = window->ScrollbarSizes[axis ^ 1]; // (ScrollbarSizes.x = width of Y scrollbar; ScrollbarSizes.y = height of X scrollbar) IM_ASSERT(scrollbar_size >= 0.0f); const float border_size = IM_ROUND(window->WindowBorderSize * 0.5f); - const float border_top = (window->Flags & ImGuiWindowFlags_MenuBar) ? IM_ROUND(g.Style.FrameBorderSize * 0.5f) : 0.0f; + const float border_top = (window->Flags & ImGuiWindowFlags_MenuBar) ? IM_ROUND(g.Style.FrameBorderSize * 0.5f) : (window->Flags & ImGuiWindowFlags_NoTitleBar) ? border_size : 0; if (axis == ImGuiAxis_X) return ImRect(inner_rect.Min.x + border_size, ImMax(outer_rect.Min.y + border_size, outer_rect.Max.y - border_size - scrollbar_size), inner_rect.Max.x - border_size, outer_rect.Max.y - border_size); else From 4b80d409e721b096b2b26e5edbf575178bdb48f0 Mon Sep 17 00:00:00 2001 From: Andy Grundman Date: Mon, 13 Apr 2026 19:16:15 -0400 Subject: [PATCH 18/23] Backends: Metal: use a dedicated bufferCacheLock to avoid crashing when bufferCache is replaced by a new object while being used for synchronize(). (#9367) This also removes the dispatch onto main when adding the finished buffers back into the cache. This operation should be fine to run on any thread as long as it's inside the sync block. --- backends/imgui_impl_metal.mm | 19 ++++++++++--------- docs/CHANGELOG.txt | 4 +++- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/backends/imgui_impl_metal.mm b/backends/imgui_impl_metal.mm index 037782bf6..7d604e6a5 100644 --- a/backends/imgui_impl_metal.mm +++ b/backends/imgui_impl_metal.mm @@ -16,6 +16,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-04-14: Metal: use a dedicated bufferCacheLock to avoid crashing when bufferCache is replaced by a new object while being used for @synchronize(). (#9367) // 2026-04-03: Metal: avoid redundant vertex buffer bind in SetupRenderState. (#9343) // 2026-03-19: Fixed issue in ImGui_ImplMetal_RenderDrawData() if ImTextureID_Invalid is defined to be != 0, which became the default since 2026-03-12. (#9295, #9310) // 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown. @@ -78,6 +79,7 @@ @property (nonatomic, strong) FramebufferDescriptor* framebufferDescriptor; // framebuffer descriptor for current frame; transient @property (nonatomic, strong) NSMutableDictionary* renderPipelineStateCache; // pipeline cache; keyed on framebuffer descriptors @property (nonatomic, strong) NSMutableArray* bufferCache; +@property (nonatomic, strong) NSObject* bufferCacheLock; @property (nonatomic, assign) double lastBufferCachePurge; - (MetalBuffer*)dequeueReusableBufferOfLength:(NSUInteger)length device:(id)device; - (id)renderPipelineStateForFramebufferDescriptor:(FramebufferDescriptor*)descriptor device:(id)device; @@ -328,13 +330,11 @@ void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id MetalContext* sharedMetalContext = bd->SharedMetalContext; [commandBuffer addCompletedHandler:^(id) { - dispatch_async(dispatch_get_main_queue(), ^{ - @synchronized(sharedMetalContext.bufferCache) - { - [sharedMetalContext.bufferCache addObject:vertexBuffer]; - [sharedMetalContext.bufferCache addObject:indexBuffer]; - } - }); + @synchronized(sharedMetalContext.bufferCacheLock) + { + [sharedMetalContext.bufferCache addObject:vertexBuffer]; + [sharedMetalContext.bufferCache addObject:indexBuffer]; + } }]; } @@ -514,6 +514,7 @@ void ImGui_ImplMetal_DestroyDeviceObjects() { self.renderPipelineStateCache = [NSMutableDictionary dictionary]; self.bufferCache = [NSMutableArray array]; + self.bufferCacheLock = [[NSObject alloc] init]; _lastBufferCachePurge = GetMachAbsoluteTimeInSeconds(); } return self; @@ -521,9 +522,9 @@ void ImGui_ImplMetal_DestroyDeviceObjects() - (MetalBuffer*)dequeueReusableBufferOfLength:(NSUInteger)length device:(id)device { - uint64_t now = GetMachAbsoluteTimeInSeconds(); + double now = GetMachAbsoluteTimeInSeconds(); - @synchronized(self.bufferCache) + @synchronized(self.bufferCacheLock) { // Purge old buffers that haven't been useful for a while if (now - self.lastBufferCachePurge > 1.0) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 5a3051551..05dd294e1 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -89,8 +89,10 @@ Other Changes: - Misc: - Minor optimization: reduce redudant label scanning in common widgets. - Backends: - - Metal: avoid redundant vertex buffer bind in SetupRenderState, which leads + - Metal: avoid redundant vertex buffer bind in `SetupRenderState()`, which leads to validation issue. (#9343) [@Hunam6] + - Metal: use a dedicated `bufferCacheLock` to avoid crashing when `bufferCache` is + replaced by a new object while being used for `@synchronize()`. (#9367) [@andygrundman] ----------------------------------------------------------------------- From 6fb74f38afd76bc5ab46e3f208d963e29676e7da Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 15 Apr 2026 14:22:42 +0200 Subject: [PATCH 19/23] Demo: Assets Browser: make default icon size font dependant. --- imgui_demo.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 461b2cd83..338a55cb3 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -10724,7 +10724,7 @@ struct ExampleAssetsBrowser bool AllowBoxSelect = true; // Will set ImGuiMultiSelectFlags_BoxSelect2d bool AllowBoxSelectInsideSelection = false; // Will set ImGuiMultiSelectFlags_SelectOnClickAlways bool AllowDragUnselected = false; // Will set ImGuiMultiSelectFlags_SelectOnClickRelease - float IconSize = 32.0f; + float IconSize = 0; int IconSpacing = 10; int IconHitSpacing = 4; // Increase hit-spacing if you want to make it possible to clear or box-select from gaps. Some spacing is required to able to amend with Shift+box-select. Value is small in Explorer. bool StretchSpacing = true; @@ -10791,6 +10791,9 @@ struct ExampleAssetsBrowser void Draw(const char* title, bool* p_open) { + if (IconSize <= 0.0f) + IconSize = ImGui::CalcTextSize("99999").x; + ImGui::SetNextWindowSize(ImVec2(IconSize * 25, IconSize * 15), ImGuiCond_FirstUseEver); if (!ImGui::Begin(title, p_open, ImGuiWindowFlags_MenuBar)) { From 19753a30d81e8daca0b2392ae45e68907590de7d Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 15 Apr 2026 15:08:41 +0200 Subject: [PATCH 20/23] Multi-Select: Box-Select+Tables: fixed Column return value when UnClipRect is active. (#7994, #8250) Amend 6ce4b44 + bbd0af7. This probably should be redesigned to be more generic, e.g. move UnclipRect concept outside of Boxselect. --- imgui_internal.h | 1 + imgui_tables.cpp | 22 +++++++++++++++++----- imgui_widgets.cpp | 2 ++ 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index 85c9daabf..9bc0f9a50 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3544,6 +3544,7 @@ namespace ImGui IMGUI_API void TableUpdateLayout(ImGuiTable* table); IMGUI_API void TableUpdateBorders(ImGuiTable* table); IMGUI_API void TableUpdateColumnsWeightFromWidth(ImGuiTable* table); + IMGUI_API void TableApplyExternalUnclipRect(ImGuiTable* table, ImRect& rect); IMGUI_API void TableDrawBorders(ImGuiTable* table); IMGUI_API void TableDrawDefaultContextMenu(ImGuiTable* table, ImGuiTableFlags flags_for_section_to_display); IMGUI_API bool TableBeginContextMenuPopup(ImGuiTable* table); diff --git a/imgui_tables.cpp b/imgui_tables.cpp index c311c9040..c58694bc3 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -1152,7 +1152,6 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) column->ClipRect.Min.y = work_rect.Min.y; column->ClipRect.Max.x = column->MaxX; //column->WorkMaxX; column->ClipRect.Max.y = FLT_MAX; - ImRect clip_rect_unclipped = column->ClipRect; column->ClipRect.ClipWithFull(host_clip_rect); // Mark column as Clipped (not in sight) @@ -1170,9 +1169,6 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // Mark column as requesting output from user. Note that fixed + non-resizable sets are auto-fitting at all times and therefore always request output. column->IsRequestOutput = is_visible || column->AutoFitQueue != 0 || column->CannotSkipItemsQueue != 0; - ImGuiBoxSelectState* bs = &g.BoxSelectState; - if (!column->IsRequestOutput && bs->UnclipMode && bs->UnclipRect.Overlaps(clip_rect_unclipped)) - column->IsRequestOutput = true; // Mark column as SkipItems (ignoring all items/layout) // (table->HostSkipItems is a copy of inner_window->SkipItems before we cleared it above in Part 2) @@ -1319,14 +1315,30 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) table->InnerWindow->DecoInnerSizeY1 = table_instance->LastFrozenHeight; table_instance->LastFrozenHeight = 0.0f; - // Initial state ImGuiWindow* inner_window = table->InnerWindow; + ImGuiBoxSelectState* bs = &g.BoxSelectState; + if (bs->Window == inner_window && bs->UnclipMode) + TableApplyExternalUnclipRect(table, bs->UnclipRect); + + // Initial state if (table->Flags & ImGuiTableFlags_NoClip) table->DrawSplitter->SetCurrentChannel(inner_window->DrawList, TABLE_DRAW_CHANNEL_NOCLIP); else inner_window->DrawList->PushClipRect(inner_window->InnerClipRect.Min, inner_window->InnerClipRect.Max, false); // FIXME: use table->InnerClipRect? } +// When starting a BeginMultiSelect() after table has been layout we update IsRequestOutput fields. +void ImGui::TableApplyExternalUnclipRect(ImGuiTable* table, ImRect& rect) +{ + for (int column_n = 0; column_n < table->ColumnsCount; column_n++) + { + ImGuiTableColumn* column = &table->Columns[column_n]; + if (!column->IsRequestOutput) + if (rect.Overlaps(ImRect(column->MinX, table->WorkRect.Min.y, column->MaxX, FLT_MAX))) + column->IsRequestOutput = true; + } +} + // Process hit-testing on resizing borders. Actual size change will be applied in EndTable() // - Set table->HoveredColumnBorder with a short delay/timer to reduce visual feedback noise. void ImGui::TableUpdateBorders(ImGuiTable* table) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 9356e268b..f5b7e49fe 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -7848,6 +7848,8 @@ bool ImGui::BeginBoxSelect(const ImRect& scope_rect, ImGuiWindow* window, ImGuiI bs->UnclipRect = bs->BoxSelectRectPrev; // FIXME-OPT: UnclipRect X coordinates could be intersection of Prev and Curr rect on X axis. bs->UnclipRect.Add(bs->BoxSelectRectCurr); } + if (bs->UnclipMode && g.CurrentTable != NULL) + TableApplyExternalUnclipRect(g.CurrentTable, bs->UnclipRect); #ifdef IMGUI_DEBUG_BOXSELECT if (ms_flags & ImGuiMultiSelectFlags_BoxSelect2d) From 0e318a518299ba606dcb5e39cfb3da0a0aa8124c Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 16 Apr 2026 11:32:07 +0200 Subject: [PATCH 21/23] Backends: SDL2: Made ImGui_ImplSDL2_GetContentScaleXXX helpers return a minimum of 1.0f, as some Linux setup seems to report <1.0f value and this breaks scaling border size. (#9369) --- backends/imgui_impl_sdl2.cpp | 6 ++++++ docs/CHANGELOG.txt | 3 +++ imgui.cpp | 1 + 3 files changed, 10 insertions(+) diff --git a/backends/imgui_impl_sdl2.cpp b/backends/imgui_impl_sdl2.cpp index f578977c4..ebad76010 100644 --- a/backends/imgui_impl_sdl2.cpp +++ b/backends/imgui_impl_sdl2.cpp @@ -21,6 +21,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-04-16: Made ImGui_ImplSDL2_GetContentScaleForWindow(), ImGui_ImplSDL2_GetContentScaleForDisplay() helpers return a minimum of 1.0f, as some Linux setup seems to report <1.0f value and this breaks scaling border size. (#9369) // 2026-02-13: Inputs: systems other than X11 are back to starting mouse capture on mouse down (reverts 2025-02-26 change). Only X11 requires waiting for a drag by default (not ideal, but a better default for X11 users). Added ImGui_ImplSDL2_SetMouseCaptureMode() for X11 debugger users. (#3650, #6410, #9235) // 2026-01-15: Changed GetClipboardText() handler to return nullptr on error aka clipboard contents is not text. Consistent with other backends. (#9168) // 2025-09-24: Skip using the SDL_GetGlobalMouseState() state when one of our window is hovered, as the SDL_MOUSEMOTION data is reliable. Fix macOS notch mouse coordinates issue in fullscreen mode + better perf on X11. (#7919, #7786) @@ -748,6 +749,7 @@ float ImGui_ImplSDL2_GetContentScaleForWindow(SDL_Window* window) return ImGui_ImplSDL2_GetContentScaleForDisplay(SDL_GetWindowDisplayIndex(window)); } +// SDL_GetDisplayDPI() seems rather unreliable on Linux. float ImGui_ImplSDL2_GetContentScaleForDisplay(int display_index) { const char* sdl_driver = SDL_GetCurrentVideoDriver(); @@ -757,7 +759,11 @@ float ImGui_ImplSDL2_GetContentScaleForDisplay(int display_index) #if !defined(__APPLE__) && !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) float dpi = 0.0f; if (SDL_GetDisplayDPI(display_index, &dpi, nullptr, nullptr) == 0) + { + if (dpi < 96.0f) + dpi = 96.0f; return dpi / 96.0f; + } #endif #endif IM_UNUSED(display_index); diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 05dd294e1..baabbcbc0 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -93,6 +93,9 @@ Other Changes: to validation issue. (#9343) [@Hunam6] - Metal: use a dedicated `bufferCacheLock` to avoid crashing when `bufferCache` is replaced by a new object while being used for `@synchronize()`. (#9367) [@andygrundman] + - SDL2: made `ImGui_ImplSDL2_GetContentScaleForWindow()`/`ImGui_ImplSDL2_GetContentScaleForDisplay()` + helpers return a minimum of 1.0f, as some Linux setup seems to report <1.0f value + and this breaks scaling border size. (#9369) ----------------------------------------------------------------------- diff --git a/imgui.cpp b/imgui.cpp index 79e9a7808..66a4a18d2 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1542,6 +1542,7 @@ ImGuiStyle::ImGuiStyle() // Scale all spacing/padding/thickness values. Do not scale fonts. +// Consider not calling this if your initial scale factor if <1.0. // Important: This operation is lossy because we round all sizes to integer. If you need to change your scale multiples, call this over a freshly initialized ImGuiStyle structure rather than scaling multiple times. void ImGuiStyle::ScaleAllSizes(float scale_factor) { From 39e7bf5a08d48f5431778862d3fc70654099e214 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 16 Apr 2026 11:32:40 +0200 Subject: [PATCH 22/23] Separator(): enforce a minimum border size if style.SeparatorSize is 0.0f. (#9369) --- imgui.h | 4 ++-- imgui_widgets.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/imgui.h b/imgui.h index fb30f3624..d9f6a8cd1 100644 --- a/imgui.h +++ b/imgui.h @@ -2335,7 +2335,7 @@ struct ImGuiStyle ImGuiDir ColorButtonPosition; // Side of the color button in the ColorEdit4 widget (left/right). Defaults to ImGuiDir_Right. ImVec2 ButtonTextAlign; // Alignment of button text when button is larger than text. Defaults to (0.5f, 0.5f) (centered). ImVec2 SelectableTextAlign; // Alignment of selectable text. Defaults to (0.0f, 0.0f) (top-left aligned). It's generally important to keep this left-aligned if you want to lay multiple items on a same line. - float SeparatorSize; // Thickness of border in Separator() + float SeparatorSize; // Thickness of border in Separator(). Must be >= 1.0f. float SeparatorTextBorderSize; // Thickness of border in SeparatorText() ImVec2 SeparatorTextAlign; // Alignment of text within the separator. Defaults to (0.0f, 0.5f) (left aligned, center). ImVec2 SeparatorTextPadding; // Horizontal offset of text from each edge of the separator + spacing on other axis. Generally small values. .y is recommended to be == FramePadding.y. @@ -2365,7 +2365,7 @@ struct ImGuiStyle // Functions IMGUI_API ImGuiStyle(); - IMGUI_API void ScaleAllSizes(float scale_factor); // Scale all spacing/padding/thickness values. Do not scale fonts. + IMGUI_API void ScaleAllSizes(float scale_factor); // Scale all spacing/padding/thickness values. Do not scale fonts. See comments in definition. Consider not calling this if your initial scale factor if <1.0. // Obsolete names #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index f5b7e49fe..50fec659d 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -1728,7 +1728,7 @@ void ImGui::Separator() if (window->DC.CurrentColumns) flags |= ImGuiSeparatorFlags_SpanAllColumns; - SeparatorEx(flags, g.Style.SeparatorSize); + SeparatorEx(flags, ImMax(g.Style.SeparatorSize, 1.0f)); } void ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end, float extra_w) From ce855cada2f1b27e4f6dd15b54b7f6e6fc22784b Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 16 Apr 2026 15:07:56 +0200 Subject: [PATCH 23/23] Tables, Multi-Select: Fixed an issue using Multi-Select within a Table causing column width measurement to be invalid when trailing column contents is not submitted in the last row. (#9341, #8250) --- docs/CHANGELOG.txt | 2 ++ imgui_widgets.cpp | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index baabbcbc0..208c58656 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -59,6 +59,8 @@ Other Changes: - Child windows with only ImGuiChildFlags_AutoResizeY flag keep using the proportional default ItemWidth. (#9355) - Multi-Select: + - Fixed an issue using Multi-Select within a Table causing column width measurement to + be invalid when trailing column contents is not submitted in the last row. (#9341, #8250) - Box-Select: fixed an issue using ImGuiMultiSelectFlags_BoxSelect2d mode, where items out of view wouldn't be properly selected while scrolling while mouse cursor is hovering outside of selection scope. (#7994, #1861, #6518) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 50fec659d..bdec3440e 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -7917,6 +7917,7 @@ static ImRect CalcScopeRect(ImGuiMultiSelectTempData* ms, ImGuiWindow* window) if (ms->Flags & ImGuiMultiSelectFlags_ScopeRect) { // Warning: this depends on CursorMaxPos so it means to be called by EndMultiSelect() only + // This probably doesn't work inside a table as there are ample ambiguities related to exact time of calling BeginMultiSelect()/EndMultiSelect(). return ImRect(ms->ScopeRectMin, ImMax(window->DC.CursorMaxPos, ms->ScopeRectMin)); } else @@ -7974,7 +7975,7 @@ ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, int sel ms->Flags = flags; ms->IsFocused = (ms->FocusScopeId == g.NavFocusScopeId); ms->BackupCursorMaxPos = window->DC.CursorMaxPos; - ms->ScopeRectMin = window->DC.CursorMaxPos = window->DC.CursorPos; + ms->ScopeRectMin = window->DC.CursorMaxPos = window->DC.CursorPos; // CalcScopeRect() for ImGuiMultiSelectFlags_ScopeRect will measure in EndMultiSelect(). PushFocusScope(ms->FocusScopeId); if (flags & ImGuiMultiSelectFlags_ScopeWindow) // Mark parent child window as navigable into, with highlight. Assume user will always submit interactive items. window->DC.NavLayersActiveMask |= 1 << ImGuiNavLayer_Main; @@ -8129,10 +8130,13 @@ ImGuiMultiSelectIO* ImGui::EndMultiSelect() if (ms->Flags & ImGuiMultiSelectFlags_NavWrapX) { IM_ASSERT(ms->Flags & ImGuiMultiSelectFlags_ScopeWindow); // Only supported at window scope - ImGui::NavMoveRequestTryWrapping(ImGui::GetCurrentWindow(), ImGuiNavMoveFlags_WrapX); + NavMoveRequestTryWrapping(GetCurrentWindow(), ImGuiNavMoveFlags_WrapX); } // Unwind + if (ImGuiTable* table = g.CurrentTable) + if (table->IsInsideRow) + TableEndRow(table); window->DC.CursorMaxPos = ImMax(ms->BackupCursorMaxPos, window->DC.CursorMaxPos); PopFocusScope();