mirror of
				https://github.com/Drezil/imgui.git
				synced 2025-11-03 22:51:06 +01:00 
			
		
		
		
	Refactor: Moved InputText functions from imgui.cpp to imgui_widgets.cpp (#2036)
This commit is contained in:
		
							
								
								
									
										975
									
								
								imgui.cpp
									
									
									
									
									
								
							
							
						
						
									
										975
									
								
								imgui.cpp
									
									
									
									
									
								
							@@ -902,10 +902,6 @@ static ImVec2           CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* wind
 | 
				
			|||||||
static void             AddDrawListToDrawData(ImVector<ImDrawList*>* out_list, ImDrawList* draw_list);
 | 
					static void             AddDrawListToDrawData(ImVector<ImDrawList*>* out_list, ImDrawList* draw_list);
 | 
				
			||||||
static void             AddWindowToSortedBuffer(ImVector<ImGuiWindow*>* out_sorted_windows, ImGuiWindow* window);
 | 
					static void             AddWindowToSortedBuffer(ImVector<ImGuiWindow*>* out_sorted_windows, ImGuiWindow* window);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static bool             InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data);
 | 
					 | 
				
			||||||
static int              InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end);
 | 
					 | 
				
			||||||
static ImVec2           InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static inline int       DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* data_ptr, const char* format);
 | 
					static inline int       DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* data_ptr, const char* format);
 | 
				
			||||||
static void             DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, void* arg_1, const void* arg_2);
 | 
					static void             DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, void* arg_1, const void* arg_2);
 | 
				
			||||||
static bool             DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* data_ptr, const char* format);
 | 
					static bool             DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* data_ptr, const char* format);
 | 
				
			||||||
@@ -10075,977 +10071,6 @@ bool ImGui::DragIntRange2(const char* label, int* v_current_min, int* v_current_
 | 
				
			|||||||
    return value_changed;
 | 
					    return value_changed;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    int line_count = 0;
 | 
					 | 
				
			||||||
    const char* s = text_begin;
 | 
					 | 
				
			||||||
    while (char c = *s++) // We are only matching for \n so we can ignore UTF-8 decoding
 | 
					 | 
				
			||||||
        if (c == '\n')
 | 
					 | 
				
			||||||
            line_count++;
 | 
					 | 
				
			||||||
    s--;
 | 
					 | 
				
			||||||
    if (s[0] != '\n' && s[0] != '\r')
 | 
					 | 
				
			||||||
        line_count++;
 | 
					 | 
				
			||||||
    *out_text_end = s;
 | 
					 | 
				
			||||||
    return line_count;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining, ImVec2* out_offset, bool stop_on_new_line)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    ImFont* font = GImGui->Font;
 | 
					 | 
				
			||||||
    const float line_height = GImGui->FontSize;
 | 
					 | 
				
			||||||
    const float scale = line_height / font->FontSize;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ImVec2 text_size = ImVec2(0,0);
 | 
					 | 
				
			||||||
    float line_width = 0.0f;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const ImWchar* s = text_begin;
 | 
					 | 
				
			||||||
    while (s < text_end)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        unsigned int c = (unsigned int)(*s++);
 | 
					 | 
				
			||||||
        if (c == '\n')
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            text_size.x = ImMax(text_size.x, line_width);
 | 
					 | 
				
			||||||
            text_size.y += line_height;
 | 
					 | 
				
			||||||
            line_width = 0.0f;
 | 
					 | 
				
			||||||
            if (stop_on_new_line)
 | 
					 | 
				
			||||||
                break;
 | 
					 | 
				
			||||||
            continue;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (c == '\r')
 | 
					 | 
				
			||||||
            continue;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const float char_width = font->GetCharAdvance((unsigned short)c) * scale;
 | 
					 | 
				
			||||||
        line_width += char_width;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (text_size.x < line_width)
 | 
					 | 
				
			||||||
        text_size.x = line_width;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (out_offset)
 | 
					 | 
				
			||||||
        *out_offset = ImVec2(line_width, text_size.y + line_height);  // offset allow for the possibility of sitting after a trailing \n
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (line_width > 0 || text_size.y == 0.0f)                        // whereas size.y will ignore the trailing \n
 | 
					 | 
				
			||||||
        text_size.y += line_height;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (remaining)
 | 
					 | 
				
			||||||
        *remaining = s;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return text_size;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Wrapper for stb_textedit.h to edit text (our wrapper is for: statically sized buffer, single-line, wchar characters. InputText converts between UTF-8 and wchar)
 | 
					 | 
				
			||||||
namespace ImGuiStb
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static int     STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING* obj)                             { return obj->CurLenW; }
 | 
					 | 
				
			||||||
static ImWchar STB_TEXTEDIT_GETCHAR(const STB_TEXTEDIT_STRING* obj, int idx)                      { return obj->TextW[idx]; }
 | 
					 | 
				
			||||||
static float   STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING* obj, int line_start_idx, int char_idx)  { ImWchar c = obj->TextW[line_start_idx+char_idx]; if (c == '\n') return STB_TEXTEDIT_GETWIDTH_NEWLINE; return GImGui->Font->GetCharAdvance(c) * (GImGui->FontSize / GImGui->Font->FontSize); }
 | 
					 | 
				
			||||||
static int     STB_TEXTEDIT_KEYTOTEXT(int key)                                                    { return key >= 0x10000 ? 0 : key; }
 | 
					 | 
				
			||||||
static ImWchar STB_TEXTEDIT_NEWLINE = '\n';
 | 
					 | 
				
			||||||
static void    STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, STB_TEXTEDIT_STRING* obj, int line_start_idx)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    const ImWchar* text = obj->TextW.Data;
 | 
					 | 
				
			||||||
    const ImWchar* text_remaining = NULL;
 | 
					 | 
				
			||||||
    const ImVec2 size = InputTextCalcTextSizeW(text + line_start_idx, text + obj->CurLenW, &text_remaining, NULL, true);
 | 
					 | 
				
			||||||
    r->x0 = 0.0f;
 | 
					 | 
				
			||||||
    r->x1 = size.x;
 | 
					 | 
				
			||||||
    r->baseline_y_delta = size.y;
 | 
					 | 
				
			||||||
    r->ymin = 0.0f;
 | 
					 | 
				
			||||||
    r->ymax = size.y;
 | 
					 | 
				
			||||||
    r->num_chars = (int)(text_remaining - (text + line_start_idx));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static bool is_separator(unsigned int c)                                        { return ImCharIsBlankW(c) || c==',' || c==';' || c=='(' || c==')' || c=='{' || c=='}' || c=='[' || c==']' || c=='|'; }
 | 
					 | 
				
			||||||
static int  is_word_boundary_from_right(STB_TEXTEDIT_STRING* obj, int idx)      { return idx > 0 ? (is_separator( obj->TextW[idx-1] ) && !is_separator( obj->TextW[idx] ) ) : 1; }
 | 
					 | 
				
			||||||
static int  STB_TEXTEDIT_MOVEWORDLEFT_IMPL(STB_TEXTEDIT_STRING* obj, int idx)   { idx--; while (idx >= 0 && !is_word_boundary_from_right(obj, idx)) idx--; return idx < 0 ? 0 : idx; }
 | 
					 | 
				
			||||||
#ifdef __APPLE__    // FIXME: Move setting to IO structure
 | 
					 | 
				
			||||||
static int  is_word_boundary_from_left(STB_TEXTEDIT_STRING* obj, int idx)       { return idx > 0 ? (!is_separator( obj->TextW[idx-1] ) && is_separator( obj->TextW[idx] ) ) : 1; }
 | 
					 | 
				
			||||||
static int  STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING* obj, int idx)  { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_left(obj, idx)) idx++; return idx > len ? len : idx; }
 | 
					 | 
				
			||||||
#else
 | 
					 | 
				
			||||||
static int  STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING* obj, int idx)  { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_right(obj, idx)) idx++; return idx > len ? len : idx; }
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
#define STB_TEXTEDIT_MOVEWORDLEFT   STB_TEXTEDIT_MOVEWORDLEFT_IMPL    // They need to be #define for stb_textedit.h
 | 
					 | 
				
			||||||
#define STB_TEXTEDIT_MOVEWORDRIGHT  STB_TEXTEDIT_MOVEWORDRIGHT_IMPL
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static void STB_TEXTEDIT_DELETECHARS(STB_TEXTEDIT_STRING* obj, int pos, int n)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    ImWchar* dst = obj->TextW.Data + pos;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // We maintain our buffer length in both UTF-8 and wchar formats
 | 
					 | 
				
			||||||
    obj->CurLenA -= ImTextCountUtf8BytesFromStr(dst, dst + n);
 | 
					 | 
				
			||||||
    obj->CurLenW -= n;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Offset remaining text
 | 
					 | 
				
			||||||
    const ImWchar* src = obj->TextW.Data + pos + n;
 | 
					 | 
				
			||||||
    while (ImWchar c = *src++)
 | 
					 | 
				
			||||||
        *dst++ = c;
 | 
					 | 
				
			||||||
    *dst = '\0';
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static bool STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING* obj, int pos, const ImWchar* new_text, int new_text_len)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    const bool is_resizable = (obj->UserFlags & ImGuiInputTextFlags_CallbackResize) != 0;
 | 
					 | 
				
			||||||
    const int text_len = obj->CurLenW;
 | 
					 | 
				
			||||||
    IM_ASSERT(pos <= text_len);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const int new_text_len_utf8 = ImTextCountUtf8BytesFromStr(new_text, new_text + new_text_len);
 | 
					 | 
				
			||||||
    if (!is_resizable && (new_text_len_utf8 + obj->CurLenA + 1 > obj->BufCapacityA))
 | 
					 | 
				
			||||||
        return false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Grow internal buffer if needed
 | 
					 | 
				
			||||||
    if (new_text_len + text_len + 1 > obj->TextW.Size)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        if (!is_resizable)
 | 
					 | 
				
			||||||
            return false;
 | 
					 | 
				
			||||||
        IM_ASSERT(text_len < obj->TextW.Size);
 | 
					 | 
				
			||||||
        obj->TextW.resize(text_len + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ImWchar* text = obj->TextW.Data;
 | 
					 | 
				
			||||||
    if (pos != text_len)
 | 
					 | 
				
			||||||
        memmove(text + pos + new_text_len, text + pos, (size_t)(text_len - pos) * sizeof(ImWchar));
 | 
					 | 
				
			||||||
    memcpy(text + pos, new_text, (size_t)new_text_len * sizeof(ImWchar));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    obj->CurLenW += new_text_len;
 | 
					 | 
				
			||||||
    obj->CurLenA += new_text_len_utf8;
 | 
					 | 
				
			||||||
    obj->TextW[obj->CurLenW] = '\0';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return true;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// We don't use an enum so we can build even with conflicting symbols (if another user of stb_textedit.h leak their STB_TEXTEDIT_K_* symbols)
 | 
					 | 
				
			||||||
#define STB_TEXTEDIT_K_LEFT         0x10000 // keyboard input to move cursor left
 | 
					 | 
				
			||||||
#define STB_TEXTEDIT_K_RIGHT        0x10001 // keyboard input to move cursor right
 | 
					 | 
				
			||||||
#define STB_TEXTEDIT_K_UP           0x10002 // keyboard input to move cursor up
 | 
					 | 
				
			||||||
#define STB_TEXTEDIT_K_DOWN         0x10003 // keyboard input to move cursor down
 | 
					 | 
				
			||||||
#define STB_TEXTEDIT_K_LINESTART    0x10004 // keyboard input to move cursor to start of line
 | 
					 | 
				
			||||||
#define STB_TEXTEDIT_K_LINEEND      0x10005 // keyboard input to move cursor to end of line
 | 
					 | 
				
			||||||
#define STB_TEXTEDIT_K_TEXTSTART    0x10006 // keyboard input to move cursor to start of text
 | 
					 | 
				
			||||||
#define STB_TEXTEDIT_K_TEXTEND      0x10007 // keyboard input to move cursor to end of text
 | 
					 | 
				
			||||||
#define STB_TEXTEDIT_K_DELETE       0x10008 // keyboard input to delete selection or character under cursor
 | 
					 | 
				
			||||||
#define STB_TEXTEDIT_K_BACKSPACE    0x10009 // keyboard input to delete selection or character left of cursor
 | 
					 | 
				
			||||||
#define STB_TEXTEDIT_K_UNDO         0x1000A // keyboard input to perform undo
 | 
					 | 
				
			||||||
#define STB_TEXTEDIT_K_REDO         0x1000B // keyboard input to perform redo
 | 
					 | 
				
			||||||
#define STB_TEXTEDIT_K_WORDLEFT     0x1000C // keyboard input to move cursor left one word
 | 
					 | 
				
			||||||
#define STB_TEXTEDIT_K_WORDRIGHT    0x1000D // keyboard input to move cursor right one word
 | 
					 | 
				
			||||||
#define STB_TEXTEDIT_K_SHIFT        0x20000
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#define STB_TEXTEDIT_IMPLEMENTATION
 | 
					 | 
				
			||||||
#include "stb_textedit.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void ImGuiInputTextState::OnKeyPressed(int key)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    stb_textedit_key(this, &StbState, key);
 | 
					 | 
				
			||||||
    CursorFollow = true;
 | 
					 | 
				
			||||||
    CursorAnimReset();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
ImGuiInputTextCallbackData::ImGuiInputTextCallbackData()
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    memset(this, 0, sizeof(*this));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Public API to manipulate UTF-8 text
 | 
					 | 
				
			||||||
// We expose UTF-8 to the user (unlike the STB_TEXTEDIT_* functions which are manipulating wchar)
 | 
					 | 
				
			||||||
// FIXME: The existence of this rarely exercised code path is a bit of a nuisance.
 | 
					 | 
				
			||||||
void ImGuiInputTextCallbackData::DeleteChars(int pos, int bytes_count)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    IM_ASSERT(pos + bytes_count <= BufTextLen);
 | 
					 | 
				
			||||||
    char* dst = Buf + pos;
 | 
					 | 
				
			||||||
    const char* src = Buf + pos + bytes_count;
 | 
					 | 
				
			||||||
    while (char c = *src++)
 | 
					 | 
				
			||||||
        *dst++ = c;
 | 
					 | 
				
			||||||
    *dst = '\0';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (CursorPos + bytes_count >= pos)
 | 
					 | 
				
			||||||
        CursorPos -= bytes_count;
 | 
					 | 
				
			||||||
    else if (CursorPos >= pos)
 | 
					 | 
				
			||||||
        CursorPos = pos;
 | 
					 | 
				
			||||||
    SelectionStart = SelectionEnd = CursorPos;
 | 
					 | 
				
			||||||
    BufDirty = true;
 | 
					 | 
				
			||||||
    BufTextLen -= bytes_count;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, const char* new_text_end)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    const bool is_resizable = (Flags & ImGuiInputTextFlags_CallbackResize) != 0;
 | 
					 | 
				
			||||||
    const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)strlen(new_text);
 | 
					 | 
				
			||||||
    if (new_text_len + BufTextLen >= BufSize)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        if (!is_resizable)
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Contrary to STB_TEXTEDIT_INSERTCHARS() this is working in the UTF8 buffer, hence the midly similar code (until we remove the U16 buffer alltogether!)
 | 
					 | 
				
			||||||
        ImGuiContext& g = *GImGui;
 | 
					 | 
				
			||||||
        ImGuiInputTextState* edit_state = &g.InputTextState;
 | 
					 | 
				
			||||||
        IM_ASSERT(edit_state->ID != 0 && g.ActiveId == edit_state->ID);
 | 
					 | 
				
			||||||
        IM_ASSERT(Buf == edit_state->TempBuffer.Data);
 | 
					 | 
				
			||||||
        int new_buf_size = BufTextLen + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1;
 | 
					 | 
				
			||||||
        edit_state->TempBuffer.reserve(new_buf_size + 1);
 | 
					 | 
				
			||||||
        Buf = edit_state->TempBuffer.Data;
 | 
					 | 
				
			||||||
        BufSize = edit_state->BufCapacityA = new_buf_size;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (BufTextLen != pos)
 | 
					 | 
				
			||||||
        memmove(Buf + pos + new_text_len, Buf + pos, (size_t)(BufTextLen - pos));
 | 
					 | 
				
			||||||
    memcpy(Buf + pos, new_text, (size_t)new_text_len * sizeof(char));
 | 
					 | 
				
			||||||
    Buf[BufTextLen + new_text_len] = '\0';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (CursorPos >= pos)
 | 
					 | 
				
			||||||
        CursorPos += new_text_len;
 | 
					 | 
				
			||||||
    SelectionStart = SelectionEnd = CursorPos;
 | 
					 | 
				
			||||||
    BufDirty = true;
 | 
					 | 
				
			||||||
    BufTextLen += new_text_len;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Return false to discard a character.
 | 
					 | 
				
			||||||
