Browse Source

Support for international text input in UTF-8. Added ImeSetInputScreenPosFn(). Removed text_end parameter from SetClipboardTextF

ocornut 11 years ago
parent
commit
b86505bf2f
2 changed files with 281 additions and 95 deletions
  1. 246 77
      imgui.cpp
  2. 35 18
      imgui.h

+ 246 - 77
imgui.cpp

@@ -1,4 +1,4 @@
-// ImGui library v1.12
+// ImGui library v1.12+
 // See ImGui::ShowTestWindow() for sample code.
 // Read 'Programmer guide' below for notes on how to setup ImGui in your codebase.
 // Get latest version at https://github.com/ocornut/imgui
@@ -139,6 +139,7 @@
  API BREAKING CHANGES
  ====================
 
+  - 2014/09/25 (1.13) removed 'text_end' parameter from IO.SetClipboardTextFn (the string is now always zero-terminated for simplicity)
   - 2014/09/24 (1.12) renamed SetFontScale() to SetWindowFontScale()
   - 2014/09/24 (1.12) moved IM_MALLOC/IM_REALLOC/IM_FREE preprocessor defines to IO.MemAllocFn/IO.MemReallocFn/IO.MemFreeFn
   - 2014/08/30 (1.09) removed IO.FontHeight (now computed automatically)
@@ -246,7 +247,7 @@ static ImGuiWindow* FindHoveredWindow(ImVec2 pos, bool excluding_childs);
 //-----------------------------------------------------------------------------
 
 static const char*  GetClipboardTextFn_DefaultImpl();
-static void         SetClipboardTextFn_DefaultImpl(const char* text, const char* text_end);
+static void         SetClipboardTextFn_DefaultImpl(const char* text);
 
 //-----------------------------------------------------------------------------
 // User facing structures
@@ -325,22 +326,23 @@ ImGuiIO::ImGuiIO()
     MouseDoubleClickTime = 0.30f;
     MouseDoubleClickMaxDist = 6.0f;
 
-    // Memory management functions, default to posix
+    // User functions
+    RenderDrawListsFn = NULL;
     MemAllocFn = malloc;
     MemReallocFn = realloc;
     MemFreeFn = free;
-
-    // Platform dependant default implementations
-    GetClipboardTextFn = GetClipboardTextFn_DefaultImpl;
+    GetClipboardTextFn = GetClipboardTextFn_DefaultImpl;   // Platform dependant default implementations
     SetClipboardTextFn = SetClipboardTextFn_DefaultImpl;
+    ImeSetInputScreenPosFn = NULL;
 }
 
 // Pass in translated ASCII characters for text input.
 // - with glfw you can get those from the callback set in glfwSetCharCallback()
 // - on Windows you can get those using ToAscii+keyboard state, or via the VM_CHAR message
-void ImGuiIO::AddInputCharacter(char c)
+static size_t ImStrlenW(const ImWchar* str);
+void ImGuiIO::AddInputCharacter(ImWchar c)
 {
-    const size_t n = strlen(InputCharacters);
+    const size_t n = ImStrlenW(InputCharacters);
     if (n < sizeof(InputCharacters) / sizeof(InputCharacters[0]))
     {
         InputCharacters[n] = c;
@@ -390,7 +392,10 @@ static inline ImVec2 ImLerp(const ImVec2& a, const ImVec2& b, float t)
 static inline ImVec2 ImLerp(const ImVec2& a, const ImVec2& b, const ImVec2& t)  { return ImVec2(a.x + (b.x - a.x) * t.x, a.y + (b.y - a.y) * t.y); }
 static inline float  ImLength(const ImVec2& lhs)                                { return sqrt(lhs.x*lhs.x + lhs.y*lhs.y); }
 
-static int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* in_text_end);  // return input UTF-8 bytes count
+static int ImTextCharToUtf8(char* buf, size_t buf_size, unsigned int in_char);                                // return output UTF-8 bytes count
+static int ImTextStrToUtf8(char* buf, size_t buf_size, const ImWchar* in_text, const ImWchar* in_text_end);   // return output UTF-8 bytes count
+static int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* in_text_end);          // return input UTF-8 bytes count
+static int ImTextStrFromUtf8(ImWchar* buf, size_t buf_size, const char* in_text, const char* in_text_end);    // return input UTF-8 bytes count
 
 static int ImStricmp(const char* str1, const char* str2)
 {
@@ -407,6 +412,14 @@ static char* ImStrdup(const char *str)
     return buff;
 }
 
