From 148d128f6e25cb7ce7d94294cdeb57173218cd18 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 8 Sep 2026 16:46:08 +0200 Subject: [PATCH 1/5] Mixed Value: use ImGuiCol_TextMixedValue (= ImGuiCol_TextDisabled). Set MixedValueLabel to null to display original value. (#5518, #5677, #6865) --- imgui.cpp | 1 + imgui_internal.h | 4 +++- imgui_widgets.cpp | 35 ++++++++++++++++++++++++++--------- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 2ef75ef1f..105b6c2cf 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -3945,6 +3945,7 @@ void ImGui::RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end // Effectively as this is called from widget doing their own coarse clipping it's not very valuable presently. Next time function will take // better advantage of the render function taking size into account for coarse clipping. void ImGui::RenderTextClippedEx(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_display_end, const ImVec2* text_size_if_known, const ImVec2& align, const ImRect* clip_rect) +// DOES NOT CALL LogRenderedText(), unlike RenderTextClipped!!! { // Perform CPU side clipping for single clipped element to avoid using scissor state ImVec2 pos = pos_min; diff --git a/imgui_internal.h b/imgui_internal.h index ca9ef8266..44e78f623 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -932,6 +932,8 @@ struct ImFontStackData // [SECTION] Style support //----------------------------------------------------------------------------- +#define ImGuiCol_TextMixedValue ImGuiCol_TextDisabled + struct ImGuiStyleVarInfo { ImU32 Count : 8; // 1+ @@ -2304,7 +2306,7 @@ struct ImGuiContext ImVec2 WheelingAxisAvg; // Item/widgets state and tracking information - const char* MixedValueLabel; // Value replacement when displaying a mixed value. Default to "-" (Unreal uses "Multiple values", Unity uses "---"). May be interpreted as a format: must not contain single %. + const char* MixedValueLabel; // Value replacement when displaying a mixed value. Default to "-" (Unreal uses "Multiple values", Unity uses "---"). May be interpreted as a format: must not contain single %. Set to NULL to display original value. ImGuiID DebugDrawIdConflictsId; // Set when we detect multiple items with the same identifier ImGuiID DebugHookIdInfoId; // Will call core hooks: DebugHookIdInfo() from GetID functions, used by ID Stack Tool [next HoveredId/ActiveId to not pull in an extra cache-line] ImGuiID HoveredId; // Hovered widget, filled during the frame diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 706849773..1cc5eb2b3 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -2817,12 +2817,16 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, MarkItemEdited(id); // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. - const char* format_for_display = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) ? g.MixedValueLabel : format; + const bool is_mixed = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) != 0; char value_buf[64]; - const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, format_for_display); + const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, (is_mixed && g.MixedValueLabel != NULL) ? g.MixedValueLabel : format); if (g.LogEnabled) LogSetNextTextDecoration("{", "}"); + if (is_mixed) + PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextMixedValue]); RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f)); + if (is_mixed) + PopStyleColor(); if (label_size.x > 0.0f) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label, label_end, false); @@ -3425,12 +3429,16 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding); // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. - const char* format_for_display = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) ? g.MixedValueLabel : format; + const bool is_mixed = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) != 0; char value_buf[64]; - const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, format_for_display); + const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, (is_mixed && g.MixedValueLabel != NULL) ? g.MixedValueLabel : format); if (g.LogEnabled) LogSetNextTextDecoration("{", "}"); + if (is_mixed) + PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextMixedValue]); RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f)); + if (is_mixed) + PopStyleColor(); if (label_size.x > 0.0f) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label, label_end, false); @@ -3580,10 +3588,15 @@ bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType d // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. // For the vertical slider we allow centered text to overlap the frame padding - const char* format_for_display = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) ? g.MixedValueLabel : format; + const bool is_mixed = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) != 0; char value_buf[64]; - const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, format_for_display); + const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, (is_mixed && g.MixedValueLabel != NULL) ? g.MixedValueLabel : format); + if (is_mixed) + PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextMixedValue]); 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 (is_mixed) + PopStyleColor(); + if (label_size.x > 0.0f) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label, label_end, false); @@ -5560,7 +5573,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (is_password && !is_displaying_hint) PushPasswordFont(); } - if (is_mixed && g.ActiveId != id && apply_new_text == NULL) + const bool is_displaying_mixed = is_mixed && g.ActiveId != id && apply_new_text == NULL; + if (is_displaying_mixed && g.MixedValueLabel != NULL) { buf_display = g.MixedValueLabel; buf_display_end = buf_display + strlen(g.MixedValueLabel); @@ -5609,7 +5623,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ ImVec2 draw_scroll; // Render text. We currently only render selection when the widget is active or while scrolling. - const ImU32 text_col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text); + const ImU32 text_col = GetColorU32(is_displaying_mixed ? ImGuiCol_TextMixedValue : is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text); if (render_cursor || render_selection) { // Render text (with cursor and selection) @@ -6583,7 +6597,10 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl if (is_mixed) { window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); - RenderTextClipped(ImVec2(bb.Min.x, bb.Min.y + g.Style.FramePadding.y), bb.Max, g.MixedValueLabel, NULL, NULL, ImVec2(0.5f, 0.0f)); + PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextMixedValue]); + if (g.MixedValueLabel != NULL) + RenderTextClipped(ImVec2(bb.Min.x, bb.Min.y + g.Style.FramePadding.y), bb.Max, g.MixedValueLabel, NULL, NULL, ImVec2(0.5f, 0.0f)); + PopStyleColor(); } else if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col_rgb.w < 1.0f) { From 32c3f6095a90d7f45f6a6a7ef43ebee0239490df Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 8 Sep 2026 17:33:42 +0200 Subject: [PATCH 2/5] BeginComboPreview: set/restore WorkRect.Max.x. (#1658, #4168) --- imgui_internal.h | 2 ++ imgui_widgets.cpp | 13 ++++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index 44e78f623..083bc01d6 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1192,6 +1192,8 @@ struct IMGUI_API ImGuiComboPreviewData ImVec2 BackupCursorMaxPos; ImVec2 BackupCursorPosPrevLine; float BackupPrevLineTextBaseOffset; + float BackupWorkRectMaxX; + float BackupContentRectMaxX; ImGuiLayoutType BackupLayout; ImGuiComboPreviewData() { memset((void*)this, 0, sizeof(*this)); } diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 1cc5eb2b3..ea62d2f71 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -2093,8 +2093,10 @@ void ImGui::EndCombo() EndPopup(); } -// Call directly after the BeginCombo/EndCombo block. The preview is designed to only host non-interactive elements -// (Experimental, see GitHub issues: #1658, #4168) +// Call directly after the BeginCombo() call. If you use nested combo, make sure you end wait for EndCombo() to call this! +// The preview is designed to only host non-interactive elements. +// - [BETA] See GitHub issues: #1658, #4168. +// - Not compatible with ImGuiComboFlags_WidthFitPreview. bool ImGui::BeginComboPreview() { ImGuiContext& g = *GImGui; @@ -2113,6 +2115,9 @@ bool ImGui::BeginComboPreview() preview_data->BackupCursorPosPrevLine = window->DC.CursorPosPrevLine; preview_data->BackupPrevLineTextBaseOffset = window->DC.PrevLineTextBaseOffset; preview_data->BackupLayout = window->DC.LayoutType; + preview_data->BackupWorkRectMaxX = window->WorkRect.Max.x; + preview_data->BackupContentRectMaxX = window->ContentRegionRect.Max.x; + window->WorkRect.Max.x = window->ContentRegionRect.Max.x = preview_data->PreviewRect.Max.x - g.Style.FramePadding.x; window->DC.CursorPos = preview_data->PreviewRect.Min + g.Style.FramePadding; window->DC.CursorMaxPos = window->DC.CursorPos; window->DC.LayoutType = ImGuiLayoutType_Horizontal; @@ -2138,9 +2143,11 @@ void ImGui::EndComboPreview() } PopClipRect(); window->DC.CursorPos = preview_data->BackupCursorPos; - window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, preview_data->BackupCursorMaxPos); + window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, preview_data->BackupCursorMaxPos); // No need to do the same with IdealMaxPos window->DC.CursorPosPrevLine = preview_data->BackupCursorPosPrevLine; window->DC.PrevLineTextBaseOffset = preview_data->BackupPrevLineTextBaseOffset; + window->WorkRect.Max.x = preview_data->BackupWorkRectMaxX; + window->ContentRegionRect.Max.x = preview_data->BackupContentRectMaxX; window->DC.LayoutType = preview_data->BackupLayout; window->DC.IsSameLine = false; preview_data->PreviewRect = ImRect(); From db5b735a5d1c9f766d2df43e419682ecffd8c3fa Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 8 Sep 2026 17:52:28 +0200 Subject: [PATCH 3/5] (Breaking) BeginComboPreview: removed ImGuiComboFlags_CustomPreview: now unnecessary + better detect issues (#1658, #4168) --- imgui_internal.h | 15 +++++++-------- imgui_widgets.cpp | 29 +++++++++++++++-------------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index 083bc01d6..6877b04ce 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1084,12 +1084,6 @@ enum ImGuiButtonFlagsPrivate_ //ImGuiButtonFlags_NoKeyModifiers = ImGuiButtonFlags_NoKeyModsAllowed, // Renamed in 1.91.4 }; -// Extend ImGuiComboFlags_ -enum ImGuiComboFlagsPrivate_ -{ - ImGuiComboFlags_CustomPreview = 1 << 20, // enable BeginComboPreview() -}; - // Extend ImGuiSliderFlags_ enum ImGuiSliderFlagsPrivate_ { @@ -1184,6 +1178,10 @@ enum ImGuiPlotType ImGuiPlotType_Histogram, }; +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +//ImGuiComboFlags_CustomPreview = 1 << 20, // enable BeginComboPreview() // [Obsoleted in 1.93.0] Unnecessary: can now use BeginComboPreview() without a flag. +#endif + // Storage data for BeginComboPreview()/EndComboPreview() struct IMGUI_API ImGuiComboPreviewData { @@ -1194,7 +1192,8 @@ struct IMGUI_API ImGuiComboPreviewData float BackupPrevLineTextBaseOffset; float BackupWorkRectMaxX; float BackupContentRectMaxX; - ImGuiLayoutType BackupLayout; + ImGuiLayoutType BackupLayout : 8; + int WithinPreview : 2; ImGuiComboPreviewData() { memset((void*)this, 0, sizeof(*this)); } }; @@ -3539,7 +3538,7 @@ namespace ImGui // Combos IMGUI_API bool BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags flags); - IMGUI_API bool BeginComboPreview(); + IMGUI_API bool BeginComboPreview(); // Submit preview contents for the *last* BeginCombo() call, to display contents that's more than just a text label. IMGUI_API void EndComboPreview(); // Keyboard/Gamepad Navigation diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index ea62d2f71..643462278 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -1951,7 +1951,7 @@ bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboF const ImGuiID id = window->GetID(label); IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together if (flags & ImGuiComboFlags_WidthFitPreview) - IM_ASSERT((flags & (ImGuiComboFlags_NoPreview | (ImGuiComboFlags)ImGuiComboFlags_CustomPreview)) == 0); + IM_ASSERT((flags & ImGuiComboFlags_NoPreview) == 0); const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight(); const char* label_end = FindRenderedTextEnd(label); @@ -1991,13 +1991,8 @@ bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboF } RenderFrameBorder(bb.Min, bb.Max, style.FrameRounding); - // Custom preview - if (flags & ImGuiComboFlags_CustomPreview) - { - g.ComboPreviewData.PreviewRect = ImRect(bb.Min.x, bb.Min.y, value_x2, bb.Max.y); - IM_ASSERT(preview_value == NULL || preview_value[0] == 0); - preview_value = NULL; - } + // Store geometry for BeginComboPreview() - only necessary when visible. + g.ComboPreviewData.PreviewRect = (flags & ImGuiComboFlags_NoPreview) ? ImRect() : ImRect(bb.Min.x, bb.Min.y, value_x2, bb.Max.y); // Render preview and label if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview)) @@ -2093,21 +2088,26 @@ void ImGui::EndCombo() EndPopup(); } -// Call directly after the BeginCombo() call. If you use nested combo, make sure you end wait for EndCombo() to call this! -// The preview is designed to only host non-interactive elements. +// Submit preview contents for the *last* BeginCombo() call, to display contents that's more than just a text label. // - [BETA] See GitHub issues: #1658, #4168. +// - The preview is designed to only host non-interactive elements. +// - If you use nested combos, make sure you call this right after BeginCombo() and not after EndCombo(), in order to target the correct one. // - Not compatible with ImGuiComboFlags_WidthFitPreview. +// - 2026-09-08 (1.93.0): removed ImGuiComboFlags_CustomPreview. You can use BeginComboPreview()/EndComboPreview() without an extra flag. bool ImGui::BeginComboPreview() { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; - ImGuiComboPreviewData* preview_data = &g.ComboPreviewData; if (window->SkipItems || !(g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible)) return false; - IM_ASSERT(g.LastItemData.Rect.Min.x == preview_data->PreviewRect.Min.x && g.LastItemData.Rect.Min.y == preview_data->PreviewRect.Min.y); // Didn't call after BeginCombo/EndCombo block or forgot to pass ImGuiComboFlags_CustomPreview flag? - if (!window->ClipRect.Overlaps(preview_data->PreviewRect)) // Narrower test (optional) + ImGuiComboPreviewData* preview_data = &g.ComboPreviewData; + if (!window->ClipRect.Overlaps(preview_data->PreviewRect) || preview_data->PreviewRect.GetWidth() <= 0.0f) // Narrower test (optional) + handle _NoPreview return false; + IM_ASSERT_USER_ERROR_RETV(g.LastItemData.Rect.Min.x == preview_data->PreviewRect.Min.x && g.LastItemData.Rect.Min.y == preview_data->PreviewRect.Min.y, false, + "Call BeginComboPreview() after BeginCombo(), not after EndCombo()!"); // Calling after EndCombo() works only if you don't nest combos. + IM_ASSERT_USER_ERROR_RETV(preview_data->WithinPreview == false, false, + "Cannot recurse BeginComboPreview(): call EndComboPreview() before opening another combo."); // FIXME: This could be contained in a PushWorkRect() api preview_data->BackupCursorPos = window->DC.CursorPos; @@ -2115,6 +2115,7 @@ bool ImGui::BeginComboPreview() preview_data->BackupCursorPosPrevLine = window->DC.CursorPosPrevLine; preview_data->BackupPrevLineTextBaseOffset = window->DC.PrevLineTextBaseOffset; preview_data->BackupLayout = window->DC.LayoutType; + preview_data->WithinPreview = true; preview_data->BackupWorkRectMaxX = window->WorkRect.Max.x; preview_data->BackupContentRectMaxX = window->ContentRegionRect.Max.x; window->WorkRect.Max.x = window->ContentRegionRect.Max.x = preview_data->PreviewRect.Max.x - g.Style.FramePadding.x; @@ -2150,7 +2151,7 @@ void ImGui::EndComboPreview() window->ContentRegionRect.Max.x = preview_data->BackupContentRectMaxX; window->DC.LayoutType = preview_data->BackupLayout; window->DC.IsSameLine = false; - preview_data->PreviewRect = ImRect(); + preview_data->WithinPreview = false; } // Getter for the old Combo() API: const char*[] From ea6d21687bec144dd7aee0f4db37f7c61a8799bb Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 8 Sep 2026 21:52:23 +0200 Subject: [PATCH 4/5] BeginComboPreview(): fixed issues with nesting. Previous comments were wrong. (#1658, #4168) --- imgui_internal.h | 6 ++++-- imgui_widgets.cpp | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index 6877b04ce..35716ba7c 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -189,6 +189,7 @@ struct ImGuiTypingSelectRequest; // Storage for GetTypingSelectRequest() (aim struct ImGuiWindow; // Storage for one window struct ImGuiWindowTempData; // Temporary storage for one window (that's the data which in theory we could ditch at the end of the frame, in practice we currently keep it for each window) struct ImGuiWindowSettings; // Storage for a window .ini settings (we keep one of those even if the actual window wasn't instanced during this session) +struct ImGuiWindowStackData; // Storage for each window pushed into the stack // Enumerations // Use your programming IDE "Go to definition" facility on the names of the center columns to find the actual flags/enum lists. @@ -1459,7 +1460,7 @@ struct IMGUI_API ImGuiErrorRecoveryState ImGuiErrorRecoveryState() { memset((void*)this, 0, sizeof(*this)); } }; -// Data saved for each window pushed into the stack +// Storage for each window pushed into the stack. struct ImGuiWindowStackData { ImGuiWindow* Window; @@ -1467,6 +1468,7 @@ struct ImGuiWindowStackData ImGuiErrorRecoveryState StackSizesInBegin; // Store size of various stacks for asserting bool DisabledOverrideReenable; // Non-child window override disabled flag float DisabledOverrideReenableAlphaBackup; + ImRect ParentLastComboPreviewRect; }; struct ImGuiShrinkWidthItem @@ -3538,7 +3540,7 @@ namespace ImGui // Combos IMGUI_API bool BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags flags); - IMGUI_API bool BeginComboPreview(); // Submit preview contents for the *last* BeginCombo() call, to display contents that's more than just a text label. + IMGUI_API bool BeginComboPreview(); // Submit preview contents a combo. Call this after EndCombo() to display contents that's more than just a text label. IMGUI_API void EndComboPreview(); // Keyboard/Gamepad Navigation diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 643462278..f42b6482f 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -2074,6 +2074,7 @@ bool ImGui::BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags return false; } g.BeginComboDepth++; + g.CurrentWindowStack.back().ParentLastComboPreviewRect = g.ComboPreviewData.PreviewRect; return true; } @@ -2081,6 +2082,7 @@ void ImGui::EndCombo() { ImGuiContext& g = *GImGui; g.BeginComboDepth--; + g.ComboPreviewData.PreviewRect = g.CurrentWindowStack.back().ParentLastComboPreviewRect; char name[16]; ImFormatString(name, IM_COUNTOF(name), "##Combo_%02d", g.BeginComboDepth); // FIXME: Move those to helpers? if (strcmp(g.CurrentWindow->Name, name) != 0) @@ -2088,10 +2090,10 @@ void ImGui::EndCombo() EndPopup(); } -// Submit preview contents for the *last* BeginCombo() call, to display contents that's more than just a text label. +// Submit preview contents for BeginCombo()/EndCombo(), to display contents that's more than just a text label. // - [BETA] See GitHub issues: #1658, #4168. +// - Make sure you call this after EndCombo(). // - The preview is designed to only host non-interactive elements. -// - If you use nested combos, make sure you call this right after BeginCombo() and not after EndCombo(), in order to target the correct one. // - Not compatible with ImGuiComboFlags_WidthFitPreview. // - 2026-09-08 (1.93.0): removed ImGuiComboFlags_CustomPreview. You can use BeginComboPreview()/EndComboPreview() without an extra flag. bool ImGui::BeginComboPreview() From e8602501bb900fc7c965e65b88fef30b2c80aff1 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 9 Sep 2026 20:44:34 +0200 Subject: [PATCH 5/5] Mixed Value: various fixes. (#5518, #5677, #6865) - With MixedValue+LiveEdit: a single edit that ends up with same output reports as edited. - Standardize path sin TempInputscalar(), InputScalar(), InputTextEx(). - Refer to changes in imgui_test_suite, in particular "widgets_datatype_1", "widgets_status_liveedit_off", --- imgui.h | 14 ++++++++++++-- imgui_demo.cpp | 4 ++++ imgui_internal.h | 3 ++- imgui_widgets.cpp | 15 +++++++++++---- 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/imgui.h b/imgui.h index 1329bad3e..fe4f82d58 100644 --- a/imgui.h +++ b/imgui.h @@ -30,7 +30,7 @@ // Library Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345') #define IMGUI_VERSION "1.93.0 WIP" -#define IMGUI_VERSION_NUM 19296 +#define IMGUI_VERSION_NUM 19297 #define IMGUI_HAS_TABLE // Added BeginTable() - from IMGUI_VERSION_NUM >= 18000 #define IMGUI_HAS_TEXTURES // Added ImGuiBackendFlags_RendererHasTextures - from IMGUI_VERSION_NUM >= 19198 @@ -1258,7 +1258,6 @@ enum ImGuiItemFlags_ ImGuiItemFlags_AutoClosePopups = 1 << 4, // true // MenuItem()/Selectable() automatically close their parent popup window. ImGuiItemFlags_AllowDuplicateId = 1 << 5, // false // Allow submitting an item with the same identifier as an item already submitted this frame without triggering a warning tooltip if io.ConfigDebugHighlightIdConflicts is set. ImGuiItemFlags_Disabled = 1 << 6, // false // [Internal] Disable interactions. DOES NOT affect visuals. This is used by BeginDisabled()/EndDisabled() and only provided here so you can read back via GetItemFlags(). - ImGuiItemFlags_MixedValue = 1 << 9, // false // [BETA] Represent a mixed/indeterminate value. Replace value label with "-" and apply edits on validation. Only supported by some widgets: Checkbox, RadioButton, Sliders and Drags. //--------------------------------------------------------------------------------- // LiveEdit refers to applying edits to backing variables _while_ typing a value using the keyboard. @@ -1272,6 +1271,17 @@ enum ImGuiItemFlags_ ImGuiItemFlags_LiveEditOnInputText = 1 << 7, // true // InputText: apply keyboard edits to backing value while typing. Otherwise, edits are applied when validating, tabbing out or losing focus. ImGuiItemFlags_LiveEditOnInputScalar = 1 << 8, // false // DragXXX, SliderXXX, InputScalar: apply keyboard edits to backing value while typing. Otherwise, edits are applied when validating, tabbing out or losing focus. ImGuiItemFlags_LiveEditOnInput = ImGuiItemFlags_LiveEditOnInputText | ImGuiItemFlags_LiveEditOnInputScalar, + + //--------------------------------------------------------------------------------- + // [BETA] MixedValue mode used to represent a mixed/indeterminate state, typically for multi-selection. + // - Replace value display with "-" or a custom label. + // - Enter key validation apply an edit and return true even if value hasn't changed (in order to apply to all). + // - Supported by selected widgets: Checkbox, RadioButton, Sliders, Drags, Inputs, Combo. + // - Note: InputText-side Undo cannot be reliably combined with MixedValue + LiveEdit On: + // - Both the initial edit and subsequent undo/revert will typically make your app code write to all backing objects. + // - If you use MixedMode and the simplest solution is to ensure LiveEdit is off but widgets where this applies. + //--------------------------------------------------------------------------------- + ImGuiItemFlags_MixedValue = 1 << 9, // false // [BETA] Represent a mixed/indeterminate value. Replace value label with "-" and apply edits on validation. }; // Flags for ImGui::InputText() diff --git a/imgui_demo.cpp b/imgui_demo.cpp index d75566170..7e0283bc2 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -2062,13 +2062,16 @@ static void DemoWindowWidgetsMixedValues() // This is designed for advanced property editors which are generally reusable and data-driven. HelpMarker("Using ImGuiItemFlags_MixedValue."); + static bool use_liveedit = false; static float items[3] = { 12.0f, 0.0f, 0.0f }; float* item_ref = &items[0]; + ImGui::Checkbox("ImGuiItemFlags_LiveEditOnInput", &use_liveedit); ImGui::SeparatorText("Scalar/Text Widgets"); const bool is_mixed = memcmp(&items[0], &items[1], sizeof(float)) != 0 || memcmp(&items[0], &items[2], sizeof(float)) != 0; // Demonstrate Drags, Sliders, Inputs + ImGui::PushItemFlag(ImGuiItemFlags_LiveEditOnInput, use_liveedit); ImGui::PushItemFlag(ImGuiItemFlags_MixedValue, is_mixed); bool edited = false; edited |= ImGui::DragFloat("DragFloat", item_ref); @@ -2084,6 +2087,7 @@ static void DemoWindowWidgetsMixedValues() ImGui::InputFloat("item 0 (ref)", &items[0]); ImGui::InputFloat("item 1", &items[1]); ImGui::InputFloat("item 2", &items[2]); + ImGui::PopItemFlag(); // Demonstrate Checkbox(), RadioButton(), Combo(), ColorEdit4() ImGui::SeparatorText("Others Widgets"); diff --git a/imgui_internal.h b/imgui_internal.h index 35716ba7c..924da068d 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1031,7 +1031,7 @@ enum ImGuiItemStatusFlags_ ImGuiItemStatusFlags_HasShortcut = 1 << 10, // g.LastItemData.Shortcut valid. Set by SetNextItemShortcut() -> ItemAdd(). //ImGuiItemStatusFlags_FocusedByTabbing = 1 << 8, // Removed IN 1.90.1 (Dec 2023). The trigger is part of g.NavActivateId. See commit 54c1bdeceb. ImGuiItemStatusFlags_EditedInternal = 1 << 11, // Similar to ImGuiItemStatusFlags_Edited but bypassing ImGuiItemFlags_NoMarkEdited. - + // Additional status + semantic for ImGuiTestEngine #ifdef IMGUI_ENABLE_TEST_ENGINE ImGuiItemStatusFlags_Openable = 1 << 20, // Item is an openable (e.g. TreeNode) @@ -1283,6 +1283,7 @@ struct IMGUI_API ImGuiInputTextState bool SelectedAllMouseLock; // after a double-click to select all, we ignore further mouse drags to update selection bool EditedBefore; // edited since activated bool EditedThisFrame; // edited this frame + bool ValidatedThisFrame; bool WantReloadUserBuf; // force a reload of user buf so it may be modified externally. may be automatic in future version. ImS8 LastMoveDirectionLR; // ImGuiDir_Left or ImGuiDir_Right. track last movement direction so when cursor cross over a word-wrapping boundaries we can display it on either line depending on last move.s int ReloadSelectionStart; diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index f42b6482f..8a1637f44 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -3843,7 +3843,11 @@ bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImG // Only mark as edited if new value is different g.LastItemData.ItemFlags &= ~ImGuiItemFlags_NoMarkEdited; - bool value_changed = memcmp(&data_backup, p_data, data_type_size) != 0 || (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue); + bool value_changed = memcmp(&data_backup, p_data, data_type_size) != 0; + if (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) + if (ImGuiInputTextState* state = GetInputTextState(g.LastItemData.ID)) + value_changed |= (g.LastItemData.ItemFlags & ImGuiItemFlags_LiveEditOnInputScalar) ? (state->EditedThisFrame | state->ValidatedThisFrame) : state->ValidatedThisFrame; + if (value_changed) MarkItemEdited(id); return value_changed; @@ -3921,7 +3925,8 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data } } if (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) - value_changed |= ret; + if (ImGuiInputTextState* state = GetInputTextState(g.LastItemData.ID)) + value_changed |= (g.LastItemData.ItemFlags & ImGuiItemFlags_LiveEditOnInputScalar) ? (state->EditedThisFrame | state->ValidatedThisFrame) : state->ValidatedThisFrame; // Step buttons if (has_step_buttons) @@ -5061,6 +5066,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (state != NULL && state->ID == id) { state->Flags = flags; + state->EditedThisFrame = state->ValidatedThisFrame = false; //state->LastFrameActive = g.FrameCount; // Word-wrapping: attempt to keep cursor in view while resizing frame/parent (FIXME-WORDWRAP: would be better to preserve same relative offset) @@ -5076,7 +5082,6 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (g.ActiveId == id) { IM_ASSERT(state != NULL); - state->EditedThisFrame = false; state->BufCapacity = buf_size; state->WrapWidth = wrap_width; @@ -5479,7 +5484,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (g.LastItemData.ItemFlags & ImGuiItemFlags_LiveEditOnInputText) { // Apply when modified - if (strcmp(state->TextSrc, buf) != 0 || (is_mixed && validated)) + if (strcmp(state->TextSrc, buf) != 0 || (is_mixed && (state->EditedThisFrame || validated))) { apply_new_text = state->TextSrc; apply_new_text_length = state->TextLen; @@ -5806,6 +5811,8 @@ 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, label_end, false); + if (state && validated) + state->ValidatedThisFrame = true; if (value_changed) MarkItemEdited(id);