static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    unsigned int c = *p_char;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (c < 128 && c != ' ' && !isprint((int)(c & 0xFF)))
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        bool pass = false;
 | 
					 | 
				
			||||||
        pass |= (c == '\n' && (flags & ImGuiInputTextFlags_Multiline));
 | 
					 | 
				
			||||||
        pass |= (c == '\t' && (flags & ImGuiInputTextFlags_AllowTabInput));
 | 
					 | 
				
			||||||
        if (!pass)
 | 
					 | 
				
			||||||
            return false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (c >= 0xE000 && c <= 0xF8FF) // Filter private Unicode range. I don't imagine anybody would want to input them. GLFW on OSX seems to send private characters for special keys like arrow keys.
 | 
					 | 
				
			||||||
        return false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CharsScientific))
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        if (flags & ImGuiInputTextFlags_CharsDecimal)
 | 
					 | 
				
			||||||
            if (!(c >= '0' && c <= '9') && (c != '.') && (c != '-') && (c != '+') && (c != '*') && (c != '/'))
 | 
					 | 
				
			||||||
                return false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (flags & ImGuiInputTextFlags_CharsScientific)
 | 
					 | 
				
			||||||
            if (!(c >= '0' && c <= '9') && (c != '.') && (c != '-') && (c != '+') && (c != '*') && (c != '/') && (c != 'e') && (c != 'E'))
 | 
					 | 
				
			||||||
                return false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (flags & ImGuiInputTextFlags_CharsHexadecimal)
 | 
					 | 
				
			||||||
            if (!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F'))
 | 
					 | 
				
			||||||
                return false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (flags & ImGuiInputTextFlags_CharsUppercase)
 | 
					 | 
				
			||||||
            if (c >= 'a' && c <= 'z')
 | 
					 | 
				
			||||||
                *p_char = (c += (unsigned int)('A'-'a'));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (flags & ImGuiInputTextFlags_CharsNoBlank)
 | 
					 | 
				
			||||||
            if (ImCharIsBlankW(c))
 | 
					 | 
				
			||||||
                return false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (flags & ImGuiInputTextFlags_CallbackCharFilter)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        ImGuiInputTextCallbackData callback_data;
 | 
					 | 
				
			||||||
        memset(&callback_data, 0, sizeof(ImGuiInputTextCallbackData));
 | 
					 | 
				
			||||||
        callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter;
 | 
					 | 
				
			||||||
        callback_data.EventChar = (ImWchar)c;
 | 
					 | 
				
			||||||
        callback_data.Flags = flags;
 | 
					 | 
				
			||||||
        callback_data.UserData = user_data;
 | 
					 | 
				
			||||||
        if (callback(&callback_data) != 0)
 | 
					 | 
				
			||||||
            return false;
 | 
					 | 
				
			||||||
        *p_char = callback_data.EventChar;
 | 
					 | 
				
			||||||
        if (!callback_data.EventChar)
 | 
					 | 
				
			||||||
            return false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return true;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Edit a string of text
 | 
					 | 
				
			||||||
// - buf_size account for the zero-terminator, so a buf_size of 6 can hold "Hello" but not "Hello!".
 | 
					 | 
				
			||||||
//   This is so we can easily call InputText() on static arrays using ARRAYSIZE() and to match 
 | 
					 | 
				
			||||||
//   Note that in std::string world, capacity() would omit 1 byte used by the zero-terminator.
 | 
					 | 
				
			||||||
// - When active, hold on a privately held copy of the text (and apply back to 'buf'). So changing 'buf' while the InputText is active has no effect.
 | 
					 | 
				
			||||||
// - If you want to use ImGui::InputText() with std::string, see misc/stl/imgui_stl.h
 | 
					 | 
				
			||||||
// (FIXME: Rather messy function partly because we are doing UTF8 > u16 > UTF8 conversions on the go to more easily handle stb_textedit calls. Ideally we should stay in UTF-8 all the time. See https://github.com/nothings/stb/issues/188)
 | 
					 | 
				
			||||||
bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* callback_user_data)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    ImGuiWindow* window = GetCurrentWindow();
 | 
					 | 
				
			||||||
    if (window->SkipItems)
 | 
					 | 
				
			||||||
        return false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline)));        // Can't use both together (they both use up/down keys)
 | 
					 | 
				
			||||||
    IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ImGuiContext& g = *GImGui;
 | 
					 | 
				
			||||||
    const ImGuiIO& io = g.IO;
 | 
					 | 
				
			||||||
    const ImGuiStyle& style = g.Style;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0;
 | 
					 | 
				
			||||||
    const bool is_editable = (flags & ImGuiInputTextFlags_ReadOnly) == 0;
 | 
					 | 
				
			||||||
    const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0;
 | 
					 | 
				
			||||||
    const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0;
 | 
					 | 
				
			||||||
    const bool is_resizable = (flags & ImGuiInputTextFlags_CallbackResize) != 0;
 | 
					 | 
				
			||||||
    if (is_resizable)
 | 
					 | 
				
			||||||
        IM_ASSERT(callback != NULL); // Must provide a callback if you set the ImGuiInputTextFlags_CallbackResize flag!
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (is_multiline) // Open group before calling GetID() because groups tracks id created within their scope, 
 | 
					 | 
				
			||||||
        BeginGroup();
 | 
					 | 
				
			||||||
    const ImGuiID id = window->GetID(label);
 | 
					 | 
				
			||||||
    const ImVec2 label_size = CalcTextSize(label, NULL, true);
 | 
					 | 
				
			||||||
    ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), (is_multiline ? GetTextLineHeight() * 8.0f : label_size.y) + style.FramePadding.y*2.0f); // Arbitrary default of 8 lines high for multi-line
 | 
					 | 
				
			||||||
    const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size);
 | 
					 | 
				
			||||||
    const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? (style.ItemInnerSpacing.x + label_size.x) : 0.0f, 0.0f));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ImGuiWindow* draw_window = window;
 | 
					 | 
				
			||||||
    if (is_multiline)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        ItemAdd(total_bb, id, &frame_bb);
 | 
					 | 
				
			||||||
        if (!BeginChildFrame(id, frame_bb.GetSize()))
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            EndChildFrame();
 | 
					 | 
				
			||||||
            EndGroup();
 | 
					 | 
				
			||||||
            return false;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        draw_window = GetCurrentWindow();
 | 
					 | 
				
			||||||
        draw_window->DC.NavLayerActiveMaskNext |= draw_window->DC.NavLayerCurrentMask; // This is to ensure that EndChild() will display a navigation highlight
 | 
					 | 
				
			||||||
        size.x -= draw_window->ScrollbarSizes.x;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    else
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        ItemSize(total_bb, style.FramePadding.y);
 | 
					 | 
				
			||||||
        if (!ItemAdd(total_bb, id, &frame_bb))
 | 
					 | 
				
			||||||
            return false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    const bool hovered = ItemHoverable(frame_bb, id);
 | 
					 | 
				
			||||||
    if (hovered)
 | 
					 | 
				
			||||||
        g.MouseCursor = ImGuiMouseCursor_TextInput;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Password pushes a temporary font with only a fallback glyph
 | 
					 | 
				
			||||||
    if (is_password)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        const ImFontGlyph* glyph = g.Font->FindGlyph('*');
 | 
					 | 
				
			||||||
        ImFont* password_font = &g.InputTextPasswordFont;
 | 
					 | 
				
			||||||
        password_font->FontSize = g.Font->FontSize;
 | 
					 | 
				
			||||||
        password_font->Scale = g.Font->Scale;
 | 
					 | 
				
			||||||
        password_font->DisplayOffset = g.Font->DisplayOffset;
 | 
					 | 
				
			||||||
        password_font->Ascent = g.Font->Ascent;
 | 
					 | 
				
			||||||
        password_font->Descent = g.Font->Descent;
 | 
					 | 
				
			||||||
        password_font->ContainerAtlas = g.Font->ContainerAtlas;
 | 
					 | 
				
			||||||
        password_font->FallbackGlyph = glyph;
 | 
					 | 
				
			||||||
        password_font->FallbackAdvanceX = glyph->AdvanceX;
 | 
					 | 
				
			||||||
        IM_ASSERT(password_font->Glyphs.empty() && password_font->IndexAdvanceX.empty() && password_font->IndexLookup.empty());
 | 
					 | 
				
			||||||
        PushFont(password_font);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // NB: we are only allowed to access 'edit_state' if we are the active widget.
 | 
					 | 
				
			||||||
    ImGuiInputTextState& edit_state = g.InputTextState;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const bool focus_requested = FocusableItemRegister(window, id, (flags & (ImGuiInputTextFlags_CallbackCompletion|ImGuiInputTextFlags_AllowTabInput)) == 0);    // Using completion callback disable keyboard tabbing
 | 
					 | 
				
			||||||
    const bool focus_requested_by_code = focus_requested && (window->FocusIdxAllCounter == window->FocusIdxAllRequestCurrent);
 | 
					 | 
				
			||||||
    const bool focus_requested_by_tab = focus_requested && !focus_requested_by_code;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const bool user_clicked = hovered && io.MouseClicked[0];
 | 
					 | 
				
			||||||
    const bool user_scrolled = is_multiline && g.ActiveId == 0 && edit_state.ID == id && g.ActiveIdPreviousFrame == draw_window->GetIDNoKeepAlive("#SCROLLY");
 | 
					 | 
				
			||||||
    const bool user_nav_input_start = (g.ActiveId != id) && ((g.NavInputId == id) || (g.NavActivateId == id && g.NavInputSource == ImGuiInputSource_NavKeyboard));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    bool clear_active_id = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    bool select_all = (g.ActiveId != id) && ((flags & ImGuiInputTextFlags_AutoSelectAll) != 0 || user_nav_input_start) && (!is_multiline);
 | 
					 | 
				
			||||||
    if (focus_requested || user_clicked || user_scrolled || user_nav_input_start)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        if (g.ActiveId != id)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            // Start edition
 | 
					 | 
				
			||||||
            // Take a copy of the initial buffer value (both in original UTF-8 format and converted to wchar)
 | 
					 | 
				
			||||||
            // From the moment we focused we are ignoring the content of 'buf' (unless we are in read-only mode)
 | 
					 | 
				
			||||||
            const int prev_len_w = edit_state.CurLenW;
 | 
					 | 
				
			||||||
            const int init_buf_len = (int)strlen(buf);
 | 
					 | 
				
			||||||
            edit_state.TextW.resize(buf_size+1);             // wchar count <= UTF-8 count. we use +1 to make sure that .Data isn't NULL so it doesn't crash.
 | 
					 | 
				
			||||||
            edit_state.InitialText.resize(init_buf_len + 1); // UTF-8. we use +1 to make sure that .Data isn't NULL so it doesn't crash.
 | 
					 | 
				
			||||||
            memcpy(edit_state.InitialText.Data, buf, init_buf_len + 1);
 | 
					 | 
				
			||||||
            const char* buf_end = NULL;
 | 
					 | 
				
			||||||
            edit_state.CurLenW = ImTextStrFromUtf8(edit_state.TextW.Data, buf_size, buf, NULL, &buf_end);
 | 
					 | 
				
			||||||
            edit_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.
 | 
					 | 
				
			||||||
            edit_state.CursorAnimReset();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Preserve cursor position and undo/redo stack if we come back to same widget
 | 
					 | 
				
			||||||
            // FIXME: We should probably compare the whole buffer to be on the safety side. Comparing buf (utf8) and edit_state.Text (wchar).
 | 
					 | 
				
			||||||
            const bool recycle_state = (edit_state.ID == id) && (prev_len_w == edit_state.CurLenW);
 | 
					 | 
				
			||||||
            if (recycle_state)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                // Recycle existing cursor/selection/undo stack but clamp position
 | 
					 | 
				
			||||||
                // Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler.
 | 
					 | 
				
			||||||
                edit_state.CursorClamp();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            else
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                edit_state.ID = id;
 | 
					 | 
				
			||||||
                edit_state.ScrollX = 0.0f;
 | 
					 | 
				
			||||||
                stb_textedit_initialize_state(&edit_state.StbState, !is_multiline);
 | 
					 | 
				
			||||||
                if (!is_multiline && focus_requested_by_code)
 | 
					 | 
				
			||||||
                    select_all = true;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            if (flags & ImGuiInputTextFlags_AlwaysInsertMode)
 | 
					 | 
				
			||||||
                edit_state.StbState.insert_mode = true;
 | 
					 | 
				
			||||||
            if (!is_multiline && (focus_requested_by_tab || (user_clicked && io.KeyCtrl)))
 | 
					 | 
				
			||||||
                select_all = true;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        SetActiveID(id, window);
 | 
					 | 
				
			||||||
        SetFocusID(id, window);
 | 
					 | 
				
			||||||
        FocusWindow(window);
 | 
					 | 
				
			||||||
        if (!is_multiline && !(flags & ImGuiInputTextFlags_CallbackHistory))
 | 
					 | 
				
			||||||
            g.ActiveIdAllowNavDirFlags |= ((1 << ImGuiDir_Up) | (1 << ImGuiDir_Down));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    else if (io.MouseClicked[0])
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        // Release focus when we click outside
 | 
					 | 
				
			||||||
        clear_active_id = true;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    bool value_changed = false;
 | 
					 | 
				
			||||||
    bool enter_pressed = false;
 | 
					 | 
				
			||||||
    int backup_current_text_length = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (g.ActiveId == id)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        if (!is_editable && !g.ActiveIdIsJustActivated)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            // When read-only we always use the live data passed to the function
 | 
					 | 
				
			||||||
            edit_state.TextW.resize(buf_size+1);
 | 
					 | 
				
			||||||
            const char* buf_end = NULL;
 | 
					 | 
				
			||||||
            edit_state.CurLenW = ImTextStrFromUtf8(edit_state.TextW.Data, edit_state.TextW.Size, buf, NULL, &buf_end);
 | 
					 | 
				
			||||||
            edit_state.CurLenA = (int)(buf_end - buf);
 | 
					 | 
				
			||||||
            edit_state.CursorClamp();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        backup_current_text_length = edit_state.CurLenA;
 | 
					 | 
				
			||||||
        edit_state.BufCapacityA = buf_size;
 | 
					 | 
				
			||||||
        edit_state.UserFlags = flags;
 | 
					 | 
				
			||||||
        edit_state.UserCallback = callback;
 | 
					 | 
				
			||||||
        edit_state.UserCallbackData = callback_user_data;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget.
 | 
					 | 
				
			||||||
        // Down the line we should have a cleaner library-wide concept of Selected vs Active.
 | 
					 | 
				
			||||||
        g.ActiveIdAllowOverlap = !io.MouseDown[0];
 | 
					 | 
				
			||||||
        g.WantTextInputNextFrame = 1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Edit in progress
 | 
					 | 
				
			||||||
        const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + edit_state.ScrollX;
 | 
					 | 
				
			||||||
        const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y - style.FramePadding.y) : (g.FontSize*0.5f));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const bool is_osx = io.ConfigMacOSXBehaviors;
 | 
					 | 
				
			||||||
        if (select_all || (hovered && !is_osx && io.MouseDoubleClicked[0]))
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            edit_state.SelectAll();
 | 
					 | 
				
			||||||
            edit_state.SelectedAllMouseLock = true;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        else if (hovered && is_osx && io.MouseDoubleClicked[0])
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            // Double-click select a word only, OS X style (by simulating keystrokes)
 | 
					 | 
				
			||||||
            edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT);
 | 
					 | 
				
			||||||
            edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        else if (io.MouseClicked[0] && !edit_state.SelectedAllMouseLock)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            if (hovered)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                stb_textedit_click(&edit_state, &edit_state.StbState, mouse_x, mouse_y);
 | 
					 | 
				
			||||||
                edit_state.CursorAnimReset();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        else if (io.MouseDown[0] && !edit_state.SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f))
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            stb_textedit_drag(&edit_state, &edit_state.StbState, mouse_x, mouse_y);
 | 
					 | 
				
			||||||
            edit_state.CursorAnimReset();
 | 
					 | 
				
			||||||
            edit_state.CursorFollow = true;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (edit_state.SelectedAllMouseLock && !io.MouseDown[0])
 | 
					 | 
				
			||||||
            edit_state.SelectedAllMouseLock = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (io.InputCharacters[0])
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            // Process text input (before we check for Return because using some IME will effectively send a Return?)
 | 
					 | 
				
			||||||
            // We ignore CTRL inputs, but need to allow ALT+CTRL as some keyboards (e.g. German) use AltGR (which _is_ Alt+Ctrl) to input certain characters.
 | 
					 | 
				
			||||||
            bool ignore_inputs = (io.KeyCtrl && !io.KeyAlt) || (is_osx && io.KeySuper);
 | 
					 | 
				
			||||||
            if (!ignore_inputs && is_editable && !user_nav_input_start)
 | 
					 | 
				
			||||||
                for (int n = 0; n < IM_ARRAYSIZE(io.InputCharacters) && io.InputCharacters[n]; n++)
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    // Insert character if they pass filtering
 | 
					 | 
				
			||||||
                    unsigned int c = (unsigned int)io.InputCharacters[n];
 | 
					 | 
				
			||||||
                    if (InputTextFilterCharacter(&c, flags, callback, callback_user_data))
 | 
					 | 
				
			||||||
                        edit_state.OnKeyPressed((int)c);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Consume characters
 | 
					 | 
				
			||||||
            memset(g.IO.InputCharacters, 0, sizeof(g.IO.InputCharacters));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    bool cancel_edit = false;
 | 
					 | 
				
			||||||
    if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        // Handle key-presses
 | 
					 | 
				
			||||||
        const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0);
 | 
					 | 
				
			||||||
        const bool is_osx = io.ConfigMacOSXBehaviors;
 | 
					 | 
				
			||||||
        const bool is_shortcut_key = (is_osx ? (io.KeySuper && !io.KeyCtrl) : (io.KeyCtrl && !io.KeySuper)) && !io.KeyAlt && !io.KeyShift; // OS X style: Shortcuts using Cmd/Super instead of Ctrl
 | 
					 | 
				
			||||||
        const bool is_osx_shift_shortcut = is_osx && io.KeySuper && io.KeyShift && !io.KeyCtrl && !io.KeyAlt;
 | 
					 | 
				
			||||||
        const bool is_wordmove_key_down = is_osx ? io.KeyAlt : io.KeyCtrl;                     // OS X style: Text editing cursor movement using Alt instead of Ctrl
 | 
					 | 
				
			||||||
        const bool is_startend_key_down = is_osx && io.KeySuper && !io.KeyCtrl && !io.KeyAlt;  // OS X style: Line/Text Start and End using Cmd+Arrows instead of Home/End
 | 
					 | 
				
			||||||
        const bool is_ctrl_key_only = io.KeyCtrl && !io.KeyShift && !io.KeyAlt && !io.KeySuper;
 | 
					 | 
				
			||||||
        const bool is_shift_key_only = io.KeyShift && !io.KeyCtrl && !io.KeyAlt && !io.KeySuper;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const bool is_cut   = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_X)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Delete))) && is_editable && !is_password && (!is_multiline || edit_state.HasSelection());
 | 
					 | 
				
			||||||
        const bool is_copy  = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_C)) || (is_ctrl_key_only  && IsKeyPressedMap(ImGuiKey_Insert))) && !is_password && (!is_multiline || edit_state.HasSelection());
 | 
					 | 
				
			||||||
        const bool is_paste = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_V)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && is_editable;
 | 
					 | 
				
			||||||
        const bool is_undo  = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Z)) && is_editable && is_undoable);
 | 
					 | 
				
			||||||
        const bool is_redo  = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Y)) || (is_osx_shift_shortcut && IsKeyPressedMap(ImGuiKey_Z))) && is_editable && is_undoable;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (IsKeyPressedMap(ImGuiKey_LeftArrow))                        { edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINESTART : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDLEFT : STB_TEXTEDIT_K_LEFT) | k_mask); }
 | 
					 | 
				
			||||||
        else if (IsKeyPressedMap(ImGuiKey_RightArrow))                  { edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINEEND : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDRIGHT : STB_TEXTEDIT_K_RIGHT) | k_mask); }
 | 
					 | 
				
			||||||
        else if (IsKeyPressedMap(ImGuiKey_UpArrow) && is_multiline)     { if (io.KeyCtrl) SetWindowScrollY(draw_window, ImMax(draw_window->Scroll.y - g.FontSize, 0.0f)); else edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTSTART : STB_TEXTEDIT_K_UP) | k_mask); }
 | 
					 | 
				
			||||||
        else if (IsKeyPressedMap(ImGuiKey_DownArrow) && is_multiline)   { if (io.KeyCtrl) SetWindowScrollY(draw_window, ImMin(draw_window->Scroll.y + g.FontSize, GetScrollMaxY())); else edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTEND : STB_TEXTEDIT_K_DOWN) | k_mask); }
 | 
					 | 
				
			||||||
        else if (IsKeyPressedMap(ImGuiKey_Home))                        { edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); }
 | 
					 | 
				
			||||||
        else if (IsKeyPressedMap(ImGuiKey_End))                         { edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); }
 | 
					 | 
				
			||||||
        else if (IsKeyPressedMap(ImGuiKey_Delete) && is_editable)       { edit_state.OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); }
 | 
					 | 
				
			||||||
        else if (IsKeyPressedMap(ImGuiKey_Backspace) && is_editable)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            if (!edit_state.HasSelection())
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                if (is_wordmove_key_down) edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT|STB_TEXTEDIT_K_SHIFT);
 | 
					 | 
				
			||||||
                else if (is_osx && io.KeySuper && !io.KeyAlt && !io.KeyCtrl) edit_state.OnKeyPressed(STB_TEXTEDIT_K_LINESTART|STB_TEXTEDIT_K_SHIFT);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            edit_state.OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        else if (IsKeyPressedMap(ImGuiKey_Enter))
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0;
 | 
					 | 
				
			||||||
            if (!is_multiline || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                enter_pressed = clear_active_id = true;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            else if (is_editable)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                unsigned int c = '\n'; // Insert new line
 | 
					 | 
				
			||||||
                if (InputTextFilterCharacter(&c, flags, callback, callback_user_data))
 | 
					 | 
				
			||||||
                    edit_state.OnKeyPressed((int)c);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        else if ((flags & ImGuiInputTextFlags_AllowTabInput) && IsKeyPressedMap(ImGuiKey_Tab) && !io.KeyCtrl && !io.KeyShift && !io.KeyAlt && is_editable)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            unsigned int c = '\t'; // Insert TAB
 | 
					 | 
				
			||||||
            if (InputTextFilterCharacter(&c, flags, callback, callback_user_data))
 | 
					 | 
				
			||||||
                edit_state.OnKeyPressed((int)c);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        else if (IsKeyPressedMap(ImGuiKey_Escape))
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            clear_active_id = cancel_edit = true;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        else if (is_undo || is_redo)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            edit_state.OnKeyPressed(is_undo ? STB_TEXTEDIT_K_UNDO : STB_TEXTEDIT_K_REDO);
 | 
					 | 
				
			||||||
            edit_state.ClearSelection();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        else if (is_shortcut_key && IsKeyPressedMap(ImGuiKey_A))
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            edit_state.SelectAll();
 | 
					 | 
				
			||||||
            edit_state.CursorFollow = true;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        else if (is_cut || is_copy)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            // Cut, Copy
 | 
					 | 
				
			||||||
            if (io.SetClipboardTextFn)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                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) : edit_state.CurLenW;
 | 
					 | 
				
			||||||
                edit_state.TempBuffer.resize((ie-ib) * 4 + 1);
 | 
					 | 
				
			||||||
                ImTextStrToUtf8(edit_state.TempBuffer.Data, edit_state.TempBuffer.Size, edit_state.TextW.Data+ib, edit_state.TextW.Data+ie);
 | 
					 | 
				
			||||||
                SetClipboardText(edit_state.TempBuffer.Data);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            if (is_cut)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                if (!edit_state.HasSelection())
 | 
					 | 
				
			||||||
                    edit_state.SelectAll();
 | 
					 | 
				
			||||||
                edit_state.CursorFollow = true;
 | 
					 | 
				
			||||||
                stb_textedit_cut(&edit_state, &edit_state.StbState);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        else if (is_paste)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            if (const char* clipboard = GetClipboardText())
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                // Filter pasted buffer
 | 
					 | 
				
			||||||
                const int clipboard_len = (int)strlen(clipboard);
 | 
					 | 
				
			||||||
                ImWchar* clipboard_filtered = (ImWchar*)ImGui::MemAlloc((clipboard_len+1) * sizeof(ImWchar));
 | 
					 | 
				
			||||||
                int clipboard_filtered_len = 0;
 | 
					 | 
				
			||||||
                for (const char* s = clipboard; *s; )
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    unsigned int c;
 | 
					 | 
				
			||||||
                    s += ImTextCharFromUtf8(&c, s, NULL);
 | 
					 | 
				
			||||||
                    if (c == 0)
 | 
					 | 
				
			||||||
                        break;
 | 
					 | 
				
			||||||
                    if (c >= 0x10000 || !InputTextFilterCharacter(&c, flags, callback, callback_user_data))
 | 
					 | 
				
			||||||
                        continue;
 | 
					 | 
				
			||||||
                    clipboard_filtered[clipboard_filtered_len++] = (ImWchar)c;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                clipboard_filtered[clipboard_filtered_len] = 0;
 | 
					 | 
				
			||||||
                if (clipboard_filtered_len > 0) // If everything was filtered, ignore the pasting operation
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    stb_textedit_paste(&edit_state, &edit_state.StbState, clipboard_filtered, clipboard_filtered_len);
 | 
					 | 
				
			||||||
                    edit_state.CursorFollow = true;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                ImGui::MemFree(clipboard_filtered);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (g.ActiveId == id)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        const char* apply_new_text = NULL;
 | 
					 | 
				
			||||||
        int apply_new_text_length = 0;
 | 
					 | 
				
			||||||
        if (cancel_edit)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            // Restore initial value. Only return true if restoring to the initial value changes the current buffer contents.
 | 
					 | 
				
			||||||
            if (is_editable && strcmp(buf, edit_state.InitialText.Data) != 0)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                apply_new_text = edit_state.InitialText.Data;
 | 
					 | 
				
			||||||
                apply_new_text_length = edit_state.InitialText.Size - 1;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // When using 'ImGuiInputTextFlags_EnterReturnsTrue' as a special case we reapply the live buffer back to the input buffer before clearing ActiveId, even though strictly speaking it wasn't modified on this frame.
 | 
					 | 
				
			||||||
        // If we didn't do that, code like InputInt() with ImGuiInputTextFlags_EnterReturnsTrue would fail. Also this allows the user to use InputText() with ImGuiInputTextFlags_EnterReturnsTrue without maintaining any user-side storage.
 | 
					 | 
				
			||||||
        bool apply_edit_back_to_user_buffer = !cancel_edit || (enter_pressed && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0);
 | 
					 | 
				
			||||||
        if (apply_edit_back_to_user_buffer)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            // Apply new value immediately - copy modified buffer back
 | 
					 | 
				
			||||||
            // Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer
 | 
					 | 
				
			||||||
            // FIXME: We actually always render 'buf' when calling DrawList->AddText, making the comment above incorrect.
 | 
					 | 
				
			||||||
            // FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks.
 | 
					 | 
				
			||||||
            if (is_editable)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                edit_state.TempBuffer.resize(edit_state.TextW.Size * 4 + 1);
 | 
					 | 
				
			||||||
                ImTextStrToUtf8(edit_state.TempBuffer.Data, edit_state.TempBuffer.Size, edit_state.TextW.Data, NULL);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // User callback
 | 
					 | 
				
			||||||
            if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackAlways)) != 0)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                IM_ASSERT(callback != NULL);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment.
 | 
					 | 
				
			||||||
                ImGuiInputTextFlags event_flag = 0;
 | 
					 | 
				
			||||||
                ImGuiKey event_key = ImGuiKey_COUNT;
 | 
					 | 
				
			||||||
                if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && IsKeyPressedMap(ImGuiKey_Tab))
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    event_flag = ImGuiInputTextFlags_CallbackCompletion;
 | 
					 | 
				
			||||||
                    event_key = ImGuiKey_Tab;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_UpArrow))
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    event_flag = ImGuiInputTextFlags_CallbackHistory;
 | 
					 | 
				
			||||||
                    event_key = ImGuiKey_UpArrow;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_DownArrow))
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    event_flag = ImGuiInputTextFlags_CallbackHistory;
 | 
					 | 
				
			||||||
                    event_key = ImGuiKey_DownArrow;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                else if (flags & ImGuiInputTextFlags_CallbackAlways)
 | 
					 | 
				
			||||||
                    event_flag = ImGuiInputTextFlags_CallbackAlways;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (event_flag)
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    ImGuiInputTextCallbackData callback_data;
 | 
					 | 
				
			||||||
                    memset(&callback_data, 0, sizeof(ImGuiInputTextCallbackData));
 | 
					 | 
				
			||||||
                    callback_data.EventFlag = event_flag;
 | 
					 | 
				
			||||||
                    callback_data.Flags = flags;
 | 
					 | 
				
			||||||
                    callback_data.UserData = callback_user_data;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    callback_data.EventKey = event_key;
 | 
					 | 
				
			||||||
                    callback_data.Buf = edit_state.TempBuffer.Data;
 | 
					 | 
				
			||||||
                    callback_data.BufTextLen = edit_state.CurLenA;
 | 
					 | 
				
			||||||
                    callback_data.BufSize = edit_state.BufCapacityA;
 | 
					 | 
				
			||||||
                    callback_data.BufDirty = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    // 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 = edit_state.TextW.Data;
 | 
					 | 
				
			||||||
                    const int utf8_cursor_pos = callback_data.CursorPos = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.cursor);
 | 
					 | 
				
			||||||
                    const int utf8_selection_start = callback_data.SelectionStart = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_start);
 | 
					 | 
				
			||||||
                    const int utf8_selection_end = callback_data.SelectionEnd = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_end);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    // Call user code
 | 
					 | 
				
			||||||
                    callback(&callback_data);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    // Read back what user may have modified
 | 
					 | 
				
			||||||
                    IM_ASSERT(callback_data.Buf == edit_state.TempBuffer.Data);  // Invalid to modify those fields
 | 
					 | 
				
			||||||
                    IM_ASSERT(callback_data.BufSize == edit_state.BufCapacityA);
 | 
					 | 
				
			||||||
                    IM_ASSERT(callback_data.Flags == flags);
 | 
					 | 
				
			||||||
                    if (callback_data.CursorPos != utf8_cursor_pos)            { edit_state.StbState.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos); edit_state.CursorFollow = true; }
 | 
					 | 
				
			||||||
                    if (callback_data.SelectionStart != utf8_selection_start)  { edit_state.StbState.select_start = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionStart); }
 | 
					 | 
				
			||||||
                    if (callback_data.SelectionEnd != utf8_selection_end)      { edit_state.StbState.select_end = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionEnd); }
 | 
					 | 
				
			||||||
                    if (callback_data.BufDirty)
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text!
 | 
					 | 
				
			||||||
                        if (callback_data.BufTextLen > backup_current_text_length && is_resizable)
 | 
					 | 
				
			||||||
                            edit_state.TextW.resize(edit_state.TextW.Size + (callback_data.BufTextLen - backup_current_text_length));
 | 
					 | 
				
			||||||
                        edit_state.CurLenW = ImTextStrFromUtf8(edit_state.TextW.Data, edit_state.TextW.Size, callback_data.Buf, NULL);
 | 
					 | 
				
			||||||
                        edit_state.CurLenA = callback_data.BufTextLen;  // Assume correct length and valid UTF-8 from user, saves us an extra strlen()
 | 
					 | 
				
			||||||
                        edit_state.CursorAnimReset();
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Will copy result string if modified
 | 
					 | 
				
			||||||
            if (is_editable && strcmp(edit_state.TempBuffer.Data, buf) != 0)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                apply_new_text = edit_state.TempBuffer.Data;
 | 
					 | 
				
			||||||
                apply_new_text_length = edit_state.CurLenA;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Copy result to user buffer
 | 
					 | 
				
			||||||
        if (apply_new_text)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            IM_ASSERT(apply_new_text_length >= 0);
 | 
					 | 
				
			||||||
            if (backup_current_text_length != apply_new_text_length && is_resizable)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                ImGuiInputTextCallbackData callback_data;
 | 
					 | 
				
			||||||
                callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize;
 | 
					 | 
				
			||||||
                callback_data.Flags = flags;
 | 
					 | 
				
			||||||
                callback_data.Buf = buf;
 | 
					 | 
				
			||||||
                callback_data.BufTextLen = apply_new_text_length;
 | 
					 | 
				
			||||||
                callback_data.BufSize = ImMax(buf_size, apply_new_text_length + 1);
 | 
					 | 
				
			||||||
                callback_data.UserData = callback_user_data;
 | 
					 | 
				
			||||||
                callback(&callback_data);
 | 
					 | 
				
			||||||
                buf = callback_data.Buf;
 | 
					 | 
				
			||||||
                buf_size = callback_data.BufSize;
 | 
					 | 
				
			||||||
                apply_new_text_length = ImMin(callback_data.BufTextLen, buf_size - 1);
 | 
					 | 
				
			||||||
                IM_ASSERT(apply_new_text_length <= buf_size);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // If the underlying buffer resize was denied or not carried to the next frame, apply_new_text_length+1 may be >= buf_size.
 | 
					 | 
				
			||||||
            ImStrncpy(buf, edit_state.TempBuffer.Data, ImMin(apply_new_text_length + 1, buf_size));
 | 
					 | 
				
			||||||
            value_changed = true;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Clear temporary user storage
 | 
					 | 
				
			||||||
        edit_state.UserFlags = 0;
 | 
					 | 
				
			||||||
        edit_state.UserCallback = NULL;
 | 
					 | 
				
			||||||
        edit_state.UserCallbackData = NULL;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value)
 | 
					 | 
				
			||||||
    if (clear_active_id && g.ActiveId == id)
 | 
					 | 
				
			||||||
        ClearActiveID();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Render
 | 
					 | 
				
			||||||
    // Select which buffer we are going to display. When ImGuiInputTextFlags_NoLiveEdit is set 'buf' might still be the old value. We set buf to NULL to prevent accidental usage from now on.
 | 
					 | 
				
			||||||
    const char* buf_display = (g.ActiveId == id && is_editable) ? edit_state.TempBuffer.Data : buf; buf = NULL;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Set upper limit of single-line InputTextEx() at 2 million characters strings. The current pathological worst case is a long line
 | 
					 | 
				
			||||||
    // without any carriage return, which would makes ImFont::RenderText() reserve too many vertices and probably crash. Avoid it altogether.
 | 
					 | 
				
			||||||
    // Note that we only use this limit on single-line InputText(), so a pathologically large line on a InputTextMultiline() would still crash.
 | 
					 | 
				
			||||||
    const int buf_display_max_length = 2 * 1024 * 1024;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!is_multiline)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        RenderNavHighlight(frame_bb, id);
 | 
					 | 
				
			||||||
        RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + size.x, frame_bb.Min.y + size.y); // Not using frame_bb.Max because we have adjusted size
 | 
					 | 
				
			||||||
    ImVec2 render_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding;
 | 
					 | 
				
			||||||
    ImVec2 text_size(0.f, 0.f);
 | 
					 | 
				
			||||||
    const bool is_currently_scrolling = (edit_state.ID == id && is_multiline && g.ActiveId == draw_window->GetIDNoKeepAlive("#SCROLLY"));
 | 
					 | 
				
			||||||
    if (g.ActiveId == id || is_currently_scrolling)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        edit_state.CursorAnim += io.DeltaTime;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // This is going to be messy. We need to:
 | 
					 | 
				
			||||||
        // - Display the text (this alone can be more easily clipped)
 | 
					 | 
				
			||||||
        // - Handle scrolling, highlight selection, display cursor (those all requires some form of 1d->2d cursor position calculation)
 | 
					 | 
				
			||||||
        // - Measure text height (for scrollbar)
 | 
					 | 
				
			||||||
        // We are attempting to do most of that in **one main pass** to minimize the computation cost (non-negligible for large amount of text) + 2nd pass for selection rendering (we could merge them by an extra refactoring effort)
 | 
					 | 
				
			||||||
        // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8.
 | 
					 | 
				
			||||||
        const ImWchar* text_begin = edit_state.TextW.Data;
 | 
					 | 
				
			||||||
        ImVec2 cursor_offset, select_start_offset;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            // Count lines + find lines numbers straddling 'cursor' and 'select_start' position.
 | 
					 | 
				
			||||||
            const ImWchar* searches_input_ptr[2];
 | 
					 | 
				
			||||||
            searches_input_ptr[0] = text_begin + edit_state.StbState.cursor;
 | 
					 | 
				
			||||||
            searches_input_ptr[1] = NULL;
 | 
					 | 
				
			||||||
            int searches_remaining = 1;
 | 
					 | 
				
			||||||
            int searches_result_line_number[2] = { -1, -999 };
 | 
					 | 
				
			||||||
            if (edit_state.StbState.select_start != edit_state.StbState.select_end)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                searches_input_ptr[1] = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end);
 | 
					 | 
				
			||||||
                searches_result_line_number[1] = -1;
 | 
					 | 
				
			||||||
                searches_remaining++;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Iterate all lines to find our line numbers
 | 
					 | 
				
			||||||
            // In multi-line mode, we never exit the loop until all lines are counted, so add one extra to the searches_remaining counter.
 | 
					 | 
				
			||||||
            searches_remaining += is_multiline ? 1 : 0;
 | 
					 | 
				
			||||||
            int line_count = 0;
 | 
					 | 
				
			||||||
            for (const ImWchar* s = text_begin; *s != 0; s++)
 | 
					 | 
				
			||||||
                if (*s == '\n')
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    line_count++;
 | 
					 | 
				
			||||||
                    if (searches_result_line_number[0] == -1 && s >= searches_input_ptr[0]) { searches_result_line_number[0] = line_count; if (--searches_remaining <= 0) break; }
 | 
					 | 
				
			||||||
                    if (searches_result_line_number[1] == -1 && s >= searches_input_ptr[1]) { searches_result_line_number[1] = line_count; if (--searches_remaining <= 0) break; }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            line_count++;
 | 
					 | 
				
			||||||
            if (searches_result_line_number[0] == -1) searches_result_line_number[0] = line_count;
 | 
					 | 
				
			||||||
            if (searches_result_line_number[1] == -1) searches_result_line_number[1] = line_count;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Calculate 2d position by finding the beginning of the line and measuring distance
 | 
					 | 
				
			||||||
            cursor_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[0], text_begin), searches_input_ptr[0]).x;
 | 
					 | 
				
			||||||
            cursor_offset.y = searches_result_line_number[0] * g.FontSize;
 | 
					 | 
				
			||||||
            if (searches_result_line_number[1] >= 0)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                select_start_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[1], text_begin), searches_input_ptr[1]).x;
 | 
					 | 
				
			||||||
                select_start_offset.y = searches_result_line_number[1] * g.FontSize;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Store text height (note that we haven't calculated text width at all, see GitHub issues #383, #1224)
 | 
					 | 
				
			||||||
            if (is_multiline)
 | 
					 | 
				
			||||||
                text_size = ImVec2(size.x, line_count * g.FontSize);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Scroll
 | 
					 | 
				
			||||||
        if (edit_state.CursorFollow)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            // Horizontal scroll in chunks of quarter width
 | 
					 | 
				
			||||||
            if (!(flags & ImGuiInputTextFlags_NoHorizontalScroll))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                const float scroll_increment_x = size.x * 0.25f;
 | 
					 | 
				
			||||||
                if (cursor_offset.x < edit_state.ScrollX)
 | 
					 | 
				
			||||||
                    edit_state.ScrollX = (float)(int)ImMax(0.0f, cursor_offset.x - scroll_increment_x);
 | 
					 | 
				
			||||||
                else if (cursor_offset.x - size.x >= edit_state.ScrollX)
 | 
					 | 
				
			||||||
                    edit_state.ScrollX = (float)(int)(cursor_offset.x - size.x + scroll_increment_x);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            else
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                edit_state.ScrollX = 0.0f;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Vertical scroll
 | 
					 | 
				
			||||||
            if (is_multiline)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                float scroll_y = draw_window->Scroll.y;
 | 
					 | 
				
			||||||
                if (cursor_offset.y - g.FontSize < scroll_y)
 | 
					 | 
				
			||||||
                    scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize);
 | 
					 | 
				
			||||||
                else if (cursor_offset.y - size.y >= scroll_y)
 | 
					 | 
				
			||||||
                    scroll_y = cursor_offset.y - size.y;
 | 
					 | 
				
			||||||
                draw_window->DC.CursorPos.y += (draw_window->Scroll.y - scroll_y);   // To avoid a frame of lag
 | 
					 | 
				
			||||||
                draw_window->Scroll.y = scroll_y;
 | 
					 | 
				
			||||||
                render_pos.y = draw_window->DC.CursorPos.y;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        edit_state.CursorFollow = false;
 | 
					 | 
				
			||||||
        const ImVec2 render_scroll = ImVec2(edit_state.ScrollX, 0.0f);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Draw selection
 | 
					 | 
				
			||||||
        if (edit_state.StbState.select_start != edit_state.StbState.select_end)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            const ImWchar* text_selected_begin = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end);
 | 
					 | 
				
			||||||
            const ImWchar* text_selected_end = text_begin + ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            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.
 | 
					 | 
				
			||||||
            float bg_offy_dn = is_multiline ? 0.0f : 2.0f;
 | 
					 | 
				
			||||||
            ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg);
 | 
					 | 
				
			||||||
            ImVec2 rect_pos = render_pos + select_start_offset - render_scroll;
 | 
					 | 
				
			||||||
            for (const ImWchar* p = text_selected_begin; p < text_selected_end; )
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                if (rect_pos.y > clip_rect.w + g.FontSize)
 | 
					 | 
				
			||||||
                    break;
 | 
					 | 
				
			||||||
                if (rect_pos.y < clip_rect.y)
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    while (p < text_selected_end)
 | 
					 | 
				
			||||||
                        if (*p++ == '\n')
 | 
					 | 
				
			||||||
                            break;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                else
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    ImVec2 rect_size = InputTextCalcTextSizeW(p, text_selected_end, &p, NULL, true);
 | 
					 | 
				
			||||||
                    if (rect_size.x <= 0.0f) rect_size.x = (float)(int)(g.Font->GetCharAdvance((unsigned short)' ') * 0.50f); // So we can see selected empty lines
 | 
					 | 
				
			||||||
                    ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos +ImVec2(rect_size.x, bg_offy_dn));
 | 
					 | 
				
			||||||
                    rect.ClipWith(clip_rect);
 | 
					 | 
				
			||||||
                    if (rect.Overlaps(clip_rect))
 | 
					 | 
				
			||||||
                        draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                rect_pos.x = render_pos.x - render_scroll.x;
 | 
					 | 
				
			||||||
                rect_pos.y += g.FontSize;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const int buf_display_len = edit_state.CurLenA;
 | 
					 | 
				
			||||||
        if (is_multiline || buf_display_len < buf_display_max_length)
 | 
					 | 
				
			||||||
            draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos - render_scroll, GetColorU32(ImGuiCol_Text), buf_display, buf_display + buf_display_len, 0.0f, is_multiline ? NULL : &clip_rect);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Draw blinking cursor
 | 
					 | 
				
			||||||
        bool cursor_is_visible = (!g.IO.ConfigCursorBlink) || (g.InputTextState.CursorAnim <= 0.0f) || ImFmod(g.InputTextState.CursorAnim, 1.20f) <= 0.80f;
 | 
					 | 
				
			||||||
        ImVec2 cursor_screen_pos = render_pos + cursor_offset - render_scroll;
 | 
					 | 
				
			||||||
        ImRect cursor_screen_rect(cursor_screen_pos.x, cursor_screen_pos.y-g.FontSize+0.5f, cursor_screen_pos.x+1.0f, cursor_screen_pos.y-1.5f);
 | 
					 | 
				
			||||||
        if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect))
 | 
					 | 
				
			||||||
            draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_Text));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Notify OS of text input position for advanced IME (-1 x offset so that Windows IME can cover our cursor. Bit of an extra nicety.)
 | 
					 | 
				
			||||||
        if (is_editable)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            g.PlatformImePos = ImVec2(cursor_screen_pos.x - 1, cursor_screen_pos.y - g.FontSize);
 | 
					 | 
				
			||||||
            g.PlatformImePosViewport = window->Viewport;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    else
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        // Render text only
 | 
					 | 
				
			||||||
        const char* buf_end = NULL;
 | 
					 | 
				
			||||||
        if (is_multiline)
 | 
					 | 
				
			||||||
            text_size = ImVec2(size.x, InputTextCalcTextLenAndLineCount(buf_display, &buf_end) * g.FontSize); // We don't need width
 | 
					 | 
				
			||||||
        else
 | 
					 | 
				
			||||||
            buf_end = buf_display + strlen(buf_display);
 | 
					 | 
				
			||||||
        if (is_multiline || (buf_end - buf_display) < buf_display_max_length)
 | 
					 | 
				
			||||||
            draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos, GetColorU32(ImGuiCol_Text), buf_display, buf_end, 0.0f, is_multiline ? NULL : &clip_rect);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (is_multiline)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        Dummy(text_size + ImVec2(0.0f, g.FontSize)); // Always add room to scroll an extra line
 | 
					 | 
				
			||||||
        EndChildFrame();
 | 
					 | 
				
			||||||
        EndGroup();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (is_password)
 | 
					 | 
				
			||||||
        PopFont();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Log as text
 | 
					 | 
				
			||||||
    if (g.LogEnabled && !is_password)
 | 
					 | 
				
			||||||
        LogRenderedText(&render_pos, buf_display, NULL);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (label_size.x > 0)
 | 
					 | 
				
			||||||
        RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (value_changed)
 | 
					 | 
				
			||||||
        MarkItemEdited(id);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0)
 | 
					 | 
				
			||||||
        return enter_pressed;
 | 
					 | 
				
			||||||
    else
 | 
					 | 
				
			||||||
        return value_changed;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline()
 | 
					 | 
				
			||||||
    return InputTextEx(label, buf, (int)buf_size, ImVec2(0,0), flags, callback, user_data);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bool ImGui::InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    return InputTextEx(label, buf, (int)buf_size, size, flags | ImGuiInputTextFlags_Multiline, callback, user_data);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// NB: format here must be a simple "%xx" format string with no prefix/suffix (unlike the Drag/Slider functions "format" argument)
 | 
					 | 
				
			||||||
bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* data_ptr, const void* step, const void* step_fast, const char* format, ImGuiInputTextFlags extra_flags)
 | 
					bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* data_ptr, const void* step, const void* step_fast, const char* format, ImGuiInputTextFlags extra_flags)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    ImGuiWindow* window = GetCurrentWindow();
 | 
					    ImGuiWindow* window = GetCurrentWindow();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -34,6 +34,11 @@
 | 
				
			|||||||
// Forward Declarations
 | 
					// Forward Declarations
 | 
				
			||||||
//-------------------------------------------------------------------------
 | 
					//-------------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// For InputTextEx()
 | 
				
			||||||
 | 
					static bool             InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data);
 | 
				
			||||||
 | 
					static int              InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end);
 | 
				
			||||||
 | 
					static ImVec2           InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//-------------------------------------------------------------------------
 | 
					//-------------------------------------------------------------------------
 | 
				
			||||||
// SHARED UTILITIES
 | 
					// SHARED UTILITIES
 | 
				
			||||||
//-------------------------------------------------------------------------
 | 
					//-------------------------------------------------------------------------
 | 
				
			||||||
@@ -1209,6 +1214,975 @@ bool ImGui::Combo(const char* label, int* current_item, const char* items_separa
 | 
				
			|||||||
// - InputTextEx() [Internal]
 | 
					// - InputTextEx() [Internal]
 | 
				
			||||||
//-------------------------------------------------------------------------
 | 
					//-------------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline()
 | 
				
			||||||
 | 
					    return InputTextEx(label, buf, (int)buf_size, ImVec2(0,0), flags, callback, user_data);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool ImGui::InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    return InputTextEx(label, buf, (int)buf_size, size, flags | ImGuiInputTextFlags_Multiline, callback, user_data);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    int line_count = 0;
 | 
				
			||||||
 | 
					    const char* s = text_begin;
 | 
				
			||||||
 | 
					    while (char c = *s++) // We are only matching for \n so we can ignore UTF-8 decoding
 | 
				
			||||||
 | 
					        if (c == '\n')
 | 
				
			||||||
 | 
					            line_count++;
 | 
				
			||||||
 | 
					    s--;
 | 
				
			||||||
 | 
					    if (s[0] != '\n' && s[0] != '\r')
 | 
				
			||||||
 | 
					        line_count++;
 | 
				
			||||||
 | 
					    *out_text_end = s;
 | 
				
			||||||
 | 
					    return line_count;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining, ImVec2* out_offset, bool stop_on_new_line)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    ImFont* font = GImGui->Font;
 | 
				
			||||||
 | 
					    const float line_height = GImGui->FontSize;
 | 
				
			||||||
 | 
					    const float scale = line_height / font->FontSize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ImVec2 text_size = ImVec2(0,0);
 | 
				
			||||||
 | 
					    float line_width = 0.0f;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const ImWchar* s = text_begin;
 | 
				
			||||||
 | 
					    while (s < text_end)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        unsigned int c = (unsigned int)(*s++);
 | 
				
			||||||
 | 
					        if (c == '\n')
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            text_size.x = ImMax(text_size.x, line_width);
 | 
				
			||||||
 | 
					            text_size.y += line_height;
 | 
				
			||||||
 | 
					            line_width = 0.0f;
 | 
				
			||||||
 | 
					            if (stop_on_new_line)
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            continue;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (c == '\r')
 | 
				
			||||||
 | 
					            continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const float char_width = font->GetCharAdvance((unsigned short)c) * scale;
 | 
				
			||||||
 | 
					        line_width += char_width;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (text_size.x < line_width)
 | 
				
			||||||
 | 
					        text_size.x = line_width;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (out_offset)
 | 
				
			||||||
 | 
					        *out_offset = ImVec2(line_width, text_size.y + line_height);  // offset allow for the possibility of sitting after a trailing \n
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (line_width > 0 || text_size.y == 0.0f)                        // whereas size.y will ignore the trailing \n
 | 
				
			||||||
 | 
					        text_size.y += line_height;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (remaining)
 | 
				
			||||||
 | 
					        *remaining = s;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return text_size;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Wrapper for stb_textedit.h to edit text (our wrapper is for: statically sized buffer, single-line, wchar characters. InputText converts between UTF-8 and wchar)
 | 
				
			||||||
 | 
					namespace ImGuiStb
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int     STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING* obj)                             { return obj->CurLenW; }
 | 
				
			||||||
 | 
					static ImWchar STB_TEXTEDIT_GETCHAR(const STB_TEXTEDIT_STRING* obj, int idx)                      { return obj->TextW[idx]; }
 | 
				
			||||||
 | 
					static float   STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING* obj, int line_start_idx, int char_idx)  { ImWchar c = obj->TextW[line_start_idx+char_idx]; if (c == '\n') return STB_TEXTEDIT_GETWIDTH_NEWLINE; return GImGui->Font->GetCharAdvance(c) * (GImGui->FontSize / GImGui->Font->FontSize); }
 | 
				
			||||||
 | 
					static int     STB_TEXTEDIT_KEYTOTEXT(int key)                                                    { return key >= 0x10000 ? 0 : key; }
 | 
				
			||||||
 | 
					static ImWchar STB_TEXTEDIT_NEWLINE = '\n';
 | 
				
			||||||
 | 
					static void    STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, STB_TEXTEDIT_STRING* obj, int line_start_idx)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    const ImWchar* text = obj->TextW.Data;
 | 
				
			||||||
 | 
					    const ImWchar* text_remaining = NULL;
 | 
				
			||||||
 | 
					    const ImVec2 size = InputTextCalcTextSizeW(text + line_start_idx, text + obj->CurLenW, &text_remaining, NULL, true);
 | 
				
			||||||
 | 
					    r->x0 = 0.0f;
 | 
				
			||||||
 | 
					    r->x1 = size.x;
 | 
				
			||||||
 | 
					    r->baseline_y_delta = size.y;
 | 
				
			||||||
 | 
					    r->ymin = 0.0f;
 | 
				
			||||||
 | 
					    r->ymax = size.y;
 | 
				
			||||||
 | 
					    r->num_chars = (int)(text_remaining - (text + line_start_idx));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool is_separator(unsigned int c)                                        { return ImCharIsBlankW(c) || c==',' || c==';' || c=='(' || c==')' || c=='{' || c=='}' || c=='[' || c==']' || c=='|'; }
 | 
				
			||||||
 | 
					static int  is_word_boundary_from_right(STB_TEXTEDIT_STRING* obj, int idx)      { return idx > 0 ? (is_separator( obj->TextW[idx-1] ) && !is_separator( obj->TextW[idx] ) ) : 1; }
 | 
				
			||||||
 | 
					static int  STB_TEXTEDIT_MOVEWORDLEFT_IMPL(STB_TEXTEDIT_STRING* obj, int idx)   { idx--; while (idx >= 0 && !is_word_boundary_from_right(obj, idx)) idx--; return idx < 0 ? 0 : idx; }
 | 
				
			||||||
 | 
					#ifdef __APPLE__    // FIXME: Move setting to IO structure
 | 
				
			||||||
 | 
					static int  is_word_boundary_from_left(STB_TEXTEDIT_STRING* obj, int idx)       { return idx > 0 ? (!is_separator( obj->TextW[idx-1] ) && is_separator( obj->TextW[idx] ) ) : 1; }
 | 
				
			||||||
 | 
					static int  STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING* obj, int idx)  { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_left(obj, idx)) idx++; return idx > len ? len : idx; }
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					static int  STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING* obj, int idx)  { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_right(obj, idx)) idx++; return idx > len ? len : idx; }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					#define STB_TEXTEDIT_MOVEWORDLEFT   STB_TEXTEDIT_MOVEWORDLEFT_IMPL    // They need to be #define for stb_textedit.h
 | 
				
			||||||
 | 
					#define STB_TEXTEDIT_MOVEWORDRIGHT  STB_TEXTEDIT_MOVEWORDRIGHT_IMPL
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void STB_TEXTEDIT_DELETECHARS(STB_TEXTEDIT_STRING* obj, int pos, int n)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    ImWchar* dst = obj->TextW.Data + pos;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // We maintain our buffer length in both UTF-8 and wchar formats
 | 
				
			||||||
 | 
					    obj->CurLenA -= ImTextCountUtf8BytesFromStr(dst, dst + n);
 | 
				
			||||||
 | 
					    obj->CurLenW -= n;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Offset remaining text
 | 
				
			||||||
 | 
					    const ImWchar* src = obj->TextW.Data + pos + n;
 | 
				
			||||||
 | 
					    while (ImWchar c = *src++)
 | 
				
			||||||
 | 
					        *dst++ = c;
 | 
				
			||||||
 | 
					    *dst = '\0';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING* obj, int pos, const ImWchar* new_text, int new_text_len)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    const bool is_resizable = (obj->UserFlags & ImGuiInputTextFlags_CallbackResize) != 0;
 | 
				
			||||||
 | 
					    const int text_len = obj->CurLenW;
 | 
				
			||||||
 | 
					    IM_ASSERT(pos <= text_len);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const int new_text_len_utf8 = ImTextCountUtf8BytesFromStr(new_text, new_text + new_text_len);
 | 
				
			||||||
 | 
					    if (!is_resizable && (new_text_len_utf8 + obj->CurLenA + 1 > obj->BufCapacityA))
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Grow internal buffer if needed
 | 
				
			||||||
 | 
					    if (new_text_len + text_len + 1 > obj->TextW.Size)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (!is_resizable)
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        IM_ASSERT(text_len < obj->TextW.Size);
 | 
				
			||||||
 | 
					        obj->TextW.resize(text_len + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ImWchar* text = obj->TextW.Data;
 | 
				
			||||||
 | 
					    if (pos != text_len)
 | 
				
			||||||
 | 
					        memmove(text + pos + new_text_len, text + pos, (size_t)(text_len - pos) * sizeof(ImWchar));
 | 
				
			||||||
 | 
					    memcpy(text + pos, new_text, (size_t)new_text_len * sizeof(ImWchar));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    obj->CurLenW += new_text_len;
 | 
				
			||||||
 | 
					    obj->CurLenA += new_text_len_utf8;
 | 
				
			||||||
 | 
					    obj->TextW[obj->CurLenW] = '\0';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// We don't use an enum so we can build even with conflicting symbols (if another user of stb_textedit.h leak their STB_TEXTEDIT_K_* symbols)
 | 
				
			||||||
 | 
					#define STB_TEXTEDIT_K_LEFT         0x10000 // keyboard input to move cursor left
 | 
				
			||||||
 | 
					#define STB_TEXTEDIT_K_RIGHT        0x10001 // keyboard input to move cursor right
 | 
				
			||||||
 | 
					#define STB_TEXTEDIT_K_UP           0x10002 // keyboard input to move cursor up
 | 
				
			||||||
 | 
					#define STB_TEXTEDIT_K_DOWN         0x10003 // keyboard input to move cursor down
 | 
				
			||||||
 | 
					#define STB_TEXTEDIT_K_LINESTART    0x10004 // keyboard input to move cursor to start of line
 | 
				
			||||||
 | 
					#define STB_TEXTEDIT_K_LINEEND      0x10005 // keyboard input to move cursor to end of line
 | 
				
			||||||
 | 
					#define STB_TEXTEDIT_K_TEXTSTART    0x10006 // keyboard input to move cursor to start of text
 | 
				
			||||||
 | 
					#define STB_TEXTEDIT_K_TEXTEND      0x10007 // keyboard input to move cursor to end of text
 | 
				
			||||||
 | 
					#define STB_TEXTEDIT_K_DELETE       0x10008 // keyboard input to delete selection or character under cursor
 | 
				
			||||||
 | 
					#define STB_TEXTEDIT_K_BACKSPACE    0x10009 // keyboard input to delete selection or character left of cursor
 | 
				
			||||||
 | 
					#define STB_TEXTEDIT_K_UNDO         0x1000A // keyboard input to perform undo
 | 
				
			||||||
 | 
					#define STB_TEXTEDIT_K_REDO         0x1000B // keyboard input to perform redo
 | 
				
			||||||
 | 
					#define STB_TEXTEDIT_K_WORDLEFT     0x1000C // keyboard input to move cursor left one word
 | 
				
			||||||
 | 
					#define STB_TEXTEDIT_K_WORDRIGHT    0x1000D // keyboard input to move cursor right one word
 | 
				
			||||||
 | 
					#define STB_TEXTEDIT_K_SHIFT        0x20000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define STB_TEXTEDIT_IMPLEMENTATION
 | 
				
			||||||
 | 
					#include "stb_textedit.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ImGuiInputTextState::OnKeyPressed(int key)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    stb_textedit_key(this, &StbState, key);
 | 
				
			||||||
 | 
					    CursorFollow = true;
 | 
				
			||||||
 | 
					    CursorAnimReset();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ImGuiInputTextCallbackData::ImGuiInputTextCallbackData()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    memset(this, 0, sizeof(*this));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Public API to manipulate UTF-8 text
 | 
				
			||||||
 | 
					// We expose UTF-8 to the user (unlike the STB_TEXTEDIT_* functions which are manipulating wchar)
 | 
				
			||||||
 | 
					// FIXME: The existence of this rarely exercised code path is a bit of a nuisance.
 | 
				
			||||||
 | 
					void ImGuiInputTextCallbackData::DeleteChars(int pos, int bytes_count)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    IM_ASSERT(pos + bytes_count <= BufTextLen);
 | 
				
			||||||
 | 
					    char* dst = Buf + pos;
 | 
				
			||||||
 | 
					    const char* src = Buf + pos + bytes_count;
 | 
				
			||||||
 | 
					    while (char c = *src++)
 | 
				
			||||||
 | 
					        *dst++ = c;
 | 
				
			||||||
 | 
					    *dst = '\0';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (CursorPos + bytes_count >= pos)
 | 
				
			||||||
 | 
					        CursorPos -= bytes_count;
 | 
				
			||||||
 | 
					    else if (CursorPos >= pos)
 | 
				
			||||||
 | 
					        CursorPos = pos;
 | 
				
			||||||
 | 
					    SelectionStart = SelectionEnd = CursorPos;
 | 
				
			||||||
 | 
					    BufDirty = true;
 | 
				
			||||||
 | 
					    BufTextLen -= bytes_count;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, const char* new_text_end)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    const bool is_resizable = (Flags & ImGuiInputTextFlags_CallbackResize) != 0;
 | 
				
			||||||
 | 
					    const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)strlen(new_text);
 | 
				
			||||||
 | 
					    if (new_text_len + BufTextLen >= BufSize)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (!is_resizable)
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Contrary to STB_TEXTEDIT_INSERTCHARS() this is working in the UTF8 buffer, hence the midly similar code (until we remove the U16 buffer alltogether!)
 | 
				
			||||||
 | 
					        ImGuiContext& g = *GImGui;
 | 
				
			||||||
 | 
					        ImGuiInputTextState* edit_state = &g.InputTextState;
 | 
				
			||||||
 | 
					        IM_ASSERT(edit_state->ID != 0 && g.ActiveId == edit_state->ID);
 | 
				
			||||||
 | 
					        IM_ASSERT(Buf == edit_state->TempBuffer.Data);
 | 
				
			||||||
 | 
					        int new_buf_size = BufTextLen + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1;
 | 
				
			||||||
 | 
					        edit_state->TempBuffer.reserve(new_buf_size + 1);
 | 
				
			||||||
 | 
					        Buf = edit_state->TempBuffer.Data;
 | 
				
			||||||
 | 
					        BufSize = edit_state->BufCapacityA = new_buf_size;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (BufTextLen != pos)
 | 
				
			||||||
 | 
					        memmove(Buf + pos + new_text_len, Buf + pos, (size_t)(BufTextLen - pos));
 | 
				
			||||||
 | 
					    memcpy(Buf + pos, new_text, (size_t)new_text_len * sizeof(char));
 | 
				
			||||||
 | 
					    Buf[BufTextLen + new_text_len] = '\0';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (CursorPos >= pos)
 | 
				
			||||||
 | 
					        CursorPos += new_text_len;
 | 
				
			||||||
 | 
					    SelectionStart = SelectionEnd = CursorPos;
 | 
				
			||||||
 | 
					    BufDirty = true;
 | 
				
			||||||
 | 
					    BufTextLen += new_text_len;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Return false to discard a character.
 | 
				
			||||||
 | 
					static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    unsigned int c = *p_char;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (c < 128 && c != ' ' && !isprint((int)(c & 0xFF)))
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        bool pass = false;
 | 
				
			||||||
 | 
					        pass |= (c == '\n' && (flags & ImGuiInputTextFlags_Multiline));
 | 
				
			||||||
 | 
					        pass |= (c == '\t' && (flags & ImGuiInputTextFlags_AllowTabInput));
 | 
				
			||||||
 | 
					        if (!pass)
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (c >= 0xE000 && c <= 0xF8FF) // Filter private Unicode range. I don't imagine anybody would want to input them. GLFW on OSX seems to send private characters for special keys like arrow keys.
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CharsScientific))
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (flags & ImGuiInputTextFlags_CharsDecimal)
 | 
				
			||||||
 | 
					            if (!(c >= '0' && c <= '9') && (c != '.') && (c != '-') && (c != '+') && (c != '*') && (c != '/'))
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (flags & ImGuiInputTextFlags_CharsScientific)
 | 
				
			||||||
 | 
					            if (!(c >= '0' && c <= '9') && (c != '.') && (c != '-') && (c != '+') && (c != '*') && (c != '/') && (c != 'e') && (c != 'E'))
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (flags & ImGuiInputTextFlags_CharsHexadecimal)
 | 
				
			||||||
 | 
					            if (!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F'))
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (flags & ImGuiInputTextFlags_CharsUppercase)
 | 
				
			||||||
 | 
					            if (c >= 'a' && c <= 'z')
 | 
				
			||||||
 | 
					                *p_char = (c += (unsigned int)('A'-'a'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (flags & ImGuiInputTextFlags_CharsNoBlank)
 | 
				
			||||||
 | 
					            if (ImCharIsBlankW(c))
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (flags & ImGuiInputTextFlags_CallbackCharFilter)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        ImGuiInputTextCallbackData callback_data;
 | 
				
			||||||
 | 
					        memset(&callback_data, 0, sizeof(ImGuiInputTextCallbackData));
 | 
				
			||||||
 | 
					        callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter;
 | 
				
			||||||
 | 
					        callback_data.EventChar = (ImWchar)c;
 | 
				
			||||||
 | 
					        callback_data.Flags = flags;
 | 
				
			||||||
 | 
					        callback_data.UserData = user_data;
 | 
				
			||||||
 | 
					        if (callback(&callback_data) != 0)
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        *p_char = callback_data.EventChar;
 | 
				
			||||||
 | 
					        if (!callback_data.EventChar)
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Edit a string of text
 | 
				
			||||||
 | 
					// - buf_size account for the zero-terminator, so a buf_size of 6 can hold "Hello" but not "Hello!".
 | 
				
			||||||
 | 
					//   This is so we can easily call InputText() on static arrays using ARRAYSIZE() and to match 
 | 
				
			||||||
 | 
					//   Note that in std::string world, capacity() would omit 1 byte used by the zero-terminator.
 | 
				
			||||||
 | 
					// - When active, hold on a privately held copy of the text (and apply back to 'buf'). So changing 'buf' while the InputText is active has no effect.
 | 
				
			||||||
 | 
					// - If you want to use ImGui::InputText() with std::string, see misc/stl/imgui_stl.h
 | 
				
			||||||
 | 
					// (FIXME: Rather messy function partly because we are doing UTF8 > u16 > UTF8 conversions on the go to more easily handle stb_textedit calls. Ideally we should stay in UTF-8 all the time. See https://github.com/nothings/stb/issues/188)
 | 
				
			||||||
 | 
					bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* callback_user_data)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    ImGuiWindow* window = GetCurrentWindow();
 | 
				
			||||||
 | 
					    if (window->SkipItems)
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline)));        // Can't use both together (they both use up/down keys)
 | 
				
			||||||
 | 
					    IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ImGuiContext& g = *GImGui;
 | 
				
			||||||
 | 
					    const ImGuiIO& io = g.IO;
 | 
				
			||||||
 | 
					    const ImGuiStyle& style = g.Style;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0;
 | 
				
			||||||
 | 
					    const bool is_editable = (flags & ImGuiInputTextFlags_ReadOnly) == 0;
 | 
				
			||||||
 | 
					    const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0;
 | 
				
			||||||
 | 
					    const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0;
 | 
				
			||||||
 | 
					    const bool is_resizable = (flags & ImGuiInputTextFlags_CallbackResize) != 0;
 | 
				
			||||||
 | 
					    if (is_resizable)
 | 
				
			||||||
 | 
					        IM_ASSERT(callback != NULL); // Must provide a callback if you set the ImGuiInputTextFlags_CallbackResize flag!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (is_multiline) // Open group before calling GetID() because groups tracks id created within their scope, 
 | 
				
			||||||
 | 
					        BeginGroup();
 | 
				
			||||||
 | 
					    const ImGuiID id = window->GetID(label);
 | 
				
			||||||
 | 
					    const ImVec2 label_size = CalcTextSize(label, NULL, true);
 | 
				
			||||||
 | 
					    ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), (is_multiline ? GetTextLineHeight() * 8.0f : label_size.y) + style.FramePadding.y*2.0f); // Arbitrary default of 8 lines high for multi-line
 | 
				
			||||||
 | 
					    const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size);
 | 
				
			||||||
 | 
					    const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? (style.ItemInnerSpacing.x + label_size.x) : 0.0f, 0.0f));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ImGuiWindow* draw_window = window;
 | 
				
			||||||
 | 
					    if (is_multiline)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        ItemAdd(total_bb, id, &frame_bb);
 | 
				
			||||||
 | 
					        if (!BeginChildFrame(id, frame_bb.GetSize()))
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            EndChildFrame();
 | 
				
			||||||
 | 
					            EndGroup();
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        draw_window = GetCurrentWindow();
 | 
				
			||||||
 | 
					        draw_window->DC.NavLayerActiveMaskNext |= draw_window->DC.NavLayerCurrentMask; // This is to ensure that EndChild() will display a navigation highlight
 | 
				
			||||||
 | 
					        size.x -= draw_window->ScrollbarSizes.x;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        ItemSize(total_bb, style.FramePadding.y);
 | 
				
			||||||
 | 
					        if (!ItemAdd(total_bb, id, &frame_bb))
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const bool hovered = ItemHoverable(frame_bb, id);
 | 
				
			||||||
 | 
					    if (hovered)
 | 
				
			||||||
 | 
					        g.MouseCursor = ImGuiMouseCursor_TextInput;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Password pushes a temporary font with only a fallback glyph
 | 
				
			||||||
 | 
					    if (is_password)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        const ImFontGlyph* glyph = g.Font->FindGlyph('*');
 | 
				
			||||||
 | 
					        ImFont* password_font = &g.InputTextPasswordFont;
 | 
				
			||||||
 | 
					        password_font->FontSize = g.Font->FontSize;
 | 
				
			||||||
 | 
					        password_font->Scale = g.Font->Scale;
 | 
				
			||||||
 | 
					        password_font->DisplayOffset = g.Font->DisplayOffset;
 | 
				
			||||||
 | 
					        password_font->Ascent = g.Font->Ascent;
 | 
				
			||||||
 | 
					        password_font->Descent = g.Font->Descent;
 | 
				
			||||||
 | 
					        password_font->ContainerAtlas = g.Font->ContainerAtlas;
 | 
				
			||||||
 | 
					        password_font->FallbackGlyph = glyph;
 | 
				
			||||||
 | 
					        password_font->FallbackAdvanceX = glyph->AdvanceX;
 | 
				
			||||||
 | 
					        IM_ASSERT(password_font->Glyphs.empty() && password_font->IndexAdvanceX.empty() && password_font->IndexLookup.empty());
 | 
				
			||||||
 | 
					        PushFont(password_font);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // NB: we are only allowed to access 'edit_state' if we are the active widget.
 | 
				
			||||||
 | 
					    ImGuiInputTextState& edit_state = g.InputTextState;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const bool focus_requested = FocusableItemRegister(window, id, (flags & (ImGuiInputTextFlags_CallbackCompletion|ImGuiInputTextFlags_AllowTabInput)) == 0);    // Using completion callback disable keyboard tabbing
 | 
				
			||||||
 | 
					    const bool focus_requested_by_code = focus_requested && (window->FocusIdxAllCounter == window->FocusIdxAllRequestCurrent);
 | 
				
			||||||
 | 
					    const bool focus_requested_by_tab = focus_requested && !focus_requested_by_code;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const bool user_clicked = hovered && io.MouseClicked[0];
 | 
				
			||||||
 | 
					    const bool user_scrolled = is_multiline && g.ActiveId == 0 && edit_state.ID == id && g.ActiveIdPreviousFrame == draw_window->GetIDNoKeepAlive("#SCROLLY");
 | 
				
			||||||
 | 
					    const bool user_nav_input_start = (g.ActiveId != id) && ((g.NavInputId == id) || (g.NavActivateId == id && g.NavInputSource == ImGuiInputSource_NavKeyboard));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool clear_active_id = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool select_all = (g.ActiveId != id) && ((flags & ImGuiInputTextFlags_AutoSelectAll) != 0 || user_nav_input_start) && (!is_multiline);
 | 
				
			||||||
 | 
					    if (focus_requested || user_clicked || user_scrolled || user_nav_input_start)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (g.ActiveId != id)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            // Start edition
 | 
				
			||||||
 | 
					            // Take a copy of the initial buffer value (both in original UTF-8 format and converted to wchar)
 | 
				
			||||||
 | 
					            // From the moment we focused we are ignoring the content of 'buf' (unless we are in read-only mode)
 | 
				
			||||||
 | 
					            const int prev_len_w = edit_state.CurLenW;
 | 
				
			||||||
 | 
					            const int init_buf_len = (int)strlen(buf);
 | 
				
			||||||
 | 
					            edit_state.TextW.resize(buf_size+1);             // wchar count <= UTF-8 count. we use +1 to make sure that .Data isn't NULL so it doesn't crash.
 | 
				
			||||||
 | 
					            edit_state.InitialText.resize(init_buf_len + 1); // UTF-8. we use +1 to make sure that .Data isn't NULL so it doesn't crash.
 | 
				
			||||||
 | 
					            memcpy(edit_state.InitialText.Data, buf, init_buf_len + 1);
 | 
				
			||||||
 | 
					            const char* buf_end = NULL;
 | 
				
			||||||
 | 
					            edit_state.CurLenW = ImTextStrFromUtf8(edit_state.TextW.Data, buf_size, buf, NULL, &buf_end);
 | 
				
			||||||
 | 
					            edit_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.
 | 
				
			||||||
 | 
					            edit_state.CursorAnimReset();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Preserve cursor position and undo/redo stack if we come back to same widget
 | 
				
			||||||
 | 
					            // FIXME: We should probably compare the whole buffer to be on the safety side. Comparing buf (utf8) and edit_state.Text (wchar).
 | 
				
			||||||
 | 
					            const bool recycle_state = (edit_state.ID == id) && (prev_len_w == edit_state.CurLenW);
 | 
				
			||||||
 | 
					            if (recycle_state)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                // Recycle existing cursor/selection/undo stack but clamp position
 | 
				
			||||||
 | 
					                // Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler.
 | 
				
			||||||
 | 
					                edit_state.CursorClamp();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                edit_state.ID = id;
 | 
				
			||||||
 | 
					                edit_state.ScrollX = 0.0f;
 | 
				
			||||||
 | 
					                stb_textedit_initialize_state(&edit_state.StbState, !is_multiline);
 | 
				
			||||||
 | 
					                if (!is_multiline && focus_requested_by_code)
 | 
				
			||||||
 | 
					                    select_all = true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (flags & ImGuiInputTextFlags_AlwaysInsertMode)
 | 
				
			||||||
 | 
					                edit_state.StbState.insert_mode = true;
 | 
				
			||||||
 | 
					            if (!is_multiline && (focus_requested_by_tab || (user_clicked && io.KeyCtrl)))
 | 
				
			||||||
 | 
					                select_all = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        SetActiveID(id, window);
 | 
				
			||||||
 | 
					        SetFocusID(id, window);
 | 
				
			||||||
 | 
					        FocusWindow(window);
 | 
				
			||||||
 | 
					        if (!is_multiline && !(flags & ImGuiInputTextFlags_CallbackHistory))
 | 
				
			||||||
 | 
					            g.ActiveIdAllowNavDirFlags |= ((1 << ImGuiDir_Up) | (1 << ImGuiDir_Down));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    else if (io.MouseClicked[0])
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        // Release focus when we click outside
 | 
				
			||||||
 | 
					        clear_active_id = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool value_changed = false;
 | 
				
			||||||
 | 
					    bool enter_pressed = false;
 | 
				
			||||||
 | 
					    int backup_current_text_length = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (g.ActiveId == id)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (!is_editable && !g.ActiveIdIsJustActivated)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            // When read-only we always use the live data passed to the function
 | 
				
			||||||
 | 
					            edit_state.TextW.resize(buf_size+1);
 | 
				
			||||||
 | 
					            const char* buf_end = NULL;
 | 
				
			||||||
 | 
					            edit_state.CurLenW = ImTextStrFromUtf8(edit_state.TextW.Data, edit_state.TextW.Size, buf, NULL, &buf_end);
 | 
				
			||||||
 | 
					            edit_state.CurLenA = (int)(buf_end - buf);
 | 
				
			||||||
 | 
					            edit_state.CursorClamp();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        backup_current_text_length = edit_state.CurLenA;
 | 
				
			||||||
 | 
					        edit_state.BufCapacityA = buf_size;
 | 
				
			||||||
 | 
					        edit_state.UserFlags = flags;
 | 
				
			||||||
 | 
					        edit_state.UserCallback = callback;
 | 
				
			||||||
 | 
					        edit_state.UserCallbackData = callback_user_data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget.
 | 
				
			||||||
 | 
					        // Down the line we should have a cleaner library-wide concept of Selected vs Active.
 | 
				
			||||||
 | 
					        g.ActiveIdAllowOverlap = !io.MouseDown[0];
 | 
				
			||||||
 | 
					        g.WantTextInputNextFrame = 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Edit in progress
 | 
				
			||||||
 | 
					        const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + edit_state.ScrollX;
 | 
				
			||||||
 | 
					        const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y - style.FramePadding.y) : (g.FontSize*0.5f));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const bool is_osx = io.ConfigMacOSXBehaviors;
 | 
				
			||||||
 | 
					        if (select_all || (hovered && !is_osx && io.MouseDoubleClicked[0]))
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            edit_state.SelectAll();
 | 
				
			||||||
 | 
					            edit_state.SelectedAllMouseLock = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else if (hovered && is_osx && io.MouseDoubleClicked[0])
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            // Double-click select a word only, OS X style (by simulating keystrokes)
 | 
				
			||||||
 | 
					            edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT);
 | 
				
			||||||
 | 
					            edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else if (io.MouseClicked[0] && !edit_state.SelectedAllMouseLock)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (hovered)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                stb_textedit_click(&edit_state, &edit_state.StbState, mouse_x, mouse_y);
 | 
				
			||||||
 | 
					                edit_state.CursorAnimReset();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else if (io.MouseDown[0] && !edit_state.SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f))
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            stb_textedit_drag(&edit_state, &edit_state.StbState, mouse_x, mouse_y);
 | 
				
			||||||
 | 
					            edit_state.CursorAnimReset();
 | 
				
			||||||
 | 
					            edit_state.CursorFollow = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (edit_state.SelectedAllMouseLock && !io.MouseDown[0])
 | 
				
			||||||
 | 
					            edit_state.SelectedAllMouseLock = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (io.InputCharacters[0])
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            // Process text input (before we check for Return because using some IME will effectively send a Return?)
 | 
				
			||||||
 | 
					            // We ignore CTRL inputs, but need to allow ALT+CTRL as some keyboards (e.g. German) use AltGR (which _is_ Alt+Ctrl) to input certain characters.
 | 
				
			||||||
 | 
					            bool ignore_inputs = (io.KeyCtrl && !io.KeyAlt) || (is_osx && io.KeySuper);
 | 
				
			||||||
 | 
					            if (!ignore_inputs && is_editable && !user_nav_input_start)
 | 
				
			||||||
 | 
					                for (int n = 0; n < IM_ARRAYSIZE(io.InputCharacters) && io.InputCharacters[n]; n++)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    // Insert character if they pass filtering
 | 
				
			||||||
 | 
					                    unsigned int c = (unsigned int)io.InputCharacters[n];
 | 
				
			||||||
 | 
					                    if (InputTextFilterCharacter(&c, flags, callback, callback_user_data))
 | 
				
			||||||
 | 
					                        edit_state.OnKeyPressed((int)c);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Consume characters
 | 
				
			||||||
 | 
					            memset(g.IO.InputCharacters, 0, sizeof(g.IO.InputCharacters));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool cancel_edit = false;
 | 
				
			||||||
 | 
					    if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        // Handle key-presses
 | 
				
			||||||
 | 
					        const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0);
 | 
				
			||||||
 | 
					        const bool is_osx = io.ConfigMacOSXBehaviors;
 | 
				
			||||||
 | 
					        const bool is_shortcut_key = (is_osx ? (io.KeySuper && !io.KeyCtrl) : (io.KeyCtrl && !io.KeySuper)) && !io.KeyAlt && !io.KeyShift; // OS X style: Shortcuts using Cmd/Super instead of Ctrl
 | 
				
			||||||
 | 
					        const bool is_osx_shift_shortcut = is_osx && io.KeySuper && io.KeyShift && !io.KeyCtrl && !io.KeyAlt;
 | 
				
			||||||
 | 
					        const bool is_wordmove_key_down = is_osx ? io.KeyAlt : io.KeyCtrl;                     // OS X style: Text editing cursor movement using Alt instead of Ctrl
 | 
				
			||||||
 | 
					        const bool is_startend_key_down = is_osx && io.KeySuper && !io.KeyCtrl && !io.KeyAlt;  // OS X style: Line/Text Start and End using Cmd+Arrows instead of Home/End
 | 
				
			||||||
 | 
					        const bool is_ctrl_key_only = io.KeyCtrl && !io.KeyShift && !io.KeyAlt && !io.KeySuper;
 | 
				
			||||||
 | 
					        const bool is_shift_key_only = io.KeyShift && !io.KeyCtrl && !io.KeyAlt && !io.KeySuper;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const bool is_cut   = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_X)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Delete))) && is_editable && !is_password && (!is_multiline || edit_state.HasSelection());
 | 
				
			||||||
 | 
					        const bool is_copy  = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_C)) || (is_ctrl_key_only  && IsKeyPressedMap(ImGuiKey_Insert))) && !is_password && (!is_multiline || edit_state.HasSelection());
 | 
				
			||||||
 | 
					        const bool is_paste = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_V)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && is_editable;
 | 
				
			||||||
 | 
					        const bool is_undo  = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Z)) && is_editable && is_undoable);
 | 
				
			||||||
 | 
					        const bool is_redo  = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Y)) || (is_osx_shift_shortcut && IsKeyPressedMap(ImGuiKey_Z))) && is_editable && is_undoable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (IsKeyPressedMap(ImGuiKey_LeftArrow))                        { edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINESTART : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDLEFT : STB_TEXTEDIT_K_LEFT) | k_mask); }
 | 
				
			||||||
 | 
					        else if (IsKeyPressedMap(ImGuiKey_RightArrow))                  { edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINEEND : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDRIGHT : STB_TEXTEDIT_K_RIGHT) | k_mask); }
 | 
				
			||||||
 | 
					        else if (IsKeyPressedMap(ImGuiKey_UpArrow) && is_multiline)     { if (io.KeyCtrl) SetWindowScrollY(draw_window, ImMax(draw_window->Scroll.y - g.FontSize, 0.0f)); else edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTSTART : STB_TEXTEDIT_K_UP) | k_mask); }
 | 
				
			||||||
 | 
					        else if (IsKeyPressedMap(ImGuiKey_DownArrow) && is_multiline)   { if (io.KeyCtrl) SetWindowScrollY(draw_window, ImMin(draw_window->Scroll.y + g.FontSize, GetScrollMaxY())); else edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTEND : STB_TEXTEDIT_K_DOWN) | k_mask); }
 | 
				
			||||||
 | 
					        else if (IsKeyPressedMap(ImGuiKey_Home))                        { edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); }
 | 
				
			||||||
 | 
					        else if (IsKeyPressedMap(ImGuiKey_End))                         { edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); }
 | 
				
			||||||
 | 
					        else if (IsKeyPressedMap(ImGuiKey_Delete) && is_editable)       { edit_state.OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); }
 | 
				
			||||||
 | 
					        else if (IsKeyPressedMap(ImGuiKey_Backspace) && is_editable)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (!edit_state.HasSelection())
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                if (is_wordmove_key_down) edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT|STB_TEXTEDIT_K_SHIFT);
 | 
				
			||||||
 | 
					                else if (is_osx && io.KeySuper && !io.KeyAlt && !io.KeyCtrl) edit_state.OnKeyPressed(STB_TEXTEDIT_K_LINESTART|STB_TEXTEDIT_K_SHIFT);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            edit_state.OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else if (IsKeyPressedMap(ImGuiKey_Enter))
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0;
 | 
				
			||||||
 | 
					            if (!is_multiline || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                enter_pressed = clear_active_id = true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else if (is_editable)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                unsigned int c = '\n'; // Insert new line
 | 
				
			||||||
 | 
					                if (InputTextFilterCharacter(&c, flags, callback, callback_user_data))
 | 
				
			||||||
 | 
					                    edit_state.OnKeyPressed((int)c);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else if ((flags & ImGuiInputTextFlags_AllowTabInput) && IsKeyPressedMap(ImGuiKey_Tab) && !io.KeyCtrl && !io.KeyShift && !io.KeyAlt && is_editable)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            unsigned int c = '\t'; // Insert TAB
 | 
				
			||||||
 | 
					            if (InputTextFilterCharacter(&c, flags, callback, callback_user_data))
 | 
				
			||||||
 | 
					                edit_state.OnKeyPressed((int)c);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else if (IsKeyPressedMap(ImGuiKey_Escape))
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            clear_active_id = cancel_edit = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else if (is_undo || is_redo)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            edit_state.OnKeyPressed(is_undo ? STB_TEXTEDIT_K_UNDO : STB_TEXTEDIT_K_REDO);
 | 
				
			||||||
 | 
					            edit_state.ClearSelection();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else if (is_shortcut_key && IsKeyPressedMap(ImGuiKey_A))
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            edit_state.SelectAll();
 | 
				
			||||||
 | 
					            edit_state.CursorFollow = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else if (is_cut || is_copy)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            // Cut, Copy
 | 
				
			||||||
 | 
					            if (io.SetClipboardTextFn)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                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) : edit_state.CurLenW;
 | 
				
			||||||
 | 
					                edit_state.TempBuffer.resize((ie-ib) * 4 + 1);
 | 
				
			||||||
 | 
					                ImTextStrToUtf8(edit_state.TempBuffer.Data, edit_state.TempBuffer.Size, edit_state.TextW.Data+ib, edit_state.TextW.Data+ie);
 | 
				
			||||||
 | 
					                SetClipboardText(edit_state.TempBuffer.Data);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (is_cut)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                if (!edit_state.HasSelection())
 | 
				
			||||||
 | 
					                    edit_state.SelectAll();
 | 
				
			||||||
 | 
					                edit_state.CursorFollow = true;
 | 
				
			||||||
 | 
					                stb_textedit_cut(&edit_state, &edit_state.StbState);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else if (is_paste)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (const char* clipboard = GetClipboardText())
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                // Filter pasted buffer
 | 
				
			||||||
 | 
					                const int clipboard_len = (int)strlen(clipboard);
 | 
				
			||||||
 | 
					                ImWchar* clipboard_filtered = (ImWchar*)ImGui::MemAlloc((clipboard_len+1) * sizeof(ImWchar));
 | 
				
			||||||
 | 
					                int clipboard_filtered_len = 0;
 | 
				
			||||||
 | 
					                for (const char* s = clipboard; *s; )
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    unsigned int c;
 | 
				
			||||||
 | 
					                    s += ImTextCharFromUtf8(&c, s, NULL);
 | 
				
			||||||
 | 
					                    if (c == 0)
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    if (c >= 0x10000 || !InputTextFilterCharacter(&c, flags, callback, callback_user_data))
 | 
				
			||||||
 | 
					                        continue;
 | 
				
			||||||
 | 
					                    clipboard_filtered[clipboard_filtered_len++] = (ImWchar)c;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                clipboard_filtered[clipboard_filtered_len] = 0;
 | 
				
			||||||
 | 
					                if (clipboard_filtered_len > 0) // If everything was filtered, ignore the pasting operation
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    stb_textedit_paste(&edit_state, &edit_state.StbState, clipboard_filtered, clipboard_filtered_len);
 | 
				
			||||||
 | 
					                    edit_state.CursorFollow = true;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                ImGui::MemFree(clipboard_filtered);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (g.ActiveId == id)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        const char* apply_new_text = NULL;
 | 
				
			||||||
 | 
					        int apply_new_text_length = 0;
 | 
				
			||||||
 | 
					        if (cancel_edit)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            // Restore initial value. Only return true if restoring to the initial value changes the current buffer contents.
 | 
				
			||||||
 | 
					            if (is_editable && strcmp(buf, edit_state.InitialText.Data) != 0)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                apply_new_text = edit_state.InitialText.Data;
 | 
				
			||||||
 | 
					                apply_new_text_length = edit_state.InitialText.Size - 1;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // When using 'ImGuiInputTextFlags_EnterReturnsTrue' as a special case we reapply the live buffer back to the input buffer before clearing ActiveId, even though strictly speaking it wasn't modified on this frame.
 | 
				
			||||||
 | 
					        // If we didn't do that, code like InputInt() with ImGuiInputTextFlags_EnterReturnsTrue would fail. Also this allows the user to use InputText() with ImGuiInputTextFlags_EnterReturnsTrue without maintaining any user-side storage.
 | 
				
			||||||
 | 
					        bool apply_edit_back_to_user_buffer = !cancel_edit || (enter_pressed && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0);
 | 
				
			||||||
 | 
					        if (apply_edit_back_to_user_buffer)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            // Apply new value immediately - copy modified buffer back
 | 
				
			||||||
 | 
					            // Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer
 | 
				
			||||||
 | 
					            // FIXME: We actually always render 'buf' when calling DrawList->AddText, making the comment above incorrect.
 | 
				
			||||||
 | 
					            // FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks.
 | 
				
			||||||
 | 
					            if (is_editable)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                edit_state.TempBuffer.resize(edit_state.TextW.Size * 4 + 1);
 | 
				
			||||||
 | 
					                ImTextStrToUtf8(edit_state.TempBuffer.Data, edit_state.TempBuffer.Size, edit_state.TextW.Data, NULL);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // User callback
 | 
				
			||||||
 | 
					            if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackAlways)) != 0)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                IM_ASSERT(callback != NULL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment.
 | 
				
			||||||
 | 
					                ImGuiInputTextFlags event_flag = 0;
 | 
				
			||||||
 | 
					                ImGuiKey event_key = ImGuiKey_COUNT;
 | 
				
			||||||
 | 
					                if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && IsKeyPressedMap(ImGuiKey_Tab))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    event_flag = ImGuiInputTextFlags_CallbackCompletion;
 | 
				
			||||||
 | 
					                    event_key = ImGuiKey_Tab;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_UpArrow))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    event_flag = ImGuiInputTextFlags_CallbackHistory;
 | 
				
			||||||
 | 
					                    event_key = ImGuiKey_UpArrow;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_DownArrow))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    event_flag = ImGuiInputTextFlags_CallbackHistory;
 | 
				
			||||||
 | 
					                    event_key = ImGuiKey_DownArrow;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else if (flags & ImGuiInputTextFlags_CallbackAlways)
 | 
				
			||||||
 | 
					                    event_flag = ImGuiInputTextFlags_CallbackAlways;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (event_flag)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    ImGuiInputTextCallbackData callback_data;
 | 
				
			||||||
 | 
					                    memset(&callback_data, 0, sizeof(ImGuiInputTextCallbackData));
 | 
				
			||||||
 | 
					                    callback_data.EventFlag = event_flag;
 | 
				
			||||||
 | 
					                    callback_data.Flags = flags;
 | 
				
			||||||
 | 
					                    callback_data.UserData = callback_user_data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    callback_data.EventKey = event_key;
 | 
				
			||||||
 | 
					                    callback_data.Buf = edit_state.TempBuffer.Data;
 | 
				
			||||||
 | 
					                    callback_data.BufTextLen = edit_state.CurLenA;
 | 
				
			||||||
 | 
					                    callback_data.BufSize = edit_state.BufCapacityA;
 | 
				
			||||||
 | 
					                    callback_data.BufDirty = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // 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 = edit_state.TextW.Data;
 | 
				
			||||||
 | 
					                    const int utf8_cursor_pos = callback_data.CursorPos = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.cursor);
 | 
				
			||||||
 | 
					                    const int utf8_selection_start = callback_data.SelectionStart = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_start);
 | 
				
			||||||
 | 
					                    const int utf8_selection_end = callback_data.SelectionEnd = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_end);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // Call user code
 | 
				
			||||||
 | 
					                    callback(&callback_data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // Read back what user may have modified
 | 
				
			||||||
 | 
					                    IM_ASSERT(callback_data.Buf == edit_state.TempBuffer.Data);  // Invalid to modify those fields
 | 
				
			||||||
 | 
					                    IM_ASSERT(callback_data.BufSize == edit_state.BufCapacityA);
 | 
				
			||||||
 | 
					                    IM_ASSERT(callback_data.Flags == flags);
 | 
				
			||||||
 | 
					                    if (callback_data.CursorPos != utf8_cursor_pos)            { edit_state.StbState.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos); edit_state.CursorFollow = true; }
 | 
				
			||||||
 | 
					                    if (callback_data.SelectionStart != utf8_selection_start)  { edit_state.StbState.select_start = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionStart); }
 | 
				
			||||||
 | 
					                    if (callback_data.SelectionEnd != utf8_selection_end)      { edit_state.StbState.select_end = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionEnd); }
 | 
				
			||||||
 | 
					                    if (callback_data.BufDirty)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text!
 | 
				
			||||||
 | 
					                        if (callback_data.BufTextLen > backup_current_text_length && is_resizable)
 | 
				
			||||||
 | 
					                            edit_state.TextW.resize(edit_state.TextW.Size + (callback_data.BufTextLen - backup_current_text_length));
 | 
				
			||||||
 | 
					                        edit_state.CurLenW = ImTextStrFromUtf8(edit_state.TextW.Data, edit_state.TextW.Size, callback_data.Buf, NULL);
 | 
				
			||||||
 | 
					                        edit_state.CurLenA = callback_data.BufTextLen;  // Assume correct length and valid UTF-8 from user, saves us an extra strlen()
 | 
				
			||||||
 | 
					                        edit_state.CursorAnimReset();
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Will copy result string if modified
 | 
				
			||||||
 | 
					            if (is_editable && strcmp(edit_state.TempBuffer.Data, buf) != 0)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                apply_new_text = edit_state.TempBuffer.Data;
 | 
				
			||||||
 | 
					                apply_new_text_length = edit_state.CurLenA;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Copy result to user buffer
 | 
				
			||||||
 | 
					        if (apply_new_text)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            IM_ASSERT(apply_new_text_length >= 0);
 | 
				
			||||||
 | 
					            if (backup_current_text_length != apply_new_text_length && is_resizable)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                ImGuiInputTextCallbackData callback_data;
 | 
				
			||||||
 | 
					                callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize;
 | 
				
			||||||
 | 
					                callback_data.Flags = flags;
 | 
				
			||||||
 | 
					                callback_data.Buf = buf;
 | 
				
			||||||
 | 
					                callback_data.BufTextLen = apply_new_text_length;
 | 
				
			||||||
 | 
					                callback_data.BufSize = ImMax(buf_size, apply_new_text_length + 1);
 | 
				
			||||||
 | 
					                callback_data.UserData = callback_user_data;
 | 
				
			||||||
 | 
					                callback(&callback_data);
 | 
				
			||||||
 | 
					                buf = callback_data.Buf;
 | 
				
			||||||
 | 
					                buf_size = callback_data.BufSize;
 | 
				
			||||||
 | 
					                apply_new_text_length = ImMin(callback_data.BufTextLen, buf_size - 1);
 | 
				
			||||||
 | 
					                IM_ASSERT(apply_new_text_length <= buf_size);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // If the underlying buffer resize was denied or not carried to the next frame, apply_new_text_length+1 may be >= buf_size.
 | 
				
			||||||
 | 
					            ImStrncpy(buf, edit_state.TempBuffer.Data, ImMin(apply_new_text_length + 1, buf_size));
 | 
				
			||||||
 | 
					            value_changed = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Clear temporary user storage
 | 
				
			||||||
 | 
					        edit_state.UserFlags = 0;
 | 
				
			||||||
 | 
					        edit_state.UserCallback = NULL;
 | 
				
			||||||
 | 
					        edit_state.UserCallbackData = NULL;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value)
 | 
				
			||||||
 | 
					    if (clear_active_id && g.ActiveId == id)
 | 
				
			||||||
 | 
					        ClearActiveID();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Render
 | 
				
			||||||
 | 
					    // Select which buffer we are going to display. When ImGuiInputTextFlags_NoLiveEdit is set 'buf' might still be the old value. We set buf to NULL to prevent accidental usage from now on.
 | 
				
			||||||
 | 
					    const char* buf_display = (g.ActiveId == id && is_editable) ? edit_state.TempBuffer.Data : buf; buf = NULL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Set upper limit of single-line InputTextEx() at 2 million characters strings. The current pathological worst case is a long line
 | 
				
			||||||
 | 
					    // without any carriage return, which would makes ImFont::RenderText() reserve too many vertices and probably crash. Avoid it altogether.
 | 
				
			||||||
 | 
					    // Note that we only use this limit on single-line InputText(), so a pathologically large line on a InputTextMultiline() would still crash.
 | 
				
			||||||
 | 
					    const int buf_display_max_length = 2 * 1024 * 1024;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!is_multiline)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        RenderNavHighlight(frame_bb, id);
 | 
				
			||||||
 | 
					        RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + size.x, frame_bb.Min.y + size.y); // Not using frame_bb.Max because we have adjusted size
 | 
				
			||||||
 | 
					    ImVec2 render_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding;
 | 
				
			||||||
 | 
					    ImVec2 text_size(0.f, 0.f);
 | 
				
			||||||
 | 
					    const bool is_currently_scrolling = (edit_state.ID == id && is_multiline && g.ActiveId == draw_window->GetIDNoKeepAlive("#SCROLLY"));
 | 
				
			||||||
 | 
					    if (g.ActiveId == id || is_currently_scrolling)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        edit_state.CursorAnim += io.DeltaTime;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // This is going to be messy. We need to:
 | 
				
			||||||
 | 
					        // - Display the text (this alone can be more easily clipped)
 | 
				
			||||||
 | 
					        // - Handle scrolling, highlight selection, display cursor (those all requires some form of 1d->2d cursor position calculation)
 | 
				
			||||||
 | 
					        // - Measure text height (for scrollbar)
 | 
				
			||||||
 | 
					        // We are attempting to do most of that in **one main pass** to minimize the computation cost (non-negligible for large amount of text) + 2nd pass for selection rendering (we could merge them by an extra refactoring effort)
 | 
				
			||||||
 | 
					        // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8.
 | 
				
			||||||
 | 
					        const ImWchar* text_begin = edit_state.TextW.Data;
 | 
				
			||||||
 | 
					        ImVec2 cursor_offset, select_start_offset;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            // Count lines + find lines numbers straddling 'cursor' and 'select_start' position.
 | 
				
			||||||
 | 
					            const ImWchar* searches_input_ptr[2];
 | 
				
			||||||
 | 
					            searches_input_ptr[0] = text_begin + edit_state.StbState.cursor;
 | 
				
			||||||
 | 
					            searches_input_ptr[1] = NULL;
 | 
				
			||||||
 | 
					            int searches_remaining = 1;
 | 
				
			||||||
 | 
					            int searches_result_line_number[2] = { -1, -999 };
 | 
				
			||||||
 | 
					            if (edit_state.StbState.select_start != edit_state.StbState.select_end)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                searches_input_ptr[1] = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end);
 | 
				
			||||||
 | 
					                searches_result_line_number[1] = -1;
 | 
				
			||||||
 | 
					                searches_remaining++;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Iterate all lines to find our line numbers
 | 
				
			||||||
 | 
					            // In multi-line mode, we never exit the loop until all lines are counted, so add one extra to the searches_remaining counter.
 | 
				
			||||||
 | 
					            searches_remaining += is_multiline ? 1 : 0;
 | 
				
			||||||
 | 
					            int line_count = 0;
 | 
				
			||||||
 | 
					            for (const ImWchar* s = text_begin; *s != 0; s++)
 | 
				
			||||||
 | 
					                if (*s == '\n')
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    line_count++;
 | 
				
			||||||
 | 
					                    if (searches_result_line_number[0] == -1 && s >= searches_input_ptr[0]) { searches_result_line_number[0] = line_count; if (--searches_remaining <= 0) break; }
 | 
				
			||||||
 | 
					                    if (searches_result_line_number[1] == -1 && s >= searches_input_ptr[1]) { searches_result_line_number[1] = line_count; if (--searches_remaining <= 0) break; }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            line_count++;
 | 
				
			||||||
 | 
					            if (searches_result_line_number[0] == -1) searches_result_line_number[0] = line_count;
 | 
				
			||||||
 | 
					            if (searches_result_line_number[1] == -1) searches_result_line_number[1] = line_count;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Calculate 2d position by finding the beginning of the line and measuring distance
 | 
				
			||||||
 | 
					            cursor_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[0], text_begin), searches_input_ptr[0]).x;
 | 
				
			||||||
 | 
					            cursor_offset.y = searches_result_line_number[0] * g.FontSize;
 | 
				
			||||||
 | 
					            if (searches_result_line_number[1] >= 0)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                select_start_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[1], text_begin), searches_input_ptr[1]).x;
 | 
				
			||||||
 | 
					                select_start_offset.y = searches_result_line_number[1] * g.FontSize;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Store text height (note that we haven't calculated text width at all, see GitHub issues #383, #1224)
 | 
				
			||||||
 | 
					            if (is_multiline)
 | 
				
			||||||
 | 
					                text_size = ImVec2(size.x, line_count * g.FontSize);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Scroll
 | 
				
			||||||
 | 
					        if (edit_state.CursorFollow)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            // Horizontal scroll in chunks of quarter width
 | 
				
			||||||
 | 
					            if (!(flags & ImGuiInputTextFlags_NoHorizontalScroll))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                const float scroll_increment_x = size.x * 0.25f;
 | 
				
			||||||
 | 
					                if (cursor_offset.x < edit_state.ScrollX)
 | 
				
			||||||
 | 
					                    edit_state.ScrollX = (float)(int)ImMax(0.0f, cursor_offset.x - scroll_increment_x);
 | 
				
			||||||
 | 
					                else if (cursor_offset.x - size.x >= edit_state.ScrollX)
 | 
				
			||||||
 | 
					                    edit_state.ScrollX = (float)(int)(cursor_offset.x - size.x + scroll_increment_x);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                edit_state.ScrollX = 0.0f;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Vertical scroll
 | 
				
			||||||
 | 
					            if (is_multiline)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                float scroll_y = draw_window->Scroll.y;
 | 
				
			||||||
 | 
					                if (cursor_offset.y - g.FontSize < scroll_y)
 | 
				
			||||||
 | 
					                    scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize);
 | 
				
			||||||
 | 
					                else if (cursor_offset.y - size.y >= scroll_y)
 | 
				
			||||||
 | 
					                    scroll_y = cursor_offset.y - size.y;
 | 
				
			||||||
 | 
					                draw_window->DC.CursorPos.y += (draw_window->Scroll.y - scroll_y);   // To avoid a frame of lag
 | 
				
			||||||
 | 
					                draw_window->Scroll.y = scroll_y;
 | 
				
			||||||
 | 
					                render_pos.y = draw_window->DC.CursorPos.y;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        edit_state.CursorFollow = false;
 | 
				
			||||||
 | 
					        const ImVec2 render_scroll = ImVec2(edit_state.ScrollX, 0.0f);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Draw selection
 | 
				
			||||||
 | 
					        if (edit_state.StbState.select_start != edit_state.StbState.select_end)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            const ImWchar* text_selected_begin = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end);
 | 
				
			||||||
 | 
					            const ImWchar* text_selected_end = text_begin + ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            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.
 | 
				
			||||||
 | 
					            float bg_offy_dn = is_multiline ? 0.0f : 2.0f;
 | 
				
			||||||
 | 
					            ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg);
 | 
				
			||||||
 | 
					            ImVec2 rect_pos = render_pos + select_start_offset - render_scroll;
 | 
				
			||||||
 | 
					            for (const ImWchar* p = text_selected_begin; p < text_selected_end; )
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                if (rect_pos.y > clip_rect.w + g.FontSize)
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                if (rect_pos.y < clip_rect.y)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    while (p < text_selected_end)
 | 
				
			||||||
 | 
					                        if (*p++ == '\n')
 | 
				
			||||||
 | 
					                            break;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    ImVec2 rect_size = InputTextCalcTextSizeW(p, text_selected_end, &p, NULL, true);
 | 
				
			||||||
 | 
					                    if (rect_size.x <= 0.0f) rect_size.x = (float)(int)(g.Font->GetCharAdvance((unsigned short)' ') * 0.50f); // So we can see selected empty lines
 | 
				
			||||||
 | 
					                    ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos +ImVec2(rect_size.x, bg_offy_dn));
 | 
				
			||||||
 | 
					                    rect.ClipWith(clip_rect);
 | 
				
			||||||
 | 
					                    if (rect.Overlaps(clip_rect))
 | 
				
			||||||
 | 
					                        draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                rect_pos.x = render_pos.x - render_scroll.x;
 | 
				
			||||||
 | 
					                rect_pos.y += g.FontSize;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const int buf_display_len = edit_state.CurLenA;
 | 
				
			||||||
 | 
					        if (is_multiline || buf_display_len < buf_display_max_length)
 | 
				
			||||||
 | 
					            draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos - render_scroll, GetColorU32(ImGuiCol_Text), buf_display, buf_display + buf_display_len, 0.0f, is_multiline ? NULL : &clip_rect);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Draw blinking cursor
 | 
				
			||||||
 | 
					        bool cursor_is_visible = (!g.IO.ConfigCursorBlink) || (g.InputTextState.CursorAnim <= 0.0f) || ImFmod(g.InputTextState.CursorAnim, 1.20f) <= 0.80f;
 | 
				
			||||||
 | 
					        ImVec2 cursor_screen_pos = render_pos + cursor_offset - render_scroll;
 | 
				
			||||||
 | 
					        ImRect cursor_screen_rect(cursor_screen_pos.x, cursor_screen_pos.y-g.FontSize+0.5f, cursor_screen_pos.x+1.0f, cursor_screen_pos.y-1.5f);
 | 
				
			||||||
 | 
					        if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect))
 | 
				
			||||||
 | 
					            draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_Text));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Notify OS of text input position for advanced IME (-1 x offset so that Windows IME can cover our cursor. Bit of an extra nicety.)
 | 
				
			||||||
 | 
					        if (is_editable)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            g.PlatformImePos = ImVec2(cursor_screen_pos.x - 1, cursor_screen_pos.y - g.FontSize);
 | 
				
			||||||
 | 
					            g.PlatformImePosViewport = window->Viewport;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        // Render text only
 | 
				
			||||||
 | 
					        const char* buf_end = NULL;
 | 
				
			||||||
 | 
					        if (is_multiline)
 | 
				
			||||||
 | 
					            text_size = ImVec2(size.x, InputTextCalcTextLenAndLineCount(buf_display, &buf_end) * g.FontSize); // We don't need width
 | 
				
			||||||
 | 
					        else
 | 
				
			||||||
 | 
					            buf_end = buf_display + strlen(buf_display);
 | 
				
			||||||
 | 
					        if (is_multiline || (buf_end - buf_display) < buf_display_max_length)
 | 
				
			||||||
 | 
					            draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos, GetColorU32(ImGuiCol_Text), buf_display, buf_end, 0.0f, is_multiline ? NULL : &clip_rect);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (is_multiline)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        Dummy(text_size + ImVec2(0.0f, g.FontSize)); // Always add room to scroll an extra line
 | 
				
			||||||
 | 
					        EndChildFrame();
 | 
				
			||||||
 | 
					        EndGroup();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (is_password)
 | 
				
			||||||
 | 
					        PopFont();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Log as text
 | 
				
			||||||
 | 
					    if (g.LogEnabled && !is_password)
 | 
				
			||||||
 | 
					        LogRenderedText(&render_pos, buf_display, NULL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (label_size.x > 0)
 | 
				
			||||||
 | 
					        RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (value_changed)
 | 
				
			||||||
 | 
					        MarkItemEdited(id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0)
 | 
				
			||||||
 | 
					        return enter_pressed;
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					        return value_changed;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//-------------------------------------------------------------------------
 | 
					//-------------------------------------------------------------------------
 | 
				
			||||||
// WIDGETS: Color Editor / Picker
 | 
					// WIDGETS: Color Editor / Picker
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user