mirror of
https://github.com/ocornut/imgui.git
synced 2024-11-30 18:34:34 +01:00
Refactor: Moved InputText functions from imgui.cpp to imgui_widgets.cpp (#2036)
This commit is contained in:
parent
ad0bfdcd95
commit
c25f48b902
971
imgui.cpp
971
imgui.cpp
@ -894,10 +894,6 @@ static void AddWindowToSortedBuffer(ImVector<ImGuiWindow*>* out_sort
|
|||||||
|
|
||||||
static ImRect GetViewportRect();
|
static ImRect GetViewportRect();
|
||||||
|
|
||||||
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);
|
||||||
@ -9065,973 +9061,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);
|
|
||||||
}
|
|
||||||
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)
|
// 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)
|
||||||
{
|
{
|
||||||
|
@ -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,972 @@ 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);
|
||||||
|
}
|
||||||
|
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
|
||||||
|
Loading…
Reference in New Issue
Block a user