InputText: Added a line index. Refactored cursor and selection rendering, now simpler, easier to reason about, and faster. (#3237, #952, #1062, #7363)

This commit is contained in:
ocornut
2025-09-11 19:05:26 +02:00
parent 67085d732a
commit 1e52e7b90c
4 changed files with 180 additions and 185 deletions

View File

@@ -66,6 +66,8 @@ Other Changes:
In theory the buffer size should always account for a zero-terminator, but idioms In theory the buffer size should always account for a zero-terminator, but idioms
such as using InputTextMultiline() with ImGuiInputTextFlags_ReadOnly to display such as using InputTextMultiline() with ImGuiInputTextFlags_ReadOnly to display
a text blob are facilitated by allowing this. 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 - 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 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. unmoved. We revert back to move to the end of line in this situation.

View File

@@ -3074,6 +3074,7 @@ void ImGuiTextBuffer::appendfv(const char* fmt, va_list args)
va_end(args_copy); va_end(args_copy);
} }
IM_MSVC_RUNTIME_CHECKS_OFF
void ImGuiTextIndex::append(const char* base, int old_size, int new_size) void ImGuiTextIndex::append(const char* base, int old_size, int new_size)
{ {
IM_ASSERT(old_size >= 0 && new_size >= old_size && new_size >= EndOffset); 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)); Offsets.push_back((int)(intptr_t)(p - base));
EndOffset = ImMax(EndOffset, new_size); EndOffset = ImMax(EndOffset, new_size);
} }
IM_MSVC_RUNTIME_CHECKS_RESTORE
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// [SECTION] ImGuiListClipper // [SECTION] ImGuiListClipper
@@ -3414,6 +3416,13 @@ bool ImGuiListClipper::Step()
return ret; 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 // [SECTION] STYLING
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@@ -4374,6 +4383,7 @@ void ImGui::Shutdown()
g.ClipboardHandlerData.clear(); g.ClipboardHandlerData.clear();
g.MenusIdSubmittedThisFrame.clear(); g.MenusIdSubmittedThisFrame.clear();
g.InputTextState.ClearFreeMemory(); g.InputTextState.ClearFreeMemory();
g.InputTextLineIndex.clear();
g.InputTextDeactivatedState.ClearFreeMemory(); g.InputTextDeactivatedState.ClearFreeMemory();
g.SettingsWindows.clear(); g.SettingsWindows.clear();
@@ -4489,6 +4499,7 @@ void ImGui::GcCompactTransientMiscBuffers()
ImGuiContext& g = *GImGui; ImGuiContext& g = *GImGui;
g.ItemFlagsStack.clear(); g.ItemFlagsStack.clear();
g.GroupStack.clear(); g.GroupStack.clear();
g.InputTextLineIndex.clear();
g.MultiSelectTempDataStacked = 0; g.MultiSelectTempDataStacked = 0;
g.MultiSelectTempData.clear_destruct(); g.MultiSelectTempData.clear_destruct();
TableGcCompactSettings(); TableGcCompactSettings();

View File

@@ -809,7 +809,7 @@ struct ImGuiTextIndex
void clear() { Offsets.clear(); EndOffset = 0; } void clear() { Offsets.clear(); EndOffset = 0; }
int size() { return Offsets.Size; } 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); } 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); void append(const char* base, int old_size, int new_size);
}; };
@@ -2430,6 +2430,7 @@ struct ImGuiContext
// Widget state // Widget state
ImGuiInputTextState InputTextState; ImGuiInputTextState InputTextState;
ImGuiTextIndex InputTextLineIndex; // Temporary storage
ImGuiInputTextDeactivatedState InputTextDeactivatedState; ImGuiInputTextDeactivatedState InputTextDeactivatedState;
ImFontBaked InputTextPasswordFontBackupBaked; ImFontBaked InputTextPasswordFontBackupBaked;
ImFontFlags InputTextPasswordFontBackupFlags; ImFontFlags InputTextPasswordFontBackupFlags;
@@ -3258,6 +3259,7 @@ namespace ImGui
IMGUI_API float CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x); IMGUI_API float CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x);
IMGUI_API void PushMultiItemsWidths(int components, float width_full); 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 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) // Parameter stacks (shared)
IMGUI_API const ImGuiStyleVarInfo* GetStyleVarInfo(ImGuiStyleVar idx); IMGUI_API const ImGuiStyleVarInfo* GetStyleVarInfo(ImGuiStyleVar idx);

View File

