Internal: InputText: Renamed is_editable to !is_readonly, Hopefully more explicit. Renamed internal member. Shuffled some code. Added comments, assert (_will_ trigger on !readonly > readonly edge, old bug).

This commit is contained in:
omar 2019-02-21 12:35:21 +01:00
parent cc3be5d428
commit 81a8730022
3 changed files with 73 additions and 70 deletions

View File

@ -58,9 +58,9 @@ CODE
// [SECTION] FORWARD DECLARATIONS // [SECTION] FORWARD DECLARATIONS
// [SECTION] CONTEXT AND MEMORY ALLOCATORS // [SECTION] CONTEXT AND MEMORY ALLOCATORS
// [SECTION] MAIN USER FACING STRUCTURES (ImGuiStyle, ImGuiIO) // [SECTION] MAIN USER FACING STRUCTURES (ImGuiStyle, ImGuiIO)
// [SECTION] MISC HELPER/UTILITIES (Maths, String, Format, Hash, File functions) // [SECTION] MISC HELPERS/UTILITIES (Maths, String, Format, Hash, File functions)
// [SECTION] MISC HELPER/UTILITIES (ImText* functions) // [SECTION] MISC HELPERS/UTILITIES (ImText* functions)
// [SECTION] MISC HELPER/UTILITIES (Color functions) // [SECTION] MISC HELPERS/UTILITIES (Color functions)
// [SECTION] ImGuiStorage // [SECTION] ImGuiStorage
// [SECTION] ImGuiTextFilter // [SECTION] ImGuiTextFilter
// [SECTION] ImGuiTextBuffer // [SECTION] ImGuiTextBuffer
@ -1226,7 +1226,7 @@ void ImGuiIO::ClearInputCharacters()
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// [SECTION] MISC HELPER/UTILITIES (Maths, String, Format, Hash, File functions) // [SECTION] MISC HELPERS/UTILITIES (Maths, String, Format, Hash, File functions)
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
ImVec2 ImLineClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& p) ImVec2 ImLineClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& p)
@ -1749,7 +1749,7 @@ int ImTextCountUtf8BytesFromStr(const ImWchar* in_text, const ImWchar* in_text_e
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// [SECTION] MISC HELPER/UTILTIES (Color functions) // [SECTION] MISC HELPERS/UTILTIES (Color functions)
// Note: The Convert functions are early design which are not consistent with other API. // Note: The Convert functions are early design which are not consistent with other API.
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -3603,9 +3603,7 @@ void ImGui::Shutdown(ImGuiContext* context)
g.DrawDataBuilder.ClearFreeMemory(); g.DrawDataBuilder.ClearFreeMemory();
g.OverlayDrawList.ClearFreeMemory(); g.OverlayDrawList.ClearFreeMemory();
g.PrivateClipboard.clear(); g.PrivateClipboard.clear();
g.InputTextState.TextW.clear(); g.InputTextState.ClearFreeMemory();
g.InputTextState.InitialText.clear();
g.InputTextState.TempBuffer.clear();
for (int i = 0; i < g.SettingsWindows.Size; i++) for (int i = 0; i < g.SettingsWindows.Size; i++)
IM_DELETE(g.SettingsWindows[i].Name); IM_DELETE(g.SettingsWindows[i].Name);

View File

@ -562,11 +562,11 @@ struct IMGUI_API ImGuiInputTextState
{ {
ImGuiID ID; // widget id owning the text state ImGuiID ID; // widget id owning the text state
ImVector<ImWchar> TextW; // edit buffer, we need to persist but can't guarantee the persistence of the user-provided buffer. so we copy into own buffer. ImVector<ImWchar> TextW; // edit buffer, we need to persist but can't guarantee the persistence of the user-provided buffer. so we copy into own buffer.
ImVector<char> InitialText; // backup of end-user buffer at the time of focus (in UTF-8, unaltered) ImVector<char> InitialTextA; // backup of end-user buffer at the time of focus (in UTF-8, unaltered)
ImVector<char> TempBuffer; // temporary buffer for callback and other other operations. size=capacity. ImVector<char> TempBufferA; // temporary buffer for callbacks and other operations. size=capacity.
int CurLenA, CurLenW; // we need to maintain our buffer length in both UTF-8 and wchar format. int CurLenA, CurLenW; // we need to maintain our buffer length in both UTF-8 and wchar format.
int BufCapacityA; // end-user buffer capacity int BufCapacityA; // end-user buffer capacity
float ScrollX; float ScrollX; // horizontal scrolling/offset
ImStb::STB_TexteditState Stb; // state for stb_textedit.h ImStb::STB_TexteditState Stb; // state for stb_textedit.h
float CursorAnim; // timer for cursor blink, reset on every user action so the cursor reappears immediately float CursorAnim; // timer for cursor blink, reset on every user action so the cursor reappears immediately
bool CursorFollow; // set when we want scrolling to follow the current cursor position (not always!) bool CursorFollow; // set when we want scrolling to follow the current cursor position (not always!)
@ -578,6 +578,7 @@ struct IMGUI_API ImGuiInputTextState
void* UserCallbackData; void* UserCallbackData;
ImGuiInputTextState() { memset(this, 0, sizeof(*this)); } ImGuiInputTextState() { memset(this, 0, sizeof(*this)); }
void ClearFreeMemory() { TextW.clear(); InitialTextA.clear(); TempBufferA.clear(); }
void CursorAnimReset() { CursorAnim = -0.30f; } // After a user-input the cursor stays on for a while without blinking void CursorAnimReset() { CursorAnim = -0.30f; } // After a user-input the cursor stays on for a while without blinking
void CursorClamp() { Stb.cursor = ImMin(Stb.cursor, CurLenW); Stb.select_start = ImMin(Stb.select_start, CurLenW); Stb.select_end = ImMin(Stb.select_end, CurLenW); } void CursorClamp() { Stb.cursor = ImMin(Stb.cursor, CurLenW); Stb.select_start = ImMin(Stb.select_start, CurLenW); Stb.select_end = ImMin(Stb.select_end, CurLenW); }
bool HasSelection() const { return Stb.select_start != Stb.select_end; } bool HasSelection() const { return Stb.select_start != Stb.select_end; }

View File

@ -2637,7 +2637,7 @@ bool ImGui::InputScalarAsWidgetReplacement(const ImRect& bb, ImGuiID id, const c
g.ScalarAsInputTextId = g.ActiveId; g.ScalarAsInputTextId = g.ActiveId;
} }
if (value_changed) if (value_changed)
return DataTypeApplyOpFromText(data_buf, g.InputTextState.InitialText.Data, data_type, data_ptr, NULL); return DataTypeApplyOpFromText(data_buf, g.InputTextState.InitialTextA.Data, data_type, data_ptr, NULL);
return false; return false;
} }
@ -2670,7 +2670,7 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* data_p
PushID(label); PushID(label);
PushItemWidth(ImMax(1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2)); PushItemWidth(ImMax(1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2));
if (InputText("", buf, IM_ARRAYSIZE(buf), flags)) // PushId(label) + "" gives us the expected ID from outside point of view if (InputText("", buf, IM_ARRAYSIZE(buf), flags)) // PushId(label) + "" gives us the expected ID from outside point of view
value_changed = DataTypeApplyOpFromText(buf, g.InputTextState.InitialText.Data, data_type, data_ptr, format); value_changed = DataTypeApplyOpFromText(buf, g.InputTextState.InitialTextA.Data, data_type, data_ptr, format);
PopItemWidth(); PopItemWidth();
// Step buttons // Step buttons
@ -2701,7 +2701,7 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* data_p
else else
{ {
if (InputText(label, buf, IM_ARRAYSIZE(buf), flags)) if (InputText(label, buf, IM_ARRAYSIZE(buf), flags))
value_changed = DataTypeApplyOpFromText(buf, g.InputTextState.InitialText.Data, data_type, data_ptr, format); value_changed = DataTypeApplyOpFromText(buf, g.InputTextState.InitialTextA.Data, data_type, data_ptr, format);
} }
return value_changed; return value_changed;
@ -3045,10 +3045,10 @@ void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, cons
ImGuiContext& g = *GImGui; ImGuiContext& g = *GImGui;
ImGuiInputTextState* edit_state = &g.InputTextState; ImGuiInputTextState* edit_state = &g.InputTextState;
IM_ASSERT(edit_state->ID != 0 && g.ActiveId == edit_state->ID); IM_ASSERT(edit_state->ID != 0 && g.ActiveId == edit_state->ID);
IM_ASSERT(Buf == edit_state->TempBuffer.Data); IM_ASSERT(Buf == edit_state->TempBufferA.Data);
int new_buf_size = BufTextLen + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1; 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); edit_state->TempBufferA.reserve(new_buf_size + 1);
Buf = edit_state->TempBuffer.Data; Buf = edit_state->TempBufferA.Data;
BufSize = edit_state->BufCapacityA = new_buf_size; BufSize = edit_state->BufCapacityA = new_buf_size;
} }
@ -3128,7 +3128,8 @@ static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags f
// Note that in std::string world, capacity() would omit 1 byte used by the zero-terminator. // 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. // - 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/cpp/imgui_stdlib.h // - If you want to use ImGui::InputText() with std::string, see misc/cpp/imgui_stdlib.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) // (FIXME: Rather confusing and messy function, among the worse part of our codebase, expecting to rewrite a V2 at some point.. Partly because we are
// doing UTF8 > U16 > UTF8 conversions on the go to easily interface with stb_textedit. Ideally 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) 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(); ImGuiWindow* window = GetCurrentWindow();
@ -3143,7 +3144,7 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
const ImGuiStyle& style = g.Style; const ImGuiStyle& style = g.Style;
const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0; const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0;
const bool is_editable = (flags & ImGuiInputTextFlags_ReadOnly) == 0; const bool is_readonly = (flags & ImGuiInputTextFlags_ReadOnly) != 0;
const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0; const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0;
const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0; const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0;
const bool is_resizable = (flags & ImGuiInputTextFlags_CallbackResize) != 0; const bool is_resizable = (flags & ImGuiInputTextFlags_CallbackResize) != 0;
@ -3228,17 +3229,18 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
// Access state even if we don't own it yet. // Access state even if we don't own it yet.
state = &g.InputTextState; state = &g.InputTextState;
// Start edition
// Take a copy of the initial buffer value (both in original UTF-8 format and converted to wchar) // 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) // From the moment we focused we are ignoring the content of 'buf' (unless we are in read-only mode)
const int buf_len = (int)strlen(buf);
state->InitialTextA.resize(buf_len + 1); // UTF-8. we use +1 to make sure that .Data is always pointing to at least an empty string.
memcpy(state->InitialTextA.Data, buf, buf_len + 1);
// Start edition
const int prev_len_w = state->CurLenW; const int prev_len_w = state->CurLenW;
const int init_buf_len = (int)strlen(buf);
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.
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(state->InitialText.Data, buf, init_buf_len + 1);
const char* buf_end = NULL; const char* buf_end = NULL;
state->TextW.resize(buf_size+1); // wchar count <= UTF-8 count. we use +1 to make sure that .Data is always pointing to at least an empty string.
state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, buf_size, buf, NULL, &buf_end); state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, buf_size, buf, NULL, &buf_end);
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. 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.
state->CursorAnimReset(); state->CursorAnimReset();
// Preserve cursor position and undo/redo stack if we come back to same widget // Preserve cursor position and undo/redo stack if we come back to same widget
@ -3286,10 +3288,11 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
bool enter_pressed = false; bool enter_pressed = false;
int backup_current_text_length = 0; int backup_current_text_length = 0;
// Process mouse inputs and character inputs
if (g.ActiveId == id) if (g.ActiveId == id)
{ {
IM_ASSERT(state != NULL); IM_ASSERT(state != NULL);
if (!is_editable && !g.ActiveIdIsJustActivated) if (is_readonly && !g.ActiveIdIsJustActivated)
{ {
// When read-only we always use the live data passed to the function // When read-only we always use the live data passed to the function
const char* buf_end = NULL; const char* buf_end = NULL;
@ -3348,7 +3351,7 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
// Process text input (before we check for Return because using some IME will effectively send a Return?) // 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. // 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); bool ignore_inputs = (io.KeyCtrl && !io.KeyAlt) || (is_osx && io.KeySuper);
if (!ignore_inputs && is_editable && !user_nav_input_start) if (!ignore_inputs && !is_readonly && !user_nav_input_start)
for (int n = 0; n < io.InputQueueCharacters.Size; n++) for (int n = 0; n < io.InputQueueCharacters.Size; n++)
{ {
// Insert character if they pass filtering // Insert character if they pass filtering
@ -3362,10 +3365,10 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
} }
} }
// Process other shortcuts/key-presses
bool cancel_edit = false; bool cancel_edit = false;
if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id) if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id)
{ {
// Handle key-presses
IM_ASSERT(state != NULL); IM_ASSERT(state != NULL);
const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0); const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0);
const bool is_osx = io.ConfigMacOSXBehaviors; const bool is_osx = io.ConfigMacOSXBehaviors;
@ -3376,11 +3379,11 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
const bool is_ctrl_key_only = io.KeyCtrl && !io.KeyShift && !io.KeyAlt && !io.KeySuper; 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_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 || state->HasSelection()); const bool is_cut = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_X)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Delete))) && !is_readonly && !is_password && (!is_multiline || state->HasSelection());
const bool is_copy = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_C)) || (is_ctrl_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && !is_password && (!is_multiline || state->HasSelection()); const bool is_copy = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_C)) || (is_ctrl_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && !is_password && (!is_multiline || state->HasSelection());
const bool is_paste = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_V)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && is_editable; const bool is_paste = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_V)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && !is_readonly;
const bool is_undo = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Z)) && is_editable && is_undoable); const bool is_undo = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Z)) && !is_readonly && is_undoable);
const bool is_redo = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Y)) || (is_osx_shift_shortcut && 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_readonly && is_undoable;
if (IsKeyPressedMap(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); } if (IsKeyPressedMap(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 (IsKeyPressedMap(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 (IsKeyPressedMap(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); }
@ -3388,8 +3391,8 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
else if (IsKeyPressedMap(ImGuiKey_DownArrow) && is_multiline) { if (io.KeyCtrl) SetWindowScrollY(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 (IsKeyPressedMap(ImGuiKey_DownArrow) && is_multiline) { if (io.KeyCtrl) SetWindowScrollY(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 (IsKeyPressedMap(ImGuiKey_Home)) { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); } else if (IsKeyPressedMap(ImGuiKey_Home)) { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); }
else if (IsKeyPressedMap(ImGuiKey_End)) { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); } else if (IsKeyPressedMap(ImGuiKey_End)) { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); }
else if (IsKeyPressedMap(ImGuiKey_Delete) && is_editable) { state->OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); } else if (IsKeyPressedMap(ImGuiKey_Delete) && !is_readonly) { state->OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); }
else if (IsKeyPressedMap(ImGuiKey_Backspace) && is_editable) else if (IsKeyPressedMap(ImGuiKey_Backspace) && !is_readonly)
{ {
if (!state->HasSelection()) if (!state->HasSelection())
{ {
@ -3407,14 +3410,14 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
{ {
enter_pressed = clear_active_id = true; enter_pressed = clear_active_id = true;
} }
else if (is_editable) else if (!is_readonly)
{ {
unsigned int c = '\n'; // Insert new line unsigned int c = '\n'; // Insert new line
if (InputTextFilterCharacter(&c, flags, callback, callback_user_data)) if (InputTextFilterCharacter(&c, flags, callback, callback_user_data))
state->OnKeyPressed((int)c); state->OnKeyPressed((int)c);
} }
} }
else if ((flags & ImGuiInputTextFlags_AllowTabInput) && IsKeyPressedMap(ImGuiKey_Tab) && !io.KeyCtrl && !io.KeyShift && !io.KeyAlt && is_editable) else if ((flags & ImGuiInputTextFlags_AllowTabInput) && IsKeyPressedMap(ImGuiKey_Tab) && !io.KeyCtrl && !io.KeyShift && !io.KeyAlt && !is_readonly)
{ {
unsigned int c = '\t'; // Insert TAB unsigned int c = '\t'; // Insert TAB
if (InputTextFilterCharacter(&c, flags, callback, callback_user_data)) if (InputTextFilterCharacter(&c, flags, callback, callback_user_data))
@ -3441,9 +3444,9 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
{ {
const int ib = state->HasSelection() ? ImMin(state->Stb.select_start, state->Stb.select_end) : 0; 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 ie = state->HasSelection() ? ImMax(state->Stb.select_start, state->Stb.select_end) : state->CurLenW;
state->TempBuffer.resize((ie-ib) * 4 + 1); state->TempBufferA.resize((ie-ib) * 4 + 1);
ImTextStrToUtf8(state->TempBuffer.Data, state->TempBuffer.Size, state->TextW.Data+ib, state->TextW.Data+ie); ImTextStrToUtf8(state->TempBufferA.Data, state->TempBufferA.Size, state->TextW.Data+ib, state->TextW.Data+ie);
SetClipboardText(state->TempBuffer.Data); SetClipboardText(state->TempBufferA.Data);
} }
if (is_cut) if (is_cut)
{ {
@ -3482,6 +3485,7 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
} }
} }
// Process callbacks and apply result back to user's buffer.
if (g.ActiveId == id) if (g.ActiveId == id)
{ {
IM_ASSERT(state != NULL); IM_ASSERT(state != NULL);
@ -3490,10 +3494,10 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
if (cancel_edit) if (cancel_edit)
{ {
// Restore initial value. Only return true if restoring to the initial value changes the current buffer contents. // Restore initial value. Only return true if restoring to the initial value changes the current buffer contents.
if (is_editable && strcmp(buf, state->InitialText.Data) != 0) if (!is_readonly && strcmp(buf, state->InitialTextA.Data) != 0)
{ {
apply_new_text = state->InitialText.Data; apply_new_text = state->InitialTextA.Data;
apply_new_text_length = state->InitialText.Size - 1; apply_new_text_length = state->InitialTextA.Size - 1;
} }
} }
@ -3506,10 +3510,10 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
// Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer // 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: 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. // 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) if (!is_readonly)
{ {
state->TempBuffer.resize(state->TextW.Size * 4 + 1); state->TempBufferA.resize(state->TextW.Size * 4 + 1);
ImTextStrToUtf8(state->TempBuffer.Data, state->TempBuffer.Size, state->TextW.Data, NULL); ImTextStrToUtf8(state->TempBufferA.Data, state->TempBufferA.Size, state->TextW.Data, NULL);
} }
// User callback // User callback
@ -3547,7 +3551,7 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
callback_data.UserData = callback_user_data; callback_data.UserData = callback_user_data;
callback_data.EventKey = event_key; callback_data.EventKey = event_key;
callback_data.Buf = state->TempBuffer.Data; callback_data.Buf = state->TempBufferA.Data;
callback_data.BufTextLen = state->CurLenA; callback_data.BufTextLen = state->CurLenA;
callback_data.BufSize = state->BufCapacityA; callback_data.BufSize = state->BufCapacityA;
callback_data.BufDirty = false; callback_data.BufDirty = false;
@ -3562,7 +3566,7 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
callback(&callback_data); callback(&callback_data);
// Read back what user may have modified // Read back what user may have modified
IM_ASSERT(callback_data.Buf == state->TempBuffer.Data); // Invalid to modify those fields IM_ASSERT(callback_data.Buf == state->TempBufferA.Data); // Invalid to modify those fields
IM_ASSERT(callback_data.BufSize == state->BufCapacityA); IM_ASSERT(callback_data.BufSize == state->BufCapacityA);
IM_ASSERT(callback_data.Flags == flags); IM_ASSERT(callback_data.Flags == flags);
if (callback_data.CursorPos != utf8_cursor_pos) { state->Stb.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos); state->CursorFollow = true; } if (callback_data.CursorPos != utf8_cursor_pos) { state->Stb.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos); state->CursorFollow = true; }
@ -3581,9 +3585,9 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
} }
// Will copy result string if modified // Will copy result string if modified
if (is_editable && strcmp(state->TempBuffer.Data, buf) != 0) if (!is_readonly && strcmp(state->TempBufferA.Data, buf) != 0)
{ {
apply_new_text = state->TempBuffer.Data; apply_new_text = state->TempBufferA.Data;
apply_new_text_length = state->CurLenA; apply_new_text_length = state->CurLenA;
} }
} }
@ -3629,7 +3633,8 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
const int buf_display_max_length = 2 * 1024 * 1024; const int buf_display_max_length = 2 * 1024 * 1024;
// Select which buffer we are going to display. We set buf to NULL to prevent accidental usage from now on. // Select which buffer we are going to display. We set buf to NULL to prevent accidental usage from now on.
const char* buf_display = (state != NULL && is_editable) ? state->TempBuffer.Data : buf; const char* buf_display = (state != NULL && !is_readonly) ? state->TempBufferA.Data : buf;
IM_ASSERT(buf_display);
buf = NULL; buf = NULL;
// Render // Render
@ -3640,20 +3645,18 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
} }
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 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 draw_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding;
ImVec2 text_size(0.f, 0.f); ImVec2 text_size(0.0f, 0.0f);
if (g.ActiveId == id || user_scroll_active) if (g.ActiveId == id || user_scroll_active)
{ {
// Animate cursor // Render text (with cursor and selection)
IM_ASSERT(state != NULL);
state->CursorAnim += io.DeltaTime;
// This is going to be messy. We need to: // This is going to be messy. We need to:
// - Display the text (this alone can be more easily clipped) // - 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) // - Handle scrolling, highlight selection, display cursor (those all requires some form of 1d->2d cursor position calculation)
// - Measure text height (for scrollbar) // - 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) // 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. // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8.
IM_ASSERT(state != NULL);
const ImWchar* text_begin = state->TextW.Data; const ImWchar* text_begin = state->TextW.Data;
ImVec2 cursor_offset, select_start_offset; ImVec2 cursor_offset, select_start_offset;
@ -3728,14 +3731,14 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
scroll_y = cursor_offset.y - size.y; scroll_y = cursor_offset.y - size.y;
draw_window->DC.CursorPos.y += (draw_window->Scroll.y - scroll_y); // Manipulate cursor pos immediately avoid a frame of lag draw_window->DC.CursorPos.y += (draw_window->Scroll.y - scroll_y); // Manipulate cursor pos immediately avoid a frame of lag
draw_window->Scroll.y = scroll_y; draw_window->Scroll.y = scroll_y;
render_pos.y = draw_window->DC.CursorPos.y; draw_pos.y = draw_window->DC.CursorPos.y;
} }
} }
const ImVec2 draw_scroll = ImVec2(state->ScrollX, 0.0f);
state->CursorFollow = false; state->CursorFollow = false;
const ImVec2 render_scroll = ImVec2(state->ScrollX, 0.0f);
// Draw selection // Draw selection
if (state->Stb.select_start != state->Stb.select_end) if (state->HasSelection())
{ {
const ImWchar* text_selected_begin = text_begin + ImMin(state->Stb.select_start, state->Stb.select_end); 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); const ImWchar* text_selected_end = text_begin + ImMax(state->Stb.select_start, state->Stb.select_end);
@ -3743,7 +3746,7 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
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_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; float bg_offy_dn = is_multiline ? 0.0f : 2.0f;
ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg); ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg);
ImVec2 rect_pos = render_pos + select_start_offset - render_scroll; ImVec2 rect_pos = draw_pos + select_start_offset - draw_scroll;
for (const ImWchar* p = text_selected_begin; p < text_selected_end; ) for (const ImWchar* p = text_selected_begin; p < text_selected_end; )
{ {
if (rect_pos.y > clip_rect.w + g.FontSize) if (rect_pos.y > clip_rect.w + g.FontSize)
@ -3765,7 +3768,7 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
if (rect.Overlaps(clip_rect)) if (rect.Overlaps(clip_rect))
draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color); draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color);
} }
rect_pos.x = render_pos.x - render_scroll.x; rect_pos.x = draw_pos.x - draw_scroll.x;
rect_pos.y += g.FontSize; rect_pos.y += g.FontSize;
} }
} }
@ -3773,29 +3776,30 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
// We test for 'buf_display_max_length' as a way to avoid some pathological cases (e.g. single-line 1 MB string) which would make ImDrawList crash. // We test for 'buf_display_max_length' as a way to avoid some pathological cases (e.g. single-line 1 MB string) which would make ImDrawList crash.
const int buf_display_len = state->CurLenA; const int buf_display_len = state->CurLenA;
if (is_multiline || buf_display_len < buf_display_max_length) 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_window->DrawList->AddText(g.Font, g.FontSize, draw_pos - draw_scroll, GetColorU32(ImGuiCol_Text), buf_display, buf_display + buf_display_len, 0.0f, is_multiline ? NULL : &clip_rect);
// Draw blinking cursor // Draw blinking cursor
bool cursor_is_visible = (!g.IO.ConfigInputTextCursorBlink) || (g.InputTextState.CursorAnim <= 0.0f) || ImFmod(g.InputTextState.CursorAnim, 1.20f) <= 0.80f; state->CursorAnim += io.DeltaTime;
ImVec2 cursor_screen_pos = render_pos + cursor_offset - render_scroll; bool cursor_is_visible = (!g.IO.ConfigInputTextCursorBlink) || (state->CursorAnim <= 0.0f) || ImFmod(state->CursorAnim, 1.20f) <= 0.80f;
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); ImVec2 cursor_screen_pos = 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);
if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect)) 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)); 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.) // 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) if (!is_readonly)
g.PlatformImePos = ImVec2(cursor_screen_pos.x - 1, cursor_screen_pos.y - g.FontSize); g.PlatformImePos = ImVec2(cursor_screen_pos.x - 1.0f, cursor_screen_pos.y - g.FontSize);
} }
else else
{ {
// Render text only // Render text only (no selection, no cursor)
const char* buf_end = NULL; const char* buf_end = NULL;
if (is_multiline) if (is_multiline)
text_size = ImVec2(size.x, InputTextCalcTextLenAndLineCount(buf_display, &buf_end) * g.FontSize); // We don't need width text_size = ImVec2(size.x, InputTextCalcTextLenAndLineCount(buf_display, &buf_end) * g.FontSize); // We don't need width
else else
buf_end = buf_display + strlen(buf_display); buf_end = buf_display + strlen(buf_display);
if (is_multiline || (buf_end - buf_display) < buf_display_max_length) 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); draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos, GetColorU32(ImGuiCol_Text), buf_display, buf_end, 0.0f, is_multiline ? NULL : &clip_rect);
} }
if (is_multiline) if (is_multiline)
@ -3810,7 +3814,7 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
// Log as text // Log as text
if (g.LogEnabled && !is_password) if (g.LogEnabled && !is_password)
LogRenderedText(&render_pos, buf_display, NULL); LogRenderedText(&draw_pos, buf_display, NULL);
if (label_size.x > 0) if (label_size.x > 0)
RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);