From 1674fe96ff57e4540b6c1860e61aa7fcc06642fe Mon Sep 17 00:00:00 2001
From: ocornut <omarcornut@gmail.com>
Date: Thu, 5 Sep 2024 16:31:41 +0200
Subject: [PATCH] InputText: amends: add stb_textedit_text() api. (#7925)

It seems sensible to push this change in stb_textedit repo eventually.
---
 imgui_internal.h  |  1 +
 imgui_widgets.cpp | 23 +++++++++++-------
 imstb_textedit.h  | 61 ++++++++++++++++++++++++++++-------------------
 3 files changed, 52 insertions(+), 33 deletions(-)

diff --git a/imgui_internal.h b/imgui_internal.h
index 4dfcfce9a..e6f3f8ec5 100644
--- a/imgui_internal.h
+++ b/imgui_internal.h
@@ -1130,6 +1130,7 @@ struct IMGUI_API ImGuiInputTextState
     void        ClearText()                 { CurLenA = 0; TextA[0] = 0; CursorClamp(); }
     void        ClearFreeMemory()           { TextA.clear(); InitialTextA.clear(); }
     void        OnKeyPressed(int key);      // Cannot be inline because we call in code in stb_textedit.h implementation
+    void        OnCharPressed(unsigned int c);
 
     // Cursor & Selection
     void        CursorAnimReset();
diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp
index 64e701407..f35563119 100644
--- a/imgui_widgets.cpp
+++ b/imgui_widgets.cpp
@@ -3889,7 +3889,6 @@ namespace ImStb
 static int     STB_TEXTEDIT_STRINGLEN(const ImGuiInputTextState* obj)                             { return obj->CurLenA; }
 static char    STB_TEXTEDIT_GETCHAR(const ImGuiInputTextState* obj, int idx)                      { IM_ASSERT(idx <= obj->CurLenA); return obj->TextA[idx]; }
 static float   STB_TEXTEDIT_GETWIDTH(ImGuiInputTextState* obj, int line_start_idx, int char_idx)  { unsigned int c; ImTextCharFromUtf8(&c, obj->TextA.Data + line_start_idx + char_idx, obj->TextA.Data + obj->TextA.Size); if ((ImWchar)c == '\n') return IMSTB_TEXTEDIT_GETWIDTH_NEWLINE; ImGuiContext& g = *obj->Ctx; return g.Font->GetCharAdvance((ImWchar)c) * g.FontScale; }
-static int     STB_TEXTEDIT_KEYTOTEXT(int key)                                                    { return key >= 0x200000 ? 0 : key; }
 static char    STB_TEXTEDIT_NEWLINE = '\n';
 static void    STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, ImGuiInputTextState* obj, int line_start_idx)
 {
@@ -4101,12 +4100,18 @@ ImGuiInputTextState::~ImGuiInputTextState()
 
 void ImGuiInputTextState::OnKeyPressed(int key)
 {
-    //We prematurely convert the key to a UTF8 byte sequence, even for keys where that doesn't even make sense (e.g. arrow keys).
-    //Not optimal but stb_textedit_key will only use the UTF8 values for valid character keys anyways.
-    //The changes we had to make to stb_textedit_key make it very much UTF8 specific which is not too great.
+    stb_textedit_key(this, Stb, key);
+    CursorFollow = true;
+    CursorAnimReset();
+}
+
+void ImGuiInputTextState::OnCharPressed(unsigned int c)
+{
+    // Convert the key to a UTF8 byte sequence.
+    // The changes we had to make to stb_textedit_key made it very much UTF-8 specific which is not too great.
     char utf8[5];
-    ImTextCharToUtf8(utf8, key);
-    stb_textedit_key(this, Stb, key, utf8, (int)strlen(utf8));
+    ImTextCharToUtf8(utf8, c);
+    stb_textedit_text(this, Stb, utf8, (int)strlen(utf8));
     CursorFollow = true;
     CursorAnimReset();
 }
@@ -4674,7 +4679,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
             {
                 unsigned int c = '\t'; // Insert TAB
                 if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data))
