From 8bcb0cda7359940c77724a2769eeb204d591bf2c Mon Sep 17 00:00:00 2001 From: Webster Sheets Date: Mon, 21 Jun 2021 02:46:21 -0400 Subject: [PATCH] Add text alignment support, fix wrapping behavior This refactors the semantics of Font::CalcWordWrapA - it had several subtle issues that rendered the line_width unsuitable for external use. Now returns the location of the first line break, and the length of the line including any leading whitespace. This PR refactors the implementation of and implements RenderText and CalcTextSize wrapping in terms of CalcWordWrapPositionA. --- imgui.cpp | 8 +++---- imgui.h | 6 +++--- imgui_draw.cpp | 55 +++++++++++++++++++++++++++++++++-------------- imgui_internal.h | 2 +- imgui_widgets.cpp | 12 +++++++---- 5 files changed, 55 insertions(+), 28 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 9330f28f..ac996853 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -3296,7 +3296,7 @@ void ImGui::RenderText(ImVec2 pos, const char* text, const char* text_end, bool } } -void ImGui::RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end, float wrap_width) +void ImGui::RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end, float wrap_width, float text_alignment) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; @@ -3306,7 +3306,7 @@ void ImGui::RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end if (text != text_end) { - window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_end, wrap_width); + window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_end, wrap_width, text_alignment); if (g.LogEnabled) LogRenderedText(&pos, text, text_end); } @@ -3337,11 +3337,11 @@ void ImGui::RenderTextClippedEx(ImDrawList* draw_list, const ImVec2& pos_min, co if (need_clipping) { ImVec4 fine_clip_rect(clip_min->x, clip_min->y, clip_max->x, clip_max->y); - draw_list->AddText(NULL, 0.0f, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, &fine_clip_rect); + draw_list->AddText(NULL, 0.0f, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, 0.0f, &fine_clip_rect); } else { - draw_list->AddText(NULL, 0.0f, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, NULL); + draw_list->AddText(NULL, 0.0f, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, 0.0f, NULL); } } diff --git a/imgui.h b/imgui.h index e5b29fa1..4e30c011 100644 --- a/imgui.h +++ b/imgui.h @@ -2678,7 +2678,7 @@ struct ImDrawList IMGUI_API void AddNgon(const ImVec2& center, float radius, ImU32 col, int num_segments, float thickness = 1.0f); IMGUI_API void AddNgonFilled(const ImVec2& center, float radius, ImU32 col, int num_segments); IMGUI_API void AddText(const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL); - IMGUI_API void AddText(const ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL, float wrap_width = 0.0f, const ImVec4* cpu_fine_clip_rect = NULL); + IMGUI_API void AddText(const ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL, float wrap_width = 0.0f, float text_align = 0.0f, const ImVec4* cpu_fine_clip_rect = NULL); IMGUI_API void AddPolyline(const ImVec2* points, int num_points, ImU32 col, ImDrawFlags flags, float thickness); IMGUI_API void AddConvexPolyFilled(const ImVec2* points, int num_points, ImU32 col); IMGUI_API void AddBezierCubic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness, int num_segments = 0); // Cubic Bezier (4 control points) @@ -3013,9 +3013,9 @@ 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 const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) const; + IMGUI_API const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width, float *line_width = NULL) const; IMGUI_API void RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, ImWchar c) const; - IMGUI_API void RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width = 0.0f, bool cpu_fine_clip = false) const; + IMGUI_API void RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width = 0.0f, float text_align = 0.0f, bool cpu_fine_clip = false) const; // [Internal] Don't use! IMGUI_API void BuildLookupTable(); diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 205ce76f..117ef054 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1569,7 +1569,7 @@ void ImDrawList::AddBezierQuadratic(const ImVec2& p1, const ImVec2& p2, const Im PathStroke(col, 0, thickness); } -void ImDrawList::AddText(const ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end, float wrap_width, const ImVec4* cpu_fine_clip_rect) +void ImDrawList::AddText(const ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end, float wrap_width, float text_align, const ImVec4* cpu_fine_clip_rect) { if ((col & IM_COL32_A_MASK) == 0) return; @@ -1595,7 +1595,7 @@ void ImDrawList::AddText(const ImFont* font, float font_size, const ImVec2& pos, clip_rect.z = ImMin(clip_rect.z, cpu_fine_clip_rect->z); clip_rect.w = ImMin(clip_rect.w, cpu_fine_clip_rect->w); } - font->RenderText(this, font_size, pos, col, clip_rect, text_begin, text_end, wrap_width, cpu_fine_clip_rect != NULL); + font->RenderText(this, font_size, pos, col, clip_rect, text_begin, text_end, wrap_width, text_align, cpu_fine_clip_rect != NULL); } void ImDrawList::AddText(const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end) @@ -3418,7 +3418,7 @@ static inline const char* CalcWordWrapNextLineStartA(const char* text, const cha // Simple word-wrapping for English, not full-featured. Please submit failing cases! // This will return the next location to wrap from. If no wrapping if necessary, this will fast-forward to e.g. text_end. // FIXME: Much possible improvements (don't cut things like "word !", "word!!!" but cut within "word,,,,", more sensible support for punctuations, support for Unicode punctuations, etc.) -const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) const +const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width, float *line_length) const { // For references, possible wrap point marked with ^ // "aaa bbb, ccc,ddd. eee fff. ggg!" @@ -3434,6 +3434,7 @@ const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const c float line_width = 0.0f; float word_width = 0.0f; float blank_width = 0.0f; + float last_blank_width = 0.0f; wrap_width /= scale; // We work with unscaled widths to avoid scaling every characters const char* word_end = text; @@ -3455,10 +3456,9 @@ const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const c { if (c == '\n') { - line_width = word_width = blank_width = 0.0f; - inside_word = true; - s = next_s; - continue; + line_width += word_width; + word_width = last_blank_width = 0.0f; + break; } if (c == '\r') { @@ -3472,7 +3472,6 @@ const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const c { if (inside_word) { - line_width += blank_width; blank_width = 0.0f; word_end = s; } @@ -3481,7 +3480,6 @@ const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const c } else { - word_width += char_width; if (inside_word) { word_end = next_s; @@ -3490,8 +3488,10 @@ const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const c { prev_word_end = word_end; line_width += word_width + blank_width; + last_blank_width = blank_width; word_width = blank_width = 0.0f; } + word_width += char_width; // Allow wrapping after punctuation. inside_word = (c != '.' && c != ',' && c != ';' && c != '!' && c != '?' && c != '\"'); @@ -3500,9 +3500,12 @@ const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const c // We ignore blank width at the end of the line (they can be skipped) if (line_width + word_width > wrap_width) { - // Words that cannot possibly fit within an entire line will be cut anywhere. if (word_width < wrap_width) s = prev_word_end ? prev_word_end : word_end; + // Words that cannot possibly fit within an entire line will be cut before + // the character that pushed them over the limit. + else + line_width += word_width - char_width; break; } @@ -3513,6 +3516,14 @@ const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const c // +1 may not be a character start point in UTF-8 but it's ok because caller loops use (text >= word_wrap_eol). if (s == text && text < text_end) return s + 1; + if (s == text_end) + line_width += word_width; + else + line_width -= last_blank_width; + + if (line_length) + *line_length = line_width * scale; + return s; } @@ -3526,6 +3537,7 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons ImVec2 text_size = ImVec2(0, 0); float line_width = 0.0f; + float wrapped_line_width = 0.0f; const bool word_wrap_enabled = (wrap_width > 0.0f); const char* word_wrap_eol = NULL; @@ -3537,12 +3549,15 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons { // Calculate how far we can render. Requires two passes on the string data but keeps the code simple and not intrusive for what's essentially an uncommon feature. if (!word_wrap_eol) - word_wrap_eol = CalcWordWrapPositionA(scale, s, text_end, wrap_width - line_width); + { + word_wrap_eol = CalcWordWrapPositionA(scale, s, text_end, wrap_width, &wrapped_line_width); + if (word_wrap_eol == s) // Wrap_width is too small to fit anything. Force displaying 1 character to minimize the height discontinuity. + word_wrap_eol++; // +1 may not be a character start point in UTF-8 but it's ok because we use s >= word_wrap_eol below + } if (s >= word_wrap_eol) { - if (text_size.x < line_width) - text_size.x = line_width; + text_size.x = ImMax(text_size.x, wrapped_line_width); text_size.y += line_height; line_width = 0.0f; word_wrap_eol = NULL; @@ -3610,7 +3625,7 @@ void ImFont::RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, Im } // Note: as with every ImDrawList drawing function, this expects that the font atlas texture is bound. -void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width, bool cpu_fine_clip) const +void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width, float text_align, bool cpu_fine_clip) const { if (!text_end) text_end = text_begin + strlen(text_begin); // ImGui:: functions generally already provides a valid text_end, so this is merely to handle direct calls. @@ -3676,13 +3691,21 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im const ImU32 col_untinted = col | ~IM_COL32_A_MASK; const char* word_wrap_eol = NULL; + float line_width = 0.0f; + while (s < text_end) { if (word_wrap_enabled) { // Calculate how far we can render. Requires two passes on the string data but keeps the code simple and not intrusive for what's essentially an uncommon feature. if (!word_wrap_eol) - word_wrap_eol = CalcWordWrapPositionA(scale, s, text_end, wrap_width - (x - start_x)); + { + word_wrap_eol = CalcWordWrapPositionA(scale, s, text_end, wrap_width, &line_width); + if (word_wrap_eol == s) // Wrap_width is too small to fit anything. Force displaying 1 character to minimize the height discontinuity. + word_wrap_eol++; // +1 may not be a character start point in UTF-8 but it's ok because we use s >= word_wrap_eol below + else + x = pos.x + IM_ROUND((wrap_width - line_width) * text_align); + } if (s >= word_wrap_eol) { @@ -3703,7 +3726,7 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im if (c < 32) { - if (c == '\n') + if (c == '\n') // if word-wrap is disabled, need to break lines manually { x = start_x; y += line_height; diff --git a/imgui_internal.h b/imgui_internal.h index 3204d48d..93cb87b7 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -3150,7 +3150,7 @@ namespace ImGui // AVOID USING OUTSIDE OF IMGUI.CPP! NOT FOR PUBLIC CONSUMPTION. THOSE FUNCTIONS ARE A MESS. THEIR SIGNATURE AND BEHAVIOR WILL CHANGE, THEY NEED TO BE REFACTORED INTO SOMETHING DECENT. // NB: All position are in absolute pixels coordinates (we are never using window coordinates internally) IMGUI_API void RenderText(ImVec2 pos, const char* text, const char* text_end = NULL, bool hide_text_after_hash = true); - IMGUI_API void RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end, float wrap_width); + IMGUI_API void RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end, float wrap_width, float text_alignment); IMGUI_API void RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align = ImVec2(0, 0), const ImRect* clip_rect = NULL); IMGUI_API void RenderTextClippedEx(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align = ImVec2(0, 0), const ImRect* clip_rect = NULL); IMGUI_API void RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, float clip_max_x, float ellipsis_max_x, const char* text, const char* text_end, const ImVec2* text_size_if_known); diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index ec3eca36..eb13a646 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -160,6 +160,7 @@ void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags) text_end = text + strlen(text); // FIXME-OPT const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset); + const float text_alignment = window->DC.TextAlignment; const float wrap_pos_x = window->DC.TextWrapPos; const bool wrap_enabled = (wrap_pos_x >= 0.0f); if (text_end - text <= 2000 || wrap_enabled) @@ -167,14 +168,17 @@ void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags) // Common case const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0f; const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width); + const float text_offset = wrap_enabled ? IM_ROUND((wrap_width - text_size.x) * text_alignment) : 0.0f; + const ImVec2 final_pos = ImVec2(text_pos.x + text_offset, text_pos.y); - ImRect bb(text_pos, text_pos + text_size); + ImRect bb(final_pos, final_pos + text_size); ItemSize(text_size, 0.0f); if (!ItemAdd(bb, 0)) return; // Render (we don't hide text after ## in this end-user function) - RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width); + // we use text_size.x here as wrap_width to correctly handle text alignment + RenderTextWrapped(bb.Min, text_begin, text_end, text_size.x, text_alignment); } else { @@ -4933,7 +4937,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length) { ImU32 col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text); - draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos - draw_scroll, col, buf_display, buf_display_end, 0.0f, is_multiline ? NULL : &clip_rect); + draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos - draw_scroll, col, buf_display, buf_display_end, 0.0f, 0.0f, is_multiline ? NULL : &clip_rect); } // Draw blinking cursor @@ -4968,7 +4972,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length) { ImU32 col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text); - draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos, col, buf_display, buf_display_end, 0.0f, is_multiline ? NULL : &clip_rect); + draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos, col, buf_display, buf_display_end, 0.0f, 0.0f, is_multiline ? NULL : &clip_rect); } }