+static size_t ImStrlenW(const ImWchar* str)
+{
+    size_t n = 0;
+    while (*str++)
+        n++;
+    return n;
+}
+
 static const char* ImStristr(const char* haystack, const char* needle, const char* needle_end)
 {
     if (!needle_end)
@@ -608,19 +621,20 @@ struct ImGuiDrawContext
 
 struct ImGuiTextEditState;
 #define STB_TEXTEDIT_STRING ImGuiTextEditState
-#define STB_TEXTEDIT_CHARTYPE char
+#define STB_TEXTEDIT_CHARTYPE ImWchar
 #include "stb_textedit.h"
 
 // State of the currently focused/edited text input box
 struct ImGuiTextEditState
 {
-    char                Text[1024];                     // edit buffer, we need to persist but can't guarantee the persistence of the user-provided buffer. so own buffer.
-    char                InitialText[1024];              // backup of end-user buffer at focusing time, to ESC key can do a revert. Also used for arithmetic operations (but could use a pre-parsed float there).
+    ImWchar             Text[1024];                     // edit buffer, we need to persist but can't guarantee the persistence of the user-provided buffer. so we copy into own buffer.
+    char                InitialText[1024*3+1];          // backup of end-user buffer at the time of focus (in UTF-8, unconverted)
     size_t              BufSize;                        // end-user buffer size, <= 1024 (or increase above)
     float               Width;                          // widget width
     float               ScrollX;
     STB_TexteditState   StbState;
     float               CursorAnim;
+    ImVec2              LastCursorPos;
     bool                SelectedAllMouseLock;
     ImFont              Font;
     float               FontSize;
@@ -630,15 +644,16 @@ struct ImGuiTextEditState
     void                CursorAnimReset()               { CursorAnim = -0.30f; }                                                // After a user-input the cursor stays on for a while without blinking
     bool                CursorIsVisible() const         { return CursorAnim <= 0.0f || fmodf(CursorAnim, 1.20f) <= 0.80f; }     // Blinking
     bool                HasSelection() const            { return StbState.select_start != StbState.select_end; }
-    void                SelectAll()                     { StbState.select_start = 0; StbState.select_end = (int)strlen(Text); StbState.cursor = StbState.select_end; StbState.has_preferred_x = false; }
+    void                SelectAll()                     { StbState.select_start = 0; StbState.select_end = (int)ImStrlenW(Text); StbState.cursor = StbState.select_end; StbState.has_preferred_x = false; }
 
     void                OnKeyboardPressed(int key);
     void                UpdateScrollOffset();
     ImVec2              CalcDisplayOffsetFromCharIdx(int i) const;
 
     // Static functions because they are used to render non-focused instances of a text input box
-    static const char*  GetTextPointerClipped(ImFont font, float font_size, const char* text, float width, ImVec2* out_text_size = NULL);
-    static void         RenderTextScrolledClipped(ImFont font, float font_size, const char* text, ImVec2 pos_base, float width, float scroll_x);
+    static const char*      GetTextPointerClippedA(ImFont font, float font_size, const char* text, float width, ImVec2* out_text_size = NULL);
+    static const ImWchar*   GetTextPointerClippedW(ImFont font, float font_size, const ImWchar* text, float width, ImVec2* out_text_size = NULL);
+    static void             RenderTextScrolledClipped(ImFont font, float font_size, const char* text, ImVec2 pos_base, float width, float scroll_x);
 };
 
 struct ImGuiIniData
@@ -1707,7 +1722,7 @@ ImVec2 CalcTextSize(const char* text, const char* text_end, const bool hide_text
     else
         text_display_end = text_end;
 
-    const ImVec2 size = window->Font()->CalcTextSize(window->FontSize(), 0, text, text_display_end, NULL);
+    const ImVec2 size = window->Font()->CalcTextSizeA(window->FontSize(), 0, text, text_display_end, NULL);
     return size;
 }
 
@@ -2375,7 +2390,7 @@ void End()
         {
             g.LogClipboard->append("\n");
             if (g.IO.SetClipboardTextFn)
-                g.IO.SetClipboardTextFn(g.LogClipboard->begin(), g.LogClipboard->end());
+                g.IO.SetClipboardTextFn(g.LogClipboard->begin());
             g.LogClipboard->clear();
         }
     }
@@ -3797,15 +3812,15 @@ bool RadioButton(const char* label, int* v, int v_button)
 }; // namespace ImGui
 
 // Wrapper for stb_textedit.h to edit text (our wrapper is for: statically sized buffer, single-line, ASCII, fixed-width font)
