From 21d03edcb0cdad9e870f94f55ebb1d6deb54bc30 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 9 Sep 2024 16:22:55 +0200 Subject: [PATCH] InputText: renamed namespace from stb_texture structure and added an indirection. --- imgui_internal.h | 27 +++++++------- imgui_widgets.cpp | 90 +++++++++++++++++++++++++++-------------------- 2 files changed, 66 insertions(+), 51 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index 5d23ebfef4fe..ab7cbc1f3bf3 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -216,6 +216,8 @@ namespace ImStb } // namespace ImStb +typedef ImStb::STB_TexteditState ImStbTexteditState; + //----------------------------------------------------------------------------- // [SECTION] Macros //----------------------------------------------------------------------------- @@ -1110,11 +1112,13 @@ struct IMGUI_API ImGuiInputTextDeactivatedState ImGuiInputTextDeactivatedState() { memset(this, 0, sizeof(*this)); } void ClearFreeMemory() { ID = 0; TextA.clear(); } }; + // Internal state of the currently focused/edited text input box // For a given item ID, access with ImGui::GetInputTextState() struct IMGUI_API ImGuiInputTextState { ImGuiContext* Ctx; // parent UI context (needs to be set explicitly by parent). + ImStbTexteditState* Stb; // State for stb_textedit.h ImGuiID ID; // widget id owning the text state int CurLenW, CurLenA; // we need to maintain our buffer length in both UTF-8 and wchar format. UTF-8 length is valid even if TextA is not. ImVector TextW; // edit buffer, we need to persist but can't guarantee the persistence of the user-provided buffer. so we copy into own buffer. @@ -1123,7 +1127,7 @@ struct IMGUI_API ImGuiInputTextState bool TextAIsValid; // temporary UTF8 buffer is not initially valid before we make the widget active (until then we pull the data from user argument) int BufCapacityA; // end-user buffer capacity ImVec2 Scroll; // horizontal offset (managed manually) + vertical scrolling (pulled from child window's own Scroll.y) - ImStb::STB_TexteditState Stb; // state for stb_textedit.h + float CursorAnim; // timer for cursor blink, reset on every user action so the cursor reappears immediately bool CursorFollow; // set when we want scrolling to follow the current cursor position (not always!) bool SelectedAllMouseLock; // after a double-click to select all, we ignore further mouse drags to update selection @@ -1133,22 +1137,21 @@ struct IMGUI_API ImGuiInputTextState int ReloadSelectionStart; // POSITIONS ARE IN IMWCHAR units *NOT* UTF-8 this is why this is not exposed yet. int ReloadSelectionEnd; - ImGuiInputTextState() { memset(this, 0, sizeof(*this)); } + ImGuiInputTextState(); + ~ImGuiInputTextState(); void ClearText() { CurLenW = CurLenA = 0; TextW[0] = 0; TextA[0] = 0; CursorClamp(); } void ClearFreeMemory() { TextW.clear(); TextA.clear(); InitialTextA.clear(); } - int GetUndoAvailCount() const { return Stb.undostate.undo_point; } - int GetRedoAvailCount() const { return IMSTB_TEXTEDIT_UNDOSTATECOUNT - Stb.undostate.redo_point; } void OnKeyPressed(int key); // Cannot be inline because we call in code in stb_textedit.h implementation // Cursor & Selection void CursorAnimReset() { CursorAnim = -0.30f; } // After a user-input the cursor stays on for a while without blinking - void CursorClamp() { Stb.cursor = ImMin(Stb.cursor, CurLenW); Stb.select_start = ImMin(Stb.select_start, CurLenW); Stb.select_end = ImMin(Stb.select_end, CurLenW); } - bool HasSelection() const { return Stb.select_start != Stb.select_end; } - void ClearSelection() { Stb.select_start = Stb.select_end = Stb.cursor; } - int GetCursorPos() const { return Stb.cursor; } - int GetSelectionStart() const { return Stb.select_start; } - int GetSelectionEnd() const { return Stb.select_end; } - void SelectAll() { Stb.select_start = 0; Stb.cursor = Stb.select_end = CurLenW; Stb.has_preferred_x = 0; } + void CursorClamp() { Stb->cursor = ImMin(Stb->cursor, CurLenW); Stb->select_start = ImMin(Stb->select_start, CurLenW); Stb->select_end = ImMin(Stb->select_end, CurLenW); } + bool HasSelection() const { return Stb->select_start != Stb->select_end; } + void ClearSelection() { Stb->select_start = Stb->select_end = Stb->cursor; } + int GetCursorPos() const { return Stb->cursor; } + int GetSelectionStart() const { return Stb->select_start; } + int GetSelectionEnd() const { return Stb->select_end; } + void SelectAll() { Stb->select_start = 0; Stb->cursor = Stb->select_end = CurLenW; Stb->has_preferred_x = 0; } // Reload user buf (WIP #2890) // If you modify underlying user-passed const char* while active you need to call this (InputText V2 may lift this) @@ -1156,7 +1159,7 @@ struct IMGUI_API ImGuiInputTextState // if (ImGuiInputTextState* state = ImGui::GetInputTextState(id)) // id may be ImGui::GetItemID() is last item // state->ReloadUserBufAndSelectAll(); void ReloadUserBufAndSelectAll() { ReloadUserBuf = true; ReloadSelectionStart = 0; ReloadSelectionEnd = INT_MAX; } - void ReloadUserBufAndKeepSelection() { ReloadUserBuf = true; ReloadSelectionStart = Stb.select_start; ReloadSelectionEnd = Stb.select_end; } + void ReloadUserBufAndKeepSelection() { ReloadUserBuf = true; ReloadSelectionStart = Stb->select_start; ReloadSelectionEnd = Stb->select_end; } void ReloadUserBufAndMoveToEnd() { ReloadUserBuf = true; ReloadSelectionStart = ReloadSelectionEnd = INT_MAX; } }; diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 7b86fb8bef9a..f359277273f8 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -4029,9 +4029,21 @@ static void stb_textedit_replace(ImGuiInputTextState* str, STB_TexteditState* st } // namespace ImStb +// We added an extra indirection where 'Stb' is heap-allocated, in order facilitate the work of bindings generators. +ImGuiInputTextState::ImGuiInputTextState() +{ + memset(this, 0, sizeof(*this)); + Stb = IM_NEW(ImStbTexteditState); +} + +ImGuiInputTextState::~ImGuiInputTextState() +{ + IM_DELETE(Stb); +} + void ImGuiInputTextState::OnKeyPressed(int key) { - stb_textedit_key(this, &Stb, key); + stb_textedit_key(this, Stb, key); CursorFollow = true; CursorAnimReset(); } @@ -4229,7 +4241,7 @@ static void InputTextReconcileUndoStateAfterUserCallback(ImGuiInputTextState* st const int insert_len = new_last_diff - first_diff + 1; const int delete_len = old_last_diff - first_diff + 1; if (insert_len > 0 || delete_len > 0) - if (IMSTB_TEXTEDIT_CHARTYPE* p = stb_text_createundo(&state->Stb.undostate, first_diff, delete_len, insert_len)) + if (IMSTB_TEXTEDIT_CHARTYPE* p = stb_text_createundo(&state->Stb->undostate, first_diff, delete_len, insert_len)) for (int i = 0; i < delete_len; i++) p[i] = ImStb::STB_TEXTEDIT_GETCHAR(state, first_diff + i); } @@ -4371,7 +4383,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ float scroll_y = is_multiline ? draw_window->Scroll.y : FLT_MAX; const bool init_reload_from_user_buf = (state != NULL && state->ReloadUserBuf); - const bool init_changed_specs = (state != NULL && state->Stb.single_line != !is_multiline); // state != NULL means its our state. + const bool init_changed_specs = (state != NULL && state->Stb->single_line != !is_multiline); // state != NULL means its our state. const bool init_make_active = (user_clicked || user_scroll_finish || input_requested_by_nav); const bool init_state = (init_make_active || user_scroll_active); if ((init_state && g.ActiveId != id) || init_changed_specs || init_reload_from_user_buf) @@ -4417,13 +4429,13 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ else { state->Scroll = ImVec2(0.0f, 0.0f); - stb_textedit_initialize_state(&state->Stb, !is_multiline); + stb_textedit_initialize_state(state->Stb, !is_multiline); } if (init_reload_from_user_buf) { - state->Stb.select_start = state->ReloadSelectionStart; - state->Stb.cursor = state->Stb.select_end = state->ReloadSelectionEnd; + state->Stb->select_start = state->ReloadSelectionStart; + state->Stb->cursor = state->Stb->select_end = state->ReloadSelectionEnd; state->CursorClamp(); } else if (!is_multiline) @@ -4437,7 +4449,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } if (flags & ImGuiInputTextFlags_AlwaysOverwrite) - state->Stb.insert_mode = 1; // stb field name is indeed incorrect (see #2863) + state->Stb->insert_mode = 1; // stb field name is indeed incorrect (see #2863) } const bool is_osx = io.ConfigMacOSXBehaviors; @@ -4545,34 +4557,34 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } else if (hovered && io.MouseClickedCount[0] >= 2 && !io.KeyShift) { - stb_textedit_click(state, &state->Stb, mouse_x, mouse_y); + stb_textedit_click(state, state->Stb, mouse_x, mouse_y); const int multiclick_count = (io.MouseClickedCount[0] - 2); if ((multiclick_count % 2) == 0) { // Double-click: Select word // We always use the "Mac" word advance for double-click select vs CTRL+Right which use the platform dependent variant: // FIXME: There are likely many ways to improve this behavior, but there's no "right" behavior (depends on use-case, software, OS) - const bool is_bol = (state->Stb.cursor == 0) || ImStb::STB_TEXTEDIT_GETCHAR(state, state->Stb.cursor - 1) == '\n'; - if (STB_TEXT_HAS_SELECTION(&state->Stb) || !is_bol) + const bool is_bol = (state->Stb->cursor == 0) || ImStb::STB_TEXTEDIT_GETCHAR(state, state->Stb->cursor - 1) == '\n'; + if (STB_TEXT_HAS_SELECTION(state->Stb) || !is_bol) state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT); //state->OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT); - if (!STB_TEXT_HAS_SELECTION(&state->Stb)) - ImStb::stb_textedit_prep_selection_at_cursor(&state->Stb); - state->Stb.cursor = ImStb::STB_TEXTEDIT_MOVEWORDRIGHT_MAC(state, state->Stb.cursor); - state->Stb.select_end = state->Stb.cursor; - ImStb::stb_textedit_clamp(state, &state->Stb); + if (!STB_TEXT_HAS_SELECTION(state->Stb)) + ImStb::stb_textedit_prep_selection_at_cursor(state->Stb); + state->Stb->cursor = ImStb::STB_TEXTEDIT_MOVEWORDRIGHT_MAC(state, state->Stb->cursor); + state->Stb->select_end = state->Stb->cursor; + ImStb::stb_textedit_clamp(state, state->Stb); } else { // Triple-click: Select line - const bool is_eol = ImStb::STB_TEXTEDIT_GETCHAR(state, state->Stb.cursor) == '\n'; + const bool is_eol = ImStb::STB_TEXTEDIT_GETCHAR(state, state->Stb->cursor) == '\n'; state->OnKeyPressed(STB_TEXTEDIT_K_LINESTART); state->OnKeyPressed(STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT); state->OnKeyPressed(STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT); if (!is_eol && is_multiline) { - ImSwap(state->Stb.select_start, state->Stb.select_end); - state->Stb.cursor = state->Stb.select_end; + ImSwap(state->Stb->select_start, state->Stb->select_end); + state->Stb->cursor = state->Stb->select_end; } state->CursorFollow = false; } @@ -4583,15 +4595,15 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (hovered) { if (io.KeyShift) - stb_textedit_drag(state, &state->Stb, mouse_x, mouse_y); + stb_textedit_drag(state, state->Stb, mouse_x, mouse_y); else - stb_textedit_click(state, &state->Stb, mouse_x, mouse_y); + stb_textedit_click(state, state->Stb, mouse_x, mouse_y); state->CursorAnimReset(); } } else if (io.MouseDown[0] && !state->SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f)) { - stb_textedit_drag(state, &state->Stb, mouse_x, mouse_y); + stb_textedit_drag(state, state->Stb, mouse_x, mouse_y); state->CursorAnimReset(); state->CursorFollow = true; } @@ -4644,7 +4656,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ IM_ASSERT(state != NULL); const int row_count_per_page = ImMax((int)((inner_size.y - style.FramePadding.y) / g.FontSize), 1); - state->Stb.row_count_per_page = row_count_per_page; + state->Stb->row_count_per_page = row_count_per_page; const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0); const bool is_wordmove_key_down = is_osx ? io.KeyAlt : io.KeyCtrl; // OS X style: Text editing cursor movement using Alt instead of Ctrl @@ -4751,8 +4763,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // Cut, Copy if (g.PlatformIO.Platform_SetClipboardTextFn != NULL) { - const int ib = state->HasSelection() ? ImMin(state->Stb.select_start, state->Stb.select_end) : 0; - const int ie = state->HasSelection() ? ImMax(state->Stb.select_start, state->Stb.select_end) : state->CurLenW; + const int ib = state->HasSelection() ? ImMin(state->Stb->select_start, state->Stb->select_end) : 0; + const int ie = state->HasSelection() ? ImMax(state->Stb->select_start, state->Stb->select_end) : state->CurLenW; const int clipboard_data_len = ImTextCountUtf8BytesFromStr(state->TextW.Data + ib, state->TextW.Data + ie) + 1; char* clipboard_data = (char*)IM_ALLOC(clipboard_data_len * sizeof(char)); ImTextStrToUtf8(clipboard_data, clipboard_data_len, state->TextW.Data + ib, state->TextW.Data + ie); @@ -4764,7 +4776,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (!state->HasSelection()) state->SelectAll(); state->CursorFollow = true; - stb_textedit_cut(state, &state->Stb); + stb_textedit_cut(state, state->Stb); } } else if (is_paste) @@ -4786,7 +4798,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ clipboard_filtered[clipboard_filtered_len] = 0; if (clipboard_filtered_len > 0) // If everything was filtered, ignore the pasting operation { - stb_textedit_paste(state, &state->Stb, clipboard_filtered, clipboard_filtered_len); + stb_textedit_paste(state, state->Stb, clipboard_filtered, clipboard_filtered_len); state->CursorFollow = true; } MemFree(clipboard_filtered); @@ -4813,7 +4825,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ apply_new_text_length = 0; value_changed = true; IMSTB_TEXTEDIT_CHARTYPE empty_string; - stb_textedit_replace(state, &state->Stb, &empty_string, 0); + stb_textedit_replace(state, state->Stb, &empty_string, 0); } else if (strcmp(buf, state->InitialTextA.Data) != 0) { @@ -4828,7 +4840,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ w_text.resize(ImTextCountCharsFromUtf8(apply_new_text, apply_new_text + apply_new_text_length) + 1); ImTextStrFromUtf8(w_text.Data, w_text.Size, apply_new_text, apply_new_text + apply_new_text_length); } - stb_textedit_replace(state, &state->Stb, w_text.Data, (apply_new_text_length > 0) ? (w_text.Size - 1) : 0); + stb_textedit_replace(state, state->Stb, w_text.Data, (apply_new_text_length > 0) ? (w_text.Size - 1) : 0); } } @@ -4903,9 +4915,9 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // We have to convert from wchar-positions to UTF-8-positions, which can be pretty slow (an incentive to ditch the ImWchar buffer, see https://github.com/nothings/stb/issues/188) ImWchar* text = state->TextW.Data; - const int utf8_cursor_pos = callback_data.CursorPos = ImTextCountUtf8BytesFromStr(text, text + state->Stb.cursor); - const int utf8_selection_start = callback_data.SelectionStart = ImTextCountUtf8BytesFromStr(text, text + state->Stb.select_start); - const int utf8_selection_end = callback_data.SelectionEnd = ImTextCountUtf8BytesFromStr(text, text + state->Stb.select_end); + const int utf8_cursor_pos = callback_data.CursorPos = ImTextCountUtf8BytesFromStr(text, text + state->Stb->cursor); + const int utf8_selection_start = callback_data.SelectionStart = ImTextCountUtf8BytesFromStr(text, text + state->Stb->select_start); + const int utf8_selection_end = callback_data.SelectionEnd = ImTextCountUtf8BytesFromStr(text, text + state->Stb->select_end); // Call user code callback(&callback_data); @@ -4916,9 +4928,9 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ IM_ASSERT(callback_data.BufSize == state->BufCapacityA); IM_ASSERT(callback_data.Flags == flags); const bool buf_dirty = callback_data.BufDirty; - if (callback_data.CursorPos != utf8_cursor_pos || buf_dirty) { state->Stb.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos); state->CursorFollow = true; } - if (callback_data.SelectionStart != utf8_selection_start || buf_dirty) { state->Stb.select_start = (callback_data.SelectionStart == callback_data.CursorPos) ? state->Stb.cursor : ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionStart); } - if (callback_data.SelectionEnd != utf8_selection_end || buf_dirty) { state->Stb.select_end = (callback_data.SelectionEnd == callback_data.SelectionStart) ? state->Stb.select_start : ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionEnd); } + if (callback_data.CursorPos != utf8_cursor_pos || buf_dirty) { state->Stb->cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos); state->CursorFollow = true; } + if (callback_data.SelectionStart != utf8_selection_start || buf_dirty) { state->Stb->select_start = (callback_data.SelectionStart == callback_data.CursorPos) ? state->Stb->cursor : ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionStart); } + if (callback_data.SelectionEnd != utf8_selection_end || buf_dirty) { state->Stb->select_end = (callback_data.SelectionEnd == callback_data.SelectionStart) ? state->Stb->select_start : ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionEnd); } if (buf_dirty) { // Callback may update buffer and thus set buf_dirty even in read-only mode. @@ -5040,13 +5052,13 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ int searches_remaining = 0; if (render_cursor) { - searches_input_ptr[0] = text_begin + state->Stb.cursor; + searches_input_ptr[0] = text_begin + state->Stb->cursor; searches_result_line_no[0] = -1; searches_remaining++; } if (render_selection) { - searches_input_ptr[1] = text_begin + ImMin(state->Stb.select_start, state->Stb.select_end); + searches_input_ptr[1] = text_begin + ImMin(state->Stb->select_start, state->Stb->select_end); searches_result_line_no[1] = -1; searches_remaining++; } @@ -5122,8 +5134,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ const ImVec2 draw_scroll = ImVec2(state->Scroll.x, 0.0f); if (render_selection) { - const ImWchar* text_selected_begin = text_begin + ImMin(state->Stb.select_start, state->Stb.select_end); - const ImWchar* text_selected_end = text_begin + ImMax(state->Stb.select_start, state->Stb.select_end); + const ImWchar* text_selected_begin = text_begin + ImMin(state->Stb->select_start, state->Stb->select_end); + const ImWchar* text_selected_end = text_begin + ImMax(state->Stb->select_start, state->Stb->select_end); ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg, render_cursor ? 1.0f : 0.6f); // FIXME: current code flow mandate that render_cursor is always true here, we are leaving the transparent one for tests. float bg_offy_up = is_multiline ? 0.0f : -1.0f; // FIXME: those offsets should be part of the style? they don't play so well with multi-line selection. @@ -5244,7 +5256,7 @@ void ImGui::DebugNodeInputTextState(ImGuiInputTextState* state) { #ifndef IMGUI_DISABLE_DEBUG_TOOLS ImGuiContext& g = *GImGui; - ImStb::STB_TexteditState* stb_state = &state->Stb; + 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); DebugLocateItemOnHover(state->ID);