mirror of
https://github.com/Drezil/imgui.git
synced 2025-07-07 13:35:49 +02:00
Font: implement a way to draw narrow ellipsis without relying on hardcoded 1 pixel dots. (#2775)
This changeset implements several pieces of the puzzle that add up to a narrow ellipsis rendering. ## EllipsisCodePoint `ImFontConfig` and `ImFont` received `ImWchar EllipsisCodePoint = -1;` field. User may configure `ImFontConfig::EllipsisCodePoint` a unicode codepoint that will be used for rendering narrow ellipsis. Not setting this field will automatically detect a suitable character or fall back to rendering 3 dots with minimal spacing between them. Autodetection prefers codepoint 0x2026 (narrow ellipsis) and falls back to 0x0085 (NEXT LINE) when missing. Wikipedia indicates that codepoint 0x0085 was used as ellipsis in some older windows fonts. So does default Dear ImGui font. When user is merging fonts - first configured and present ellipsis codepoint will be used, ellipsis characters from subsequently merged fonts will be ignored. ## Narrow ellipsis Rendering a narrow ellipsis is surprisingly not straightforward task. There are cases when ellipsis is bigger than the last visible character therefore `RenderTextEllipsis()` has to hide last two characters. In a subset of those cases ellipsis is as big as last visible character + space before it. `RenderTextEllipsis()` tries to work around this case by taking free space between glyph edges into account. Code responsible for this functionality is within `if (text_end_ellipsis != text_end_full) { ... }`. ## Fallback (manually rendered dots) There are cases when font does not have ellipsis character defined. In this case RenderTextEllipsis() falls back to rendering ellipsis as 3 dots, but with reduced spacing between them. 1 pixel space is used in all cases. This results in a somewhat wider ellipsis, but avoids issues where spaces between dots are uneven (visible in larger/monospace fonts) or squish dots way too much (visible in default font where dot is essentially a pixel). This fallback method obsoleted `RenderPixelEllipsis()` and this function was removed. Note that fallback ellipsis will always be somewhat wider than it could be, however it will fit in visually into every font used unlike what `RenderPixelEllipsis()` produced.
This commit is contained in:
96
imgui.cpp
96
imgui.cpp
@ -2489,7 +2489,7 @@ void ImGui::RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, cons
|
||||
// Another overly complex function until we reorganize everything into a nice all-in-one helper.
|
||||
// This is made more complex because we have dissociated the layout rectangle (pos_min..pos_max) which define _where_ the ellipsis is, from actual clipping of text and limit of the ellipsis display.
|
||||
// This is because in the context of tabs we selectively hide part of the text when the Close Button appears, but we don't want the ellipsis to move.
|
||||
void ImGui::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_full, const ImVec2* text_size_if_known)
|
||||
void ImGui::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_full, const ImVec2* text_size_if_known)
|
||||
{
|
||||
ImGuiContext& g = *GImGui;
|
||||
if (text_end_full == NULL)
|
||||
@ -2503,15 +2503,42 @@ void ImGui::RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min,
|
||||
// min max ellipsis_max
|
||||
// <-> this is generally some padding value
|
||||
|
||||
// FIXME-STYLE: RenderPixelEllipsis() style should use actual font data.
|
||||
const ImFont* font = draw_list->_Data->Font;
|
||||
const float font_size = draw_list->_Data->FontSize;
|
||||
const int ellipsis_dot_count = 3;
|
||||
const float ellipsis_width = (1.0f + 1.0f) * ellipsis_dot_count - 1.0f;
|
||||
const char* text_end_ellipsis = NULL;
|
||||
const ImFontGlyph* glyph;
|
||||
int ellipsis_char_num = 1;
|
||||
ImWchar ellipsis_codepoint = font->EllipsisCodePoint;
|
||||
|
||||
if (ellipsis_codepoint != (ImWchar)-1)
|
||||
glyph = font->FindGlyph(ellipsis_codepoint);
|
||||
else
|
||||
{
|
||||
ellipsis_codepoint = (ImWchar)'.';
|
||||
glyph = font->FindGlyph(ellipsis_codepoint);
|
||||
ellipsis_char_num = 3;
|
||||
}
|
||||
|
||||
float ellipsis_glyph_width = glyph->X1; // Width of the glyph with no padding on either side
|
||||
float ellipsis_width = ellipsis_glyph_width; // Full width of entire ellipsis
|
||||
float push_left = 1.f;
|
||||
|
||||
if (ellipsis_char_num > 1)
|
||||
{
|
||||
const float spacing_between_dots = 1.f * (draw_list->_Data->FontSize / font->FontSize);
|
||||
ellipsis_glyph_width = glyph->X1 - glyph->X0 + spacing_between_dots;
|
||||
// Full ellipsis size without free spacing after it.
|
||||
ellipsis_width = ellipsis_glyph_width * (float)ellipsis_char_num - spacing_between_dots;
|
||||
if (glyph->X0 > 1.f)
|
||||
{
|
||||
// Pushing ellipsis to the left will be accomplished by rendering the dot (X0).
|
||||
push_left = 0.f;
|
||||
}
|
||||
}
|
||||
|
||||
float text_width = ImMax((pos_max.x - ellipsis_width) - pos_min.x, 1.0f);
|
||||
float text_size_clipped_x = font->CalcTextSizeA(font_size, text_width, 0.0f, text, text_end_full, &text_end_ellipsis).x;
|
||||
|
||||
if (text == text_end_ellipsis && text_end_ellipsis < text_end_full)
|
||||
{
|
||||
// Always display at least 1 character if there's no room for character + ellipsis
|
||||
@ -2524,11 +2551,66 @@ void ImGui::RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min,
|
||||
text_end_ellipsis--;
|
||||
text_size_clipped_x -= font->CalcTextSizeA(font_size, FLT_MAX, 0.0f, text_end_ellipsis, text_end_ellipsis + 1).x; // Ascii blanks are always 1 byte
|
||||
}
|
||||
|
||||
if (text_end_ellipsis != text_end_full)
|
||||
{
|
||||
// +---- First invisible character we arrived at.
|
||||
// / +-- Character that we hope to be first invisible.
|
||||
// [l][i]
|
||||
// ||||
|
||||
// \ \__ extra_spacing when two characters got hidden
|
||||
// \___ extra_spacing when one character got hidden
|
||||
unsigned c = 0;
|
||||
float extra_spacing = 0;
|
||||
const char* text_end_ellipsis_prev = text_end_ellipsis;
|
||||
text_end_ellipsis += ImTextCharFromUtf8(&c, text_end_ellipsis, text_end_full);
|
||||
if (c && !ImCharIsBlankW(c))
|
||||
{
|
||||
const ImFontGlyph* hidden_glyph = font->FindGlyph(c);
|
||||
// Free space after first invisible glyph
|
||||
extra_spacing = hidden_glyph->AdvanceX - hidden_glyph->X1;
|
||||
c = 0;
|
||||
text_end_ellipsis += ImTextCharFromUtf8(&c, text_end_ellipsis, text_end_full);
|
||||
if (c && !ImCharIsBlankW(c))
|
||||
{
|
||||
hidden_glyph = font->FindGlyph(text_end_ellipsis[1]);
|
||||
// Space before next invisible glyph. This intentionally ignores space from the first invisible
|
||||
// glyph as that space will serve as spacing between ellipsis and last visible character. Without
|
||||
// doing this we may get into awkward situations where ellipsis pretty much sticks to the last
|
||||
// visible character. This issue manifests with the default font for word "Brocolli" there both i
|
||||
// and l are very thin. Unfortunately this makes fonts with wider gaps (like monospace) look a bit
|
||||
// worse, but it is a fair middle ground.
|
||||
extra_spacing = hidden_glyph->X0;
|
||||
}
|
||||
}
|
||||
|
||||
if (extra_spacing > 0)
|
||||
{
|
||||
// Repeat calculation hoping that we will get extra character visible
|
||||
text_width += extra_spacing;
|
||||
// Text length calculation is essentially an optimized version of this:
|
||||
// text_size_clipped_x = font->CalcTextSizeA(font_size, text_width, 0.0f, text, text_end_full, &text_end_ellipsis).x;
|
||||
// It avoids calculating entire width of the string.
|
||||
text_size_clipped_x += font->CalcTextSizeA(font_size, text_width - text_size_clipped_x, 0.0f, text_end_ellipsis_prev, text_end_full, &text_end_ellipsis).x;
|
||||
}
|
||||
else
|
||||
text_end_ellipsis = text_end_ellipsis_prev;
|
||||
}
|
||||
|
||||
RenderTextClippedEx(draw_list, pos_min, ImVec2(clip_max_x, pos_max.y), text, text_end_ellipsis, &text_size, ImVec2(0.0f, 0.0f));
|
||||
|
||||
const float ellipsis_x = pos_min.x + text_size_clipped_x + 1.0f;
|
||||
if (ellipsis_x + ellipsis_width - 1.0f <= ellipsis_max_x)
|
||||
RenderPixelEllipsis(draw_list, ImVec2(ellipsis_x, pos_min.y), GetColorU32(ImGuiCol_Text), ellipsis_dot_count);
|
||||
// This variable pushes ellipsis to the left from last visible character. This is mostly useful when rendering
|
||||
// ellipsis character contained in the font. If we render ellipsis manually space is already adequate and extra
|
||||
// spacing is not needed.
|
||||
float ellipsis_x = pos_min.x + text_size_clipped_x + push_left;
|
||||
if (ellipsis_x + ellipsis_width - push_left <= ellipsis_max_x)
|
||||
{
|
||||
for (int i = 0; i < ellipsis_char_num; i++)
|
||||
{
|
||||
font->RenderChar(draw_list, font_size, ImVec2(ellipsis_x, pos_min.y), GetColorU32(ImGuiCol_Text), ellipsis_codepoint);
|
||||
ellipsis_x += ellipsis_glyph_width;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
Reference in New Issue
Block a user