Skip to content

Commit

Permalink
InputTextMultiline() painfully merged most computation passes into on…
Browse files Browse the repository at this point in the history
…e, better clipping, much faster for large text (#200)
  • Loading branch information
ocornut committed Jun 19, 2015
1 parent 84987ac commit 73db855
Showing 1 changed file with 80 additions and 26 deletions.
106 changes: 80 additions & 26 deletions imgui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -551,8 +551,9 @@ static void CloseInactivePopups();
// Helpers: String
static int ImStricmp(const char* str1, const char* str2);
static int ImStrnicmp(const char* str1, const char* str2, int count);
static char* ImStrdup(const char *str);
static char* ImStrdup(const char* str);
static size_t ImStrlenW(const ImWchar* str);
static const ImWchar* ImStrbolW(const ImWchar* buf_mid_line, const ImWchar* buf_begin); // Find beginning-of-line
static const char* ImStristr(const char* haystack, const char* needle, const char* needle_end);
static size_t ImFormatString(char* buf, size_t buf_size, const char* fmt, ...);
static size_t ImFormatStringV(char* buf, size_t buf_size, const char* fmt, va_list args);
Expand Down Expand Up @@ -786,6 +787,13 @@ static size_t ImStrlenW(const ImWchar* str)
return n;
}

static const ImWchar* ImStrbolW(const ImWchar* buf_mid_line, const ImWchar* buf_begin) // find beginning-of-line
{
while (buf_mid_line > buf_begin && buf_mid_line[-1] != '\n')
buf_mid_line--;
return buf_mid_line;
}

static const char* ImStristr(const char* haystack, const char* needle, const char* needle_end)
{
if (!needle_end)
Expand Down Expand Up @@ -7102,26 +7110,64 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV
ImVec2 text_size(0.f, 0.f);
if (g.ActiveId == id || (edit_state.Id == id && is_multiline && g.ActiveId == draw_window->GetID("#SCROLLY")))
{
ImVec2 render_scroll = ImVec2((edit_state.Id == id) ? edit_state.ScrollX : 0.0f, 0.0f);
edit_state.CursorAnim += g.IO.DeltaTime;

// 1. Display the text (this can be more easily clipped)
// 2. Handle scrolling, highlight selection, display cursor: those all requires some form of 1d->2d cursor position calculation, which we will try to merge to minimize the cost.
ImVec2 cursor_offset;
InputTextCalcTextSizeW(edit_state.Text.begin(), edit_state.Text.begin() + edit_state.StbState.cursor, NULL, &cursor_offset);
// We need to:
// - Display the text (this can be more easily clipped)
// - Handle scrolling, highlight selection, display cursor (those all requires some form of 1d->2d cursor position calculation)
// - 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)
const ImWchar* text_begin = edit_state.Text.begin();
const ImWchar* text_end = text_begin + edit_state.CurLenW;
ImVec2 cursor_offset, select_start_offset;

{
// Count lines + find lines numbers of cursor and select_start
int matches_remaining = 0;
int matches_line_no[2] = { -1, -999 };
const ImWchar* matches_ptr[2];
matches_ptr[0] = text_begin + edit_state.StbState.cursor; matches_remaining++;
if (edit_state.StbState.select_start != edit_state.StbState.select_end)
{
matches_ptr[1] = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end);
matches_line_no[1] = -1;
matches_remaining++;
}
matches_remaining += is_multiline ? 1 : 0; // So that we never exit the loop until all lines are counted.

if (is_multiline)
text_size = InputTextCalcTextSizeW(edit_state.Text.begin(), edit_state.Text.begin() + edit_state.CurLenW);
int line_count = 0;
for (const ImWchar* s = text_begin; s < text_end+1; s++)
if ((*s) == '\n' || s == text_end)
{
line_count++;
if (matches_line_no[0] == -1 && s >= matches_ptr[0]) { matches_line_no[0] = line_count; if (--matches_remaining <= 0) break; }
if (matches_line_no[1] == -1 && s >= matches_ptr[1]) { matches_line_no[1] = line_count; if (--matches_remaining <= 0) break; }
}

// Calculate 2d position
IM_ASSERT(matches_line_no[0] != -1);
cursor_offset.x = InputTextCalcTextSizeW(ImStrbolW(matches_ptr[0], text_begin), matches_ptr[0]).x;
cursor_offset.y = matches_line_no[0] * g.FontSize;
if (matches_line_no[1] >= 0)
{
select_start_offset.x = InputTextCalcTextSizeW(ImStrbolW(matches_ptr[1], text_begin), matches_ptr[1]).x;
select_start_offset.y = matches_line_no[1] * g.FontSize;
}

// Calculate text height
if (is_multiline)
text_size = ImVec2(size.x, line_count * g.FontSize);
}

// Scroll
if (edit_state.CursorFollow)
{
// Horizontal scroll in chunks of quarter width
const float scroll_increment_x = size.x * 0.25f;
if (cursor_offset.x < edit_state.ScrollX)
render_scroll.x = edit_state.ScrollX = ImMax(0.0f, cursor_offset.x - scroll_increment_x);
edit_state.ScrollX = ImMax(0.0f, cursor_offset.x - scroll_increment_x);
else if (cursor_offset.x - size.x >= edit_state.ScrollX)
render_scroll.x = edit_state.ScrollX = cursor_offset.x - size.x + scroll_increment_x;
edit_state.ScrollX = cursor_offset.x - size.x + scroll_increment_x;

// Vertical scroll
if (is_multiline)
Expand All @@ -7137,38 +7183,46 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV
}
}
edit_state.CursorFollow = false;
ImVec2 render_scroll = ImVec2(edit_state.ScrollX, 0.0f);

