Skip to content

Commit 18d63b1

Browse files
committed
Added ImGuiItemFlags_LiveEdit flag, and much-awaited support for disabling it. (#9476, #701)
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
1 parent 2392a52 commit 18d63b1

4 files changed

Lines changed: 64 additions & 28 deletions

File tree

imgui.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4833,6 +4833,7 @@ void ImGui::MarkItemEdited(ImGuiID id)
48334833
// ActiveId might have been released by the time we call this (as in the typical press/release button behavior) but still need to fill the data.
48344834
ImGuiContext& g = *GImGui;
48354835

4836+
//IM_ASSERT(g.LastItemData.ID == id); // Failing cases include: "widgets_inputtext_scrolling", "widgets_inputtext_multiline_status", "widgets_selectable_input" = case of e.g TempInputText() overlayed manually with different ID (#2718)
48364837
g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_EditedInternal;
48374838
if (g.LastItemData.ItemFlags & ImGuiItemFlags_NoMarkEdited)
48384839
return;
@@ -4843,14 +4844,14 @@ void ImGui::MarkItemEdited(ImGuiID id)
48434844
// FIXME: Can't we fully rely on LastItemData yet?
48444845
g.ActiveIdHasBeenEditedThisFrame = true;
48454846
g.ActiveIdHasBeenEditedBefore = true;
4846-
if (g.DeactivatedItemData.ID == id)
4847-
g.DeactivatedItemData.HasBeenEditedBefore = true;
48484847
}
4848+
if (g.DeactivatedItemData.ID == id)
4849+
g.DeactivatedItemData.HasBeenEditedBefore = true;
48494850

48504851
// We accept a MarkItemEdited() on drag and drop targets (see https://github.com/ocornut/imgui/issues/1875#issuecomment-978243343)
48514852
// We accept 'ActiveIdPreviousFrame == id' for InputText() returning an edit after it has been taken ActiveId away (#4714)
48524853
// FIXME: This assert is getting a bit meaningless over time. It helped detect some unusual use cases but eventually it is becoming an unnecessary restriction.
4853-
IM_ASSERT(g.DragDropActive || g.ActiveId == id || g.ActiveId == 0 || g.ActiveIdPreviousFrame == id || g.NavJustMovedToId || (g.CurrentMultiSelect != NULL && g.BoxSelectState.IsActive));
4854+
IM_ASSERT(g.DragDropActive || g.ActiveId == id || g.ActiveId == 0 || g.DeactivatedItemData.ID == id || g.ActiveIdPreviousFrame == id || g.NavJustMovedToId || (g.CurrentMultiSelect != NULL && g.BoxSelectState.IsActive));
48544855
}
48554856

48564857
bool ImGui::IsWindowContentHoverable(ImGuiWindow* window, ImGuiHoveredFlags flags)

imgui.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
// Library Version
3131
// (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345')
3232
#define IMGUI_VERSION "1.92.9 WIP"
33-
#define IMGUI_VERSION_NUM 19285
33+
#define IMGUI_VERSION_NUM 19286
3434
#define IMGUI_HAS_TABLE // Added BeginTable() - from IMGUI_VERSION_NUM >= 18000
3535
#define IMGUI_HAS_TEXTURES // Added ImGuiBackendFlags_RendererHasTextures - from IMGUI_VERSION_NUM >= 19198
3636

@@ -1246,6 +1246,8 @@ enum ImGuiItemFlags_
12461246
ImGuiItemFlags_AutoClosePopups = 1 << 4, // true // MenuItem()/Selectable() automatically close their parent popup window.
12471247
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.
12481248
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().
1249+
1250+
ImGuiItemFlags_LiveEdit = 1 << 7, // true // WIP
12491251
};
12501252

12511253
// Flags for ImGui::InputText()
@@ -1275,6 +1277,7 @@ enum ImGuiInputTextFlags_
12751277
ImGuiInputTextFlags_DisplayEmptyRefVal = 1 << 14, // InputFloat(), InputInt(), InputScalar() etc. only: when value is zero, do not display it. Generally used with ImGuiInputTextFlags_ParseEmptyRefVal.
12761278
ImGuiInputTextFlags_NoHorizontalScroll = 1 << 15, // Disable following the cursor horizontally
12771279
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().
1280+
//ImGuiInputTextFlags_NoLiveEdit = 1 << 25,
12781281

12791282
// Elide display / Alignment
12801283
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!

imgui_internal.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1006,7 +1006,7 @@ enum ImGuiItemFlagsPrivate_
10061006
ImGuiItemFlags_HasSelectionUserData = 1 << 21, // false // Set by SetNextItemSelectionUserData()
10071007
ImGuiItemFlags_IsMultiSelect = 1 << 22, // false // Set by SetNextItemSelectionUserData()
10081008

1009-
ImGuiItemFlags_Default_ = ImGuiItemFlags_AutoClosePopups, // Please don't change, use PushItemFlag() instead.
1009+
ImGuiItemFlags_Default_ = ImGuiItemFlags_AutoClosePopups | ImGuiItemFlags_LiveEdit, // Please don't change, use PushItemFlag() instead.
10101010

