Added ImGuiItemFlags_MixedValue support for Drags, Sliders, InputScalar, InputText, RadioButton, Combo. (#5518, #5677, #6865)

This commit is contained in:
ocornut 2026-09-07 18:40:01 +02:00
parent 9090ba71e5
commit 04b37d6d23
6 changed files with 97 additions and 15 deletions

View File

@ -74,6 +74,9 @@ Other Changes:
hasn't been owned by another items. This is more correct and also necessary hasn't been owned by another items. This is more correct and also necessary
to avoid Drags/Sliders cancelling also triggering a cancel menu. (#8564, #9534) to avoid Drags/Sliders cancelling also triggering a cancel menu. (#8564, #9534)
- Misc: - Misc:
- Added `ImGuiItemFlags_MixedValue` flag for representing mixed/indeterminate values
in Checkbox(), RadioButton(), DragXXX(), SliderXXX(), InputXXX(), Combo() functions.
The scoped flag is designed for usage by advanced property editors. (#5518, #5677, #6865)
- Fixed `GetBackgroundDrawList()`/`GetForegroundDrawList()` not being properly reset - Fixed `GetBackgroundDrawList()`/`GetForegroundDrawList()` not being properly reset
every frame when cumulated session time is very large (e.g. a few days). [@lailoken] every frame when cumulated session time is very large (e.g. a few days). [@lailoken]
- Added `IM_NODEBUGSTEP` helper to tag functions with `__declspec(non_user_code)` (MSVC) - Added `IM_NODEBUGSTEP` helper to tag functions with `__declspec(non_user_code)` (MSVC)
@ -90,6 +93,8 @@ Other Changes:
- Reworked assert in PushTexture() to allow convenience grace period of using a - Reworked assert in PushTexture() to allow convenience grace period of using a
texture that was queue for destroy during the frame. Make it safe to stick to texture that was queue for destroy during the frame. Make it safe to stick to
an existing texture during the frame. (#9528, #8465) an existing texture during the frame. (#9528, #8465)
- Demo:
- Added `Widgets->Mixed Values` section. (#5518, #5677, #6865)
- Backends: - Backends:
- QNX: added QNX Screen backend. (#9492) [@mgorchak-blackberry] - QNX: added QNX Screen backend. (#9492) [@mgorchak-blackberry]
- SDL2: fixed querying framebuffer scale/density when using Metal without - SDL2: fixed querying framebuffer scale/density when using Metal without

View File

@ -4380,6 +4380,7 @@ ImGuiContext::ImGuiContext(ImFontAtlas* shared_font_atlas)
MouseCursor = ImGuiMouseCursor_Arrow; MouseCursor = ImGuiMouseCursor_Arrow;
MouseStationaryTimer = 0.0f; MouseStationaryTimer = 0.0f;
MixedValueLabel = "-";
InputTextPasswordFontBackupFlags = ImFontFlags_None; InputTextPasswordFontBackupFlags = ImFontFlags_None;
InputTextReactivateId = 0; InputTextReactivateId = 0;
TempInputId = 0; TempInputId = 0;

View File

@ -30,7 +30,7 @@
// Library Version // Library Version
// (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345') // (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 "1.93.0 WIP"
#define IMGUI_VERSION_NUM 19295 #define IMGUI_VERSION_NUM 19296
#define IMGUI_HAS_TABLE // Added BeginTable() - from IMGUI_VERSION_NUM >= 18000 #define IMGUI_HAS_TABLE // Added BeginTable() - from IMGUI_VERSION_NUM >= 18000
#define IMGUI_HAS_TEXTURES // Added ImGuiBackendFlags_RendererHasTextures - from IMGUI_VERSION_NUM >= 19198 #define IMGUI_HAS_TEXTURES // Added ImGuiBackendFlags_RendererHasTextures - from IMGUI_VERSION_NUM >= 19198
@ -1258,6 +1258,7 @@ enum ImGuiItemFlags_
ImGuiItemFlags_AutoClosePopups = 1 << 4, // true // MenuItem()/Selectable() automatically close their parent popup window. 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_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_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. // LiveEdit refers to applying edits to backing variables _while_ typing a value using the keyboard.

View File

@ -87,6 +87,7 @@ Index of this file:
// [SECTION] DemoWindowWidgetsImages() // [SECTION] DemoWindowWidgetsImages()
// [SECTION] DemoWindowWidgetsListBoxes() // [SECTION] DemoWindowWidgetsListBoxes()
// [SECTION] DemoWindowWidgetsLiveEdit() // [SECTION] DemoWindowWidgetsLiveEdit()
// [SECTION] DemoWindowWidgetsMixedValues()
// [SECTION] DemoWindowWidgetsMultiComponents() // [SECTION] DemoWindowWidgetsMultiComponents()
// [SECTION] DemoWindowWidgetsPlotting() // [SECTION] DemoWindowWidgetsPlotting()
// [SECTION] DemoWindowWidgetsProgressBars() // [SECTION] DemoWindowWidgetsProgressBars()
@ -2020,7 +2021,7 @@ static void DemoWindowWidgetsLiveEdit(ImGuiDemoWindowData* demo_data)
{ {
if (ImGui::TreeNode("Live Edit Flags")) if (ImGui::TreeNode("Live Edit Flags"))
{ {
IMGUI_DEMO_MARKER("Widgets/Live Edit Flgs"); IMGUI_DEMO_MARKER("Widgets/Live Edit Flags");
ImGui::TextWrapped("Select whether to apply keyboard edits to backing variables _while_ typing."); ImGui::TextWrapped("Select whether to apply keyboard edits to backing variables _while_ typing.");
@ -2050,6 +2051,59 @@ static void DemoWindowWidgetsLiveEdit(ImGuiDemoWindowData* demo_data)
} }
} }
//-----------------------------------------------------------------------------
// [SECTION] DemoWindowWidgetsMixedValues()
//-----------------------------------------------------------------------------
static void DemoWindowWidgetsMixedValues()
{
if (ImGui::TreeNode("Mixed Values"))
{
// This is designed for advanced property editors which are generally reusable and data-driven.
HelpMarker("Using ImGuiItemFlags_MixedValue.");
static float items[3] = { 12.0f, 0.0f, 0.0f };
float* item_ref = &items[0];
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_MixedValue, is_mixed);
bool edited = false;
edited |= ImGui::DragFloat("DragFloat", item_ref);
edited |= ImGui::SliderFloat("SliderFloat", item_ref, 0.0f, 100.0f);
edited |= ImGui::InputFloat("InputFloat", item_ref, 1.0f);
if (edited)
for (float& item : items)
if (&item != item_ref)
item = *item_ref;
ImGui::PopItemFlag();
ImGui::Text("Underlying data:");
ImGui::InputFloat("item 0 (ref)", &items[0]);
ImGui::InputFloat("item 1", &items[1]);
ImGui::InputFloat("item 2", &items[2]);
// Demonstrate Checkbox()
// (this is automatically used by e.g. CheckboxFlags())
ImGui::SeparatorText("Others Widgets");
bool b_on = true, b_off = false;
ImGui::Checkbox("Checkbox On", &b_on);
ImGui::Checkbox("Checkbox Off", &b_off);
ImGui::PushItemFlag(ImGuiItemFlags_MixedValue, true);
ImGui::Checkbox("Checkbox Mixed", &b_off);
ImGui::RadioButton("RadioButton Mixed", true);
ImGui::SameLine();
ImGui::RadioButton("RadioButton Mixed##2", true); // Showing 2 radio buttons makes the example more clear
int combo_idx = 0;
ImGui::Combo("Combo", &combo_idx, "One\0Two\0Three\0");
ImGui::PopItemFlag();
ImGui::TreePop();
}
}
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// [SECTION] DemoWindowWidgetsMultiComponents() // [SECTION] DemoWindowWidgetsMultiComponents()
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -2230,12 +2284,14 @@ static void DemoWindowWidgetsQueryingStatuses()
}; };
static int item_type = 4; static int item_type = 4;
static bool item_disabled = false; static bool item_disabled = false;
static bool item_mixedvalue = false;
static bool liveedit_flags_override = false; static bool liveedit_flags_override = false;
static ImGuiItemFlags liveedit_flags = 0; static ImGuiItemFlags liveedit_flags = 0;
ImGui::Combo("Item Type", &item_type, item_names, IM_COUNTOF(item_names), IM_COUNTOF(item_names)); ImGui::Combo("Item Type", &item_type, item_names, IM_COUNTOF(item_names), IM_COUNTOF(item_names));
ImGui::SameLine(); ImGui::SameLine();
HelpMarker("Testing how various types of items are interacting with the IsItemXXX functions. Note that the bool return value of most ImGui function is generally equivalent to calling ImGui::IsItemHovered()."); HelpMarker("Testing how various types of items are interacting with the IsItemXXX functions. Note that the bool return value of most ImGui function is generally equivalent to calling ImGui::IsItemHovered().");
ImGui::Checkbox("Item Disabled", &item_disabled); ImGui::Checkbox("Item Disabled", &item_disabled);
ImGui::Checkbox("Item MixedValue", &item_mixedvalue);
ImGui::Checkbox("Override LiveEdit:", &liveedit_flags_override); ImGui::Checkbox("Override LiveEdit:", &liveedit_flags_override);
ImGui::SameLine(); ImGui::SameLine();
if (!liveedit_flags_override) if (!liveedit_flags_override)
@ -2260,6 +2316,8 @@ static void DemoWindowWidgetsQueryingStatuses()
static char str[16] = {}; static char str[16] = {};
if (item_disabled) if (item_disabled)
ImGui::BeginDisabled(true); ImGui::BeginDisabled(true);
if (item_mixedvalue)
ImGui::PushItemFlag(ImGuiItemFlags_MixedValue, true);
if (item_type == 0) { ImGui::Text("ITEM: Text"); } // Testing text items with no identifier/interaction if (item_type == 0) { ImGui::Text("ITEM: Text"); } // Testing text items with no identifier/interaction
if (item_type == 1) { ret = ImGui::Button("ITEM: Button"); } // Testing button if (item_type == 1) { ret = ImGui::Button("ITEM: Button"); } // Testing button
if (item_type == 2) { ImGui::PushItemFlag(ImGuiItemFlags_ButtonRepeat, true); ret = ImGui::Button("ITEM: Button"); ImGui::PopItemFlag(); } // Testing button (with repeater) if (item_type == 2) { ImGui::PushItemFlag(ImGuiItemFlags_ButtonRepeat, true); ret = ImGui::Button("ITEM: Button"); ImGui::PopItemFlag(); } // Testing button (with repeater)
@ -2343,6 +2401,8 @@ static void DemoWindowWidgetsQueryingStatuses()
ImGui::PopItemFlag(); ImGui::PopItemFlag();
ImGui::PopItemFlag(); ImGui::PopItemFlag();
} }
if (item_mixedvalue)
ImGui::PopItemFlag();
if (item_disabled) if (item_disabled)
ImGui::EndDisabled(); ImGui::EndDisabled();
@ -4505,6 +4565,7 @@ static void DemoWindowWidgets(ImGuiDemoWindowData* demo_data)
DemoWindowWidgetsImages(); DemoWindowWidgetsImages();
DemoWindowWidgetsListBoxes(); DemoWindowWidgetsListBoxes();
DemoWindowWidgetsLiveEdit(demo_data); DemoWindowWidgetsLiveEdit(demo_data);
DemoWindowWidgetsMixedValues();
DemoWindowWidgetsMultiComponents(); DemoWindowWidgetsMultiComponents();
DemoWindowWidgetsPlotting(); DemoWindowWidgetsPlotting();
DemoWindowWidgetsProgressBars(); DemoWindowWidgetsProgressBars();

View File

@ -993,7 +993,6 @@ enum ImGuiItemFlagsPrivate_
{ {
// Controlled by user // Controlled by user
ImGuiItemFlags_ReadOnly = 1 << 11, // false // [ALPHA] Allow hovering interactions but underlying value is not changed. ImGuiItemFlags_ReadOnly = 1 << 11, // false // [ALPHA] Allow hovering interactions but underlying value is not changed.
ImGuiItemFlags_MixedValue = 1 << 12, // false // [BETA] Represent a mixed/indeterminate value, generally multi-selection where values differ. Currently only supported by Checkbox() (later should support all sorts of widgets)
ImGuiItemFlags_NoWindowHoverableCheck = 1 << 13, // false // Disable hoverable check in ItemHoverable() ImGuiItemFlags_NoWindowHoverableCheck = 1 << 13, // false // Disable hoverable check in ItemHoverable()
ImGuiItemFlags_AllowOverlap = 1 << 14, // false // Allow being overlapped by another widget. Not-hovered to Hovered transition deferred by a frame. ImGuiItemFlags_AllowOverlap = 1 << 14, // false // Allow being overlapped by another widget. Not-hovered to Hovered transition deferred by a frame.
ImGuiItemFlags_NoNavDisableMouseHover = 1 << 15, // false // Nav keyboard/gamepad mode doesn't disable hover highlight (behave as if NavHighlightItemUnderNav==false). ImGuiItemFlags_NoNavDisableMouseHover = 1 << 15, // false // Nav keyboard/gamepad mode doesn't disable hover highlight (behave as if NavHighlightItemUnderNav==false).
@ -2305,6 +2304,7 @@ struct ImGuiContext
ImVec2 WheelingAxisAvg; ImVec2 WheelingAxisAvg;
// Item/widgets state and tracking information // 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 %.
ImGuiID DebugDrawIdConflictsId; // Set when we detect multiple items with the same identifier 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 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 ImGuiID HoveredId; // Hovered widget, filled during the frame

View File

@ -1398,7 +1398,7 @@ bool ImGui::RadioButton(const char* label, bool active)
RenderNavCursor(total_bb, id); RenderNavCursor(total_bb, id);
const int num_segment = window->DrawList->_CalcCircleAutoSegmentCount(radius); const int num_segment = window->DrawList->_CalcCircleAutoSegmentCount(radius);
window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), num_segment); window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), num_segment);
if (active) if (active && (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) == 0)
{ {
const float pad = ImMax(1.0f, IM_TRUNC(square_sz / 6.0f)); const float pad = ImMax(1.0f, IM_TRUNC(square_sz / 6.0f));
window->DrawList->AddCircleFilled(center, radius - pad, GetColorU32(ImGuiCol_CheckMark)); window->DrawList->AddCircleFilled(center, radius - pad, GetColorU32(ImGuiCol_CheckMark));
@ -2176,7 +2176,9 @@ bool ImGui::Combo(const char* label, int* current_item, const char* (*getter)(vo
// Call the getter to obtain the preview string which is a parameter to BeginCombo() // Call the getter to obtain the preview string which is a parameter to BeginCombo()
const char* preview_value = NULL; const char* preview_value = NULL;
if (*current_item >= 0 && *current_item < items_count) if ((g.NextItemData.ItemFlagsSet | g.CurrentItemFlags) & ImGuiItemFlags_MixedValue)
preview_value = "";
else if (*current_item >= 0 && *current_item < items_count)
preview_value = getter(user_data, *current_item); preview_value = getter(user_data, *current_item);
// The old Combo() API exposed "popup_max_height_in_items". The new more general BeginCombo() API doesn't have/need it, but we emulate it here. // The old Combo() API exposed "popup_max_height_in_items". The new more general BeginCombo() API doesn't have/need it, but we emulate it here.
@ -2815,8 +2817,9 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data,
MarkItemEdited(id); MarkItemEdited(id);
// Display value using user-provided display format so user can add prefix/suffix/decorations to the value. // 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;
char value_buf[64]; char value_buf[64];
const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, format); const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, format_for_display);
if (g.LogEnabled) if (g.LogEnabled)
LogSetNextTextDecoration("{", "}"); LogSetNextTextDecoration("{", "}");
RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f)); RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f));
@ -3422,8 +3425,9 @@ 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); 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. // 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;
char value_buf[64]; char value_buf[64];
const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, format); const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, format_for_display);
if (g.LogEnabled) if (g.LogEnabled)
LogSetNextTextDecoration("{", "}"); LogSetNextTextDecoration("{", "}");
RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f)); RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f));
@ -3576,8 +3580,9 @@ 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. // 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 // 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;
char value_buf[64]; char value_buf[64];
const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, format); const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, format_for_display);
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)); 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) 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); RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label, label_end, false);
@ -3815,7 +3820,7 @@ bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImG
// Only mark as edited if new value is different // Only mark as edited if new value is different
g.LastItemData.ItemFlags &= ~ImGuiItemFlags_NoMarkEdited; g.LastItemData.ItemFlags &= ~ImGuiItemFlags_NoMarkEdited;
bool value_changed = memcmp(&data_backup, p_data, data_type_size) != 0; bool value_changed = memcmp(&data_backup, p_data, data_type_size) != 0 || (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue);
if (value_changed) if (value_changed)
MarkItemEdited(id); MarkItemEdited(id);
return value_changed; return value_changed;
@ -3892,6 +3897,8 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data
value_changed = DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL); value_changed = DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL);
} }
} }
if (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue)
value_changed |= ret;
// Step buttons // Step buttons
if (has_step_buttons) if (has_step_buttons)
@ -4857,6 +4864,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0; const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0;
const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0; const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0;
const bool is_resizable = (flags & ImGuiInputTextFlags_CallbackResize) != 0; const bool is_resizable = (flags & ImGuiInputTextFlags_CallbackResize) != 0;
const bool is_mixed = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) != 0;
if (is_resizable) if (is_resizable)
IM_ASSERT(callback != NULL); // Must provide a callback if you set the ImGuiInputTextFlags_CallbackResize flag! IM_ASSERT(callback != NULL); // Must provide a callback if you set the ImGuiInputTextFlags_CallbackResize flag!
@ -5021,7 +5029,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
// Select the buffer to render. // Select the buffer to render.
const bool buf_display_from_state = (render_cursor || render_selection || g.ActiveId == id) && !is_readonly && state; const bool buf_display_from_state = (render_cursor || render_selection || g.ActiveId == id) && !is_readonly && state;
bool is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0); bool is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0) && !is_mixed;
// Password pushes a temporary font with only a fallback glyph // Password pushes a temporary font with only a fallback glyph
if (is_password && !is_displaying_hint) if (is_password && !is_displaying_hint)
@ -5448,7 +5456,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
if (g.LastItemData.ItemFlags & ImGuiItemFlags_LiveEditOnInputText) if (g.LastItemData.ItemFlags & ImGuiItemFlags_LiveEditOnInputText)
{ {
// Apply when modified // Apply when modified
if (strcmp(state->TextSrc, buf) != 0) if (strcmp(state->TextSrc, buf) != 0 || (is_mixed && validated))
{ {
apply_new_text = state->TextSrc; apply_new_text = state->TextSrc;
apply_new_text_length = state->TextLen; apply_new_text_length = state->TextLen;
@ -5458,7 +5466,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
else else
{ {
// Apply on validation/deactivation, otherwise cancel out previous apply attempts (e.g. revert) // Apply on validation/deactivation, otherwise cancel out previous apply attempts (e.g. revert)
value_changed = ((validated || clear_active_id || revert_edit) && strcmp(state->TextSrc, buf) != 0); value_changed = (validated || clear_active_id || revert_edit) && (strcmp(state->TextSrc, buf) != 0 || (is_mixed && validated));
apply_new_text = value_changed ? state->TextSrc : NULL; apply_new_text = value_changed ? state->TextSrc : NULL;
apply_new_text_length = value_changed ? state->TextLen : 0; apply_new_text_length = value_changed ? state->TextLen : 0;
} }
@ -5543,7 +5551,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
// Display hint when contents is empty // Display hint when contents is empty
// At this point we need to handle the possibility that a callback could have modified the underlying buffer (#8368) // At this point we need to handle the possibility that a callback could have modified the underlying buffer (#8368)
const bool new_is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0); const bool new_is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0) && !is_mixed;
if (new_is_displaying_hint != is_displaying_hint) if (new_is_displaying_hint != is_displaying_hint)
{ {
if (is_password && !is_displaying_hint) if (is_password && !is_displaying_hint)
@ -5552,10 +5560,16 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
if (is_password && !is_displaying_hint) if (is_password && !is_displaying_hint)
PushPasswordFont(); PushPasswordFont();
} }
if (is_displaying_hint) if (is_mixed && g.ActiveId != id && apply_new_text == NULL)
{
buf_display = g.MixedValueLabel;
buf_display_end = buf_display + strlen(g.MixedValueLabel);
render_cursor = render_selection = false;
}
else if (is_displaying_hint)
{ {
buf_display = hint; buf_display = hint;
buf_display_end = hint + ImStrlen(hint); buf_display_end = buf_display + ImStrlen(buf_display);
} }
else else
{ {