From 64d6c30562e1e866cd3be6db6e5b9c9529f1f3f1 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 7 Jun 2022 15:24:52 +0200 Subject: [PATCH] InputText: Fixed an undo-state corruption issue when editing buffer before reactivating item. (#4947) + Metrics: Added "InputText" section. --- docs/CHANGELOG.txt | 2 ++ imgui.cpp | 7 +++++++ imgui.h | 2 +- imgui_internal.h | 1 + imgui_widgets.cpp | 46 ++++++++++++++++++++++++++++++++++++++++++---- 5 files changed, 53 insertions(+), 5 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 155a85bba..7a59c214d 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -88,6 +88,7 @@ Other Changes: trickled with the new input queue (happened on some backends only). (#2467, #1336) - InputText: Fixed a one-frame display glitch where pressing Escape to revert after a deletion would lead to small garbage being displayed for one frame. Curiously a rather old bug! (#3008) +- InputText: Fixed an undo-state corruption issue when editing buffer before reactivating item. (#4947) - Tables: Fixed incorrect border height used for logic when resizing one of several synchronized instance of a same table ID, when instances have a different height. (#3955). - Tables: Fixed incorrect auto-fit of parent windows when using non-resizable weighted columns. (#5276) @@ -108,6 +109,7 @@ Other Changes: you have a UTF-8 text encoding issue or a font loading issue. [@LaMarche05, @ocornut] - Demo: Add better demo of how to use SetNextFrameWantCaptureMouse()/SetNextFrameWantCaptureKeyboard(). - Metrics: Added a "UTF-8 Encoding Viewer" section using the aforementioned DebugTextEncoding() function. +- Metrics: Added "InputText" section to visualize internal state (#4947, #4949). - Misc: Fixed calling GetID("label") _before_ a widget emitting this item inside a group (such as InputInt()) from causing an assertion when closing the group. (#5181). - Misc: Fixed IsAnyItemHovered() returning false when using navigation. diff --git a/imgui.cpp b/imgui.cpp index 4af42dd45..a9d027c3b 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -12480,6 +12480,13 @@ void ImGui::ShowMetricsWindow(bool* p_open) TreePop(); } + // Details for InputText + if (TreeNode("InputText")) + { + DebugNodeInputTextState(&g.InputTextState); + TreePop(); + } + // Details for Docking #ifdef IMGUI_HAS_DOCK if (TreeNode("Docking")) diff --git a/imgui.h b/imgui.h index f89cb0252..c9bdb2d7c 100644 --- a/imgui.h +++ b/imgui.h @@ -65,7 +65,7 @@ Index of this file: // Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals. Work in progress versions typically starts at XYY99 then bounce up to XYY00, XYY01 etc. when release tagging happens) #define IMGUI_VERSION "1.88 WIP" -#define IMGUI_VERSION_NUM 18726 +#define IMGUI_VERSION_NUM 18727 #define IMGUI_CHECKVERSION() ImGui::DebugCheckVersionAndDataLayout(IMGUI_VERSION, sizeof(ImGuiIO), sizeof(ImGuiStyle), sizeof(ImVec2), sizeof(ImVec4), sizeof(ImDrawVert), sizeof(ImDrawIdx)) #define IMGUI_HAS_TABLE diff --git a/imgui_internal.h b/imgui_internal.h index 69277540e..95f316d63 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2878,6 +2878,7 @@ namespace ImGui IMGUI_API void DebugNodeTabBar(ImGuiTabBar* tab_bar, const char* label); IMGUI_API void DebugNodeTable(ImGuiTable* table); IMGUI_API void DebugNodeTableSettings(ImGuiTableSettings* settings); + IMGUI_API void DebugNodeInputTextState(ImGuiInputTextState* state); IMGUI_API void DebugNodeWindow(ImGuiWindow* window, const char* label); IMGUI_API void DebugNodeWindowSettings(ImGuiWindowSettings* settings); IMGUI_API void DebugNodeWindowsList(ImVector* windows, const char* label); diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 78b2c9271..94de60ae9 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -3571,6 +3571,7 @@ bool ImGui::InputDouble(const char* label, double* v, double step, double step_f // - InputTextReindexLines() [Internal] // - InputTextReindexLinesRange() [Internal] // - InputTextEx() [Internal] +// - DebugNodeInputTextState() [Internal] //------------------------------------------------------------------------- bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) @@ -4048,17 +4049,21 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ state->InitialTextA.resize(buf_len + 1); // UTF-8. we use +1 to make sure that .Data is always pointing to at least an empty string. memcpy(state->InitialTextA.Data, buf, buf_len + 1); + // Preserve cursor position and undo/redo stack if we come back to same widget + // FIXME: Since we reworked this on 2022/06, may want to differenciate recycle_cursor vs recycle_undostate? + bool recycle_state = (state->ID == id && !init_changed_specs); + if (recycle_state && (state->CurLenA != buf_len || (state->TextAIsValid && strncmp(state->TextA.Data, buf, buf_len) != 0))) + recycle_state = false; + // Start edition const char* buf_end = NULL; + state->ID = id; state->TextW.resize(buf_size + 1); // wchar count <= UTF-8 count. we use +1 to make sure that .Data is always pointing to at least an empty string. state->TextA.resize(0); state->TextAIsValid = false; // TextA is not valid yet (we will display buf until then) state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, buf_size, buf, NULL, &buf_end); state->CurLenA = (int)(buf_end - buf); // We can't get the result from ImStrncpy() above because it is not UTF-8 aware. Here we'll cut off malformed UTF-8. - // Preserve cursor position and undo/redo stack if we come back to same widget - // FIXME: For non-readonly widgets we might be able to require that TextAIsValid && TextA == buf ? (untested) and discard undo stack if user buffer has changed. - const bool recycle_state = (state->ID == id && !init_changed_specs); if (recycle_state) { // Recycle existing cursor/selection/undo stack but clamp position @@ -4067,7 +4072,6 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } else { - state->ID = id; state->ScrollX = 0.0f; stb_textedit_initialize_state(&state->Stb, !is_multiline); } @@ -4816,6 +4820,40 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ return value_changed; } +void ImGui::DebugNodeInputTextState(ImGuiInputTextState* state) +{ +#ifndef IMGUI_DISABLE_METRICS_WINDOW + ImGuiContext& g = *GImGui; + ImStb::STB_TexteditState* stb_state = &state->Stb; + ImStb::StbUndoState* undo_state = &stb_state->undostate; + Text("ID: 0x%08X, ActiveID: 0x%08X", state->ID, g.ActiveId); + Text("CurLenW: %d, CurLenA: %d, Cursor: %d, Selection: %d..%d", state->CurLenA, state->CurLenW, stb_state->cursor, stb_state->select_start, stb_state->select_end); + Text("undo_point: %d, redo_point: %d, undo_char_point: %d, redo_char_point: %d", undo_state->undo_point, undo_state->redo_point, undo_state->undo_char_point, undo_state->redo_char_point); + if (BeginChild("undopoints", ImVec2(0.0f, GetTextLineHeight() * 15), true)) // Visualize undo state + { + PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + for (int n = 0; n < STB_TEXTEDIT_UNDOSTATECOUNT; n++) + { + ImStb::StbUndoRecord* undo_rec = &undo_state->undo_rec[n]; + const char undo_rec_type = (n < undo_state->undo_point) ? 'u' : (n >= undo_state->redo_point) ? 'r' : ' '; + if (undo_rec_type == ' ') + BeginDisabled(); + char buf[64] = ""; + if (undo_rec_type != ' ' && undo_rec->char_storage != -1) + ImTextStrToUtf8(buf, IM_ARRAYSIZE(buf), undo_state->undo_char + undo_rec->char_storage, undo_state->undo_char + undo_rec->char_storage + undo_rec->insert_length); + Text("%c [%02d] where %03d, insert %03d, delete %03d, char_storage %03d \"%s\"", + undo_rec_type, n, undo_rec->where, undo_rec->insert_length, undo_rec->delete_length, undo_rec->char_storage, buf); + if (undo_rec_type == ' ') + EndDisabled(); + } + PopStyleVar(); + } + EndChild(); +#else + IM_UNUSED(state); +#endif +} + //------------------------------------------------------------------------- // [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc. //-------------------------------------------------------------------------