-int     STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING* obj)                                  { return (int)strlen(obj->Text); }
-char    STB_TEXTEDIT_GETCHAR(const STB_TEXTEDIT_STRING* obj, int idx)                           { return (char)obj->Text[idx]; }
-float   STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING* obj, int line_start_idx, int char_idx)       { (void)line_start_idx; return obj->Font->CalcTextSize(obj->FontSize, 0, &obj->Text[char_idx], &obj->Text[char_idx]+1, NULL).x; }
-char    STB_TEXTEDIT_KEYTOTEXT(int key)                                                         { return key >= 0x10000 ? 0 : (char)key; }
-char    STB_TEXTEDIT_NEWLINE = '\n';
+int     STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING* obj)                                  { return (int)ImStrlenW(obj->Text); }
+ImWchar STB_TEXTEDIT_GETCHAR(const STB_TEXTEDIT_STRING* obj, int idx)                           { return obj->Text[idx]; }
+float   STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING* obj, int line_start_idx, int char_idx)       { (void)line_start_idx; return obj->Font->CalcTextSizeW(obj->FontSize, 0, &obj->Text[char_idx], &obj->Text[char_idx]+1, NULL).x; }
+int     STB_TEXTEDIT_KEYTOTEXT(int key)                                                         { return key >= 0x10000 ? 0 : key; }
+ImWchar STB_TEXTEDIT_NEWLINE = '\n';
 void    STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, STB_TEXTEDIT_STRING* obj, int line_start_idx)
 {
-    const char* text_remaining = NULL;
-    const ImVec2 size = obj->Font->CalcTextSize(obj->FontSize, FLT_MAX, obj->Text + line_start_idx, NULL, &text_remaining);
+    const ImWchar* text_remaining = NULL;
+    const ImVec2 size = obj->Font->CalcTextSizeW(obj->FontSize, FLT_MAX, obj->Text + line_start_idx, NULL, &text_remaining);
     r->x0 = 0.0f;
     r->x1 = size.x;
     r->baseline_y_delta = size.y;
@@ -3814,22 +3829,22 @@ void    STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, STB_TEXTEDIT_STRING* obj, int
     r->num_chars = (int)(text_remaining - (obj->Text + line_start_idx));
 }
 
-static bool is_white(char c)        { return c==0 || c==' ' || c=='\t' || c=='\r' || c=='\n'; }
-static bool is_separator(char c)    { return c==',' || c==';' || c=='(' || c==')' || c=='{' || c=='}' || c=='[' || c==']' || c=='|'; }
+static bool is_white(unsigned int c)        { return c==0 || c==' ' || c=='\t' || c=='\r' || c=='\n'; }
+static bool is_separator(unsigned int c)    { return c==',' || c==';' || c=='(' || c==')' || c=='{' || c=='}' || c=='[' || c==']' || c=='|'; }
 
-#define STB_TEXTEDIT_IS_SPACE(c)                                                                (is_white(c) || is_separator(c))
-void    STB_TEXTEDIT_DELETECHARS(STB_TEXTEDIT_STRING* obj, int idx, int n)                      { char* dst = obj->Text+idx; const char* src = obj->Text+idx+n; while (char c = *src++) *dst++ = c; *dst = '\0'; }
+#define STB_TEXTEDIT_IS_SPACE(c)                                                                (is_white((unsigned int)c) || is_separator((unsigned int)c))
+void    STB_TEXTEDIT_DELETECHARS(STB_TEXTEDIT_STRING* obj, int idx, int n)                      { ImWchar* dst = obj->Text+idx; const ImWchar* src = obj->Text+idx+n; while (ImWchar c = *src++) *dst++ = c; *dst = '\0'; }
 