// Draw selection
int select_begin_idx = edit_state.StbState.select_start;
int select_end_idx = edit_state.StbState.select_end;
if (select_begin_idx != select_end_idx)
if (edit_state.StbState.select_start != edit_state.StbState.select_end)
{
// FIXME-OPT
ImWchar* text_selected_begin = edit_state.Text.begin() + ImMin(select_begin_idx,select_end_idx);
ImWchar* text_selected_end = edit_state.Text.begin() + ImMax(select_begin_idx,select_end_idx);
ImVec2 rect_pos;
InputTextCalcTextSizeW(edit_state.Text.begin(), text_selected_begin, NULL, &rect_pos);
const ImWchar* text_selected_begin = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end);
const ImWchar* text_selected_end = text_begin + ImMax(edit_state.StbState.select_start, edit_state.StbState.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.
float bg_offy_dn = is_multiline ? 0.0f : 2.0f;
ImU32 bg_color = draw_window->Color(ImGuiCol_TextSelectedBg);
ImVec2 rect_pos = render_pos + select_start_offset - render_scroll;
for (const ImWchar* p = text_selected_begin; p < text_selected_end; )
{
ImVec2 rect_size = InputTextCalcTextSizeW(p, text_selected_end, &p, NULL, true);
if (rect_size.x <= 0.0f) rect_size.x = (float)(int)(g.Font->GetCharAdvance((unsigned short)' ') * 0.50f); // So we can see selected empty lines
ImRect rect(render_pos - render_scroll + rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), render_pos - render_scroll + rect_pos + ImVec2(rect_size.x, bg_offy_dn));
rect.Clip(clip_rect);
if (rect.Overlaps(clip_rect))
draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color);
rect_pos.x = 0.0f;
if (rect_pos.y > clip_rect.w + g.FontSize)
break;
if (rect_pos.y < clip_rect.y)
{
while (p < text_selected_end)
if (*p++ == '\n')
break;
}
else
{
ImVec2 rect_size = InputTextCalcTextSizeW(p, text_selected_end, &p, NULL, true);
if (rect_size.x <= 0.0f) rect_size.x = (float)(int)(g.Font->GetCharAdvance((unsigned short)' ') * 0.50f); // So we can see selected empty lines
ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos +ImVec2(rect_size.x, bg_offy_dn));
rect.Clip(clip_rect);
if (rect.Overlaps(clip_rect))
draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color);
}
rect_pos.x = render_pos.x - render_scroll.x;
rect_pos.y += g.FontSize;
}
}

draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos - render_scroll, draw_window->Color(ImGuiCol_Text), buf, buf+edit_state.CurLenA, 0.0f, is_multiline ? NULL : &clip_rect);

// Draw blinking cursor
ImVec2 cursor_screen_pos = render_pos + cursor_offset - ImVec2(edit_state.ScrollX, 0.0f);
ImVec2 cursor_screen_pos = render_pos + cursor_offset - render_scroll;
if (g.InputTextState.CursorIsVisible())
draw_window->DrawList->AddLine(cursor_screen_pos + ImVec2(0,-g.FontSize+1), cursor_screen_pos + ImVec2(0,-1), window->Color(ImGuiCol_Text));

Expand Down

0 comments on commit 73db855

Please sign in to comment.