-                    state->OnKeyPressed((int)c);
+                    state->OnCharPressed(c);
             }
             // FIXME: Implement Shift+Tab
             /*
@@ -4697,7 +4702,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
                     if (c == '\t') // Skip Tab, see above.
                         continue;
                     if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data))
-                        state->OnKeyPressed((int)c);
+                        state->OnCharPressed(c);
                 }
 
             // Consume characters
@@ -4781,7 +4786,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
             {
                 unsigned int c = '\n'; // Insert new line
                 if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data))
-                    state->OnKeyPressed((int)c);
+                    state->OnCharPressed(c);
             }
         }
         else if (is_cancel)
diff --git a/imstb_textedit.h b/imstb_textedit.h
index 22122c08b..b7a761c85 100644
--- a/imstb_textedit.h
+++ b/imstb_textedit.h
@@ -211,6 +211,7 @@
 //    int  stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state)
 //    int  stb_textedit_paste(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int len)
 //    void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXEDIT_KEYTYPE key)
+//    void stb_textedit_text(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_CHARTYPE *text, int text_len)
 //
 //    Each of these functions potentially updates the string and updates the
 //    state.
@@ -245,7 +246,12 @@
 //          various definitions like STB_TEXTEDIT_K_LEFT have the is-key-event bit
 //          set, and make STB_TEXTEDIT_KEYTOCHAR check that the is-key-event bit is
 //          clear. STB_TEXTEDIT_KEYTYPE defaults to int, but you can #define it to
-//          anything other type you wante before including.
+//          anything other type you want before including.
+//          if the STB_TEXTEDIT_KEYTOTEXT function is defined, selected keys are
+//          transformed into text and stb_textedit_text() is automatically called.
+//
+//      text: [DEAR IMGUI] added 2024-09
+//          call this to text inputs sent to the textfield.
 //
 //
 //   When rendering, you can read the cursor position and selection state from
@@ -733,37 +739,44 @@ static int stb_textedit_paste_internal(IMSTB_TEXTEDIT_STRING *str, STB_TexteditS
 #define STB_TEXTEDIT_KEYTYPE int
 #endif
 
+// [DEAR IMGUI] Added stb_textedit_text(), extracted out and called by stb_textedit_key() for backward compatibility.
+static void stb_textedit_text(IMSTB_TEXTEDIT_STRING* str, STB_TexteditState* state, const IMSTB_TEXTEDIT_CHARTYPE* text, int text_len)
+{
+   // can't add newline in single-line mode
+   if (text[0] == '\n' && state->single_line)
+      return;
+
+   if (state->insert_mode && !STB_TEXT_HAS_SELECTION(state) && state->cursor < STB_TEXTEDIT_STRINGLEN(str)) {
+      stb_text_makeundo_replace(str, state, state->cursor, 1, 1);
+      STB_TEXTEDIT_DELETECHARS(str, state->cursor, 1);
+      if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, text_len)) {
+         state->cursor += text_len;
+         state->has_preferred_x = 0;
+      }
+   }
+   else {
+      stb_textedit_delete_selection(str, state); // implicitly clamps
+      if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, text_len)) {
+         stb_text_makeundo_insert(state, state->cursor, text_len);
+         state->cursor += text_len;
+         state->has_preferred_x = 0;
+      }
+   }
+}
+
 // API key: process a keyboard input
-//[DEAR IMGUI] In addition to the key we also pass in the decoded UTF8 byte sequence, if it is a character key.
-//This is a bit ugly and only makes sense for UTF8. One could think of other solutions that wouldn't make this function so UTF8 specific.
-//If the idea is to push the changes upstream to stb_textedit it might be worth thinking about but since this is just for [DEAR IMGUI], it might not be worth the complication
-static void stb_textedit_key(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_KEYTYPE key, const IMSTB_TEXTEDIT_CHARTYPE* decoded, int decoded_size)
+static void stb_textedit_key(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_KEYTYPE key)
 {
 retry:
    switch (key) {
       default: {
+#ifdef STB_TEXTEDIT_KEYTOTEXT
          int c = STB_TEXTEDIT_KEYTOTEXT(key);
          if (c > 0) {
-            // can't add newline in single-line mode
-            if (c == '\n' && state->single_line)
-               break;
-
-            if (state->insert_mode && !STB_TEXT_HAS_SELECTION(state) && state->cursor < STB_TEXTEDIT_STRINGLEN(str)) {
-               stb_text_makeundo_replace(str, state, state->cursor, 1, 1);
-               STB_TEXTEDIT_DELETECHARS(str, state->cursor, 1);
-               if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, decoded, decoded_size)) {
-                  state->cursor += decoded_size;
-                  state->has_preferred_x = 0;
-               }
-            } else {
-               stb_textedit_delete_selection(str,state); // implicitly clamps
-               if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, decoded, decoded_size)) {
-                  stb_text_makeundo_insert(state, state->cursor, decoded_size);
-                  state->cursor += decoded_size;
-                  state->has_preferred_x = 0;
-               }
-            }
+            IMSTB_TEXTEDIT_CHARTYPE ch = (IMSTB_TEXTEDIT_CHARTYPE)c;
+            stb_textedit_text(str, state, &ch, 1);
          }
+#endif
          break;
       }