ソースを参照

InputTextMultiline() painfully merged most computation passes into one, better clipping, much faster for large text (#200)

ocornut 10 年 前
コミット
73db855c77
1 ファイル変更80 行追加26 行削除
  1. 80 26
      imgui.cpp

+ 80 - 26
imgui.cpp

@@ -551,8 +551,9 @@ static void         CloseInactivePopups();
 // Helpers: String
 // Helpers: String
 static int          ImStricmp(const char* str1, const char* str2);
 static int          ImStricmp(const char* str1, const char* str2);
 static int          ImStrnicmp(const char* str1, const char* str2, int count);
 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 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 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       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);
 static size_t       ImFormatStringV(char* buf, size_t buf_size, const char* fmt, va_list args);
@@ -786,6 +787,13 @@ static size_t ImStrlenW(const ImWchar* str)
     return n;
     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)
 static const char* ImStristr(const char* haystack, const char* needle, const char* needle_end)
 {
 {
     if (!needle_end)
     if (!needle_end)
@@ -7102,16 +7110,54 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV
     ImVec2 text_size(0.f, 0.f);
     ImVec2 text_size(0.f, 0.f);
     if (g.ActiveId == id || (edit_state.Id == id && is_multiline && g.ActiveId == draw_window->GetID("#SCROLLY")))
     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;
         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
         // Scroll
         if (edit_state.CursorFollow)
         if (edit_state.CursorFollow)
@@ -7119,9 +7165,9 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV
             // Horizontal scroll in chunks of quarter width
             // Horizontal scroll in chunks of quarter width
             const float scroll_increment_x = size.x * 0.25f;
             const float scroll_increment_x = size.x * 0.25f;
             if (cursor_offset.x < edit_state.ScrollX)
             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)
             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
             // Vertical scroll
             if (is_multiline)
             if (is_multiline)
@@ -7137,30 +7183,38 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV
             }
             }
         }
         }
         edit_state.CursorFollow = false;
         edit_state.CursorFollow = false;
+        ImVec2 render_scroll = ImVec2(edit_state.ScrollX, 0.0f);
 
 
         // Draw selection
         // 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_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 = draw_window->Color(ImGuiCol_TextSelectedBg);
             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; )
             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;
                 rect_pos.y += g.FontSize;
             }
             }
         }
         }
@@ -7168,7 +7222,7 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV
         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_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
         // 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())
         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));
             draw_window->DrawList->AddLine(cursor_screen_pos + ImVec2(0,-g.FontSize+1), cursor_screen_pos + ImVec2(0,-1), window->Color(ImGuiCol_Text));