From 0df7b472c25487336abdf57e2f4ff41bc54674eb Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 16 Jun 2015 15:46:45 -0600 Subject: [PATCH] InputTextMultiline() WIP - still gazillion problems with selection visualisation & scrolling --- imgui.cpp | 242 +++++++++++++++++++++++++++++++++--------------------- imgui.h | 10 ++- 2 files changed, 155 insertions(+), 97 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index f71d54f84..c15a34637 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -495,6 +495,7 @@ namespace IMGUI_STB_NAMESPACE #undef STB_TEXTEDIT_CHARTYPE #define STB_TEXTEDIT_STRING ImGuiTextEditState #define STB_TEXTEDIT_CHARTYPE ImWchar +#define STB_TEXTEDIT_GETWIDTH_NEWLINE -1.0f #include "stb_textedit.h" #ifdef __clang__ @@ -1187,7 +1188,7 @@ struct ImGuiTextEditState char InitialText[1024*3+1]; // backup of end-user buffer at the time of focus (in UTF-8, unaltered) size_t CurLenA, CurLenW; // we need to maintain our buffer length in both UTF-8 and wchar format. size_t BufSizeA; // end-user buffer size, <= 1024 (or increase above) - float Width; // widget width + ImVec2 Size; // widget width/height float ScrollX; STB_TexteditState StbState; float CursorAnim; @@ -1201,7 +1202,7 @@ struct ImGuiTextEditState void CursorAnimReset() { CursorAnim = -0.30f; } // After a user-input the cursor stays on for a while without blinking bool CursorIsVisible() const { return CursorAnim <= 0.0f || fmodf(CursorAnim, 1.20f) <= 0.80f; } // Blinking bool HasSelection() const { return StbState.select_start != StbState.select_end; } - void SelectAll() { StbState.select_start = 0; StbState.select_end = (int)ImStrlenW(Text); StbState.cursor = StbState.select_end; StbState.has_preferred_x = false; } + void SelectAll() { StbState.select_start = 0; StbState.select_end = (int)CurLenW; StbState.cursor = StbState.select_end; StbState.has_preferred_x = false; } void OnKeyPressed(int key); void UpdateScrollOffset(); @@ -1209,8 +1210,8 @@ struct ImGuiTextEditState // Static functions because they are used to render non-focused instances of a text input box static const char* GetTextPointerClippedA(ImFont* font, float font_size, const char* text, float width, ImVec2* out_text_size = NULL); - static const ImWchar* GetTextPointerClippedW(ImFont* font, float font_size, const ImWchar* text, float width, ImVec2* out_text_size = NULL); - static void RenderTextScrolledClipped(ImFont* font, float font_size, const char* text, ImVec2 pos_base, float width, float scroll_x); + static const ImWchar* GetTextPointerClippedW(ImFont* font, float font_size, const ImWchar* text, const ImWchar* text_end, float width, ImVec2* out_text_size = NULL); + static void RenderTextScrolledClipped(ImFont* font, float font_size, const char* text, const ImVec2& pos, const ImVec2& size, float scroll_x); }; // Data saved in imgui.ini file @@ -6511,16 +6512,71 @@ bool ImGui::RadioButton(const char* label, int* v, int v_button) return pressed; } +static ImVec2 CalcTextSizeW(ImFont* font, float font_size, float max_width, const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false) +{ + IM_ASSERT(text_end); + + const float scale = font_size / font->FontSize; + const float line_height = font->FontSize * scale; + + ImVec2 text_size = ImVec2(0,0); + float line_width = 0.0f; + + const ImWchar* s = text_begin; + while (s < text_end) + { + const unsigned int c = (unsigned int)(*s++); + + if (c < 32) + { + 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); + if (line_width + char_width >= max_width) + { + s--; + break; + } + + 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) -static int STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING* obj) { return (int)ImStrlenW(obj->Text); } +static int STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING* obj) { return (int)obj->CurLenW; } static ImWchar STB_TEXTEDIT_GETCHAR(const STB_TEXTEDIT_STRING* obj, int idx) { return obj->Text[idx]; } -static float STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING* obj, int line_start_idx, int char_idx) { (void)line_start_idx; return obj->Font->CalcTextSizeW(obj->FontSize, FLT_MAX, &obj->Text[char_idx], &obj->Text[char_idx]+1, NULL).x; } +static float STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING* obj, int line_start_idx, int char_idx) { ImWchar* s = &obj->Text[line_start_idx+char_idx]; if (*s == '\n') return STB_TEXTEDIT_GETWIDTH_NEWLINE; return CalcTextSizeW(obj->Font, obj->FontSize, FLT_MAX, s, s+1, NULL).x; } 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_remaining = NULL; - const ImVec2 size = obj->Font->CalcTextSizeW(obj->FontSize, FLT_MAX, obj->Text + line_start_idx, NULL, &text_remaining); + const ImVec2 size = CalcTextSizeW(obj->Font, obj->FontSize, FLT_MAX, obj->Text + line_start_idx, obj->Text + obj->CurLenW, &text_remaining, NULL, true); r->x0 = 0.0f; r->x1 = size.x; r->baseline_y_delta = size.y; @@ -6603,14 +6659,14 @@ void ImGuiTextEditState::OnKeyPressed(int key) void ImGuiTextEditState::UpdateScrollOffset() { // Scroll in chunks of quarter width - const float scroll_x_increment = Width * 0.25f; - const float cursor_offset_x = Font->CalcTextSizeW(FontSize, FLT_MAX, Text, Text+StbState.cursor, NULL).x; + const float scroll_x_increment = Size.x * 0.25f; + const float cursor_offset_x = CalcTextSizeW(Font, FontSize, FLT_MAX, Text, Text+StbState.cursor, NULL).x; // If widget became bigger than text (because of a resize), reset horizontal scrolling if (ScrollX > 0.0f) { - const float text_width = cursor_offset_x + Font->CalcTextSizeW(FontSize, FLT_MAX, Text+StbState.cursor, NULL, NULL).x; - if (text_width < Width) + const float text_width = cursor_offset_x + CalcTextSizeW(Font, FontSize, FLT_MAX, Text+StbState.cursor, NULL, NULL).x; + if (text_width < Size.x) { ScrollX = 0.0f; return; @@ -6619,17 +6675,19 @@ void ImGuiTextEditState::UpdateScrollOffset() if (cursor_offset_x < ScrollX) ScrollX = ImMax(0.0f, cursor_offset_x - scroll_x_increment); - else if (cursor_offset_x - Width >= ScrollX) - ScrollX = cursor_offset_x - Width + scroll_x_increment; + else if (cursor_offset_x - Size.x >= ScrollX) + ScrollX = cursor_offset_x - Size.x + scroll_x_increment; } ImVec2 ImGuiTextEditState::CalcDisplayOffsetFromCharIdx(int i) const { - const ImWchar* text_start = GetTextPointerClippedW(Font, FontSize, Text, ScrollX, NULL); + const ImWchar* text_start = GetTextPointerClippedW(Font, FontSize, Text, Text+CurLenW, ScrollX, NULL); const ImWchar* text_end = (Text+i >= text_start) ? Text+i : text_start; // Clip if requested character is outside of display IM_ASSERT(text_end >= text_start); - const ImVec2 offset = Font->CalcTextSizeW(FontSize, Width+1, text_start, text_end, NULL); + // FIXME-WIP-MULTILINE + ImVec2 offset; + CalcTextSizeW(Font, FontSize, Size.x+1, text_start, text_end, NULL, &offset); return offset; } @@ -6647,20 +6705,20 @@ const char* ImGuiTextEditState::GetTextPointerClippedA(ImFont* font, float font_ } // [Static] -const ImWchar* ImGuiTextEditState::GetTextPointerClippedW(ImFont* font, float font_size, const ImWchar* text, float width, ImVec2* out_text_size) +const ImWchar* ImGuiTextEditState::GetTextPointerClippedW(ImFont* font, float font_size, const ImWchar* text, const ImWchar* text_end, float width, ImVec2* out_text_size) { if (width <= 0.0f) return text; const ImWchar* text_clipped_end = NULL; - const ImVec2 text_size = font->CalcTextSizeW(font_size, width, text, NULL, &text_clipped_end); + const ImVec2 text_size = CalcTextSizeW(font, font_size, width, text, text_end, &text_clipped_end); if (out_text_size) *out_text_size = text_size; return text_clipped_end; } // [Static] -void ImGuiTextEditState::RenderTextScrolledClipped(ImFont* font, float font_size, const char* buf, ImVec2 pos, float width, float scroll_x) +void ImGuiTextEditState::RenderTextScrolledClipped(ImFont* font, float font_size, const char* buf, const ImVec2& pos, const ImVec2& size, float scroll_x) { ImGuiWindow* window = GetCurrentWindow(); const ImU32 font_color = window->Color(ImGuiCol_Text); @@ -6669,7 +6727,7 @@ void ImGuiTextEditState::RenderTextScrolledClipped(ImFont* font, float font_size // Determine start and end of visible string // FIXME-OPT: This is pretty slow for what it does. const char* text_start = scroll_x <= 0.0f ? buf : GetTextPointerClippedA(font, font_size, buf, scroll_x, NULL); - const char* text_end = GetTextPointerClippedA(font, font_size, text_start, width + 1, NULL); // +1 to allow character spacing to fit outside the allowed width + const char* text_end = GetTextPointerClippedA(font, font_size, text_start, size.x + 1, NULL); // +1 to allow character spacing to fit outside the allowed width window->DrawList->AddText(font, font_size, pos, font_color, text_start, text_end); // Log as text @@ -6793,7 +6851,13 @@ static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags f unsigned int c = *p_char; if (c < 128 && c != ' ' && !isprint((int)(c & 0xFF))) - return false; + { + 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; @@ -6836,21 +6900,30 @@ static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags f } // Edit a string of text -bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data) +static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* 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) + ImGuiState& g = *GImGui; const ImGuiIO& io = g.IO; const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(label); - const float w = ImGui::CalcItemWidth(); + const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0; - const ImVec2 label_size = CalcTextSize(label, NULL, true); - const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y) + style.FramePadding*2.0f); + ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true); + ImVec2 size = size_arg; + if (size.x == 0.0f) + size.x = ImGui::CalcItemWidth(); + if (size.y == 0.0f) + size.y = is_multiline ? label_size.y * 8.0f : label_size.y; // Arbitrary default + + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size + style.FramePadding*2.0f); 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)); ItemSize(total_bb, style.FramePadding.y); if (!ItemAdd(total_bb, &id)) @@ -6862,7 +6935,7 @@ bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputT const bool is_ctrl_down = io.KeyCtrl; const bool is_shift_down = io.KeyShift; const bool is_alt_down = io.KeyAlt; - const bool focus_requested = window->FocusItemRegister(g.ActiveId == id, (flags & ImGuiInputTextFlags_CallbackCompletion) == 0); // Using completion callback disable keyboard tabbing + const bool focus_requested = window->FocusItemRegister(g.ActiveId == 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; @@ -6886,7 +6959,7 @@ bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputT const char* buf_end = NULL; edit_state.CurLenW = ImTextStrFromUtf8(edit_state.Text, IM_ARRAYSIZE(edit_state.Text), buf, NULL, &buf_end); edit_state.CurLenA = buf_end - buf; // We can't get the result from ImFormatString() above because it is not UTF-8 aware. Here we'll cut off malformed UTF-8. - edit_state.Width = w + style.FramePadding.x; + edit_state.Size = size + style.FramePadding; edit_state.InputCursorScreenPos = ImVec2(-1.f,-1.f); edit_state.CursorAnimReset(); @@ -6894,7 +6967,7 @@ bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputT { edit_state.Id = id; edit_state.ScrollX = 0.0f; - stb_textedit_initialize_state(&edit_state.StbState, true); + stb_textedit_initialize_state(&edit_state.StbState, !is_multiline); if (focus_requested_by_code) select_all = true; } @@ -6931,7 +7004,7 @@ bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputT //if (edit_state.Id == id) // Works, but double-click to select-all sets cursors to end which in turn tends to scroll toward the right when shrinking widget. { // Update some data if we are active or last active - edit_state.Width = w + style.FramePadding.x; + edit_state.Size = size + style.FramePadding; edit_state.BufSizeA = buf_size; edit_state.Font = g.Font; edit_state.FontSize = g.FontSize; @@ -6941,7 +7014,7 @@ bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputT { // Edit in progress const float mx = g.IO.MousePos.x - frame_bb.Min.x - style.FramePadding.x; - const float my = g.FontSize*0.5f; // Flatten mouse because we are doing a single-line edit + const float my = is_multiline ? (g.IO.MousePos.y - frame_bb.Min.y - style.FramePadding.y) : g.FontSize*0.5f; if (select_all || (hovered && io.MouseDoubleClicked[0])) { @@ -6966,8 +7039,7 @@ bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputT // Process text input (before we check for Return because using some IME will effectively send a Return?) for (int n = 0; n < IM_ARRAYSIZE(g.IO.InputCharacters) && g.IO.InputCharacters[n]; n++) { - unsigned int c = (unsigned int)g.IO.InputCharacters[n]; - if (c) + if (unsigned int c = (unsigned int)g.IO.InputCharacters[n]) { // Insert character if they pass filtering if (!InputTextFilterCharacter(&c, flags, callback, user_data)) @@ -6982,13 +7054,35 @@ bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputT const int k_mask = (is_shift_down ? STB_TEXTEDIT_K_SHIFT : 0); const bool is_ctrl_only = is_ctrl_down && !is_alt_down && !is_shift_down; - if (IsKeyPressedMap(ImGuiKey_LeftArrow)) { edit_state.OnKeyPressed(is_ctrl_down ? STB_TEXTEDIT_K_WORDLEFT | k_mask : STB_TEXTEDIT_K_LEFT | k_mask); } - else if (IsKeyPressedMap(ImGuiKey_RightArrow)) { edit_state.OnKeyPressed(is_ctrl_down ? STB_TEXTEDIT_K_WORDRIGHT | k_mask : STB_TEXTEDIT_K_RIGHT | k_mask); } - else if (IsKeyPressedMap(ImGuiKey_Home)) { edit_state.OnKeyPressed(is_ctrl_down ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); } - else if (IsKeyPressedMap(ImGuiKey_End)) { edit_state.OnKeyPressed(is_ctrl_down ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); } - else if (IsKeyPressedMap(ImGuiKey_Delete)) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); } - else if (IsKeyPressedMap(ImGuiKey_Backspace)) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask); } - else if (IsKeyPressedMap(ImGuiKey_Enter)) { SetActiveId(0); enter_pressed = true; } + if (IsKeyPressedMap(ImGuiKey_LeftArrow)) { edit_state.OnKeyPressed(is_ctrl_down ? STB_TEXTEDIT_K_WORDLEFT | k_mask : STB_TEXTEDIT_K_LEFT | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_RightArrow)) { edit_state.OnKeyPressed(is_ctrl_down ? STB_TEXTEDIT_K_WORDRIGHT | k_mask : STB_TEXTEDIT_K_RIGHT | k_mask); } + else if (is_multiline && IsKeyPressedMap(ImGuiKey_UpArrow)) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_UP | k_mask); } + else if (is_multiline && IsKeyPressedMap(ImGuiKey_DownArrow)) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_DOWN| k_mask); } + else if (IsKeyPressedMap(ImGuiKey_Home)) { edit_state.OnKeyPressed(is_ctrl_down ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_End)) { edit_state.OnKeyPressed(is_ctrl_down ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_Delete)) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_Backspace)) { 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 && !is_ctrl_down) || (!ctrl_enter_for_new_line && is_ctrl_down)) + { + SetActiveId(0); + enter_pressed = true; + } + else // New line + { + unsigned int c = '\n'; + if (InputTextFilterCharacter(&c, flags, callback, user_data)) + edit_state.OnKeyPressed((int)c); + } + } + else if ((flags & ImGuiInputTextFlags_AllowTabInput) && IsKeyPressedMap(ImGuiKey_Tab)) + { + unsigned int c = '\t'; + if (InputTextFilterCharacter(&c, flags, callback, user_data)) + edit_state.OnKeyPressed((int)c); + } else if (IsKeyPressedMap(ImGuiKey_Escape)) { SetActiveId(0); cancel_edit = true; } else if (is_ctrl_only && IsKeyPressedMap(ImGuiKey_Z)) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_UNDO); } else if (is_ctrl_only && IsKeyPressedMap(ImGuiKey_Y)) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_REDO); } @@ -7146,7 +7240,7 @@ bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputT //const float render_scroll_x = (g.ActiveId == id) ? edit_state.ScrollX : 0.0f; const float render_scroll_x = (edit_state.Id == id) ? edit_state.ScrollX : 0.0f; - ImGuiTextEditState::RenderTextScrolledClipped(g.Font, g.FontSize, buf, frame_bb.Min + style.FramePadding, w + style.FramePadding.x, render_scroll_x); + ImGuiTextEditState::RenderTextScrolledClipped(g.Font, g.FontSize, buf, frame_bb.Min + style.FramePadding, size + style.FramePadding, render_scroll_x); if (g.ActiveId == id) { @@ -7172,6 +7266,19 @@ bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputT return value_changed; } +bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data) +{ + IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline() + bool ret = InputTextEx(label, buf, buf_size, ImVec2(0,0), flags, callback, user_data); + return ret; +} + +bool ImGui::InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data) +{ + bool ret = InputTextEx(label, buf, buf_size, size, flags | ImGuiInputTextFlags_Multiline, callback, user_data); + return ret; +} + static bool InputFloatN(const char* label, float* v, int components, int decimal_precision, ImGuiInputTextFlags extra_flags) { ImGuiState& g = *GImGui; @@ -9874,64 +9981,11 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons line_width += char_width; } - if (line_width > 0 || text_size.y == 0.0f) - { - if (text_size.x < line_width) - text_size.x = line_width; - text_size.y += line_height; - } - - if (remaining) - *remaining = s; - - return text_size; -} - -ImVec2 ImFont::CalcTextSizeW(float size, float max_width, const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining) const -{ - if (!text_end) - text_end = text_begin + ImStrlenW(text_begin); - - const float scale = size / FontSize; - const float line_height = FontSize * scale; - - ImVec2 text_size = ImVec2(0,0); - float line_width = 0.0f; - - const ImWchar* s = text_begin; - while (s < text_end) - { - const unsigned int c = (unsigned int)(*s++); - - if (c < 32) - { - if (c == '\n') - { - text_size.x = ImMax(text_size.x, line_width); - text_size.y += line_height; - line_width = 0.0f; - continue; - } - if (c == '\r') - continue; - } - - const float char_width = ((size_t)c < IndexXAdvance.size()) ? IndexXAdvance[(size_t)c] * scale : FallbackXAdvance; - if (line_width + char_width >= max_width) - { - s--; - break; - } - - line_width += char_width; - } + if (text_size.x < line_width) + text_size.x = line_width; if (line_width > 0 || text_size.y == 0.0f) - { - if (text_size.x < line_width) - text_size.x = line_width; text_size.y += line_height; - } if (remaining) *remaining = s; diff --git a/imgui.h b/imgui.h index a5a94e066..785d56b7a 100644 --- a/imgui.h +++ b/imgui.h @@ -267,6 +267,7 @@ namespace ImGui // Widgets: Input IMGUI_API bool InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags = 0, ImGuiTextEditCallback callback = NULL, void* user_data = NULL); + IMGUI_API bool InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size = ImVec2(0,0), ImGuiInputTextFlags flags = 0, ImGuiTextEditCallback callback = NULL, void* user_data = NULL); IMGUI_API bool InputFloat(const char* label, float* v, float step = 0.0f, float step_fast = 0.0f, int decimal_precision = -1, ImGuiInputTextFlags extra_flags = 0); IMGUI_API bool InputFloat2(const char* label, float v[2], int decimal_precision = -1, ImGuiInputTextFlags extra_flags = 0); IMGUI_API bool InputFloat3(const char* label, float v[3], int decimal_precision = -1, ImGuiInputTextFlags extra_flags = 0); @@ -441,7 +442,11 @@ enum ImGuiInputTextFlags_ ImGuiInputTextFlags_CallbackCompletion = 1 << 6, // Call user function on pressing TAB (for completion handling) ImGuiInputTextFlags_CallbackHistory = 1 << 7, // Call user function on pressing Up/Down arrows (for history handling) ImGuiInputTextFlags_CallbackAlways = 1 << 8, // Call user function every time - ImGuiInputTextFlags_CallbackCharFilter = 1 << 9 // Call user function to filter character. Modify data->EventChar to replace/filter input, or return 1 to discard character. + ImGuiInputTextFlags_CallbackCharFilter = 1 << 9, // Call user function to filter character. Modify data->EventChar to replace/filter input, or return 1 to discard character. + ImGuiInputTextFlags_AllowTabInput = 1 << 10, // Pressing TAB input a '\t' character into the text field + ImGuiInputTextFlags_CtrlEnterForNewLine = 1 << 11, // In multi-line mode, allow exiting edition by pressing Enter. Ctrl+Enter to add new line. + // [Internal] + ImGuiInputTextFlags_Multiline = 1 << 20 // For internal use by InputTextMultiline() }; // Flags for ImGui::Selectable() @@ -1135,8 +1140,7 @@ struct ImFont // 'max_width' stops rendering after a certain width (could be turned into a 2d size). FLT_MAX to disable. // 'wrap_width' enable automatic word-wrapping across multiple lines to fit into given width. 0.0f to disable. - IMGUI_API ImVec2 CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end = NULL, const char** remaining = NULL) const; // utf8 - IMGUI_API ImVec2 CalcTextSizeW(float size, float max_width, const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL) const; // wchar + IMGUI_API ImVec2 CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end = NULL, const char** remaining = NULL) const; // utf8 IMGUI_API void RenderText(float size, ImVec2 pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, ImDrawList* draw_list, float wrap_width = 0.0f, const ImVec2* cpu_clip_max = NULL) const; IMGUI_API const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) const; };