@@ -135,7 +135,6 @@ static const ImU64 IM_U64_MAX = (2ULL * 9223372036854775807LL + 1);
// For InputTextEx() // 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 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); 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); 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) 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; 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 // Edit a string of text
// - buf_size account for the zero-terminator, so a buf_size of 6 can hold "Hello" but not "Hello!". // - 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 // 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 = hint;
buf_display_end = hint + ImStrlen(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. // 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) 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) // Render text (with cursor and selection)
// 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)
@@ -5343,85 +5404,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
// - 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.
const char* text_begin = buf_display; IM_ASSERT(state != NULL);
const char* text_end = text_begin + state->TextLen; state->LineCount = line_count;
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;
}
// Scroll // Scroll
float new_scroll_y = scroll_y; 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 // Test if cursor is vertically visible
if (cursor_offset.y - g.FontSize < scroll_y) 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) 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; 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); 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_pos.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;
CalcClipRectVisibleItemsY(clip_rect, draw_pos, g.FontSize, &line_visible_n0, &line_visible_n1);
line_visible_n1 = ImMin(line_visible_n1, line_count);
} }
// Draw selection // Draw selection
const ImVec2 draw_scroll = ImVec2(state->Scroll.x, 0.0f); draw_scroll.x = state->Scroll.x;
if (render_selection) if (render_selection)
{ {
const char* text_selected_begin = text_begin + ImMin(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 char* text_selected_end = text_begin + ImMax(state->Stb->select_start, state->Stb->select_end); 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. const char* text_selected_begin = buf_display + ImMin(state->Stb->select_start, state->Stb->select_end);
float bg_offy_up = is_multiline ? 0.0f : -1.0f; // FIXME: those offsets should be part of the style? they don't play so well with multi-line selection. const char* text_selected_end = buf_display + ImMax(state->Stb->select_start, state->Stb->select_end);
float bg_offy_dn = is_multiline ? 0.0f : 2.0f; for (int line_n = line_visible_n0; line_n < line_visible_n1; line_n++)
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)
{ {
if (rect_pos.y > clip_rect.Max.y + g.FontSize) const char* p = line_index->get_line_begin(buf_display, line_n);
break; const char* p_eol = line_index->get_line_end(buf_display, line_n);
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); const bool p_eol_is_wrap = (p_eol < buf_display_end && *p_eol != '\n');
if (p_eol == NULL) if (p_eol_is_wrap)
p_eol = text_selected_end; p_eol++;
const char* p_next = is_wordwrap ? (*p_eol == '\n' ? p_eol + 1 : p_eol) : (p_eol + 1); const char* line_selected_begin = (text_selected_begin > p) ? text_selected_begin : p;
if (rect_pos.y >= clip_rect.Min.y) const char* line_selected_end = (text_selected_end < p_eol) ? text_selected_end : p_eol;
{
const char* line_selected_begin = (text_selected_begin > p) ? text_selected_begin : p; float rect_width = 0.0f;
const char* line_selected_end = (text_selected_end < p_eol) ? text_selected_end : p_eol; if (line_selected_begin < line_selected_end)
if ((*p_eol == '\n' && text_selected_begin <= p_eol) || (text_selected_begin < p_eol)) 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)
ImVec2 rect_offset = CalcTextSize(p, line_selected_begin); rect_width += bg_eol_width; // So we can see selected empty lines
ImVec2 rect_size = CalcTextSize(line_selected_begin, line_selected_end); if (rect_width == 0.0f)
rect_size.x = ImMax(rect_size.x, bg_min_width); // So we can see selected empty lines continue;
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); ImRect rect;
if (rect.Overlaps(clip_rect)) rect.Min.x = draw_pos.x - draw_scroll.x + CalcTextSize(p, line_selected_begin).x;
draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color); 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;
p = p_next; 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. // 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. // 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) 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,
ImU32 col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text); draw_pos - draw_scroll + ImVec2(0.0f, line_visible_n0 * g.FontSize),
if (col & IM_COL32_A_MASK) text_col, clip_rect.AsVec4(),
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); line_index->get_line_begin(buf_display, line_visible_n0),
//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()); line_index->get_line_end(buf_display, line_visible_n1 - 1),
} wrap_width, ImDrawTextFlags_WrapKeepBlanks);
// Draw blinking cursor // Draw blinking cursor
if (render_cursor) if (render_cursor)
@@ -5544,26 +5531,19 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
} }
else 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) // Render text only (no selection, no cursor)
if (is_multiline) //draw_scroll.x = state->Scroll.x; // Preserve scroll when inactive?
text_size = ImVec2(inner_size.x, InputTextCalcTextLenAndLineCount(&g, buf_display, &buf_display_end, wrap_width) * g.FontSize); // We don't need width if ((is_multiline || (buf_display_end - buf_display) < buf_display_max_length) && (text_col & IM_COL32_A_MASK) && (line_visible_n0 < line_visible_n1))
else if (!is_displaying_hint && g.ActiveId == id) g.Font->RenderText(draw_window->DrawList, g.FontSize,
buf_display_end = buf_display + state->TextLen; draw_pos - draw_scroll + ImVec2(0.0f, line_visible_n0 * g.FontSize),
else if (!is_displaying_hint) text_col, clip_rect.AsVec4(),
buf_display_end = buf_display + ImStrlen(buf_display); line_index->get_line_begin(buf_display, line_visible_n0),
line_index->get_line_end(buf_display, line_visible_n1 - 1),
if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length) wrap_width, ImDrawTextFlags_WrapKeepBlanks);
{
// 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());
}
} }
if (is_password && !is_displaying_hint) if (is_password && !is_displaying_hint)