10111011
// Obsolete
10121012
//ImGuiItemFlags_SelectableDontClosePopup = !ImGuiItemFlags_AutoClosePopups, // Can't have a redirect as we inverted the behavior
@@ -3799,7 +3799,7 @@ namespace ImGui
37993799
IMGUI_API void InputTextDeactivateHook(ImGuiID id);
38003800
IMGUI_API bool TempInputText(const ImRect& bb, ImGuiID id, const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL);
38013801
IMGUI_API bool TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImGuiDataType data_type, void* p_data, const char* format, const void* p_clamp_min = NULL, const void* p_clamp_max = NULL);
3802-
inline bool TempInputIsActive(ImGuiID id) { ImGuiContext& g = *GImGui; return g.ActiveId == id && g.TempInputId == id; }
3802+
inline bool TempInputIsActive(ImGuiID id) { ImGuiContext& g = *GImGui; return (g.TempInputId == id && g.ActiveId == id) || (g.InputTextDeactivatedState.ID == id); }
38033803
inline ImGuiInputTextState* GetInputTextState(ImGuiID id) { ImGuiContext& g = *GImGui; return (id != 0 && g.InputTextState.ID == id) ? &g.InputTextState : NULL; } // Get input text state if active
38043804
IMGUI_API void SetNextItemRefVal(ImGuiDataType data_type, void* p_data);
38053805
inline bool IsItemActiveAsInputText() { ImGuiContext& g = *GImGui; return g.ActiveId != 0 && g.ActiveId == g.LastItemData.ID && g.InputTextState.ID == g.LastItemData.ID; } // This may be useful to apply workaround that a based on distinguish whenever an item is active as a text input field.

imgui_widgets.cpp

Lines changed: 54 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3722,22 +3722,23 @@ bool ImGui::TempInputText(const ImRect& bb, ImGuiID id, const char* label, char*
37223722
ImGuiContext& g = *GImGui;
37233723
ImGuiWindow* window = g.CurrentWindow;
37243724

3725-
const bool init = (g.TempInputId != id);
3726-
if (init)
3725+
const bool is_deactivated = (g.InputTextDeactivatedState.ID == id);
3726+
const bool is_active = (g.TempInputId == id);
3727+
if (!is_active && !is_deactivated)
37273728
ClearActiveID();
37283729

37293730
ImVec2 backup_pos = window->DC.CursorPos;
37303731
window->DC.CursorPos = bb.Min;
37313732
g.LastItemData.ItemFlags |= ImGuiItemFlags_AllowDuplicateId; // Using ImGuiInputTextFlags_MergedItem above will skip ItemAdd() so we poke here.
37323733
bool value_changed = InputTextEx(label, NULL, buf, (int)buf_size, bb.GetSize(), flags | ImGuiInputTextFlags_TempInput | ImGuiInputTextFlags_AutoSelectAll, callback, user_data);
37333734
KeepAliveID(id); // Not done because of ImGuiInputTextFlags_TempInput
3734-
if (init)
3735+
if (!is_active && !is_deactivated)
37353736
{
37363737
// First frame we started displaying the InputText widget, we expect it to take the active id.
37373738
IM_ASSERT(g.ActiveId == id);
37383739
g.TempInputId = g.ActiveId;
37393740
}
3740-
if (g.ActiveId != id)
3741+
if (is_active && g.ActiveId != id)
37413742
g.TempInputId = 0;
37423743
window->DC.CursorPos = backup_pos;
37433744
return value_changed;
@@ -3841,8 +3842,23 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data
38413842
}
38423843

38433844
// Apply
3844-
bool input_edited = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_EditedInternal) != 0; // We would be using 'ret' if ImGuiInputTextFlags_EnterReturnsTrue was not involved.
3845-
bool value_changed = input_edited ? DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL) : false;
3845+
bool value_changed = false;
3846+
if (g.LastItemData.ItemFlags & ImGuiItemFlags_LiveEdit)
3847+
{
3848+
bool input_edited = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_EditedInternal) != 0; // We would be using 'ret' if ImGuiInputTextFlags_EnterReturnsTrue was not involved.
3849+
if (input_edited)
3850+
value_changed = DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL);
3851+
}
3852+
else
3853+
{
3854+
//g.LastItemData.StatusFlags &= ~ImGuiItemStatusFlags_Edited;
3855+
if (g.DeactivatedItemData.ID == g.LastItemData.ID)
3856+
{
3857+
//g.DeactivatedItemData.HasBeenEditedBefore = false; // Will be set below by MarkItemEdited()
3858+
//if (IsItemDeactivated()) // Should be unnecessary
3859+
value_changed = DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL);
3860+
}
3861+
}
38463862

