From e8602501bb900fc7c965e65b88fef30b2c80aff1 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 9 Sep 2026 20:44:34 +0200 Subject: [PATCH] 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);