Split ImGuiItemFlags_LiveEdit into ImGuiItemFlags_LiveEditText, ImGuiItemFlags_LiveEditScalar. Added Demo contents. (#9476, #701)
build / Build - Windows (push) Canceled after 0s
build / Build - Linux (push) Canceled after 0s
build / Build - MacOS (push) Canceled after 0s
build / Build - iOS (push) Canceled after 0s
build / Build - Emscripten (push) Canceled after 0s
build / Build - Android (push) Canceled after 0s
build / Test - Windows (push) Canceled after 0s
build / Test - Linux (push) Canceled after 0s

cc #3936, #3946, #5904, #6284, #8149, #8065, #8665, #9117, #9299, #700, #1351, #1875, #2060, #2215, #2380, #2550, #3083, #3338, #3556, #4373, #4714, #4885, #5184,#5777, #6707, #6766, #8004, #8303, #8915, #9308
This commit is contained in:
ocornut
2026-07-16 15:46:47 +02:00
parent c958f16059
commit 26b8292635
6 changed files with 141 additions and 9 deletions
+39
View File
@@ -60,6 +60,45 @@ Other Changes:
- Fixed double-click collapse toggle not owning the mouse button. If a
`SetNextWindowPos()` with pivot was queued in the same frame, the second
click could trigger another item in the same window. (#9439) [@Cleroth]
- Added `ImGuiItemFlags_LiveEditOnInputText` and `ImGuiItemFlags_LiveEditOnInputScalar`
flags to configure the timing of applying edits of backing variables when typing
values using a keyboard.
(#701, #9476, #3936, #3946, #5904, #6284, #8149, #8065, #8665, #9117, #9299, #700, #1351,
#1875, #2060, #2215, #2380, #2550, #3083, #3338, #3556, #4373, #4714, #4885, #5184,
#5777, #6707, #6766, #8004, #8303, #8915, #9308)
- Until now:
- Edits where always applied immediately to backing variable, which is equivalent
to the `ImGuiItemFlags_LiveEditXXX` flags being enabled.
- Typing '123' in an integer field would output successively 1, 12 then 123.
- Most uses of `IsItemDeactivatedAfterEdit()` or `ImGuiInputTextFlags_EnterReturnsTrue`
were actually workarounds for this issue. Advanced applications would typically
use `IsItemDeactivatedAfterEdit()` to distinguish transactions.
Workarounds often required a backing store for scalar values, and there were
also a few niggles related to `IsItemDeactivatedAfterEdit()` when using +/-
buttons of an `InputInt()` widgets.
Many of those situations can now be naturally simplified by disabling
`ImGuiItemFlags_LiveEditOnInputScalar`, which is expected to become the default.
- The new flags allows disabling this behavior selectively for strings fields
such as `InputText()` vs scalar fields: `SliderInt()`, `InputFloat()`, etc.
- When LiveEdit is disabled, edits are applied when pressing enter, tabbing out,
clearing a field or deactivating due to a focus loss.
- The flag may be altered programmatically:
PushItemFlag(ImGuiItemFlags_LiveEditOnInputScalar, false); // Disable for scalars
SliderInt(...);
PopItemFlag();
PushItemFlag(ImGuiItemFlags_LiveEditOnInput, true); // Enable for all
SliderInt(...);
PopItemFlag();
But with upcoming new defaults it is expected you shouldn't touch them much.
- Both flags currently defaults to true, which matches previous behavior.
- The expectation is that for strings/text, enabling LiveEdit is a better default.
- The expectation is that for scalars, disable LiveEdit is a better default.
- The short-term intent is to change `ImGuiItemFlags_LiveEditOnInputScalar` to
default to being disabled, as soon as we get more feedback from users (SOON).
- We intentionally are not adding `io.ConfigLiveEditXXX` fields to dictate the
default value of each `ImGuiItemFlags_LiveEditXXX`, because this is not
expected to be a user preference but a programmer/widget preferences.
Also, we strive to make the toolkit consistent.
- InputText:
- Added `style.InputTextCursorSize` to configure cursor/caret thickness. (#7031, #9409)
This is automatically scaled by `style.ScaleAllSizes()`.
+1
View File
@@ -6358,6 +6358,7 @@ bool ImGui::IsItemDeactivated()
return g.DeactivatedItemData.ID == g.LastItemData.ID && g.LastItemData.ID != 0 && g.DeactivatedItemData.ElapseFrame >= g.FrameCount;
}
// Since 1.92.9: consider using NoLiveEdit flags if all you need to that values are not written bo backing variable while typing.
bool ImGui::IsItemDeactivatedAfterEdit()
{
ImGuiContext& g = *GImGui;
+20 -5
View File
@@ -1238,16 +1238,29 @@ enum ImGuiChildFlags_
// (Those are shared by all submitted items)
enum ImGuiItemFlags_
{
ImGuiItemFlags_None = 0, // (Default)
ImGuiItemFlags_None = 0, // Default:
ImGuiItemFlags_NoTabStop = 1 << 0, // false // Disable keyboard tabbing. This is a "lighter" version of ImGuiItemFlags_NoNav.
ImGuiItemFlags_NoNav = 1 << 1, // false // Disable any form of focusing (keyboard/gamepad directional navigation and SetKeyboardFocusHere() calls).
ImGuiItemFlags_NoNav = 1 << 1, // false // Disable any form of focusing: keyboard/gamepad directional navigation and SetKeyboardFocusHere() calls.
ImGuiItemFlags_NoNavDefaultFocus = 1 << 2, // false // Disable item being a candidate for default focus (e.g. used by title bar items).
ImGuiItemFlags_ButtonRepeat = 1 << 3, // false // Any button-like behavior will have repeat mode enabled (based on io.KeyRepeatDelay and io.KeyRepeatRate values). Note that you can also call IsItemActive() after any button to tell if it is being held.
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_LiveEdit = 1 << 7, // true // WIP
//---------------------------------------------------------------------------------
// LiveEdit refers to applying edits to backing variables _while_ typing.
// Widget: | Input: | w/ LiveEdit: | Output:
// InputText() | "123" | on(default) | "1" then "12" then "123"
// InputText() | "123" | off | "123" after validating or tabbing out or losing focus.
// DragFloat(), SliderInt(), etc. | "123" | on(default)* | 1 then 12 then 123
// DragFloat(), SliderInt(), etc. | "123" | off* | 123 after validation or tabbing out or losing focus.
//---------------------------------------------------------------------------------
// (*) The feature is currently being evaluated, and the aim is to change ImGuiItemFlags_LiveEditOnInputScalar to OFF by default.
// The legacy behavior until July 2026 was that LiveEdit was always ON for everything.
//---------------------------------------------------------------------------------
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, // true* // 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,
};
// Flags for ImGui::InputText()
@@ -1264,7 +1277,7 @@ enum ImGuiInputTextFlags_
// Inputs
ImGuiInputTextFlags_AllowTabInput = 1 << 5, // Pressing TAB input a '\t' character into the text field
ImGuiInputTextFlags_EnterReturnsTrue = 1 << 6, // Return 'true' when Enter is pressed (as opposed to every time the value was modified). Consider using IsItemDeactivatedAfterEdit() instead!
ImGuiInputTextFlags_EnterReturnsTrue = 1 << 6, // Return 'true' when Enter is pressed (as opposed to every time the value was modified). Consider disabling LiveEdit! or using IsItemDeactivatedAfterEdit() instead!
ImGuiInputTextFlags_EscapeClearsAll = 1 << 7, // Escape key clears content if not empty, and deactivate otherwise (contrast to default behavior of Escape to revert)
ImGuiInputTextFlags_CtrlEnterForNewLine = 1 << 8, // In multi-line mode: validate with Enter, add new line with Ctrl+Enter (default is opposite: validate with Ctrl+Enter, add line with Enter). Note that Shift+Enter always enter a new line either way.
@@ -1277,7 +1290,7 @@ enum ImGuiInputTextFlags_
ImGuiInputTextFlags_DisplayEmptyRefVal = 1 << 14, // InputFloat(), InputInt(), InputScalar() etc. only: when value is zero, do not display it. Generally used with ImGuiInputTextFlags_ParseEmptyRefVal.
ImGuiInputTextFlags_NoHorizontalScroll = 1 << 15, // Disable following the cursor horizontally
ImGuiInputTextFlags_NoUndoRedo = 1 << 16, // Disable undo/redo. Note that input text owns the text data while active, if you want to provide your own undo/redo stack you need e.g. to call ClearActiveID().
//ImGuiInputTextFlags_NoLiveEdit = 1 << 25,
//ImGuiInputTextFlags_NoLiveEdit = 1 << 25, // Disable applying output to backing variable while typing. Same as setting ImGuiItemFlags_LiveEditOnInput to false.
// Elide display / Alignment
ImGuiInputTextFlags_ElideLeft = 1 << 17, // When text doesn't fit, elide left side to ensure right side stays visible. Useful for path/filenames. Single-line only!
@@ -1956,6 +1969,8 @@ enum ImGuiSliderFlags_
ImGuiSliderFlags_NoSpeedTweaks = 1 << 11, // Disable keyboard modifiers altering tweak speed. Useful if you want to alter tweak speed yourself based on your own logic.
ImGuiSliderFlags_ColorMarkers = 1 << 12, // DragScalarN(), SliderScalarN(): Draw R/G/B/A color markers on each component.
ImGuiSliderFlags_AlwaysClamp = ImGuiSliderFlags_ClampOnInput | ImGuiSliderFlags_ClampZeroRange,
//ImGuiSliderFlags_LiveEditOnInput = 1 << 13, // Shortcut to enable LiveEdit for this field.
//ImGuiSliderFlags_NoLiveEditOnInput= 1 << 14, // Shortcut to disable LiveEdit for this field.
ImGuiSliderFlags_InvalidMask_ = 0x7000000F, // [Internal] We treat using those bits as being potentially a 'float power' argument from legacy API (obsoleted 2020-08) that has got miscast to this enum, and will trigger an assert if needed.
};
+76
View File
@@ -86,6 +86,7 @@ Index of this file:
// [SECTION] DemoWindowWidgetsFonts()
// [SECTION] DemoWindowWidgetsImages()
// [SECTION] DemoWindowWidgetsListBoxes()
// [SECTION] DemoWindowWidgetsLiveEdit()
// [SECTION] DemoWindowWidgetsMultiComponents()
// [SECTION] DemoWindowWidgetsPlotting()
// [SECTION] DemoWindowWidgetsProgressBars()
@@ -331,6 +332,8 @@ struct ImGuiDemoWindowData
// Other data
bool DisableSections = false;
bool LiveEditOverride = false;
ImGuiItemFlags LiveEditFlags = ImGuiItemFlags_LiveEditOnInputText;
ExampleTreeNode* DemoTree = NULL;
~ImGuiDemoWindowData() { if (DemoTree) ExampleTree_DestroyNode(DemoTree); }
@@ -2009,6 +2012,44 @@ static void DemoWindowWidgetsListBoxes()
}
}
//-----------------------------------------------------------------------------
// [SECTION] DemoWindowWidgetsLiveEdit()
//-----------------------------------------------------------------------------
static void DemoWindowWidgetsLiveEdit(ImGuiDemoWindowData* demo_data)
{
if (ImGui::TreeNode("Live Edit Flags"))
{
IMGUI_DEMO_MARKER("Widgets/Live Edit Flgs");
ImGui::TextWrapped("Select whether to apply keyboard edits to backing variables _while_ typing.");
ImGui::Checkbox("Override Live Edit Flags in Demo Window", &demo_data->LiveEditOverride);
if (!demo_data->LiveEditOverride)
demo_data->LiveEditFlags = ImGui::GetItemFlags();
ImGui::BeginDisabled(demo_data->LiveEditOverride == false);
ImGui::Indent();
ImGui::CheckboxFlags("ImGuiItemFlags_LiveEditOnInputText", &demo_data->LiveEditFlags, ImGuiItemFlags_LiveEditOnInputText);
ImGui::CheckboxFlags("ImGuiItemFlags_LiveEditOnInputScalar", &demo_data->LiveEditFlags, ImGuiItemFlags_LiveEditOnInputScalar);
ImGui::Unindent();
ImGui::EndDisabled();
ImGui::Text("Try typing '123' and seeing effect on backing value:");
static char str[32] = "";
ImGui::InputText("str", str, IM_COUNTOF(str));
ImGui::Text("Backing value: \"%s\"", str);
static int i = 0;
ImGui::InputInt("int", &i, 0, 0);
ImGui::Text("Backing value: %d", i);
static float f = 0.0f;
ImGui::SliderFloat("float", &f, 0.0f, 100.0f);
ImGui::Text("Backing value: %f", f);
ImGui::TreePop();
}
}
//-----------------------------------------------------------------------------
// [SECTION] DemoWindowWidgetsMultiComponents()
//-----------------------------------------------------------------------------
@@ -2189,10 +2230,28 @@ static void DemoWindowWidgetsQueryingStatuses()
};
static int item_type = 4;
static bool item_disabled = false;
static bool liveedit_flags_override = false;
static ImGuiItemFlags liveedit_flags = 0;
ImGui::Combo("Item Type", &item_type, item_names, IM_COUNTOF(item_names), IM_COUNTOF(item_names));
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().");
ImGui::Checkbox("Item Disabled", &item_disabled);
ImGui::Checkbox("Override LiveEdit:", &liveedit_flags_override);
ImGui::SameLine();
if (!liveedit_flags_override)
liveedit_flags = ImGui::GetItemFlags();
ImGui::BeginDisabled(liveedit_flags_override == false);
ImGui::CheckboxFlags("_LiveEditOnInput", &liveedit_flags, ImGuiItemFlags_LiveEditOnInput);
ImGui::SameLine();
ImGui::CheckboxFlags("_LiveEditOnInputText", &liveedit_flags, ImGuiItemFlags_LiveEditOnInputText);
ImGui::SameLine();
ImGui::CheckboxFlags("_LiveEditOnInputScalar", &liveedit_flags, ImGuiItemFlags_LiveEditOnInputScalar);
ImGui::EndDisabled();
if (liveedit_flags_override)
{
ImGui::PushItemFlag(ImGuiItemFlags_LiveEditOnInputText, (liveedit_flags & ImGuiItemFlags_LiveEditOnInputText) != 0);
ImGui::PushItemFlag(ImGuiItemFlags_LiveEditOnInputScalar, (liveedit_flags & ImGuiItemFlags_LiveEditOnInputScalar) != 0);
}
// Submit selected items so we can query their status in the code following it.
bool ret = false;
@@ -2279,6 +2338,11 @@ static void DemoWindowWidgetsQueryingStatuses()
"IsItemHovered(_Tooltip) = %d",
hovered_delay_none, hovered_delay_stationary, hovered_delay_short, hovered_delay_normal, hovered_delay_tooltip);
if (liveedit_flags_override)
{
ImGui::PopItemFlag();
ImGui::PopItemFlag();
}
if (item_disabled)
ImGui::EndDisabled();
@@ -4413,8 +4477,14 @@ static void DemoWindowWidgets(ImGuiDemoWindowData* demo_data)
// IMGUI_DEMO_MARKER("Widgets");
const bool disable_all = demo_data->DisableSections; // The Checkbox for that is inside the "Disabled" section at the bottom
const bool override_liveedit = demo_data->LiveEditOverride;
if (disable_all)
ImGui::BeginDisabled();
if (override_liveedit)
{
ImGui::PushItemFlag(ImGuiItemFlags_LiveEditOnInputText, (demo_data->LiveEditFlags & ImGuiItemFlags_LiveEditOnInputText) != 0);
ImGui::PushItemFlag(ImGuiItemFlags_LiveEditOnInputScalar, (demo_data->LiveEditFlags & ImGuiItemFlags_LiveEditOnInputScalar) != 0);
}
DemoWindowWidgetsBasic();
DemoWindowWidgetsBullets();
@@ -4434,6 +4504,7 @@ static void DemoWindowWidgets(ImGuiDemoWindowData* demo_data)
DemoWindowWidgetsFonts();
DemoWindowWidgetsImages();
DemoWindowWidgetsListBoxes();
DemoWindowWidgetsLiveEdit(demo_data);
DemoWindowWidgetsMultiComponents();
DemoWindowWidgetsPlotting();
DemoWindowWidgetsProgressBars();
@@ -4448,6 +4519,11 @@ static void DemoWindowWidgets(ImGuiDemoWindowData* demo_data)
DemoWindowWidgetsTreeNodes();
DemoWindowWidgetsVerticalSliders();
if (override_liveedit)
{
ImGui::PopItemFlag();
ImGui::PopItemFlag();
}
if (disable_all)
ImGui::EndDisabled();
}
+1 -1
View File
@@ -1006,7 +1006,7 @@ enum ImGuiItemFlagsPrivate_
ImGuiItemFlags_HasSelectionUserData = 1 << 21, // false // Set by SetNextItemSelectionUserData()
ImGuiItemFlags_IsMultiSelect = 1 << 22, // false // Set by SetNextItemSelectionUserData()
ImGuiItemFlags_Default_ = ImGuiItemFlags_AutoClosePopups | ImGuiItemFlags_LiveEdit, // Please don't change, use PushItemFlag() instead.
ImGuiItemFlags_Default_ = ImGuiItemFlags_AutoClosePopups | ImGuiItemFlags_LiveEditOnInputText | ImGuiItemFlags_LiveEditOnInputScalar, // Please don't change, use PushItemFlag() instead.
// Obsolete
//ImGuiItemFlags_SelectableDontClosePopup = !ImGuiItemFlags_AutoClosePopups, // Can't have a redirect as we inverted the behavior
+4 -3
View File
@@ -3763,6 +3763,7 @@ bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImG
ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint;
g.LastItemData.ItemFlags |= ImGuiItemFlags_NoMarkEdited; // Because TempInputText() uses ImGuiInputTextFlags_MergedItem it doesn't submit a new item, so we poke LastItemData.
g.LastItemData.ItemFlags = (g.LastItemData.ItemFlags & ImGuiItemFlags_LiveEditOnInputScalar) ? (g.LastItemData.ItemFlags | ImGuiItemFlags_LiveEditOnInputText) : (g.LastItemData.ItemFlags & ~ImGuiItemFlags_LiveEditOnInputText);
if (!TempInputText(bb, id, label, data_buf, IM_COUNTOF(data_buf), flags))
return false;
@@ -3843,7 +3844,7 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data
// Apply
bool value_changed = false;
if (g.LastItemData.ItemFlags & ImGuiItemFlags_LiveEdit)
if (g.LastItemData.ItemFlags & ImGuiItemFlags_LiveEditOnInputScalar)
{
bool input_edited = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_EditedInternal) != 0; // We would be using 'ret' if ImGuiInputTextFlags_EnterReturnsTrue was not involved.
if (input_edited)
@@ -4868,7 +4869,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
state->CursorAnimReset();
// Backup state of deactivating item so they'll have a chance to do a write to output buffer on the same frame they report IsItemDeactivatedAfterEdit (#4714)
if (state->ID != id && state->ID == g.ActiveId && (init_make_active && g.ActiveId != id))
if (state->ID != id && state->ID == g.ActiveId && (init_make_active && g.ActiveId != id)) //-V560
InputTextDeactivateHook(state->ID); // <-- this is essentially an earlier call to what SetActiveID() would do below.
// Take a copy of the initial buffer value.
@@ -5410,7 +5411,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
// FIXME-OPT: Could mark dirty state from the stb_textedit callbacks
if (!is_readonly)
{
if (g.LastItemData.ItemFlags & ImGuiItemFlags_LiveEdit)
if (g.LastItemData.ItemFlags & ImGuiItemFlags_LiveEditOnInputText)
{
// Apply when modified
if (strcmp(state->TextSrc, buf) != 0)