diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index c195ed1eb..b589eeb2f 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -66,6 +66,8 @@ Other Changes: In theory the buffer size should always account for a zero-terminator, but idioms such as using InputTextMultiline() with ImGuiInputTextFlags_ReadOnly to display a text blob are facilitated by allowing this. +- InputText: refactored internals to simplify and optimizing rendering of selection. + Very large selection (e.g. 1 MB) now take less overhead. - InputText: revert a change in 1.79 where pressing Down or PageDown on the last line of a multi-line buffer without a trailing carriage return would keep the cursor unmoved. We revert back to move to the end of line in this situation. diff --git a/imgui.cpp b/imgui.cpp index b66474b0a..5c0b11014 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -3074,6 +3074,7 @@ void ImGuiTextBuffer::appendfv(const char* fmt, va_list args) va_end(args_copy); } +IM_MSVC_RUNTIME_CHECKS_OFF void ImGuiTextIndex::append(const char* base, int old_size, int new_size) { IM_ASSERT(old_size >= 0 && new_size >= old_size && new_size >= EndOffset); @@ -3087,6 +3088,7 @@ void ImGuiTextIndex::append(const char* base, int old_size, int new_size) Offsets.push_back((int)(intptr_t)(p - base)); EndOffset = ImMax(EndOffset, new_size); } +IM_MSVC_RUNTIME_CHECKS_RESTORE //----------------------------------------------------------------------------- // [SECTION] ImGuiListClipper @@ -3414,6 +3416,13 @@ bool ImGuiListClipper::Step() return ret; } +// Generic helper, equivalent to old ImGui::CalcListClipping() but statelesss +void ImGui::CalcClipRectVisibleItemsY(const ImRect& clip_rect, const ImVec2& pos, float items_height, int* out_visible_start, int* out_visible_end) +{ + *out_visible_start = ImMax((int)((clip_rect.Min.y - pos.y) / items_height), 0); + *out_visible_end = ImMax((int)ImCeil((clip_rect.Max.y - pos.y) / items_height), *out_visible_start); +} + //----------------------------------------------------------------------------- // [SECTION] STYLING //----------------------------------------------------------------------------- @@ -4374,6 +4383,7 @@ void ImGui::Shutdown() g.ClipboardHandlerData.clear(); g.MenusIdSubmittedThisFrame.clear(); g.InputTextState.ClearFreeMemory(); + g.InputTextLineIndex.clear(); g.InputTextDeactivatedState.ClearFreeMemory(); g.SettingsWindows.clear(); @@ -4489,6 +4499,7 @@ void ImGui::GcCompactTransientMiscBuffers() ImGuiContext& g = *GImGui; g.ItemFlagsStack.clear(); g.GroupStack.clear(); + g.InputTextLineIndex.clear(); g.MultiSelectTempDataStacked = 0; g.MultiSelectTempData.clear_destruct(); TableGcCompactSettings(); diff --git a/imgui_internal.h b/imgui_internal.h index a206357b2..021a24d98 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -809,7 +809,7 @@ struct ImGuiTextIndex void clear() { Offsets.clear(); EndOffset = 0; } int size() { return Offsets.Size; } - const char* get_line_begin(const char* base, int n) { return base + Offsets[n]; } + const char* get_line_begin(const char* base, int n) { return base + (Offsets.Size != 0 ? Offsets[n] : 0); } const char* get_line_end(const char* base, int n) { return base + (n + 1 < Offsets.Size ? (Offsets[n + 1] - 1) : EndOffset); } void append(const char* base, int old_size, int new_size); }; @@ -2430,6 +2430,7 @@ struct ImGuiContext // Widget state ImGuiInputTextState InputTextState; + ImGuiTextIndex InputTextLineIndex; // Temporary storage ImGuiInputTextDeactivatedState InputTextDeactivatedState; ImFontBaked InputTextPasswordFontBackupBaked; ImFontFlags InputTextPasswordFontBackupFlags; @@ -3258,6 +3259,7 @@ namespace ImGui IMGUI_API float CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x); IMGUI_API void PushMultiItemsWidths(int components, float width_full); IMGUI_API void ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess, float width_min); + IMGUI_API void CalcClipRectVisibleItemsY(const ImRect& clip_rect, const ImVec2& pos, float items_height, int* out_visible_start, int* out_visible_end); // Parameter stacks (shared) IMGUI_API const ImGuiStyleVarInfo* GetStyleVarInfo(ImGuiStyleVar idx); diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index ebdee29b1..f506b111e 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -135,7 +135,6 @@ static const ImU64 IM_U64_MAX = (2ULL * 9223372036854775807LL + 1); // For InputTextEx() static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard = false); -static int InputTextCalcTextLenAndLineCount(ImGuiContext* ctx, const char* text_begin, const char** out_text_end, float wrap_width); static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, const char* text_end_display, const char* text_end, const char** out_remaining = NULL, ImVec2* out_offset = NULL, ImDrawTextFlags flags = 0); //------------------------------------------------------------------------- @@ -3938,46 +3937,6 @@ bool ImGui::InputTextWithHint(const char* label, const char* hint, char* buf, si return InputTextEx(label, hint, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data); } -// This is only used in the path where the multiline widget is inactive. -static int InputTextCalcTextLenAndLineCount(ImGuiContext* ctx, const char* text_begin, const char** out_text_end, float wrap_width) -{ - int line_count = 0; - const char* s = text_begin; - if (wrap_width == 0.0f) - { - while (true) - { - const char* s_eol = strchr(s, '\n'); - line_count++; - if (s_eol == NULL) - { - s = s + ImStrlen(s); - break; - } - s = s_eol + 1; - } - } - else - { - // FIXME-WORDWRAP, FIXME-OPT: This is very suboptimal. - // We basically want both text_end and text_size, they could more optimally be emitted from a RenderText call that uses word-wrapping. - ImGuiContext& g = *ctx; - ImFont* font = g.Font; - const char* text_end = text_begin + strlen(text_begin); - while (s < text_end) - { - s = ImFontCalcWordWrapPositionEx(font, g.FontSize, s, text_end, wrap_width, ImDrawTextFlags_WrapKeepBlanks); - s = (*s == '\n') ? s + 1 : s; - line_count++; - } - if (text_end > text_begin && text_end[-1] == '\n') - line_count++; - IM_ASSERT(s == text_end); - } - *out_text_end = s; - return line_count; -} - static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, const char* text_end_display, const char* text_end, const char** out_remaining, ImVec2* out_offset, ImDrawTextFlags flags) { ImGuiContext& g = *ctx; @@ -4555,6 +4514,83 @@ void ImGui::InputTextDeactivateHook(ImGuiID id) } } +static int* ImLowerBound(int* in_begin, int* in_end, int v) +{ + int* in_p = in_begin; + for (size_t count = (size_t)(in_end - in_p); count > 0; ) + { + size_t count2 = count >> 1; + int* mid = in_p + count2; + if (*mid < v) + { + in_p = ++mid; + count -= count2 + 1; + } + else + { + count = count2; + } + } + return in_p; +} + +// FIXME-WORDWRAP: Bundle some of this into ImGuiTextIndex and/or extract as a different tool? +// 'max_output_buffer_size' happens to be a meaningful optimization to avoid writing the full line_index when not necessarily needed (e.g. very large buffer, scrolled up, inactive) +static int InputTextLineIndexBuild(ImGuiInputTextFlags flags, ImGuiTextIndex* line_index, const char* buf, const char* buf_end, float wrap_width, int max_output_buffer_size) +{ + ImGuiContext& g = *GImGui; + int size = 0; + if (flags & ImGuiInputTextFlags_WordWrap) + { + for (const char* s = buf; s < buf_end; ) + { + if (size++ <= max_output_buffer_size) + line_index->Offsets.push_back((int)(s - buf)); + s = ImFontCalcWordWrapPositionEx(g.Font, g.FontSize, s, buf_end, wrap_width, ImDrawTextFlags_WrapKeepBlanks); + s = (*s == '\n') ? s + 1 : s; + } + } + else + { + for (const char* s = buf; s < buf_end; ) + { + if (size++ <= max_output_buffer_size) + line_index->Offsets.push_back((int)(s - buf)); + s = (const char*)ImMemchr(s, '\n', buf_end - s); + s = s ? s + 1 : buf_end; + } + } + if (size == 0) + { + line_index->Offsets.push_back(0); + size++; + } + if (buf_end > buf && buf_end[-1] == '\n' && size <= max_output_buffer_size) + { + line_index->Offsets.push_back((int)(buf_end - buf)); + size++; + } + return size; +} + +static ImVec2 InputTextLineIndexGetPosOffset(ImGuiContext& g, ImGuiInputTextState* state, ImGuiTextIndex* line_index, const char* buf, const char* buf_end, int cursor_n) +{ + const char* cursor_ptr = buf + cursor_n; + int* it_begin = line_index->Offsets.begin(); + int* it_end = line_index->Offsets.end(); + const int* it = ImLowerBound(it_begin, it_end, cursor_n); + if (it > it_begin) + if (it == it_end || *it != cursor_n || (cursor_ptr[-1] != '\n' && cursor_ptr[-1] != 0 && state != NULL && state->LastMoveDirectionLR == ImGuiDir_Right)) + it--; + + const int line_no = (it == it_begin) ? 0 : line_index->Offsets.index_from_ptr(it); + const char* line_start = line_index->get_line_begin(buf, line_no); + ImVec2 offset; + offset.x = InputTextCalcTextSize(&g, line_start, cursor_ptr, buf_end, NULL, NULL, ImDrawTextFlags_WrapKeepBlanks).x; + offset.y = (line_no + 1) * g.FontSize; + return offset; +} + // Edit a string of text // - buf_size account for the zero-terminator, so a buf_size of 6 can hold "Hello" but not "Hello!". // This is so we can easily call InputText() on static arrays using ARRAYSIZE() and to match @@ -5327,15 +5363,40 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ buf_display = hint; buf_display_end = hint + ImStrlen(hint); } + else + { + if (render_cursor || render_selection || g.ActiveId == id) + buf_display_end = buf_display + state->TextLen; //-V595 + else + buf_display_end = buf_display + ImStrlen(buf_display); // FIXME-OPT: For multi-line path this would optimally be folded into the InputTextLineIndex build below. + } + + // Calculate visibility + int line_visible_n0 = 0, line_visible_n1 = 1; + if (is_multiline) + CalcClipRectVisibleItemsY(clip_rect, draw_pos, g.FontSize, &line_visible_n0, &line_visible_n1); + + // Build line index for easy data access (makes code below simpler and faster) + ImGuiTextIndex* line_index = &g.InputTextLineIndex; + line_index->Offsets.resize(0); + line_index->EndOffset = (int)(buf_display_end - buf_display); + int line_count = 1; + if (is_multiline) + line_count = InputTextLineIndexBuild(flags, line_index, buf_display, buf_display_end, wrap_width, (render_cursor && state && state->CursorFollow) ? INT_MAX : line_visible_n1 + 1); + line_visible_n1 = ImMin(line_visible_n1, line_count); + + // Store text height (we don't need width) + text_size = ImVec2(inner_size.x, line_count * g.FontSize); + //GetForegroundDrawList()->AddRect(draw_pos + ImVec2(0, line_visible_n0 * g.FontSize), draw_pos + ImVec2(frame_size.x, line_visible_n1 * g.FontSize), IM_COL32(255, 0, 0, 255)); + + // Calculate blinking cursor position + const ImVec2 cursor_offset = render_cursor && state ? InputTextLineIndexGetPosOffset(g, state, line_index, buf_display, buf_display_end, state->Stb->cursor) : ImVec2(0.0f, 0.0f); + ImVec2 draw_scroll; // Render text. We currently only render selection when the widget is active or while scrolling. - // FIXME: This is one of the messiest piece of the whole codebase. + const ImU32 text_col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text); if (render_cursor || render_selection) { - IM_ASSERT(state != NULL); - if (!is_displaying_hint) - buf_display_end = buf_display + state->TextLen; - // Render text (with cursor and selection) // This is going to be messy. We need to: // - Display the text (this alone can be more easily clipped) @@ -5343,85 +5404,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // - Measure text height (for scrollbar) // We are attempting to do most of that in **one main pass** to minimize the computation cost (non-negligible for large amount of text) + 2nd pass for selection rendering (we could merge them by an extra refactoring effort) // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8. - const char* text_begin = buf_display; - const char* text_end = text_begin + state->TextLen; - ImVec2 cursor_offset; - float select_start_offset_y = 0.0f; // Offset of beginning of non-wrapped line for selection. - - { - // Find lines numbers straddling cursor and selection min position - int cursor_line_no = render_cursor ? -1 : -1000; - int selmin_line_no = render_selection ? -1 : -1000; - const char* cursor_ptr = render_cursor ? text_begin + state->Stb->cursor : NULL; - const char* selmin_ptr = render_selection ? text_begin + ImMin(state->Stb->select_start, state->Stb->select_end) : NULL; - const char* cursor_line_start = NULL; - const char* selmin_line_start = NULL; - bool cursor_straddle_word_wrap = false; - - // Count lines and find line number for cursor and selection ends - // FIXME: Switch to zero-based index to reduce confusion. - int line_count = 1; - if (is_multiline) - { - if (!is_wordwrap) - { - for (const char* s = text_begin; (s = (const char*)ImMemchr(s, '\n', (size_t)(text_end - s))) != NULL; s++) - { - if (cursor_line_no == -1 && s >= cursor_ptr) { cursor_line_no = line_count; } - if (selmin_line_no == -1 && s >= selmin_ptr) { selmin_line_no = line_count; } - line_count++; - } - } - else - { - bool is_start_of_non_wrapped_line = true; - int line_count_for_non_wrapped_line = 1; - for (const char* s = text_begin; s < text_end; s = (*s == '\n') ? s + 1 : s) - { - const char* s_eol = ImFontCalcWordWrapPositionEx(g.Font, g.FontSize, s, text_end, wrap_width, ImDrawTextFlags_WrapKeepBlanks); - const char* s_prev = s; - s = s_eol; - if (cursor_line_no == -1 && s >= cursor_ptr) { cursor_line_start = s_prev; cursor_line_no = line_count; } - if (selmin_line_no == -1 && s >= selmin_ptr) { selmin_line_start = s_prev; selmin_line_no = line_count_for_non_wrapped_line; } - if (s == cursor_ptr && *cursor_ptr != '\n' && *cursor_ptr != 0) - cursor_straddle_word_wrap = true; - is_start_of_non_wrapped_line = (*s == '\n'); - line_count++; - if (is_start_of_non_wrapped_line) - line_count_for_non_wrapped_line = line_count; - } - } - //IMGUI_DEBUG_LOG("%d\n", selmin_line_no); - } - if (cursor_line_no == -1) - cursor_line_no = line_count; - if (cursor_line_start == NULL) - cursor_line_start = ImStrbol(cursor_ptr, text_begin); - if (selmin_line_no == -1) - selmin_line_no = line_count; - if (selmin_line_start == NULL) - selmin_line_start = ImStrbol(cursor_ptr, text_begin); - - // Calculate 2d position by finding the beginning of the line and measuring distance - if (render_cursor) - { - cursor_offset.x = InputTextCalcTextSize(&g, cursor_line_start, cursor_ptr, text_end, NULL, NULL, ImDrawTextFlags_WrapKeepBlanks).x; - cursor_offset.y = cursor_line_no * g.FontSize; - if (is_multiline && cursor_straddle_word_wrap && state->LastMoveDirectionLR == ImGuiDir_Left) - cursor_offset = ImVec2(0.0f, cursor_offset.y + g.FontSize); - } - if (selmin_line_no >= 0) - select_start_offset_y = selmin_line_no * g.FontSize; - - // Store text height (note that we haven't calculated text width at all, see GitHub issues #383, #1224) - if (is_multiline) - { - if (is_wordwrap && text_end > text_begin && text_end[-1] != '\n') - line_count--; - text_size = ImVec2(inner_size.x, line_count * g.FontSize); - } - state->LineCount = line_count; - } + IM_ASSERT(state != NULL); + state->LineCount = line_count; // Scroll float new_scroll_y = scroll_y; @@ -5447,7 +5431,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ { // Test if cursor is vertically visible if (cursor_offset.y - g.FontSize < scroll_y) - new_scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize); + new_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) new_scroll_y = cursor_offset.y - inner_size.y + style.FramePadding.y * 2.0f; } @@ -5466,57 +5450,60 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ scroll_y = ImClamp(new_scroll_y, 0.0f, scroll_max_y); draw_pos.y += (draw_window->Scroll.y - scroll_y); // Manipulate cursor pos immediately avoid a frame of lag draw_window->Scroll.y = scroll_y; + CalcClipRectVisibleItemsY(clip_rect, draw_pos, g.FontSize, &line_visible_n0, &line_visible_n1); + line_visible_n1 = ImMin(line_visible_n1, line_count); } // Draw selection - const ImVec2 draw_scroll = ImVec2(state->Scroll.x, 0.0f); + draw_scroll.x = state->Scroll.x; if (render_selection) { - const char* text_selected_begin = text_begin + ImMin(state->Stb->select_start, state->Stb->select_end); - const char* text_selected_end = text_begin + ImMax(state->Stb->select_start, state->Stb->select_end); + const ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg, render_cursor ? 1.0f : 0.6f); // FIXME: current code flow mandate that render_cursor is always true here, we are leaving the transparent one for tests. + const 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. + const float bg_offy_dn = is_multiline ? 0.0f : 2.0f; + const float bg_eol_width = IM_TRUNC(g.FontBaked->GetCharAdvance((ImWchar)' ') * 0.50f); // So we can see selected empty lines - ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg, render_cursor ? 1.0f : 0.6f); // FIXME: current code flow mandate that render_cursor is always true here, we are leaving the transparent one for tests. - 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_min_width = IM_TRUNC(g.FontBaked->GetCharAdvance((ImWchar)' ') * 0.50f); // So we can see selected empty lines - ImVec2 rect_pos = draw_pos - draw_scroll; - rect_pos.y += select_start_offset_y; - for (const char* p = ImStrbol(text_selected_begin, text_begin); p < text_selected_end; rect_pos.y += g.FontSize) + const char* text_selected_begin = buf_display + ImMin(state->Stb->select_start, state->Stb->select_end); + const char* text_selected_end = buf_display + ImMax(state->Stb->select_start, state->Stb->select_end); + for (int line_n = line_visible_n0; line_n < line_visible_n1; line_n++) { - if (rect_pos.y > clip_rect.Max.y + g.FontSize) - break; - const char* p_eol = is_wordwrap ? ImFontCalcWordWrapPositionEx(g.Font, g.FontSize, p, text_end, wrap_width, ImDrawTextFlags_WrapKeepBlanks) : (const char*)ImMemchr((void*)p, '\n', text_selected_end - p); - if (p_eol == NULL) - p_eol = text_selected_end; - const char* p_next = is_wordwrap ? (*p_eol == '\n' ? p_eol + 1 : p_eol) : (p_eol + 1); - if (rect_pos.y >= clip_rect.Min.y) - { - const char* line_selected_begin = (text_selected_begin > p) ? text_selected_begin : p; - const char* line_selected_end = (text_selected_end < p_eol) ? text_selected_end : p_eol; - if ((*p_eol == '\n' && text_selected_begin <= p_eol) || (text_selected_begin < p_eol)) - { - ImVec2 rect_offset = CalcTextSize(p, line_selected_begin); - ImVec2 rect_size = CalcTextSize(line_selected_begin, line_selected_end); - rect_size.x = ImMax(rect_size.x, bg_min_width); // So we can see selected empty lines - ImRect rect(rect_pos + ImVec2(rect_offset.x, bg_offy_up - g.FontSize), rect_pos + ImVec2(rect_offset.x + rect_size.x, bg_offy_dn)); - rect.ClipWith(clip_rect); - if (rect.Overlaps(clip_rect)) - draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color); - } - } - p = p_next; + const char* p = line_index->get_line_begin(buf_display, line_n); + const char* p_eol = line_index->get_line_end(buf_display, line_n); + const bool p_eol_is_wrap = (p_eol < buf_display_end && *p_eol != '\n'); + if (p_eol_is_wrap) + p_eol++; + const char* line_selected_begin = (text_selected_begin > p) ? text_selected_begin : p; + const char* line_selected_end = (text_selected_end < p_eol) ? text_selected_end : p_eol; + + float rect_width = 0.0f; + if (line_selected_begin < line_selected_end) + rect_width += CalcTextSize(line_selected_begin, line_selected_end).x; + if (text_selected_begin <= p_eol && text_selected_end > p_eol && !p_eol_is_wrap) + rect_width += bg_eol_width; // So we can see selected empty lines + if (rect_width == 0.0f) + continue; + + ImRect rect; + rect.Min.x = draw_pos.x - draw_scroll.x + CalcTextSize(p, line_selected_begin).x; + rect.Min.y = draw_pos.y - draw_scroll.y + line_n * g.FontSize; + rect.Max.x = rect.Min.x + rect_width; + rect.Max.y = rect.Min.y + bg_offy_dn + g.FontSize; + rect.Min.y -= bg_offy_up; + rect.ClipWith(clip_rect); + draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color); } } + // Render text // 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. // FIXME-OPT: Multiline could submit a smaller amount of contents to AddText() since we already iterated through it. - if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length) - { - ImU32 col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text); - if (col & IM_COL32_A_MASK) - g.Font->RenderText(draw_window->DrawList, g.FontSize, draw_pos - draw_scroll, col, clip_rect.AsVec4(), buf_display, buf_display_end, wrap_width, ImDrawTextFlags_WrapKeepBlanks); - //draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos - draw_scroll, col, buf_display, buf_display_end, wrap_width, is_multiline ? NULL : &clip_rect.AsVec4()); - } + if ((is_multiline || (buf_display_end - buf_display) < buf_display_max_length) && (text_col & IM_COL32_A_MASK) && (line_visible_n0 < line_visible_n1)) + g.Font->RenderText(draw_window->DrawList, g.FontSize, + draw_pos - draw_scroll + ImVec2(0.0f, line_visible_n0 * g.FontSize), + text_col, clip_rect.AsVec4(), + line_index->get_line_begin(buf_display, line_visible_n0), + line_index->get_line_end(buf_display, line_visible_n1 - 1), + wrap_width, ImDrawTextFlags_WrapKeepBlanks); // Draw blinking cursor if (render_cursor) @@ -5544,26 +5531,19 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } else { + // Find render position for right alignment (single-line only) + if (flags & ImGuiInputTextFlags_ElideLeft) + draw_pos.x = ImMin(draw_pos.x, frame_bb.Max.x - CalcTextSize(buf_display, NULL).x - style.FramePadding.x); + // Render text only (no selection, no cursor) - if (is_multiline) - text_size = ImVec2(inner_size.x, InputTextCalcTextLenAndLineCount(&g, buf_display, &buf_display_end, wrap_width) * g.FontSize); // We don't need width - else if (!is_displaying_hint && g.ActiveId == id) - buf_display_end = buf_display + state->TextLen; - else if (!is_displaying_hint) - buf_display_end = buf_display + ImStrlen(buf_display); - - if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length) - { - // Find render position for right alignment - if (flags & ImGuiInputTextFlags_ElideLeft) - draw_pos.x = ImMin(draw_pos.x, frame_bb.Max.x - CalcTextSize(buf_display, NULL).x - style.FramePadding.x); - - const ImVec2 draw_scroll = /*state ? ImVec2(state->Scroll.x, 0.0f) :*/ ImVec2(0.0f, 0.0f); // Preserve scroll when inactive? - ImU32 col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text); - if (col & IM_COL32_A_MASK) - g.Font->RenderText(draw_window->DrawList, g.FontSize, draw_pos - draw_scroll, col, clip_rect.AsVec4(), buf_display, buf_display_end, wrap_width, ImDrawTextFlags_WrapKeepBlanks); - //draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos - draw_scroll, col, buf_display, buf_display_end, wrap_width, is_multiline ? NULL : &clip_rect.AsVec4()); - } + //draw_scroll.x = state->Scroll.x; // Preserve scroll when inactive? + if ((is_multiline || (buf_display_end - buf_display) < buf_display_max_length) && (text_col & IM_COL32_A_MASK) && (line_visible_n0 < line_visible_n1)) + g.Font->RenderText(draw_window->DrawList, g.FontSize, + draw_pos - draw_scroll + ImVec2(0.0f, line_visible_n0 * g.FontSize), + text_col, clip_rect.AsVec4(), + line_index->get_line_begin(buf_display, line_visible_n0), + line_index->get_line_end(buf_display, line_visible_n1 - 1), + wrap_width, ImDrawTextFlags_WrapKeepBlanks); } if (is_password && !is_displaying_hint)