-bool    STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING* obj, int idx, const char* new_text, int new_text_len)
+bool    STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING* obj, int idx, const ImWchar* new_text, int new_text_len)
 {
-    char* buf_end = obj->Text + obj->BufSize;
-    const size_t text_len = strlen(obj->Text);
+    ImWchar* buf_end = obj->Text + obj->BufSize;
+    const size_t text_len = ImStrlenW(obj->Text);
 
     if (new_text_len > buf_end - (obj->Text + text_len + 1))
         return false;
 
-    memmove(obj->Text + (size_t)idx + new_text_len, obj->Text + (size_t)idx, text_len - (size_t)idx);
-    memcpy(obj->Text + (size_t)idx, new_text, (size_t)new_text_len);
+    memmove(obj->Text + (size_t)idx + new_text_len, obj->Text + (size_t)idx, (text_len - (size_t)idx) * sizeof(ImWchar));
+    memcpy(obj->Text + (size_t)idx, new_text, (size_t)new_text_len * sizeof(ImWchar));
     obj->Text[text_len + (size_t)new_text_len] = 0;
 
     return true;
@@ -3867,7 +3882,7 @@ void ImGuiTextEditState::UpdateScrollOffset()
 {
     // Scroll in chunks of quarter width
     const float scroll_x_increment = Width * 0.25f;
-    const float cursor_offset_x = Font->CalcTextSize(FontSize, 0, Text, Text+StbState.cursor, NULL).x;
+    const float cursor_offset_x = Font->CalcTextSizeW(FontSize, 0, Text, Text+StbState.cursor, NULL).x;
     if (ScrollX > cursor_offset_x)
         ScrollX = ImMax(0.0f, cursor_offset_x - scroll_x_increment);    
     else if (ScrollX < cursor_offset_x - Width)
@@ -3876,22 +3891,35 @@ void ImGuiTextEditState::UpdateScrollOffset()
 
 ImVec2 ImGuiTextEditState::CalcDisplayOffsetFromCharIdx(int i) const
 {
-    const char* text_start = GetTextPointerClipped(Font, FontSize, Text, ScrollX, NULL);
-    const char* text_end = (Text+i >= text_start) ? Text+i : text_start;                    // Clip if requested character is outside of display
+    const ImWchar* text_start = GetTextPointerClippedW(Font, FontSize, Text, ScrollX, NULL);
+    const ImWchar* text_end = (Text+i >= text_start) ? Text+i : text_start;                    // Clip if requested character is outside of display
     IM_ASSERT(text_end >= text_start);
 
-    const ImVec2 offset = Font->CalcTextSize(FontSize, Width, text_start, text_end, NULL);
+    const ImVec2 offset = Font->CalcTextSizeW(FontSize, Width, text_start, text_end, NULL);
     return offset;
 }
 
 // [Static]
-const char* ImGuiTextEditState::GetTextPointerClipped(ImFont font, float font_size, const char* text, float width, ImVec2* out_text_size)
+const char* ImGuiTextEditState::GetTextPointerClippedA(ImFont font, float font_size, const char* text, float width, ImVec2* out_text_size)
 {
     if (width <= 0.0f)
         return text;
 
     const char* text_clipped_end = NULL;
-    const ImVec2 text_size = font->CalcTextSize(font_size, width, text, NULL, &text_clipped_end);
+    const ImVec2 text_size = font->CalcTextSizeA(font_size, width, text, NULL, &text_clipped_end);
+    if (out_text_size)
+        *out_text_size = text_size;
+    return text_clipped_end;
+}
+
+// [Static]
+const ImWchar* ImGuiTextEditState::GetTextPointerClippedW(ImFont font, float font_size, const ImWchar* text, float width, ImVec2* out_text_size)
+{
+    if (width <= 0.0f)
+        return text;
+
+    const ImWchar* text_clipped_end = NULL;
+    const ImVec2 text_size = font->CalcTextSizeW(font_size, width, text, NULL, &text_clipped_end);
     if (out_text_size)
         *out_text_size = text_size;
     return text_clipped_end;
@@ -3902,8 +3930,8 @@ void ImGuiTextEditState::RenderTextScrolledClipped(ImFont font, float font_size,
 {
     // NB- We start drawing at character boundary
     ImVec2 text_size;
-    const char* text_start = GetTextPointerClipped(font, font_size, buf, scroll_x, NULL);
-    const char* text_end = GetTextPointerClipped(font, font_size, text_start, width, &text_size);
+    const char* text_start = GetTextPointerClippedA(font, font_size, buf, scroll_x, NULL);
+    const char* text_end = GetTextPointerClippedA(font, font_size, text_start, width, &text_size);
 
     // Draw a little clip symbol if we've got text on either left or right of the box
     const char symbol_c = '~';
@@ -4030,12 +4058,14 @@ bool InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlag
         if (g.ActiveId != id)
         {
             // Start edition
-            strcpy(edit_state.Text, buf);
-            strcpy(edit_state.InitialText, buf);
+            // Take a copy of the initial buffer value (both in original UTF-8 format and converted to wchar)
+            ImFormatString(edit_state.InitialText, IM_ARRAYSIZE(edit_state.InitialText), "%s", buf);
+            ImTextStrFromUtf8(edit_state.Text, IM_ARRAYSIZE(edit_state.Text), buf, NULL);
             edit_state.ScrollX = 0.0f;
             edit_state.Width = w;
             stb_textedit_initialize_state(&edit_state.StbState, true); 
             edit_state.CursorAnimReset();
+            edit_state.LastCursorPos = ImVec2(-1.f,-1.f);
 
             if (tab_focus_requested || is_ctrl_down)
                 select_all = true;
@@ -4054,7 +4084,8 @@ bool InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlag
     bool value_changed = false;
     bool cancel_edit = false;
     bool enter_pressed = false;
-    if (g.ActiveId == id)
+    static char text_tmp_utf8[IM_ARRAYSIZE(edit_state.InitialText)];
+	if (g.ActiveId == id)
     {
         // Edit in progress
         edit_state.BufSize = buf_size < IM_ARRAYSIZE(edit_state.Text) ? buf_size : IM_ARRAYSIZE(edit_state.Text);
@@ -4098,40 +4129,45 @@ bool InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlag
         else if (is_ctrl_down && IsKeyPressedMap(ImGuiKey_Z))   edit_state.OnKeyboardPressed(STB_TEXTEDIT_K_UNDO);      // I don't want to use shortcuts but we should probably have an Input-catch stack
         else if (is_ctrl_down && IsKeyPressedMap(ImGuiKey_Y))   edit_state.OnKeyboardPressed(STB_TEXTEDIT_K_REDO);
         else if (is_ctrl_down && IsKeyPressedMap(ImGuiKey_A))   edit_state.SelectAll();
-        else if (is_ctrl_down && IsKeyPressedMap(ImGuiKey_X))
+        else if (is_ctrl_down && (IsKeyPressedMap(ImGuiKey_X) || IsKeyPressedMap(ImGuiKey_C)))
         {
-            if (!edit_state.HasSelection())
+            // Cut, Copy
+            const bool cut = IsKeyPressedMap(ImGuiKey_X);
+            if (cut && !edit_state.HasSelection())
                 edit_state.SelectAll();
 
-            const int ib = ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end);
-            const int ie = ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end);
-            if (g.IO.SetClipboardTextFn)
-                g.IO.SetClipboardTextFn(edit_state.Text+ib, edit_state.Text+ie);
-            stb_textedit_cut(&edit_state, &edit_state.StbState);
-        }
-        else if (is_ctrl_down && IsKeyPressedMap(ImGuiKey_C))   
-        {
-            const int ib = edit_state.HasSelection() ? ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end) : 0;
-            const int ie = edit_state.HasSelection() ? ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end) : (int)strlen(edit_state.Text);
             if (g.IO.SetClipboardTextFn)
-                g.IO.SetClipboardTextFn(edit_state.Text+ib, edit_state.Text+ie);
+            {
+                const int ib = edit_state.HasSelection() ? ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end) : 0;
+                const int ie = edit_state.HasSelection() ? ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end) : (int)ImStrlenW(edit_state.Text);
+				ImTextStrToUtf8(text_tmp_utf8, IM_ARRAYSIZE(text_tmp_utf8), edit_state.Text+ib, edit_state.Text+ie);
+                g.IO.SetClipboardTextFn(text_tmp_utf8);
+            }
+
+            if (cut)
+                stb_textedit_cut(&edit_state, &edit_state.StbState);
         }
         else if (is_ctrl_down && IsKeyPressedMap(ImGuiKey_V))
         {
+            // Paste
             if (g.IO.GetClipboardTextFn)
             {
                 if (const char* clipboard = g.IO.GetClipboardTextFn())
                 {
                     // Remove new-line from pasted buffer
                     size_t clipboard_len = strlen(clipboard);
-                    char* clipboard_filtered = (char*)ImGui::MemAlloc(clipboard_len+1);
+                    ImWchar* clipboard_filtered = (ImWchar*)ImGui::MemAlloc((clipboard_len+1) * sizeof(ImWchar));
                     int clipboard_filtered_len = 0;
-                    for (int i = 0; clipboard[i]; i++)
+                    for (const char* s = clipboard; *s; )
                     {
-                        const char c = clipboard[i];
+                        unsigned int c;
+                        const int bytes_count = ImTextCharFromUtf8(&c, s, NULL);
+                        if (bytes_count <= 0)
+                            break;
+                        s += bytes_count;
                         if (c == '\n' || c == '\r')
                             continue;
-                        clipboard_filtered[clipboard_filtered_len++] = clipboard[i];
+                        clipboard_filtered[clipboard_filtered_len++] = c;
                     }
                     clipboard_filtered[clipboard_filtered_len] = 0;
                     stb_textedit_paste(&edit_state, &edit_state.StbState, clipboard_filtered, clipboard_filtered_len);
@@ -4144,11 +4180,11 @@ bool InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlag
             // Text input
             for (int n = 0; n < IM_ARRAYSIZE(g.IO.InputCharacters) && g.IO.InputCharacters[n]; n++)
             {
-                const char c = g.IO.InputCharacters[n];
+                const ImWchar c = g.IO.InputCharacters[n];
                 if (c)
                 {
                     // Filter
-                    if (!isprint(c) && c != ' ')
+                    if (c < 256 && !isprint((char)(c & 0xFF)) && c != ' ')
                         continue;
                     if (flags & ImGuiInputTextFlags_CharsDecimal)
                         if (!(c >= '0' && c <= '9') && (c != '.') && (c != '-') && (c != '+') && (c != '*') && (c != '/'))
@@ -4175,9 +4211,13 @@ bool InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlag
         else
         {
             // Apply new value immediately - copy modified buffer back
-            if (strcmp(edit_state.Text, buf) != 0)
+            // Note that as soon as we can focus into the input box, the in-widget value gets priority over any underlying modification of the input buffer
+            // FIXME: We actually always render 'buf' in RenderTextScrolledClipped
+            // FIXME-OPT: CPU waste to do this everytime the widget is active, should mark dirty state from the textedit callbacks
+            ImTextStrToUtf8(text_tmp_utf8, IM_ARRAYSIZE(text_tmp_utf8), edit_state.Text, NULL);
+            if (strcmp(text_tmp_utf8, buf) != 0)
             {
-                ImFormatString(buf, buf_size, "%s", edit_state.Text);
+                ImFormatString(buf, buf_size, "%s", text_tmp_utf8);
                 value_changed = true;
             }
         }
@@ -4206,12 +4246,17 @@ bool InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlag
 
     if (g.ActiveId == id)
     {
+        const ImVec2 cursor_pos = frame_bb.Min + style.FramePadding + edit_state.CalcDisplayOffsetFromCharIdx(edit_state.StbState.cursor);
+
         // Draw blinking cursor
         if (g.InputTextState.CursorIsVisible())
-        {
-            const ImVec2 cursor_pos = frame_bb.Min + style.FramePadding + edit_state.CalcDisplayOffsetFromCharIdx(edit_state.StbState.cursor);
             window->DrawList->AddRect(cursor_pos - font_off_up + ImVec2(0,2), cursor_pos + font_off_dn - ImVec2(0,3), window->Color(ImGuiCol_Text));
-        }
+        
+        // Notify OS of text input position
+        if (io.ImeSetInputScreenPosFn && ImLength(edit_state.LastCursorPos - cursor_pos) > 0.01f)
+            io.ImeSetInputScreenPosFn((int)cursor_pos.x - 1, (int)(cursor_pos.y - window->FontSize()));   // -1 x offset so that Windows IME can cover our cursor. Bit of an extra nicety.
+
+        edit_state.LastCursorPos = cursor_pos;
     }
 
     RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
@@ -5450,7 +5495,80 @@ static int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const
     return 0;
 }
 
-ImVec2 ImBitmapFont::CalcTextSize(float size, float max_width, const char* text_begin, const char* text_end, const char** remaining) const
+static int ImTextStrFromUtf8(ImWchar* buf, size_t buf_size, const char* in_text, const char* in_text_end)
+{
+    ImWchar* buf_out = buf;
+    ImWchar* buf_end = buf + buf_size;
+    while (buf_out < buf_end-1 && (!in_text_end || in_text < in_text_end) && *in_text)
+    {
+        unsigned int c;
+        in_text += ImTextCharFromUtf8(&c, in_text, in_text_end);
+        if (c < 0x10000)    // FIXME: Losing characters that don't fit in 2 bytes
+            *buf_out++ = (ImWchar)c;
+    }
+    *buf_out = 0;
+    return buf_out - buf;
+}
+
+// Based on stb_to_utf8() from github.com/nothings/stb/
+static int ImTextCharToUtf8(char* buf, size_t buf_size, unsigned int c)
+{
+    if (c)
+    {
+        int i = 0;
+        int n = (size_t)buf_size;
+        if (c < 0x80) 
+        {
+            if (i+1 > n) return 0;
+            buf[i++] = (char)c;
+            return 1;
+        } 
+        else if (c < 0x800) 
+        {
+            if (i+2 > n) return 0;
+            buf[i++] = 0xc0 + (c >> 6);
+            buf[i++] = 0x80 + (c & 0x3f);
+            return 2;
+        }
+        else if (c >= 0xdc00 && c < 0xe000)
+        {
+            return 0;
+        } 
+        else if (c >= 0xd800 && c < 0xdc00) 
+        {
+            if (i+4 > n) return NULL;
+            buf[i++] = 0xf0 + (c >> 18);
+            buf[i++] = 0x80 + ((c >> 12) & 0x3f);
+            buf[i++] = 0x80 + ((c >> 6) & 0x3f);
+            buf[i++] = 0x80 + ((c ) & 0x3f);
+            return 4;
+        }
+        //else if (c < 0x10000)
+        {
+            if (i+3 > n) return 0;
+            buf[i++] = 0xe0 + (c >> 12);
+            buf[i++] = 0x80 + ((c>> 6) & 0x3f);
+            buf[i++] = 0x80 + ((c ) & 0x3f);
+            return 3;
+        }
+    }
+    return 0;
+}
+
+static int ImTextStrToUtf8(char* buf, size_t buf_size, const ImWchar* in_text, const ImWchar* in_text_end)
+{
+    char* buf_out = buf;
+    const char* buf_end = buf + buf_size;
+    while (buf_out < buf_end-1 && (!in_text_end || in_text < in_text_end) && *in_text)
+    {
+        buf_out += ImTextCharToUtf8(buf_out, buf_end-buf_out-1, (unsigned int)*in_text);
+        in_text++;
+    }
+    *buf_out = 0;
+    return buf_out - buf;
+}
+
+ImVec2 ImBitmapFont::CalcTextSizeA(float size, float max_width, const char* text_begin, const char* text_end, const char** remaining) const
 {
     if (max_width == 0.0f)
         max_width = FLT_MAX;
@@ -5469,8 +5587,6 @@ ImVec2 ImBitmapFont::CalcTextSize(float size, float max_width, const char* text_
         unsigned int c;
         const int bytes_count = ImTextCharFromUtf8(&c, s, text_end);
         s += bytes_count > 0 ? bytes_count : 1;  // Handle decoding failure by skipping to next byte
-        if (c > 0x10000)
-            continue;
 
         if (c == '\n')
         {
@@ -5489,6 +5605,61 @@ ImVec2 ImBitmapFont::CalcTextSize(float size, float max_width, const char* text_
         }
         else if (c == '\t')
         {
+            // FIXME: Better TAB handling needed.
+            if (const FntGlyph* glyph = FindGlyph((unsigned short)' '))
+                line_width += (glyph->XAdvance + Info->SpacingHoriz) * 4 * scale;
+        }
+    }
+
+    if (line_width > 0 || text_size.y == 0.0f)
+    {
+        if (text_size.x < line_width)
+            text_size.x = line_width;
+        text_size.y += line_height;
+    }
+
+    if (remaining)
+        *remaining = s;
+
+    return text_size;
+}
+
+ImVec2 ImBitmapFont::CalcTextSizeW(float size, float max_width, const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining) const
+{
+    if (max_width == 0.0f)
+        max_width = FLT_MAX;
+    if (!text_end)
+        text_end = text_begin + ImStrlenW(text_begin);
+
+    const float scale = size / (float)Info->FontSize;
+    const float line_height = (float)Info->FontSize * scale;
+
+    ImVec2 text_size = ImVec2(0,0);
+    float line_width = 0.0f;
+
+    const ImWchar* s = text_begin;
+    while (s < text_end)
+    {
+        const unsigned int c = (unsigned int)(*s++);
+
+        if (c == '\n')
+        {
+            if (text_size.x < line_width)
+                text_size.x = line_width;
+            text_size.y += line_height;
+            line_width = 0;
+        }
+        else if (const FntGlyph* glyph = FindGlyph((unsigned short)c))
+        {
+            const float char_width = (glyph->XAdvance + Info->SpacingHoriz) * scale;
+            //const float char_extend = (glyph->XOffset + glyph->Width * scale);
+            if (line_width + char_width >= max_width)
+                break;
+            line_width += char_width;
+        }
+        else if (c == '\t')
+        {
+            // FIXME: Better TAB handling needed.
             if (const FntGlyph* glyph = FindGlyph((unsigned short)' '))
                 line_width += (glyph->XAdvance + Info->SpacingHoriz) * 4 * scale;
         }
@@ -5633,12 +5804,11 @@ static const char*  GetClipboardTextFn_DefaultImpl()
 }
 
 // Win32 API clipboard implementation
-static void SetClipboardTextFn_DefaultImpl(const char* text, const char* text_end)
+static void SetClipboardTextFn_DefaultImpl(const char* text)
 {
     if (!OpenClipboard(NULL))
         return;
-    if (!text_end)
-        text_end = text + strlen(text);
+    const char* text_end = text + strlen(text);
     const int buf_length = (int)(text_end - text) + 1;
     HGLOBAL buf_handle = GlobalAlloc(GMEM_MOVEABLE, buf_length * sizeof(char)); 
     if (buf_handle == NULL)
@@ -5661,15 +5831,14 @@ static const char*  GetClipboardTextFn_DefaultImpl()
 }
 
 // Local ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers
-static void SetClipboardTextFn_DefaultImpl(const char* text, const char* text_end)
+static void SetClipboardTextFn_DefaultImpl(const char* text)
 {
     if (GImGui.PrivateClipboard)
     {
         ImGui::MemFree(GImGui.PrivateClipboard);
         GImGui.PrivateClipboard = NULL;
     }
-    if (!text_end)
-        text_end = text + strlen(text);
+    const char* text_end = text + strlen(text);
     GImGui.PrivateClipboard = (char*)ImGui::MemAlloc((size_t)(text_end - text) + 1);
     memcpy(GImGui.PrivateClipboard, text, (size_t)(text_end - text));
     GImGui.PrivateClipboard[(size_t)(text_end - text)] = 0;

+ 35 - 18
imgui.h

@@ -1,4 +1,4 @@
-// ImGui library v1.12
+// ImGui library v1.12+
 // See .cpp file for commentary.
 // See ImGui::ShowTestWindow() for sample code.
 // Read 'Programmer guide' in .cpp for notes on how to setup ImGui in your codebase.
@@ -402,7 +402,10 @@ struct ImGuiStyle
 // Read 'Programmer guide' section in .cpp file for general usage.
 struct ImGuiIO
 {
+    //------------------------------------------------------------------
     // Settings (fill once)                 // Default value:
+    //------------------------------------------------------------------
+
     ImVec2      DisplaySize;                // <unset>                  // Display size, in pixels. For clamping windows positions.
     float       DeltaTime;                  // = 1.0f/60.0f             // Time elapsed since last frame, in seconds.
     float       IniSavingRate;              // = 5.0f                   // Maximum time between saving .ini file, in seconds. Set to a negative value to disable .ini saving.
@@ -418,40 +421,53 @@ struct ImGuiIO
     bool        FontAllowUserScaling;       // = false                  // Set to allow scaling text with CTRL+Wheel.
     float       PixelCenterOffset;          // = 0.0f                   // Try to set to 0.5f or 0.375f if rendering is blurry
 
-    // Settings - Rendering function (REQUIRED)
+    //------------------------------------------------------------------
+    // User Functions
+    //------------------------------------------------------------------
+
+    // REQUIRED: rendering function. 
     // See example code if you are unsure of how to implement this.
-    void        (*RenderDrawListsFn)(ImDrawList** const draw_lists, int count);
-
-    // Settings - Clipboard Support
-    // Override to provide your clipboard handlers.
-    // On Windows architecture, defaults to use the native Win32 clipboard, otherwise default to use a ImGui private clipboard. 
-    // NB- for SetClipboardTextFn, the string is *NOT* zero-terminated at 'text_end'
-    const char* (*GetClipboardTextFn)();                                        
-    void        (*SetClipboardTextFn)(const char* text, const char* text_end);
-    
-    // Settings - Memory allocation
-    // Default to posix malloc/realloc/free functions.
+    void        (*RenderDrawListsFn)(ImDrawList** const draw_lists, int count);      
+
+    // Optional: access OS clipboard (default to use native Win32 clipboard on Windows, otherwise use a ImGui private clipboard)
+    // Override to access OS clipboard on other architectures.
+    const char* (*GetClipboardTextFn)();
+    void        (*SetClipboardTextFn)(const char* text);
+
+    // Optional: override memory allocations (default to posix malloc/realloc/free)
     void*       (*MemAllocFn)(size_t sz);
     void*       (*MemReallocFn)(void* ptr, size_t sz);
     void        (*MemFreeFn)(void* ptr);
 
+    // Optional: notify OS Input Method Editor of text input position (e.g. when using Japanese/Chinese inputs, otherwise this isn't needed)
+    void        (*ImeSetInputScreenPosFn)(int x, int y);
+
+    //------------------------------------------------------------------
     // Input - Fill before calling NewFrame()
+    //------------------------------------------------------------------
+
     ImVec2      MousePos;                   // Mouse position, in pixels (set to -1,-1 if no mouse / on another screen, etc.)
     bool        MouseDown[5];               // Mouse buttons. ImGui itself only uses button 0 (left button) but you can use others as storage for convenience.
     int         MouseWheel;                 // Mouse wheel: -1,0,+1
     bool        KeyCtrl;                    // Keyboard modifier pressed: Control
     bool        KeyShift;                   // Keyboard modifier pressed: Shift
     bool        KeysDown[512];              // Keyboard keys that are pressed (in whatever order user naturally has access to keyboard data)
-    char        InputCharacters[16];        // List of characters input (translated by user from keypress+keyboard state). Fill using AddInputCharacter() helper.
+    ImWchar     InputCharacters[16];        // List of characters input (translated by user from keypress+keyboard state). Fill using AddInputCharacter() helper.
 
+    // Function
+    void        AddInputCharacter(ImWchar); // Helper to add a new character into InputCharacters[]
+
+    //------------------------------------------------------------------
     // Output - Retrieve after calling NewFrame(), you can use them to discard inputs or hide them from the rest of your application
+    //------------------------------------------------------------------
+
     bool        WantCaptureMouse;           // Mouse is hovering a window or widget is active (= ImGui will use your mouse input)
     bool        WantCaptureKeyboard;        // Widget is active (= ImGui will use your keyboard input)
 
-    // Function
-    void        AddInputCharacter(char c);  // Helper to add a new character into InputCharacters[]
-
+    //------------------------------------------------------------------
     // [Internal] ImGui will maintain those fields for you
+    //------------------------------------------------------------------
+
     ImVec2      MousePosPrev;
     ImVec2      MouseDelta;
     bool        MouseClicked[5];
@@ -672,6 +688,7 @@ struct ImBitmapFont
     float                   GetFontSize() const { return (float)Info->FontSize; }
     bool                    IsLoaded() const { return Info != NULL && Common != NULL && Glyphs != NULL; }
 
-    ImVec2                  CalcTextSize(float size, float max_width, const char* text_begin, const char* text_end, const char** remaining = NULL) const;
+    ImVec2                  CalcTextSizeA(float size, float max_width, const char* text_begin, const char* text_end, const char** remaining = NULL) const;            // utf8
+    ImVec2                  CalcTextSizeW(float size, float max_width, const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL) const;  // wchar
     void                    RenderText(float size, ImVec2 pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, ImDrawVert*& out_vertices) const;
 };