InputText: added native support for UTF-8 text editing and god rid of the wchar buffer. (#7925)

WIP (requires subsequent commits for fixes)
This commit is contained in:
alektron
2024-09-11 14:46:29 +02:00
committed by omar
parent 67cd4ead65
commit abd07f6d30
4 changed files with 189 additions and 156 deletions
+1 -1
View File
@@ -1975,7 +1975,7 @@ const char* ImStreolRange(const char* str, const char* str_end)
return p ? p : str_end; return p ? p : str_end;
} }
const ImWchar* ImStrbolW(const ImWchar* buf_mid_line, const ImWchar* buf_begin) // find beginning-of-line const char* ImStrbol(const char* buf_mid_line, const char* buf_begin) // find beginning-of-line
{ {
while (buf_mid_line > buf_begin && buf_mid_line[-1] != '\n') while (buf_mid_line > buf_begin && buf_mid_line[-1] != '\n')
buf_mid_line--; buf_mid_line--;
+5 -6
View File
@@ -366,7 +366,7 @@ IMGUI_API const char* ImStristr(const char* haystack, const char* haystack_end
IMGUI_API void ImStrTrimBlanks(char* str); // Remove leading and trailing blanks from a buffer. IMGUI_API void ImStrTrimBlanks(char* str); // Remove leading and trailing blanks from a buffer.
IMGUI_API const char* ImStrSkipBlank(const char* str); // Find first non-blank character. IMGUI_API const char* ImStrSkipBlank(const char* str); // Find first non-blank character.
IMGUI_API int ImStrlenW(const ImWchar* str); // Computer string length (ImWchar string) IMGUI_API int ImStrlenW(const ImWchar* str); // Computer string length (ImWchar string)
IMGUI_API const ImWchar*ImStrbolW(const ImWchar* buf_mid_line, const ImWchar* buf_begin); // Find beginning-of-line (ImWchar string) IMGUI_API const char* ImStrbol(const char* buf_mid_line, const char* buf_begin); // Find beginning-of-line
IM_MSVC_RUNTIME_CHECKS_OFF IM_MSVC_RUNTIME_CHECKS_OFF
static inline char ImToUpper(char c) { return (c >= 'a' && c <= 'z') ? c &= ~32 : c; } static inline char ImToUpper(char c) { return (c >= 'a' && c <= 'z') ? c &= ~32 : c; }
static inline bool ImCharIsBlankA(char c) { return c == ' ' || c == '\t'; } static inline bool ImCharIsBlankA(char c) { return c == ' ' || c == '\t'; }
@@ -1097,7 +1097,7 @@ struct IMGUI_API ImGuiInputTextDeactivatedState
#undef IMSTB_TEXTEDIT_STRING #undef IMSTB_TEXTEDIT_STRING
#undef IMSTB_TEXTEDIT_CHARTYPE #undef IMSTB_TEXTEDIT_CHARTYPE
#define IMSTB_TEXTEDIT_STRING ImGuiInputTextState #define IMSTB_TEXTEDIT_STRING ImGuiInputTextState
#define IMSTB_TEXTEDIT_CHARTYPE ImWchar #define IMSTB_TEXTEDIT_CHARTYPE char
#define IMSTB_TEXTEDIT_GETWIDTH_NEWLINE (-1.0f) #define IMSTB_TEXTEDIT_GETWIDTH_NEWLINE (-1.0f)
#define IMSTB_TEXTEDIT_UNDOSTATECOUNT 99 #define IMSTB_TEXTEDIT_UNDOSTATECOUNT 99
#define IMSTB_TEXTEDIT_UNDOCHARCOUNT 999 #define IMSTB_TEXTEDIT_UNDOCHARCOUNT 999
@@ -1111,8 +1111,7 @@ struct IMGUI_API ImGuiInputTextState
ImGuiContext* Ctx; // parent UI context (needs to be set explicitly by parent). ImGuiContext* Ctx; // parent UI context (needs to be set explicitly by parent).
ImStbTexteditState* Stb; // State for stb_textedit.h ImStbTexteditState* Stb; // State for stb_textedit.h
ImGuiID ID; // widget id owning the text state 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. int CurLenA; // UTF-8 length of the string in TextA (in bytes)
ImVector<ImWchar> TextW; // edit buffer, we need to persist but can't guarantee the persistence of the user-provided buffer. so we copy into own buffer.
ImVector<char> TextA; // temporary UTF8 buffer for callbacks and other operations. this is not updated in every code-path! size=capacity. ImVector<char> TextA; // temporary UTF8 buffer for callbacks and other operations. this is not updated in every code-path! size=capacity.
ImVector<char> InitialTextA; // value to revert to when pressing Escape = backup of end-user buffer at the time of focus (in UTF-8, unaltered) ImVector<char> InitialTextA; // value to revert to when pressing Escape = backup of end-user buffer at the time of focus (in UTF-8, unaltered)
bool TextAIsValid; // temporary UTF8 buffer is not initially valid before we make the widget active (until then we pull the data from user argument) bool TextAIsValid; // temporary UTF8 buffer is not initially valid before we make the widget active (until then we pull the data from user argument)
@@ -1129,8 +1128,8 @@ struct IMGUI_API ImGuiInputTextState
ImGuiInputTextState(); ImGuiInputTextState();
~ImGuiInputTextState(); ~ImGuiInputTextState();
void ClearText() { CurLenW = CurLenA = 0; TextW[0] = 0; TextA[0] = 0; CursorClamp(); } void ClearText() { CurLenA = 0; TextA[0] = 0; CursorClamp(); }
void ClearFreeMemory() { TextW.clear(); TextA.clear(); InitialTextA.clear(); } void ClearFreeMemory() { TextA.clear(); InitialTextA.clear(); }
void OnKeyPressed(int key); // Cannot be inline because we call in code in stb_textedit.h implementation void OnKeyPressed(int key); // Cannot be inline because we call in code in stb_textedit.h implementation
// Cursor & Selection // Cursor & Selection
+149 -129
View File
File diff suppressed because it is too large Load Diff
+34 -20
View File
@@ -4,6 +4,7 @@
// - Fix in stb_textedit_discard_redo (see https://github.com/nothings/stb/issues/321) // - Fix in stb_textedit_discard_redo (see https://github.com/nothings/stb/issues/321)
// - Fix in stb_textedit_find_charpos to handle last line (see https://github.com/ocornut/imgui/issues/6000 + #6783) // - Fix in stb_textedit_find_charpos to handle last line (see https://github.com/ocornut/imgui/issues/6000 + #6783)
// - Added name to struct or it may be forward declared in our code. // - Added name to struct or it may be forward declared in our code.
// - Added UTF8 support https://github.com/nothings/stb/issues/188
// Grep for [DEAR IMGUI] to find the changes. // Grep for [DEAR IMGUI] to find the changes.
// - Also renamed macros used or defined outside of IMSTB_TEXTEDIT_IMPLEMENTATION block from STB_TEXTEDIT_* to IMSTB_TEXTEDIT_* // - Also renamed macros used or defined outside of IMSTB_TEXTEDIT_IMPLEMENTATION block from STB_TEXTEDIT_* to IMSTB_TEXTEDIT_*
@@ -439,13 +440,13 @@ static int stb_text_locate_coord(IMSTB_TEXTEDIT_STRING *str, float x, float y)
if (x < r.x1) { if (x < r.x1) {
// search characters in row for one that straddles 'x' // search characters in row for one that straddles 'x'
prev_x = r.x0; prev_x = r.x0;
for (k=0; k < r.num_chars; ++k) { for (k=0; k < r.num_chars; k = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, i + k) - i) {
float w = STB_TEXTEDIT_GETWIDTH(str, i, k); float w = STB_TEXTEDIT_GETWIDTH(str, i, k);
if (x < prev_x+w) { if (x < prev_x+w) {
if (x < prev_x+w/2) if (x < prev_x+w/2)
return k+i; return k+i;
else else
return k+i+1; return IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, i + k);
} }
prev_x += w; prev_x += w;
} }
@@ -564,7 +565,7 @@ static void stb_textedit_find_charpos(StbFindState *find, IMSTB_TEXTEDIT_STRING
// now scan to find xpos // now scan to find xpos
find->x = r.x0; find->x = r.x0;
for (i=0; first+i < n; ++i) for (i=0; first+i < n; i = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, first + i) - first)
find->x += STB_TEXTEDIT_GETWIDTH(str, first, i); find->x += STB_TEXTEDIT_GETWIDTH(str, first, i);
} }
@@ -641,6 +642,17 @@ static void stb_textedit_move_to_last(IMSTB_TEXTEDIT_STRING *str, STB_TexteditSt
} }
} }
//[DEAR IMGUI]
//Functions must be implemented for UTF8 support
//Code in this file that uses them, is modified for [DEAR IMGUI] and deviates from the original stb_textedit.
//There is not necessarily a '[DEAR IMGUI]' at the usage sites
#ifndef IMSTB_TEXTEDIT_GETPREVIOUSCHARINDEX
#define IMSTB_TEXTEDIT_GETPREVIOUSCHARINDEX(obj, idx) idx - 1
#endif
#ifndef IMSTB_TEXTEDIT_GETNEXTCHARINDEX
#define IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx) idx + 1
#endif
#ifdef STB_TEXTEDIT_IS_SPACE #ifdef STB_TEXTEDIT_IS_SPACE
static int is_word_boundary( IMSTB_TEXTEDIT_STRING *str, int idx ) static int is_word_boundary( IMSTB_TEXTEDIT_STRING *str, int idx )
{ {
@@ -722,15 +734,16 @@ static int stb_textedit_paste_internal(IMSTB_TEXTEDIT_STRING *str, STB_TexteditS
#endif #endif
// API key: process a keyboard input // API key: process a keyboard input
static void stb_textedit_key(IMSTB_TEXTEDIT_STRING *str, STB_TexteditState *state, STB_TEXTEDIT_KEYTYPE key) //[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)
{ {
retry: retry:
switch (key) { switch (key) {
default: { default: {
int c = STB_TEXTEDIT_KEYTOTEXT(key); int c = STB_TEXTEDIT_KEYTOTEXT(key);
if (c > 0) { if (c > 0) {
IMSTB_TEXTEDIT_CHARTYPE ch = (IMSTB_TEXTEDIT_CHARTYPE) c;
// can't add newline in single-line mode // can't add newline in single-line mode
if (c == '\n' && state->single_line) if (c == '\n' && state->single_line)
break; break;
@@ -738,15 +751,15 @@ retry:
if (state->insert_mode && !STB_TEXT_HAS_SELECTION(state) && state->cursor < STB_TEXTEDIT_STRINGLEN(str)) { 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_text_makeundo_replace(str, state, state->cursor, 1, 1);
STB_TEXTEDIT_DELETECHARS(str, state->cursor, 1); STB_TEXTEDIT_DELETECHARS(str, state->cursor, 1);
if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) { if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, decoded, decoded_size)) {
++state->cursor; state->cursor += decoded_size;
state->has_preferred_x = 0; state->has_preferred_x = 0;
} }
} else { } else {
stb_textedit_delete_selection(str,state); // implicitly clamps stb_textedit_delete_selection(str,state); // implicitly clamps
if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) { if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, decoded, decoded_size)) {
stb_text_makeundo_insert(state, state->cursor, 1); stb_text_makeundo_insert(state, state->cursor, decoded_size);
++state->cursor; state->cursor += decoded_size;
state->has_preferred_x = 0; state->has_preferred_x = 0;
} }
} }
@@ -776,7 +789,7 @@ retry:
stb_textedit_move_to_first(state); stb_textedit_move_to_first(state);
else else
if (state->cursor > 0) if (state->cursor > 0)
--state->cursor; state->cursor = IMSTB_TEXTEDIT_GETPREVIOUSCHARINDEX(str, state->cursor);
state->has_preferred_x = 0; state->has_preferred_x = 0;
break; break;
@@ -785,7 +798,7 @@ retry:
if (STB_TEXT_HAS_SELECTION(state)) if (STB_TEXT_HAS_SELECTION(state))
stb_textedit_move_to_last(str, state); stb_textedit_move_to_last(str, state);
else else
++state->cursor; state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);
stb_textedit_clamp(str, state); stb_textedit_clamp(str, state);
state->has_preferred_x = 0; state->has_preferred_x = 0;
break; break;
@@ -795,7 +808,7 @@ retry:
stb_textedit_prep_selection_at_cursor(state); stb_textedit_prep_selection_at_cursor(state);
// move selection left // move selection left
if (state->select_end > 0) if (state->select_end > 0)
--state->select_end; state->select_end = IMSTB_TEXTEDIT_GETPREVIOUSCHARINDEX(str, state->select_end);
state->cursor = state->select_end; state->cursor = state->select_end;
state->has_preferred_x = 0; state->has_preferred_x = 0;
break; break;
@@ -845,7 +858,7 @@ retry:
case STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT: case STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT:
stb_textedit_prep_selection_at_cursor(state); stb_textedit_prep_selection_at_cursor(state);
// move selection right // move selection right
++state->select_end; state->select_end = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->select_end);
stb_textedit_clamp(str, state); stb_textedit_clamp(str, state);
state->cursor = state->select_end; state->cursor = state->select_end;
state->has_preferred_x = 0; state->has_preferred_x = 0;
@@ -901,7 +914,7 @@ retry:
x += dx; x += dx;
if (x > goal_x) if (x > goal_x)
break; break;
++state->cursor; state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);
} }
stb_textedit_clamp(str, state); stb_textedit_clamp(str, state);
@@ -963,7 +976,7 @@ retry:
x += dx; x += dx;
if (x > goal_x) if (x > goal_x)
break; break;
++state->cursor; state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);
} }
stb_textedit_clamp(str, state); stb_textedit_clamp(str, state);
@@ -991,7 +1004,7 @@ retry:
else { else {
int n = STB_TEXTEDIT_STRINGLEN(str); int n = STB_TEXTEDIT_STRINGLEN(str);
if (state->cursor < n) if (state->cursor < n)
stb_textedit_delete(str, state, state->cursor, 1); stb_textedit_delete(str, state, state->cursor, IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor) - state->cursor);
} }
state->has_preferred_x = 0; state->has_preferred_x = 0;
break; break;
@@ -1003,8 +1016,9 @@ retry:
else { else {
stb_textedit_clamp(str, state); stb_textedit_clamp(str, state);
if (state->cursor > 0) { if (state->cursor > 0) {
stb_textedit_delete(str, state, state->cursor-1, 1); int prev = IMSTB_TEXTEDIT_GETPREVIOUSCHARINDEX(str, state->cursor);
--state->cursor; stb_textedit_delete(str, state, prev, state->cursor - prev);
state->cursor = prev;
} }
} }
state->has_preferred_x = 0; state->has_preferred_x = 0;