mirror of
https://github.com/Bloodysharp/Synthetic.git
synced 2026-10-10 15:13:13 +08:00
1191 lines
56 KiB
C++
1191 lines
56 KiB
C++
#include "../settings/functions.h"
|
|
using namespace ImStb;
|
|
|
|
static ImVec2 input_text_calc_text_size_w(ImGuiContext* ctx, const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false)
|
|
{
|
|
ImGuiContext& g = *ctx;
|
|
ImFont* font = g.Font;
|
|
const float line_height = g.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((ImWchar)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;
|
|
}
|
|
|
|
namespace ImStb
|
|
{
|
|
|
|
static int STB_TEXTEDIT_STRINGLEN(const ImGuiInputTextState* obj) { return obj->CurLenW; }
|
|
static ImWchar STB_TEXTEDIT_GETCHAR(const ImGuiInputTextState* obj, int idx) { IM_ASSERT(idx <= obj->CurLenW); return obj->TextW[idx]; }
|
|
static float STB_TEXTEDIT_GETWIDTH(ImGuiInputTextState* obj, int line_start_idx, int char_idx) { ImWchar c = obj->TextW[line_start_idx + char_idx]; if (c == '\n') return IMSTB_TEXTEDIT_GETWIDTH_NEWLINE; ImGuiContext& g = *obj->Ctx; return g.Font->GetCharAdvance(c) * (g.FontSize / g.Font->FontSize); }
|
|
static int STB_TEXTEDIT_KEYTOTEXT(int key) { return key >= 0x200000 ? 0 : key; }
|
|
static ImWchar STB_TEXTEDIT_NEWLINE = '\n';
|
|
static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, ImGuiInputTextState* obj, int line_start_idx)
|
|
{
|
|
const ImWchar* text = obj->TextW.Data;
|
|
const ImWchar* text_remaining = NULL;
|
|
const ImVec2 size = input_text_calc_text_size_w(obj->Ctx, 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 c == ',' || c == ';' || c == '(' || c == ')' || c == '{' || c == '}' || c == '[' || c == ']' || c == '|' || c == '\n' || c == '\r' || c == '.' || c == '!';
|
|
}
|
|
|
|
static int is_word_boundary_from_right(ImGuiInputTextState* obj, int idx)
|
|
{
|
|
// When ImGuiInputTextFlags_Password is set, we don't want actions such as CTRL+Arrow to leak the fact that underlying data are blanks or separators.
|
|
if ((obj->Flags & ImGuiInputTextFlags_Password) || idx <= 0)
|
|
return 0;
|
|
|
|
bool prev_white = ImCharIsBlankW(obj->TextW[idx - 1]);
|
|
bool prev_separ = is_separator(obj->TextW[idx - 1]);
|
|
bool curr_white = ImCharIsBlankW(obj->TextW[idx]);
|
|
bool curr_separ = is_separator(obj->TextW[idx]);
|
|
return ((prev_white || prev_separ) && !(curr_separ || curr_white)) || (curr_separ && !prev_separ);
|
|
}
|
|
static int is_word_boundary_from_left(ImGuiInputTextState* obj, int idx)
|
|
{
|
|
if ((obj->Flags & ImGuiInputTextFlags_Password) || idx <= 0)
|
|
return 0;
|
|
|
|
bool prev_white = ImCharIsBlankW(obj->TextW[idx]);
|
|
bool prev_separ = is_separator(obj->TextW[idx]);
|
|
bool curr_white = ImCharIsBlankW(obj->TextW[idx - 1]);
|
|
bool curr_separ = is_separator(obj->TextW[idx - 1]);
|
|
return ((prev_white) && !(curr_separ || curr_white)) || (curr_separ && !prev_separ);
|
|
}
|
|
static int STB_TEXTEDIT_MOVEWORDLEFT_IMPL(ImGuiInputTextState* obj, int idx) { idx--; while (idx >= 0 && !is_word_boundary_from_right(obj, idx)) idx--; return idx < 0 ? 0 : idx; }
|
|
static int STB_TEXTEDIT_MOVEWORDRIGHT_MAC(ImGuiInputTextState* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_left(obj, idx)) idx++; return idx > len ? len : idx; }
|
|
static int STB_TEXTEDIT_MOVEWORDRIGHT_WIN(ImGuiInputTextState* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_right(obj, idx)) idx++; return idx > len ? len : idx; }
|
|
static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(ImGuiInputTextState* obj, int idx) { ImGuiContext& g = *obj->Ctx; if (g.IO.ConfigMacOSXBehaviors) return STB_TEXTEDIT_MOVEWORDRIGHT_MAC(obj, idx); else return STB_TEXTEDIT_MOVEWORDRIGHT_WIN(obj, idx); }
|
|
#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(ImGuiInputTextState* obj, int pos, int n)
|
|
{
|
|
ImWchar* dst = obj->TextW.Data + pos;
|
|
|
|
// We maintain our buffer length in both UTF-8 and wchar formats
|
|
obj->Edited = true;
|
|
obj->CurLenA -= ImTextCountUtf8BytesFromStr(dst, dst + n);
|
|
obj->CurLenW -= n;
|
|
|
|
// Offset remaining text (FIXME-OPT: Use memmove)
|
|
const ImWchar* src = obj->TextW.Data + pos + n;
|
|
while (ImWchar c = *src++)
|
|
*dst++ = c;
|
|
*dst = '\0';
|
|
}
|
|
|
|
static bool STB_TEXTEDIT_INSERTCHARS(ImGuiInputTextState* obj, int pos, const ImWchar* new_text, int new_text_len)
|
|
{
|
|
const bool is_resizable = (obj->Flags & 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->Edited = true;
|
|
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 0x200000 // keyboard input to move cursor left
|
|
#define STB_TEXTEDIT_K_RIGHT 0x200001 // keyboard input to move cursor right
|
|
#define STB_TEXTEDIT_K_UP 0x200002 // keyboard input to move cursor up
|
|
#define STB_TEXTEDIT_K_DOWN 0x200003 // keyboard input to move cursor down
|
|
#define STB_TEXTEDIT_K_LINESTART 0x200004 // keyboard input to move cursor to start of line
|
|
#define STB_TEXTEDIT_K_LINEEND 0x200005 // keyboard input to move cursor to end of line
|
|
#define STB_TEXTEDIT_K_TEXTSTART 0x200006 // keyboard input to move cursor to start of text
|
|
#define STB_TEXTEDIT_K_TEXTEND 0x200007 // keyboard input to move cursor to end of text
|
|
#define STB_TEXTEDIT_K_DELETE 0x200008 // keyboard input to delete selection or character under cursor
|
|
#define STB_TEXTEDIT_K_BACKSPACE 0x200009 // keyboard input to delete selection or character left of cursor
|
|
#define STB_TEXTEDIT_K_UNDO 0x20000A // keyboard input to perform undo
|
|
#define STB_TEXTEDIT_K_REDO 0x20000B // keyboard input to perform redo
|
|
#define STB_TEXTEDIT_K_WORDLEFT 0x20000C // keyboard input to move cursor left one word
|
|
#define STB_TEXTEDIT_K_WORDRIGHT 0x20000D // keyboard input to move cursor right one word
|
|
#define STB_TEXTEDIT_K_PGUP 0x20000E // keyboard input to move cursor up a page
|
|
#define STB_TEXTEDIT_K_PGDOWN 0x20000F // keyboard input to move cursor down a page
|
|
#define STB_TEXTEDIT_K_SHIFT 0x400000
|
|
|
|
#define IMSTB_TEXTEDIT_IMPLEMENTATION
|
|
#define IMSTB_TEXTEDIT_memmove memmove
|
|
#include "imstb_textedit.h"
|
|
|
|
// stb_textedit internally allows for a single undo record to do addition and deletion, but somehow, calling
|
|
// the stb_textedit_paste() function creates two separate records, so we perform it manually. (FIXME: Report to nothings/stb?)
|
|
static void stb_textedit_replace(ImGuiInputTextState* str, STB_TexteditState* state, const IMSTB_TEXTEDIT_CHARTYPE* text, int text_len)
|
|
{
|
|
ImStb::stb_text_makeundo_replace(str, state, 0, str->CurLenW, text_len);
|
|
ImStb::STB_TEXTEDIT_DELETECHARS(str, 0, str->CurLenW);
|
|
state->cursor = state->select_start = state->select_end = 0;
|
|
if (text_len <= 0)
|
|
return;
|
|
if (ImStb::STB_TEXTEDIT_INSERTCHARS(str, 0, text, text_len))
|
|
{
|
|
state->cursor = state->select_start = state->select_end = text_len;
|
|
state->has_preferred_x = 0;
|
|
return;
|
|
}
|
|
IM_ASSERT(0); // Failed to insert character, normally shouldn't happen because of how we currently use stb_textedit_replace()
|
|
}
|
|
|
|
} // namespace ImStb
|
|
|
|
static bool input_text_filter_character(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard = false)
|
|
{
|
|
unsigned int c = *p_char;
|
|
|
|
// Filter non-printable (NB: isprint is unreliable! see #2467)
|
|
bool apply_named_filters = true;
|
|
if (c < 0x20)
|
|
{
|
|
bool pass = false;
|
|
pass |= (c == '\n') && (flags & ImGuiInputTextFlags_Multiline) != 0; // Note that an Enter KEY will emit \r and be ignored (we poll for KEY in InputText() code)
|
|
pass |= (c == '\t') && (flags & ImGuiInputTextFlags_AllowTabInput) != 0;
|
|
if (!pass)
|
|
return false;
|
|
apply_named_filters = false; // Override named filters below so newline and tabs can still be inserted.
|
|
}
|
|
|
|
if (input_source_is_clipboard == false)
|
|
{
|
|
// We ignore Ascii representation of delete (emitted from Backspace on OSX, see #2578, #2817)
|
|
if (c == 127)
|
|
return false;
|
|
|
|
// Filter private Unicode range. GLFW on OSX seems to send private characters for special keys like arrow keys (FIXME)
|
|
if (c >= 0xE000 && c <= 0xF8FF)
|
|
return false;
|
|
}
|
|
|
|
// Filter Unicode ranges we are not handling in this build
|
|
if (c > IM_UNICODE_CODEPOINT_MAX)
|
|
return false;
|
|
|
|
// Generic named filters
|
|
if (apply_named_filters && (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CharsScientific | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint)))
|
|
{
|
|
// The libc allows overriding locale, with e.g. 'setlocale(LC_NUMERIC, "de_DE.UTF-8");' which affect the output/input of printf/scanf to use e.g. ',' instead of '.'.
|
|
// The standard mandate that programs starts in the "C" locale where the decimal point is '.'.
|
|
// We don't really intend to provide widespread support for it, but out of empathy for people stuck with using odd API, we support the bare minimum aka overriding the decimal point.
|
|
// Change the default decimal_point with:
|
|
// ImGui::GetIO()->PlatformLocaleDecimalPoint = *localeconv()->decimal_point;
|
|
// Users of non-default decimal point (in particular ',') may be affected by word-selection logic (is_word_boundary_from_right/is_word_boundary_from_left) functions.
|
|
ImGuiContext& g = *ctx;
|
|
const unsigned c_decimal_point = (unsigned int)g.IO.PlatformLocaleDecimalPoint;
|
|
if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsScientific | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint))
|
|
if (c == '.' || c == ',')
|
|
c = c_decimal_point;
|
|
|
|
// Full-width -> half-width conversion for numeric fields (https://en.wikipedia.org/wiki/Halfwidth_and_Fullwidth_Forms_(Unicode_block)
|
|
// While this is mostly convenient, this has the side-effect for uninformed users accidentally inputting full-width characters that they may
|
|
// scratch their head as to why it works in numerical fields vs in generic text fields it would require support in the font.
|
|
if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsScientific | ImGuiInputTextFlags_CharsHexadecimal))
|
|
if (c >= 0xFF01 && c <= 0xFF5E)
|
|
c = c - 0xFF01 + 0x21;
|
|
|
|
// Allow 0-9 . - + * /
|
|
if (flags & ImGuiInputTextFlags_CharsDecimal)
|
|
if (!(c >= '0' && c <= '9') && (c != c_decimal_point) && (c != '-') && (c != '+') && (c != '*') && (c != '/'))
|
|
return false;
|
|
|
|
// Allow 0-9 . - + * / e E
|
|
if (flags & ImGuiInputTextFlags_CharsScientific)
|
|
if (!(c >= '0' && c <= '9') && (c != c_decimal_point) && (c != '-') && (c != '+') && (c != '*') && (c != '/') && (c != 'e') && (c != 'E'))
|
|
return false;
|
|
|
|
// Allow 0-9 a-F A-F
|
|
if (flags & ImGuiInputTextFlags_CharsHexadecimal)
|
|
if (!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F'))
|
|
return false;
|
|
|
|
// Turn a-z into A-Z
|
|
if (flags & ImGuiInputTextFlags_CharsUppercase)
|
|
if (c >= 'a' && c <= 'z')
|
|
c += (unsigned int)('A' - 'a');
|
|
|
|
if (flags & ImGuiInputTextFlags_CharsNoBlank)
|
|
if (ImCharIsBlankW(c))
|
|
return false;
|
|
|
|
*p_char = c;
|
|
}
|
|
|
|
// Custom callback filter
|
|
if (flags & ImGuiInputTextFlags_CallbackCharFilter)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiInputTextCallbackData callback_data;
|
|
callback_data.Ctx = &g;
|
|
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;
|
|
}
|
|
|
|
static int input_text_calc_text_len_and_line_count(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 void input_text_reconcile_undo_state_after_user_callback(ImGuiInputTextState* state, const char* new_buf_a, int new_length_a)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
const ImWchar* old_buf = state->TextW.Data;
|
|
const int old_length = state->CurLenW;
|
|
const int new_length = ImTextCountCharsFromUtf8(new_buf_a, new_buf_a + new_length_a);
|
|
g.TempBuffer.reserve_discard((new_length + 1) * sizeof(ImWchar));
|
|
ImWchar* new_buf = (ImWchar*)(void*)g.TempBuffer.Data;
|
|
ImTextStrFromUtf8(new_buf, new_length + 1, new_buf_a, new_buf_a + new_length_a);
|
|
|
|
const int shorter_length = ImMin(old_length, new_length);
|
|
int first_diff;
|
|
for (first_diff = 0; first_diff < shorter_length; first_diff++)
|
|
if (old_buf[first_diff] != new_buf[first_diff])
|
|
break;
|
|
if (first_diff == old_length && first_diff == new_length)
|
|
return;
|
|
|
|
int old_last_diff = old_length - 1;
|
|
int new_last_diff = new_length - 1;
|
|
for (; old_last_diff >= first_diff && new_last_diff >= first_diff; old_last_diff--, new_last_diff--)
|
|
if (old_buf[old_last_diff] != new_buf[new_last_diff])
|
|
break;
|
|
|
|
const int insert_len = new_last_diff - first_diff + 1;
|
|
const int delete_len = old_last_diff - first_diff + 1;
|
|
if (insert_len > 0 || delete_len > 0)
|
|
if (IMSTB_TEXTEDIT_CHARTYPE* p = stb_text_createundo(&state->Stb.undostate, first_diff, delete_len, insert_len))
|
|
for (int i = 0; i < delete_len; i++)
|
|
p[i] = ImStb::STB_TEXTEDIT_GETCHAR(state, first_diff + i);
|
|
}
|
|
|
|
bool input_text_ex(std::string_view label, const char* hint, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* callback_user_data)
|
|
{
|
|
|
|
struct c_field
|
|
{
|
|
float line_offset;
|
|
};
|
|
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
if (window->SkipItems) return false;
|
|
|
|
IM_ASSERT(buf != NULL && buf_size >= 0);
|
|
IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline)));
|
|
IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput)));
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiIO& io = g.IO;
|
|
const ImGuiStyle& style = g.Style;
|
|
|
|
const bool RENDER_SELECTION_WHEN_INACTIVE = false;
|
|
const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0;
|
|
|
|
if (is_multiline) BeginGroup();
|
|
|
|
const ImGuiID id = window->GetID(label.data());
|
|
const ImVec2 label_size = CalcTextSize(label.data(), NULL, true);
|
|
const ImRect rect(window->DC.CursorPos, window->DC.CursorPos + size_arg);
|
|
|
|
ImGuiWindow* draw_window = window;
|
|
ImVec2 inner_size = size_arg;
|
|
ImGuiLastItemData item_data_backup;
|
|
if (is_multiline)
|
|
{
|
|
ImVec2 backup_pos = window->DC.CursorPos;
|
|
ItemSize(rect, style.FramePadding.y);
|
|
if (!ItemAdd(rect, id, &rect, ImGuiItemFlags_Inputable))
|
|
{
|
|
EndGroup();
|
|
return false;
|
|
}
|
|
item_data_backup = g.LastItemData;
|
|
window->DC.CursorPos = backup_pos;
|
|
|
|
if (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_FromTabbing) && (flags & ImGuiInputTextFlags_AllowTabInput))
|
|
g.NavActivateId = 0;
|
|
|
|
const ImGuiID backup_activate_id = g.NavActivateId;
|
|
if (g.ActiveId == id) g.NavActivateId = 0;
|
|
|
|
PushStyleColor(ImGuiCol_ChildBg, style.Colors[ImGuiCol_FrameBg]);
|
|
PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding);
|
|
PushStyleVar(ImGuiStyleVar_ChildBorderSize, style.FrameBorderSize);
|
|
PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
|
|
bool child_visible = BeginChildEx(label.data(), id, rect.GetSize(), true, ImGuiWindowFlags_NoMove);
|
|
g.NavActivateId = backup_activate_id;
|
|
PopStyleVar(3);
|
|
PopStyleColor();
|
|
if (!child_visible)
|
|
{
|
|
EndChild();
|
|
EndGroup();
|
|
return false;
|
|
}
|
|
draw_window = g.CurrentWindow;
|
|
draw_window->DC.NavLayersActiveMaskNext |= (1 << draw_window->DC.NavLayerCurrent);
|
|
draw_window->DC.CursorPos += style.FramePadding;
|
|
inner_size.x -= draw_window->ScrollbarSizes.x;
|
|
}
|
|
else
|
|
{
|
|
ItemSize(rect, style.FramePadding.y);
|
|
if (!(flags & ImGuiInputTextFlags_MergedItem))
|
|
if (!ItemAdd(rect, id, &rect, ImGuiItemFlags_Inputable)) return false;
|
|
}
|
|
const bool hovered = ItemHoverable(rect, id, g.LastItemData.InFlags);
|
|
if (hovered)
|
|
g.MouseCursor = ImGuiMouseCursor_TextInput;
|
|
|
|
ImGuiInputTextState* state = GetInputTextState(id);
|
|
|
|
if (g.LastItemData.InFlags & ImGuiItemFlags_ReadOnly)
|
|
flags |= ImGuiInputTextFlags_ReadOnly;
|
|
const bool is_readonly = (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);
|
|
|
|
const bool input_requested_by_nav = (g.ActiveId != id) && ((g.NavActivateId == id) && ((g.NavActivateFlags & ImGuiActivateFlags_PreferInput) || (g.NavInputSource == ImGuiInputSource_Keyboard)));
|
|
|
|
const bool user_clicked = hovered && io.MouseClicked[0];
|
|
const bool user_scroll_finish = is_multiline && state != NULL && g.ActiveId == 0 && g.ActiveIdPreviousFrame == GetWindowScrollbarID(draw_window, ImGuiAxis_Y);
|
|
const bool user_scroll_active = is_multiline && state != NULL && g.ActiveId == GetWindowScrollbarID(draw_window, ImGuiAxis_Y);
|
|
bool clear_active_id = false;
|
|
bool select_all = false;
|
|
|
|
float scroll_y = is_multiline ? draw_window->Scroll.y : FLT_MAX;
|
|
|
|
const bool init_reload_from_user_buf = (state != NULL && state->ReloadUserBuf);
|
|
const bool init_changed_specs = (state != NULL && state->Stb.single_line != !is_multiline);
|
|
const bool init_make_active = (user_clicked || user_scroll_finish || input_requested_by_nav);
|
|
const bool init_state = (init_make_active || user_scroll_active);
|
|
if ((init_state && g.ActiveId != id) || init_changed_specs || init_reload_from_user_buf)
|
|
{
|
|
state = &g.InputTextState;
|
|
state->CursorAnimReset();
|
|
state->ReloadUserBuf = false;
|
|
|
|
InputTextDeactivateHook(state->ID);
|
|
|
|
const int buf_len = (int)strlen(buf);
|
|
if (!init_reload_from_user_buf)
|
|
{
|
|
state->InitialTextA.resize(buf_len + 1);
|
|
memcpy(state->InitialTextA.Data, buf, buf_len + 1);
|
|
}
|
|
|
|
bool recycle_state = (state->ID == id && !init_changed_specs && !init_reload_from_user_buf);
|
|
if (recycle_state && (state->CurLenA != buf_len || (state->TextAIsValid && strncmp(state->TextA.Data, buf, buf_len) != 0)))
|
|
recycle_state = false;
|
|
|
|
const char* buf_end = NULL;
|
|
state->ID = id;
|
|
state->TextW.resize(buf_size + 1);
|
|
state->TextA.resize(0);
|
|
state->TextAIsValid = false;
|
|
state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, buf_size, buf, NULL, &buf_end);
|
|
state->CurLenA = (int)(buf_end - buf);
|
|
|
|
if (recycle_state)
|
|
{
|
|
state->CursorClamp();
|
|
}
|
|
else
|
|
{
|
|
state->ScrollX = 0.0f;
|
|
stb_textedit_initialize_state(&state->Stb, !is_multiline);
|
|
}
|
|
|
|
if (init_reload_from_user_buf)
|
|
{
|
|
state->Stb.select_start = state->ReloadSelectionStart;
|
|
state->Stb.cursor = state->Stb.select_end = state->ReloadSelectionEnd;
|
|
state->CursorClamp();
|
|
}
|
|
else if (!is_multiline)
|
|
{
|
|
if (flags & ImGuiInputTextFlags_AutoSelectAll)
|
|
select_all = true;
|
|
if (input_requested_by_nav && (!recycle_state || !(g.NavActivateFlags & ImGuiActivateFlags_TryToPreserveState)))
|
|
select_all = true;
|
|
if (user_clicked && io.KeyCtrl)
|
|
select_all = true;
|
|
}
|
|
|
|
if (flags & ImGuiInputTextFlags_AlwaysOverwrite)
|
|
state->Stb.insert_mode = 1;
|
|
}
|
|
|
|
const bool is_osx = io.ConfigMacOSXBehaviors;
|
|
if (g.ActiveId != id && init_make_active)
|
|
{
|
|
IM_ASSERT(state && state->ID == id);
|
|
SetActiveID(id, window);
|
|
SetFocusID(id, window);
|
|
FocusWindow(window);
|
|
}
|
|
if (g.ActiveId == id)
|
|
{
|
|
if (user_clicked)
|
|
SetKeyOwner(ImGuiKey_MouseLeft, id);
|
|
g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
|
|
if (is_multiline || (flags & ImGuiInputTextFlags_CallbackHistory))
|
|
g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
|
|
SetKeyOwner(ImGuiKey_Enter, id);
|
|
SetKeyOwner(ImGuiKey_KeypadEnter, id);
|
|
SetKeyOwner(ImGuiKey_Home, id);
|
|
SetKeyOwner(ImGuiKey_End, id);
|
|
if (is_multiline)
|
|
{
|
|
SetKeyOwner(ImGuiKey_PageUp, id);
|
|
SetKeyOwner(ImGuiKey_PageDown, id);
|
|
}
|
|
if (is_osx)
|
|
SetKeyOwner(ImGuiMod_Alt, id);
|
|
}
|
|
|
|
if (g.ActiveId == id && state == NULL)
|
|
ClearActiveID();
|
|
|
|
if (g.ActiveId == id && io.MouseClicked[0] && !init_state && !init_make_active)
|
|
clear_active_id = true;
|
|
|
|
bool render_cursor = (g.ActiveId == id) || (state && user_scroll_active);
|
|
bool render_selection = state && (state->HasSelection() || select_all) && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor);
|
|
bool value_changed = false;
|
|
bool validated = false;
|
|
|
|
if (is_readonly && state != NULL && (render_cursor || render_selection))
|
|
{
|
|
const char* buf_end = NULL;
|
|
state->TextW.resize(buf_size + 1);
|
|
state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, state->TextW.Size, buf, NULL, &buf_end);
|
|
state->CurLenA = (int)(buf_end - buf);
|
|
state->CursorClamp();
|
|
render_selection &= state->HasSelection();
|
|
}
|
|
|
|
const bool buf_display_from_state = (render_cursor || render_selection || g.ActiveId == id) && !is_readonly && state && state->TextAIsValid;
|
|
const bool is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0);
|
|
|
|
if (is_password && !is_displaying_hint)
|
|
{
|
|
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->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);
|
|
}
|
|
|
|
int backup_current_text_length = 0;
|
|
if (g.ActiveId == id)
|
|
{
|
|
IM_ASSERT(state != NULL);
|
|
backup_current_text_length = state->CurLenA;
|
|
state->Edited = false;
|
|
state->BufCapacityA = buf_size;
|
|
state->Flags = flags;
|
|
|
|
g.ActiveIdAllowOverlap = !io.MouseDown[0];
|
|
|
|
const float mouse_x = (io.MousePos.x - rect.Min.x - style.FramePadding.x) + state->ScrollX;
|
|
const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y) : (g.FontSize * 0.5f));
|
|
|
|
if (select_all)
|
|
{
|
|
state->SelectAll();
|
|
state->SelectedAllMouseLock = true;
|
|
}
|
|
else if (hovered && io.MouseClickedCount[0] >= 2 && !io.KeyShift)
|
|
{
|
|
stb_textedit_click(state, &state->Stb, mouse_x, mouse_y);
|
|
const int multiclick_count = (io.MouseClickedCount[0] - 2);
|
|
if ((multiclick_count % 2) == 0)
|
|
{
|
|
const bool is_bol = (state->Stb.cursor == 0) || ImStb::STB_TEXTEDIT_GETCHAR(state, state->Stb.cursor - 1) == '\n';
|
|
if (STB_TEXT_HAS_SELECTION(&state->Stb) || !is_bol)
|
|
state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT);
|
|
|
|
if (!STB_TEXT_HAS_SELECTION(&state->Stb))
|
|
ImStb::stb_textedit_prep_selection_at_cursor(&state->Stb);
|
|
state->Stb.cursor = ImStb::STB_TEXTEDIT_MOVEWORDRIGHT_MAC(state, state->Stb.cursor);
|
|
state->Stb.select_end = state->Stb.cursor;
|
|
ImStb::stb_textedit_clamp(state, &state->Stb);
|
|
}
|
|
else
|
|
{
|
|
const bool is_eol = ImStb::STB_TEXTEDIT_GETCHAR(state, state->Stb.cursor) == '\n';
|
|
state->OnKeyPressed(STB_TEXTEDIT_K_LINESTART);
|
|
state->OnKeyPressed(STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT);
|
|
state->OnKeyPressed(STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT);
|
|
if (!is_eol && is_multiline)
|
|
{
|
|
ImSwap(state->Stb.select_start, state->Stb.select_end);
|
|
state->Stb.cursor = state->Stb.select_end;
|
|
}
|
|
state->CursorFollow = false;
|
|
}
|
|
state->CursorAnimReset();
|
|
}
|
|
else if (io.MouseClicked[0] && !state->SelectedAllMouseLock)
|
|
{
|
|
if (hovered)
|
|
{
|
|
if (io.KeyShift)
|
|
stb_textedit_drag(state, &state->Stb, mouse_x, mouse_y);
|
|
else
|
|
stb_textedit_click(state, &state->Stb, mouse_x, mouse_y);
|
|
state->CursorAnimReset();
|
|
}
|
|
}
|
|
else if (io.MouseDown[0] && !state->SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f))
|
|
{
|
|
stb_textedit_drag(state, &state->Stb, mouse_x, mouse_y);
|
|
state->CursorAnimReset();
|
|
state->CursorFollow = true;
|
|
}
|
|
if (state->SelectedAllMouseLock && !io.MouseDown[0])
|
|
state->SelectedAllMouseLock = false;
|
|
|
|
if ((flags & ImGuiInputTextFlags_AllowTabInput) && !is_readonly)
|
|
{
|
|
if (Shortcut(ImGuiKey_Tab, ImGuiInputFlags_Repeat, id))
|
|
{
|
|
unsigned int c = '\t';
|
|
if (input_text_filter_character(&g, &c, flags, callback, callback_user_data))
|
|
state->OnKeyPressed((int)c);
|
|
}
|
|
}
|
|
|
|
const bool ignore_char_inputs = (io.KeyCtrl && !io.KeyAlt) || (is_osx && io.KeyCtrl);
|
|
if (io.InputQueueCharacters.Size > 0)
|
|
{
|
|
if (!ignore_char_inputs && !is_readonly && !input_requested_by_nav)
|
|
for (int n = 0; n < io.InputQueueCharacters.Size; n++)
|
|
{
|
|
unsigned int c = (unsigned int)io.InputQueueCharacters[n];
|
|
if (c == '\t')
|
|
continue;
|
|
if (input_text_filter_character(&g, &c, flags, callback, callback_user_data))
|
|
state->OnKeyPressed((int)c);
|
|
}
|
|
|
|
io.InputQueueCharacters.resize(0);
|
|
}
|
|
}
|
|
|
|
bool revert_edit = false;
|
|
if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id)
|
|
{
|
|
IM_ASSERT(state != NULL);
|
|
|
|
const int row_count_per_page = ImMax((int)((inner_size.y - style.FramePadding.y) / g.FontSize), 1);
|
|
state->Stb.row_count_per_page = row_count_per_page;
|
|
|
|
const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0);
|
|
const bool is_wordmove_key_down = is_osx ? io.KeyAlt : io.KeyCtrl;
|
|
const bool is_startend_key_down = is_osx && io.KeyCtrl && !io.KeySuper && !io.KeyAlt;
|
|
|
|
const ImGuiInputFlags f_repeat = ImGuiInputFlags_Repeat;
|
|
const bool is_cut = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_X, f_repeat, id) || Shortcut(ImGuiMod_Shift | ImGuiKey_Delete, f_repeat, id)) && !is_readonly && !is_password && (!is_multiline || state->HasSelection());
|
|
const bool is_copy = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_C, 0, id) || Shortcut(ImGuiMod_Ctrl | ImGuiKey_Insert, 0, id)) && !is_password && (!is_multiline || state->HasSelection());
|
|
const bool is_paste = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_V, f_repeat, id) || Shortcut(ImGuiMod_Shift | ImGuiKey_Insert, f_repeat, id)) && !is_readonly;
|
|
const bool is_undo = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_Z, f_repeat, id)) && !is_readonly && is_undoable;
|
|
const bool is_redo = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_Y, f_repeat, id) || (is_osx && Shortcut(ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Z, f_repeat, id))) && !is_readonly && is_undoable;
|
|
const bool is_select_all = Shortcut(ImGuiMod_Ctrl | ImGuiKey_A, 0, id);
|
|
|
|
const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0;
|
|
const bool is_enter_pressed = IsKeyPressed(ImGuiKey_Enter, true) || IsKeyPressed(ImGuiKey_KeypadEnter, true);
|
|
const bool is_gamepad_validate = nav_gamepad_active && (IsKeyPressed(ImGuiKey_NavGamepadActivate, false) || IsKeyPressed(ImGuiKey_NavGamepadInput, false));
|
|
const bool is_cancel = Shortcut(ImGuiKey_Escape, f_repeat, id) || (nav_gamepad_active && Shortcut(ImGuiKey_NavGamepadCancel, f_repeat, id));
|
|
|
|
if (IsKeyPressed(ImGuiKey_LeftArrow)) { 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 (IsKeyPressed(ImGuiKey_RightArrow)) { 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 (IsKeyPressed(ImGuiKey_UpArrow) && is_multiline) { if (io.KeyCtrl) SetScrollY(draw_window, ImMax(draw_window->Scroll.y - g.FontSize, 0.0f)); else state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTSTART : STB_TEXTEDIT_K_UP) | k_mask); }
|
|
else if (IsKeyPressed(ImGuiKey_DownArrow) && is_multiline) { if (io.KeyCtrl) SetScrollY(draw_window, ImMin(draw_window->Scroll.y + g.FontSize, GetScrollMaxY())); else state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTEND : STB_TEXTEDIT_K_DOWN) | k_mask); }
|
|
else if (IsKeyPressed(ImGuiKey_PageUp) && is_multiline) { state->OnKeyPressed(STB_TEXTEDIT_K_PGUP | k_mask); scroll_y -= row_count_per_page * g.FontSize; }
|
|
else if (IsKeyPressed(ImGuiKey_PageDown) && is_multiline) { state->OnKeyPressed(STB_TEXTEDIT_K_PGDOWN | k_mask); scroll_y += row_count_per_page * g.FontSize; }
|
|
else if (IsKeyPressed(ImGuiKey_Home)) { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); }
|
|
else if (IsKeyPressed(ImGuiKey_End)) { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); }
|
|
else if (IsKeyPressed(ImGuiKey_Delete) && !is_readonly && !is_cut)
|
|
{
|
|
if (!state->HasSelection())
|
|
{
|
|
if (is_wordmove_key_down)
|
|
state->OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT);
|
|
}
|
|
state->OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask);
|
|
}
|
|
else if (IsKeyPressed(ImGuiKey_Backspace) && !is_readonly)
|
|
{
|
|
if (!state->HasSelection())
|
|
{
|
|
if (is_wordmove_key_down)
|
|
state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT);
|
|
else if (is_osx && io.KeyCtrl && !io.KeyAlt && !io.KeySuper)
|
|
state->OnKeyPressed(STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT);
|
|
}
|
|
state->OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask);
|
|
}
|
|
else if (is_enter_pressed || is_gamepad_validate)
|
|
{
|
|
bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0;
|
|
if (!is_multiline || is_gamepad_validate || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl))
|
|
{
|
|
validated = true;
|
|
if (io.ConfigInputTextEnterKeepActive && !is_multiline)
|
|
state->SelectAll();
|
|
else
|
|
clear_active_id = true;
|
|
}
|
|
else if (!is_readonly)
|
|
{
|
|
unsigned int c = '\n';
|
|
if (input_text_filter_character(&g, &c, flags, callback, callback_user_data))
|
|
state->OnKeyPressed((int)c);
|
|
}
|
|
}
|
|
else if (is_cancel)
|
|
{
|
|
if (flags & ImGuiInputTextFlags_EscapeClearsAll)
|
|
{
|
|
if (buf[0] != 0)
|
|
{
|
|
revert_edit = true;
|
|
}
|
|
else
|
|
{
|
|
render_cursor = render_selection = false;
|
|
clear_active_id = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
clear_active_id = revert_edit = true;
|
|
render_cursor = render_selection = false;
|
|
}
|
|
}
|
|
else if (is_undo || is_redo)
|
|
{
|
|
state->OnKeyPressed(is_undo ? STB_TEXTEDIT_K_UNDO : STB_TEXTEDIT_K_REDO);
|
|
state->ClearSelection();
|
|
}
|
|
else if (is_select_all)
|
|
{
|
|
state->SelectAll();
|
|
state->CursorFollow = true;
|
|
}
|
|
else if (is_cut || is_copy)
|
|
{
|
|
if (io.SetClipboardTextFn)
|
|
{
|
|
const int ib = state->HasSelection() ? ImMin(state->Stb.select_start, state->Stb.select_end) : 0;
|
|
const int ie = state->HasSelection() ? ImMax(state->Stb.select_start, state->Stb.select_end) : state->CurLenW;
|
|
const int clipboard_data_len = ImTextCountUtf8BytesFromStr(state->TextW.Data + ib, state->TextW.Data + ie) + 1;
|
|
char* clipboard_data = (char*)IM_ALLOC(clipboard_data_len * sizeof(char));
|
|
ImTextStrToUtf8(clipboard_data, clipboard_data_len, state->TextW.Data + ib, state->TextW.Data + ie);
|
|
SetClipboardText(clipboard_data);
|
|
MemFree(clipboard_data);
|
|
}
|
|
if (is_cut)
|
|
{
|
|
if (!state->HasSelection())
|
|
state->SelectAll();
|
|
state->CursorFollow = true;
|
|
stb_textedit_cut(state, &state->Stb);
|
|
}
|
|
}
|
|
else if (is_paste)
|
|
{
|
|
if (const char* clipboard = GetClipboardText())
|
|
{
|
|
const int clipboard_len = (int)strlen(clipboard);
|
|
ImWchar* clipboard_filtered = (ImWchar*)IM_ALLOC((clipboard_len + 1) * sizeof(ImWchar));
|
|
int clipboard_filtered_len = 0;
|
|
for (const char* s = clipboard; *s != 0; )
|
|
{
|
|
unsigned int c;
|
|
s += ImTextCharFromUtf8(&c, s, NULL);
|
|
if (!input_text_filter_character(&g, &c, flags, callback, callback_user_data, true))
|
|
continue;
|
|
clipboard_filtered[clipboard_filtered_len++] = (ImWchar)c;
|
|
}
|
|
clipboard_filtered[clipboard_filtered_len] = 0;
|
|
if (clipboard_filtered_len > 0)
|
|
{
|
|
stb_textedit_paste(state, &state->Stb, clipboard_filtered, clipboard_filtered_len);
|
|
state->CursorFollow = true;
|
|
}
|
|
MemFree(clipboard_filtered);
|
|
}
|
|
}
|
|
|
|
render_selection |= state->HasSelection() && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor);
|
|
}
|
|
|
|
const char* apply_new_text = NULL;
|
|
int apply_new_text_length = 0;
|
|
if (g.ActiveId == id)
|
|
{
|
|
IM_ASSERT(state != NULL);
|
|
if (revert_edit && !is_readonly)
|
|
{
|
|
if (flags & ImGuiInputTextFlags_EscapeClearsAll)
|
|
{
|
|
IM_ASSERT(buf[0] != 0);
|
|
apply_new_text = "";
|
|
apply_new_text_length = 0;
|
|
value_changed = true;
|
|
IMSTB_TEXTEDIT_CHARTYPE empty_string;
|
|
stb_textedit_replace(state, &state->Stb, &empty_string, 0);
|
|
}
|
|
else if (strcmp(buf, state->InitialTextA.Data) != 0)
|
|
{
|
|
apply_new_text = state->InitialTextA.Data;
|
|
apply_new_text_length = state->InitialTextA.Size - 1;
|
|
value_changed = true;
|
|
ImVector<ImWchar> w_text;
|
|
if (apply_new_text_length > 0)
|
|
{
|
|
w_text.resize(ImTextCountCharsFromUtf8(apply_new_text, apply_new_text + apply_new_text_length) + 1);
|
|
ImTextStrFromUtf8(w_text.Data, w_text.Size, apply_new_text, apply_new_text + apply_new_text_length);
|
|
}
|
|
stb_textedit_replace(state, &state->Stb, w_text.Data, (apply_new_text_length > 0) ? (w_text.Size - 1) : 0);
|
|
}
|
|
}
|
|
|
|
if (!is_readonly)
|
|
{
|
|
state->TextAIsValid = true;
|
|
state->TextA.resize(state->TextW.Size * 4 + 1);
|
|
ImTextStrToUtf8(state->TextA.Data, state->TextA.Size, state->TextW.Data, NULL);
|
|
}
|
|
|
|
const bool apply_edit_back_to_user_buffer = !revert_edit || (validated && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0);
|
|
if (apply_edit_back_to_user_buffer)
|
|
{
|
|
if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackEdit | ImGuiInputTextFlags_CallbackAlways)) != 0)
|
|
{
|
|
IM_ASSERT(callback != NULL);
|
|
|
|
ImGuiInputTextFlags event_flag = 0;
|
|
ImGuiKey event_key = ImGuiKey_None;
|
|
if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && Shortcut(ImGuiKey_Tab, 0, id))
|
|
{
|
|
event_flag = ImGuiInputTextFlags_CallbackCompletion;
|
|
event_key = ImGuiKey_Tab;
|
|
}
|
|
else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressed(ImGuiKey_UpArrow))
|
|
{
|
|
event_flag = ImGuiInputTextFlags_CallbackHistory;
|
|
event_key = ImGuiKey_UpArrow;
|
|
}
|
|
else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressed(ImGuiKey_DownArrow))
|
|
{
|
|
event_flag = ImGuiInputTextFlags_CallbackHistory;
|
|
event_key = ImGuiKey_DownArrow;
|
|
}
|
|
else if ((flags & ImGuiInputTextFlags_CallbackEdit) && state->Edited)
|
|
{
|
|
event_flag = ImGuiInputTextFlags_CallbackEdit;
|
|
}
|
|
else if (flags & ImGuiInputTextFlags_CallbackAlways)
|
|
{
|
|
event_flag = ImGuiInputTextFlags_CallbackAlways;
|
|
}
|
|
|
|
if (event_flag)
|
|
{
|
|
ImGuiInputTextCallbackData callback_data;
|
|
callback_data.Ctx = &g;
|
|
callback_data.EventFlag = event_flag;
|
|
callback_data.Flags = flags;
|
|
callback_data.UserData = callback_user_data;
|
|
|
|
char* callback_buf = is_readonly ? buf : state->TextA.Data;
|
|
callback_data.EventKey = event_key;
|
|
callback_data.Buf = callback_buf;
|
|
callback_data.BufTextLen = state->CurLenA;
|
|
callback_data.BufSize = state->BufCapacityA;
|
|
callback_data.BufDirty = false;
|
|
|
|
ImWchar* text = state->TextW.Data;
|
|
const int utf8_cursor_pos = callback_data.CursorPos = ImTextCountUtf8BytesFromStr(text, text + state->Stb.cursor);
|
|
const int utf8_selection_start = callback_data.SelectionStart = ImTextCountUtf8BytesFromStr(text, text + state->Stb.select_start);
|
|
const int utf8_selection_end = callback_data.SelectionEnd = ImTextCountUtf8BytesFromStr(text, text + state->Stb.select_end);
|
|
|
|
callback(&callback_data);
|
|
|
|
callback_buf = is_readonly ? buf : state->TextA.Data;
|
|
IM_ASSERT(callback_data.Buf == callback_buf);
|
|
IM_ASSERT(callback_data.BufSize == state->BufCapacityA);
|
|
IM_ASSERT(callback_data.Flags == flags);
|
|
const bool buf_dirty = callback_data.BufDirty;
|
|
if (callback_data.CursorPos != utf8_cursor_pos || buf_dirty) { state->Stb.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos); state->CursorFollow = true; }
|
|
if (callback_data.SelectionStart != utf8_selection_start || buf_dirty) { state->Stb.select_start = (callback_data.SelectionStart == callback_data.CursorPos) ? state->Stb.cursor : ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionStart); }
|
|
if (callback_data.SelectionEnd != utf8_selection_end || buf_dirty) { state->Stb.select_end = (callback_data.SelectionEnd == callback_data.SelectionStart) ? state->Stb.select_start : ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionEnd); }
|
|
if (buf_dirty)
|
|
{
|
|
IM_ASSERT(!is_readonly);
|
|
IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf));
|
|
input_text_reconcile_undo_state_after_user_callback(state, callback_data.Buf, callback_data.BufTextLen);
|
|
if (callback_data.BufTextLen > backup_current_text_length && is_resizable)
|
|
state->TextW.resize(state->TextW.Size + (callback_data.BufTextLen - backup_current_text_length));
|
|
state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, state->TextW.Size, callback_data.Buf, NULL);
|
|
state->CurLenA = callback_data.BufTextLen;
|
|
state->CursorAnimReset();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!is_readonly && strcmp(state->TextA.Data, buf) != 0)
|
|
{
|
|
apply_new_text = state->TextA.Data;
|
|
apply_new_text_length = state->CurLenA;
|
|
value_changed = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (g.InputTextDeactivatedState.ID == id)
|
|
{
|
|
if (g.ActiveId != id && IsItemDeactivatedAfterEdit() && !is_readonly && strcmp(g.InputTextDeactivatedState.TextA.Data, buf) != 0)
|
|
{
|
|
apply_new_text = g.InputTextDeactivatedState.TextA.Data;
|
|
apply_new_text_length = g.InputTextDeactivatedState.TextA.Size - 1;
|
|
value_changed = true;
|
|
}
|
|
g.InputTextDeactivatedState.ID = 0;
|
|
}
|
|
|
|
if (apply_new_text != NULL)
|
|
{
|
|
IM_ASSERT(apply_new_text_length >= 0);
|
|
if (is_resizable)
|
|
{
|
|
ImGuiInputTextCallbackData callback_data;
|
|
callback_data.Ctx = &g;
|
|
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);
|
|
}
|
|
ImStrncpy(buf, apply_new_text, ImMin(apply_new_text_length + 1, buf_size));
|
|
}
|
|
|
|
if (g.ActiveId == id && clear_active_id)
|
|
ClearActiveID();
|
|
else if (g.ActiveId == id)
|
|
g.WantTextInputNextFrame = 1;
|
|
|
|
c_field* c_state = gui->anim_container(&c_state, id);
|
|
|
|
if (!is_multiline)
|
|
{
|
|
draw->add_rect_filled(window->DrawList, rect.Min, rect.Max, gui->get_clr(clr->c_element.layout), SCALE(set->c_element.rounding));
|
|
draw->render_text(window->DrawList, set->c_font.icon[0], rect.Max - ImVec2(size_arg.y, size_arg.y), rect.Max, gui->get_clr(clr->c_other_clr.accent_clr), label.data(), 0, 0, { 0.5, 0.5 });
|
|
}
|
|
|
|
const ImVec4 clip_rect(rect.Min.x, rect.Min.y, rect.Min.x + (inner_size.x - size_arg.y), rect.Min.y + inner_size.y);
|
|
|
|
ImVec2 draw_pos = is_multiline ? draw_window->DC.CursorPos : rect.Min + ImVec2((size_arg.y - CalcTextSize(label.data()).y) / 2, (size_arg.y - CalcTextSize(label.data()).y) / 2);
|
|
ImVec2 text_size(0.0f, 0.0f);
|
|
|
|
const int buf_display_max_length = 2 * 1024 * 1024;
|
|
const char* buf_display = buf_display_from_state ? state->TextA.Data : buf;
|
|
const char* buf_display_end = NULL;
|
|
if (is_displaying_hint)
|
|
{
|
|
buf_display = hint;
|
|
buf_display_end = hint + strlen(hint);
|
|
}
|
|
|
|
if (render_cursor || render_selection)
|
|
{
|
|
IM_ASSERT(state != NULL);
|
|
if (!is_displaying_hint)
|
|
buf_display_end = buf_display + state->CurLenA;
|
|
|
|
const ImWchar* text_begin = state->TextW.Data;
|
|
ImVec2 cursor_offset, select_start_offset;
|
|
|
|
{
|
|
const ImWchar* searches_input_ptr[2] = { NULL, NULL };
|
|
int searches_result_line_no[2] = { -1000, -1000 };
|
|
int searches_remaining = 0;
|
|
if (render_cursor)
|
|
{
|
|
searches_input_ptr[0] = text_begin + state->Stb.cursor;
|
|
searches_result_line_no[0] = -1;
|
|
searches_remaining++;
|
|
}
|
|
if (render_selection)
|
|
{
|
|
searches_input_ptr[1] = text_begin + ImMin(state->Stb.select_start, state->Stb.select_end);
|
|
searches_result_line_no[1] = -1;
|
|
searches_remaining++;
|
|
}
|
|
|
|
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_no[0] == -1 && s >= searches_input_ptr[0]) { searches_result_line_no[0] = line_count; if (--searches_remaining <= 0) break; }
|
|
if (searches_result_line_no[1] == -1 && s >= searches_input_ptr[1]) { searches_result_line_no[1] = line_count; if (--searches_remaining <= 0) break; }
|
|
}
|
|
line_count++;
|
|
if (searches_result_line_no[0] == -1)
|
|
searches_result_line_no[0] = line_count;
|
|
if (searches_result_line_no[1] == -1)
|
|
searches_result_line_no[1] = line_count;
|
|
|
|
cursor_offset.x = input_text_calc_text_size_w(&g, ImStrbolW(searches_input_ptr[0], text_begin), searches_input_ptr[0]).x;
|
|
cursor_offset.y = searches_result_line_no[0] * g.FontSize;
|
|
if (searches_result_line_no[1] >= 0)
|
|
{
|
|
select_start_offset.x = input_text_calc_text_size_w(&g, ImStrbolW(searches_input_ptr[1], text_begin), searches_input_ptr[1]).x;
|
|
select_start_offset.y = searches_result_line_no[1] * g.FontSize;
|
|
}
|
|
|
|
if (is_multiline)
|
|
text_size = ImVec2(inner_size.x, line_count * g.FontSize);
|
|
}
|
|
|
|
if (render_cursor && state->CursorFollow)
|
|
{
|
|
if (!(flags & ImGuiInputTextFlags_NoHorizontalScroll))
|
|
{
|
|
const float scroll_increment_x = inner_size.x * 0.25f;
|
|
const float visible_width = inner_size.x - style.FramePadding.x;
|
|
if (cursor_offset.x < state->ScrollX)
|
|
state->ScrollX = IM_TRUNC(ImMax(0.0f, cursor_offset.x - scroll_increment_x));
|
|
else if (cursor_offset.x - visible_width >= state->ScrollX)
|
|
state->ScrollX = IM_TRUNC(cursor_offset.x - visible_width + scroll_increment_x);
|
|
}
|
|
else
|
|
{
|
|
state->ScrollX = 0.0f;
|
|
}
|
|
|
|
if (is_multiline)
|
|
{
|
|
if (cursor_offset.y - g.FontSize < scroll_y)
|
|
scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize);
|
|
else if (cursor_offset.y - (inner_size.y - style.FramePadding.y * 2.0f) >= scroll_y)
|
|
scroll_y = cursor_offset.y - inner_size.y + style.FramePadding.y * 2.0f;
|
|
const float scroll_max_y = ImMax((text_size.y + style.FramePadding.y * 2.0f) - inner_size.y, 0.0f);
|
|
scroll_y = ImClamp(scroll_y, 0.0f, scroll_max_y);
|
|
draw_pos.y += (draw_window->Scroll.y - scroll_y);
|
|
draw_window->Scroll.y = scroll_y;
|
|
}
|
|
|
|
state->CursorFollow = false;
|
|
}
|
|
|
|
// Draw selection
|
|
const ImVec2 draw_scroll = ImVec2(state->ScrollX, 0.0f);
|
|
if (render_selection)
|
|
{
|
|
const ImWchar* text_selected_begin = text_begin + ImMin(state->Stb.select_start, state->Stb.select_end);
|
|
const ImWchar* text_selected_end = text_begin + ImMax(state->Stb.select_start, state->Stb.select_end);
|
|
|
|
ImU32 bg_color = gui->get_clr(clr->c_other_clr.accent_clr, render_cursor ? 0.2f : 0.2f);
|
|
float bg_offy_up = is_multiline ? 0.0f : -1.0f;
|
|
float bg_offy_dn = is_multiline ? 0.0f : 2.0f;
|
|
ImVec2 rect_pos = draw_pos + select_start_offset - draw_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 = input_text_calc_text_size_w(&g, p, text_selected_end, &p, NULL, true);
|
|
if (rect_size.x <= 0.0f) rect_size.x = IM_TRUNC(g.Font->GetCharAdvance((ImWchar)' ') * 0.50f);
|
|
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->add_rect_filled(window->DrawList, rect.Min, rect.Max, bg_color);
|
|
}
|
|
rect_pos.x = draw_pos.x - draw_scroll.x;
|
|
rect_pos.y += g.FontSize;
|
|
}
|
|
}
|
|
|
|
if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length)
|
|
{
|
|
ImU32 col = gui->get_clr(is_displaying_hint ? clr->c_text.text : clr->c_text.text_active);
|
|
draw->add_text(window->DrawList, g.Font, g.FontSize, draw_pos - draw_scroll, col, buf_display, buf_display_end, 0.0f, is_multiline ? NULL : &clip_rect);
|
|
}
|
|
|
|
if (render_cursor)
|
|
{
|
|
state->CursorAnim += io.DeltaTime;
|
|
bool cursor_is_visible = (!g.IO.ConfigInputTextCursorBlink) || (state->CursorAnim <= 0.0f) || ImFmod(state->CursorAnim, 1.20f) <= 0.80f;
|
|
ImVec2 cursor_screen_pos = ImTrunc(draw_pos + cursor_offset - draw_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);
|
|
|
|
c_state->line_offset = ImLerp(c_state->line_offset, cursor_screen_rect.Min.x - rect.Min.x, gui->fixed_speed(16.f));
|
|
if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect)) draw->add_line(window->DrawList, ImVec2(rect.Min.x + c_state->line_offset, cursor_screen_rect.Min.y), ImVec2(rect.Min.x + c_state->line_offset, cursor_screen_rect.GetBL().y), gui->get_clr(clr->c_text.text_active));
|
|
|
|
if (!is_readonly)
|
|
{
|
|
g.PlatformImeData.WantVisible = true;
|
|
g.PlatformImeData.InputPos = ImVec2(cursor_screen_pos.x - 1.0f, cursor_screen_pos.y - g.FontSize);
|
|
g.PlatformImeData.InputLineHeight = g.FontSize;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (is_multiline) text_size = ImVec2(inner_size.x, input_text_calc_text_len_and_line_count(buf_display, &buf_display_end) * g.FontSize);
|
|
|
|
else if (!is_displaying_hint && g.ActiveId == id) buf_display_end = buf_display + state->CurLenA;
|
|
else if (!is_displaying_hint) buf_display_end = buf_display + strlen(buf_display);
|
|
|
|
if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length)
|
|
{
|
|
ImU32 col = gui->get_clr(is_displaying_hint ? clr->c_text.text : clr->c_text.text_active);
|
|
draw->add_text(window->DrawList, g.Font, g.FontSize, draw_pos, col, buf_display, buf_display_end, 0.0f, is_multiline ? NULL : &clip_rect);
|
|
}
|
|
}
|
|
|
|
if (is_password && !is_displaying_hint)
|
|
PopFont();
|
|
|
|
if (is_multiline)
|
|
{
|
|
Dummy(ImVec2(text_size.x, text_size.y + style.FramePadding.y));
|
|
g.NextItemData.ItemFlags |= ImGuiItemFlags_Inputable | ImGuiItemFlags_NoTabStop;
|
|
EndChild();
|
|
item_data_backup.StatusFlags |= (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredWindow);
|
|
|
|
EndGroup();
|
|
if (g.LastItemData.ID == 0)
|
|
{
|
|
g.LastItemData.ID = id;
|
|
g.LastItemData.InFlags = item_data_backup.InFlags;
|
|
g.LastItemData.StatusFlags = item_data_backup.StatusFlags;
|
|
}
|
|
}
|
|
|
|
if (value_changed && !(flags & ImGuiInputTextFlags_NoMarkEdited)) MarkItemEdited(id);
|
|
|
|
IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable);
|
|
if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0)
|
|
return validated;
|
|
else
|
|
return value_changed;
|
|
}
|
|
|
|
bool c_widget::text_field(std::string_view hint, std::string_view label, char* buf, size_t buf_size, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
|
|
{
|
|
IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline));
|
|
return input_text_ex(label.data(), hint.data(), buf, (int)buf_size, size, flags, callback, user_data);
|
|
}
|