38473863
// Step buttons
38483864
if (has_step_buttons)
@@ -4584,7 +4600,7 @@ void ImGui::InputTextDeactivateHook(ImGuiID id)
45844600
{
45854601
ImGuiContext& g = *GImGui;
45864602
ImGuiInputTextState* state = &g.InputTextState;
4587-
if (id == 0 || state->ID != id)
4603+
if (id == 0 || state->ID != id || g.ActiveId != id)
45884604
return;
45894605
if (!state->EditedBefore)
45904606
return;
@@ -4820,7 +4836,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
48204836
const bool user_clicked = hovered && io.MouseClicked[0];
48214837
const bool input_requested_by_nav = (g.ActiveId != id) && (g.NavActivateId == id);
48224838
const bool input_requested_by_reactivate = (g.InputTextReactivateId == id); // for io.ConfigInputTextEnterKeepActive
4823-
const bool input_requested_by_user = (user_clicked) || (g.ActiveId == 0 && (flags & ImGuiInputTextFlags_TempInput));
4839+
const bool input_requested_by_user = (user_clicked) || (g.ActiveId == 0 && (flags & ImGuiInputTextFlags_TempInput) && g.InputTextDeactivatedState.ID != id);
48244840
const ImGuiID scrollbar_id = (is_multiline && state != NULL) ? GetWindowScrollbarID(draw_window, ImGuiAxis_Y) : 0;
48254841
const bool user_scroll_finish = is_multiline && state != NULL && g.ActiveId == 0 && g.ActiveIdPreviousFrame == scrollbar_id;
48264842
const bool user_scroll_active = is_multiline && state != NULL && g.ActiveId == scrollbar_id;
@@ -4852,7 +4868,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
48524868
state->CursorAnimReset();
48534869

48544870
// 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)
4855-
InputTextDeactivateHook(state->ID);
4871+
if (state->ID != id && state->ID == g.ActiveId && (init_make_active && g.ActiveId != id))
4872+
InputTextDeactivateHook(state->ID); // <-- this is essentially an earlier call to what SetActiveID() would do below.
48564873

48574874
// Take a copy of the initial buffer value.
48584875
// From the moment we focused we are normally ignoring the content of 'buf' (unless we are in read-only mode)
@@ -5389,28 +5406,43 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
53895406
}
53905407
}
53915408

5392-
// Will copy result string if modified.
5409+
// Write back result string if modified.
53935410
// FIXME-OPT: Could mark dirty state from the stb_textedit callbacks
5394-
if (!is_readonly && strcmp(state->TextSrc, buf) != 0)
5411+
if (!is_readonly)
53955412
{
5396-
apply_new_text = state->TextSrc;
5397-
apply_new_text_length = state->TextLen;
5398-
value_changed = true;
5413+
if (g.LastItemData.ItemFlags & ImGuiItemFlags_LiveEdit)
5414+
{
5415+
// Apply when modified
5416+
if (strcmp(state->TextSrc, buf) != 0)
5417+
{
5418+
apply_new_text = state->TextSrc;
5419+
apply_new_text_length = state->TextLen;
5420+
value_changed = true;
5421+
}
5422+
}
5423+
else
5424+
{
5425+
// Apply on validation/deactivation, otherwise cancel out previous apply attempts (e.g. revert)
5426+
value_changed = ((validated || clear_active_id || revert_edit) && strcmp(state->TextSrc, buf) != 0);
5427+
apply_new_text = value_changed ? state->TextSrc : NULL;
5428+
apply_new_text_length = value_changed ? state->TextLen : 0;
5429+
}
53995430
}
54005431
}
54015432

54025433
// Handle reapplying final data on deactivation (see InputTextDeactivateHook() for details)
54035434
// This is used when e.g. losing focus or tabbing out into another InputText() which may already be using the temp buffer.
54045435
if (g.InputTextDeactivatedState.ID == id)
54055436
{
5406-
// The state only exists after an Edit. More-over we cannot use IsItemDeactivatedAfterEdit().
5407-
if (g.ActiveId != id && IsItemDeactivated() && !is_readonly && strcmp(g.InputTextDeactivatedState.TextA.Data, buf) != 0)
5408-
{
5409-
apply_new_text = g.InputTextDeactivatedState.TextA.Data;
5410-
apply_new_text_length = g.InputTextDeactivatedState.TextA.Size - 1;
5411-
value_changed = true;
5412-
//IMGUI_DEBUG_LOG("InputText(): apply Deactivated data for 0x%08X: \"%.*s\".\n", id, apply_new_text_length, apply_new_text);
5413-
}
5437+
// The state only exists after an Edit. IsItemDeactivatedAfterEdit() is not valid in every code path (see "widgets_inputtext_status_noliveedit" test).
5438+
if ((g.ActiveId != id && IsItemDeactivated()) || (g.ActiveId == id && (flags & ImGuiInputTextFlags_TempInput)))
5439+
if (!is_readonly && strcmp(g.InputTextDeactivatedState.TextA.Data, buf) != 0)
5440+
{
5441+
apply_new_text = g.InputTextDeactivatedState.TextA.Data;
5442+
apply_new_text_length = g.InputTextDeactivatedState.TextA.Size - 1;
5443+
value_changed = true;
5444+
//IMGUI_DEBUG_LOG("InputText(): apply Deactivated data for 0x%08X: \"%.*s\".\n", id, apply_new_text_length, apply_new_text);
5445+
}
54145446
g.InputTextDeactivatedState.ID = 0;
54155447
}
54165448

0 commit comments

Comments
 (0)