Added word-wrapping API TextWrapped(), PushTextWrapPos(), PopTextWrapPos()
Added word-wrapping sample in the test window.
Added IsItemFocused() to tell if last widget is being focused for keyboard input.
diff --git a/imgui.cpp b/imgui.cpp
index 082565e..5018e20 100644
--- a/imgui.cpp
+++ b/imgui.cpp
@@ -122,7 +122,7 @@
some functions like TreeNode() implicitly creates a scope for you by calling PushID()
- when dealing with trees, ID are important because you want to preserve the opened/closed state of tree nodes.
depending on your use cases you may want to use strings, indices or pointers as ID. experiment and see what makes more sense!
- e.g. When displaying a single object, using a static string as ID will preserve your node open/closed state when the targetted object change
+ e.g. When displaying a single object, using a static string as ID will preserve your node open/closed state when the targeted object change
e.g. When displaying a list of objects, using indices or pointers as ID will preserve the node open/closed state per object
- when passing a label you can optionally specify extra unique ID information within the same string using "##". This helps solving the simpler collision cases.
e.g. "Label" display "Label" and uses "Label" as ID
@@ -144,7 +144,7 @@
- 2014/09/24 (1.12) moved IM_MALLOC/IM_REALLOC/IM_FREE preprocessor defines to IO.MemAllocFn/IO.MemReallocFn/IO.MemFreeFn
- 2014/08/30 (1.09) removed IO.FontHeight (now computed automatically)
- 2014/08/30 (1.09) moved IMGUI_FONT_TEX_UV_FOR_WHITE preprocessor define to IO.FontTexUvForWhite
- - 2014/08/28 (1.09) changed the behaviour of IO.PixelCenterOffset following various rendering fixes
+ - 2014/08/28 (1.09) changed the behavior of IO.PixelCenterOffset following various rendering fixes
ISSUES & TODO-LIST
==================
@@ -225,7 +225,7 @@
static bool ButtonBehaviour(const ImGuiAabb& bb, const ImGuiID& id, bool* out_hovered, bool* out_held, bool allow_key_modifiers, bool repeat = false);
static void LogText(const ImVec2& ref_pos, const char* text, const char* text_end = NULL);
-static void RenderText(ImVec2 pos, const char* text, const char* text_end = NULL, const bool hide_text_after_hash = true);
+static void RenderText(ImVec2 pos, const char* text, const char* text_end = NULL, bool hide_text_after_hash = true, float wrap_width = 0.0f);
static void RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border = true, float rounding = 0.0f);
static void RenderCollapseTriangle(ImVec2 p_min, bool open, float scale = 1.0f, bool shadow = false);
@@ -590,9 +590,11 @@
int TreeDepth;
ImGuiAabb LastItemAabb;
bool LastItemHovered;
+ bool LastItemFocused;
ImVector<ImGuiWindow*> ChildWindows;
ImVector<bool> AllowKeyboardFocus;
ImVector<float> ItemWidth;
+ ImVector<float> TextWrapPos;
ImVector<ImGuiColMod> ColorModifiers;
ImGuiColorEditMode ColorEditMode;
ImGuiStorage* StateStorage;
@@ -613,6 +615,7 @@
TreeDepth = 0;
LastItemAabb = ImGuiAabb(0.0f,0.0f,0.0f,0.0f);
LastItemHovered = false;
+ LastItemFocused = true;
StateStorage = NULL;
OpenNextNode = -1;
@@ -1068,6 +1071,9 @@
if (allow_keyboard_focus)
FocusIdxTabCounter++;
+ if (is_active)
+ window->DC.LastItemFocused = true;
+
// Process keyboard input at this point: TAB, Shift-TAB switch focus
// We can always TAB out of a widget that doesn't allow tabbing in.
if (FocusIdxAllRequestNext == IM_INT_MAX && FocusIdxTabRequestNext == IM_INT_MAX && is_active && ImGui::IsKeyPressedMap(ImGuiKey_Tab))
@@ -1649,8 +1655,24 @@
}
}
+static float CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x)
+{
+ if (wrap_pos_x < 0.0f)
+ return 0.0f;
+
+ ImGuiWindow* window = GetCurrentWindow();
+ if (wrap_pos_x == 0.0f)
+ wrap_pos_x = GetWindowContentRegionMax().x;
+ if (wrap_pos_x > 0.0f)
+ wrap_pos_x += window->Pos.x; // wrap_pos_x is provided is window local space
+
+ const float wrap_width = wrap_pos_x > 0.0f ? ImMax(wrap_pos_x - pos.x, 0.00001f) : 0.0f;
+ return wrap_width;
+}
+
// Internal ImGui function to render text (called from ImGui::Text(), ImGui::TextUnformatted(), etc.)
-static void RenderText(ImVec2 pos, const char* text, const char* text_end, const bool hide_text_after_hash)
+// ImGui::RenderText() calls ImDrawList::AddText() calls ImBitmapFont::RenderText()
+static void RenderText(ImVec2 pos, const char* text, const char* text_end, bool hide_text_after_hash, float wrap_width)
{
ImGuiState& g = GImGui;
ImGuiWindow* window = GetCurrentWindow();
@@ -1669,13 +1691,12 @@
}
const int text_len = (int)(text_display_end - text);
- //IM_ASSERT(text_len >= 0 && text_len < 10000); // Suspicious text length
if (text_len > 0)
{
// Render
- window->DrawList->AddText(window->Font(), window->FontSize(), pos, window->Color(ImGuiCol_Text), text, text + text_len);
+ window->DrawList->AddText(window->Font(), window->FontSize(), pos, window->Color(ImGuiCol_Text), text, text + text_len, wrap_width);
- // Log as text. We split text into individual lines to add the tree level padding
+ // Log as text. We split text into individual lines to add current tree level padding
if (g.LogEnabled)
LogText(pos, text, text_display_end);
}
@@ -1689,7 +1710,7 @@
window->DrawList->AddRectFilled(p_min, p_max, fill_col, rounding);
if (border && (window->Flags & ImGuiWindowFlags_ShowBorders))
{
- // FIXME: I have no idea how this is working correctly but it is the best I've found that works on multiple rendering
+ // FIXME: This is the best I've found that works on multiple renderer/back ends. Rather dodgy.
const float offset = GImGui.IO.PixelCenterOffset;
window->DrawList->AddRect(p_min+ImVec2(1.5f-offset,1.5f-offset), p_max+ImVec2(1.0f-offset*2,1.0f-offset*2), window->Color(ImGuiCol_BorderShadow), rounding);
window->DrawList->AddRect(p_min+ImVec2(0.5f-offset,0.5f-offset), p_max+ImVec2(0.0f-offset*2,0.0f-offset*2), window->Color(ImGuiCol_Border), rounding);
@@ -1727,7 +1748,7 @@
// Calculate text size. Text can be multi-line. Optionally ignore text after a ## marker.
// CalcTextSize("") should return ImVec2(0.0f, GImGui.FontSize)
-ImVec2 CalcTextSize(const char* text, const char* text_end, const bool hide_text_after_hash)
+ImVec2 CalcTextSize(const char* text, const char* text_end, bool hide_text_after_hash, float wrap_width)
{
ImGuiWindow* window = GetCurrentWindow();
@@ -1737,8 +1758,8 @@
else
text_display_end = text_end;
- const ImVec2 size = window->Font()->CalcTextSizeA(window->FontSize(), 0, text, text_display_end, NULL);
- return size;
+ const ImVec2 text_size = window->Font()->CalcTextSizeA(window->FontSize(), FLT_MAX, wrap_width, text, text_display_end, NULL);
+ return text_size;
}
// Find window given position, search front-to-back
@@ -1864,6 +1885,12 @@
return window->DC.LastItemHovered;
}
+bool IsItemFocused()
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ return window->DC.LastItemFocused;
+}
+
ImVec2 GetItemBoxMin()
{
ImGuiWindow* window = GetCurrentWindow();
@@ -2299,6 +2326,8 @@
window->DC.ItemWidth.push_back(window->ItemWidthDefault);
window->DC.AllowKeyboardFocus.resize(0);
window->DC.AllowKeyboardFocus.push_back(true);
+ window->DC.TextWrapPos.resize(0);
+ window->DC.TextWrapPos.push_back(-1.0f); // disabled
window->DC.ColorModifiers.resize(0);
window->DC.ColorEditMode = ImGuiColorEditMode_UserSelect;
window->DC.ColumnCurrent = 0;
@@ -2464,6 +2493,18 @@
window->DC.AllowKeyboardFocus.pop_back();
}
+void PushTextWrapPos(float wrap_x)
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ window->DC.TextWrapPos.push_back(wrap_x);
+}
+
+void PopTextWrapPos()
+{
+ ImGuiWindow* window = GetCurrentWindow();
+ window->DC.TextWrapPos.pop_back();
+}
+
void PushStyleColor(ImGuiCol idx, const ImVec4& col)
{
ImGuiState& g = GImGui;
@@ -2583,6 +2624,7 @@
return ImVec2(0, window->TitleBarHeight()) + window->WindowPadding();
}
+// FIXME: Provide an equivalent that gives the min/max region considering columns.
ImVec2 GetWindowContentRegionMax()
{
ImGuiWindow* window = GetCurrentWindow();
@@ -2718,6 +2760,21 @@
va_end(args);
}
+void TextWrappedV(const char* fmt, va_list args)
+{
+ ImGui::PushTextWrapPos(0.0f);
+ TextV(fmt, args);
+ ImGui::PopTextWrapPos();
+}
+
+void TextWrapped(const char* fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ TextWrappedV(fmt, args);
+ va_end(args);
+}
+
void TextUnformatted(const char* text, const char* text_end)
{
ImGuiState& g = GImGui;
@@ -2730,11 +2787,14 @@
if (text_end == NULL)
text_end = text + strlen(text);
- if (text_end - text > 2000)
+ const float wrap_pos_x = window->DC.TextWrapPos.back();
+ const bool wrap_enabled = wrap_pos_x >= 0.0f;
+ if (text_end - text > 2000 && !wrap_enabled)
{
// Long text!
// Perform manual coarse clipping to optimize for long multi-line text
// From this point we will only compute the width of lines that are visible.
+ // Optimization only available when word-wrapping is disabled.
const char* line = text;
const float line_height = ImGui::GetTextLineHeight();
const ImVec2 start_pos = window->DC.CursorPos;
@@ -2804,7 +2864,8 @@
}
else
{
- const ImVec2 text_size = CalcTextSize(text_begin, text_end, false);
+ 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);
ImGuiAabb bb(window->DC.CursorPos, window->DC.CursorPos + text_size);
ItemSize(bb.GetSize(), &bb.Min);
@@ -2813,7 +2874,7 @@
// Render
// We don't hide text after ## in this end-user function.
- RenderText(bb.Min, text_begin, text_end, false);
+ RenderText(bb.Min, text_begin, text_end, false, wrap_width);
}
}
@@ -3359,8 +3420,6 @@
}
}
- const bool tab_focus_requested = window->FocusItemRegister(g.ActiveId == id);
-
const ImVec2 text_size = CalcTextSize(label);
const ImGuiAabb frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, text_size.y) + style.FramePadding*2.0f);
const ImGuiAabb slider_bb(frame_bb.Min+g.Style.FramePadding, frame_bb.Max-g.Style.FramePadding);
@@ -3373,6 +3432,8 @@
return false;
}
+ const bool tab_focus_requested = window->FocusItemRegister(g.ActiveId == id);
+
const bool is_unbound = v_min == -FLT_MAX || v_min == FLT_MAX || v_max == -FLT_MAX || v_max == FLT_MAX;
const float grab_size_in_units = 1.0f; // In 'v' units. Probably needs to be parametrized, based on a 'v_step' value? decimal precision?
@@ -3879,7 +3940,7 @@
// Wrapper for stb_textedit.h to edit text (our wrapper is for: statically sized buffer, single-line, ASCII, fixed-width font)
int STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING* obj) { return (int)ImStrlenW(obj->Text); }
ImWchar STB_TEXTEDIT_GETCHAR(const STB_TEXTEDIT_STRING* obj, int idx) { return obj->Text[idx]; }
-float STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING* obj, int line_start_idx, int char_idx) { (void)line_start_idx; return obj->Font->CalcTextSizeW(obj->FontSize, 0, &obj->Text[char_idx], &obj->Text[char_idx]+1, NULL).x; }
+float STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING* obj, int line_start_idx, int char_idx) { (void)line_start_idx; return obj->Font->CalcTextSizeW(obj->FontSize, FLT_MAX, &obj->Text[char_idx], &obj->Text[char_idx]+1, NULL).x; }
int STB_TEXTEDIT_KEYTOTEXT(int key) { return key >= 0x10000 ? 0 : key; }
ImWchar STB_TEXTEDIT_NEWLINE = '\n';
void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, STB_TEXTEDIT_STRING* obj, int line_start_idx)
@@ -3947,7 +4008,7 @@
{
// Scroll in chunks of quarter width
const float scroll_x_increment = Width * 0.25f;
- const float cursor_offset_x = Font->CalcTextSizeW(FontSize, 0, Text, Text+StbState.cursor, NULL).x;
+ const float cursor_offset_x = Font->CalcTextSizeW(FontSize, FLT_MAX, Text, Text+StbState.cursor, NULL).x;
if (ScrollX > cursor_offset_x)
ScrollX = ImMax(0.0f, cursor_offset_x - scroll_x_increment);
else if (ScrollX < cursor_offset_x - Width)
@@ -3971,7 +4032,7 @@
return text;
const char* text_clipped_end = NULL;
- const ImVec2 text_size = font->CalcTextSizeA(font_size, width, text, NULL, &text_clipped_end);
+ const ImVec2 text_size = font->CalcTextSizeA(font_size, width, 0.0f, text, NULL, &text_clipped_end);
if (out_text_size)
*out_text_size = text_size;
return text_clipped_end;
@@ -4835,6 +4896,7 @@
{
ImGuiWindow* window = GetCurrentWindow();
window->DC.LastItemAabb = bb;
+ window->DC.LastItemFocused = false;
if (ImGui::IsClipped(bb))
{
window->DC.LastItemHovered = false;
@@ -5331,7 +5393,7 @@
}
}
-void ImDrawList::AddText(ImFont font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end)
+void ImDrawList::AddText(ImFont font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end, float wrap_width)
{
if ((col >> 24) == 0)
return;
@@ -5345,7 +5407,7 @@
const size_t vtx_begin = vtx_buffer.size();
ReserveVertices(vtx_count_max);
- font->RenderText(font_size, pos, col, clip_rect_stack.back(), text_begin, text_end, vtx_write);
+ font->RenderText(font_size, pos, col, clip_rect_stack.back(), text_begin, text_end, vtx_write, wrap_width);
// give back unused vertices
vtx_buffer.resize((size_t)(vtx_write - &vtx_buffer.front()));
@@ -5491,7 +5553,7 @@
IndexLookup[Glyphs[i].Id] = (int)i;
}
-const ImBitmapFont::FntGlyph* ImBitmapFont::FindGlyph(unsigned short c) const
+const ImBitmapFont::FntGlyph* ImBitmapFont::FindGlyph(unsigned short c, const ImBitmapFont::FntGlyph* fallback) const
{
if (c < (int)IndexLookup.size())
{
@@ -5499,7 +5561,7 @@
if (i >= 0 && i < (int)GlyphsCount)
return &Glyphs[i];
}
- return NULL;
+ return fallback;
}
// Convert UTF-8 to 32-bits character, process single character input.
@@ -5635,10 +5697,110 @@
return buf_out - buf;
}
-ImVec2 ImBitmapFont::CalcTextSizeA(float size, float max_width, const char* text_begin, const char* text_end, const char** remaining) const
+const char* ImBitmapFont::CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width, const FntGlyph* fallback_glyph) const
{
- if (max_width == 0.0f)
- max_width = FLT_MAX;
+ // Simple word-wrapping for English, not full-featured. Please submit failing cases!
+ // 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.)
+
+ // For references, possible wrap point marked with ^
+ // "aaa bbb, ccc,ddd. eee fff. ggg!"
+ // ^ ^ ^ ^ ^__ ^ ^
+
+ // List of hardcoded separators: .,;!?'"
+
+ // Skip extra blanks after a line returns (that includes not counting them in width computation)
+ // e.g. "Hello world"
+ // -->
+ // "Hello"
+ // "world"
+
+ // Cut words that cannot possibly fit within one line.
+ // e.g.: "The tropical fish" with ~5 characters worth of width
+ // -->
+ // "The tr"
+ // "opical"
+ // "fish"
+
+ float line_width = 0.0f;
+ float word_width = 0.0f;
+ float blank_width = 0.0f;
+
+ const char* word_end = text;
+ const char* prev_word_end = NULL;
+ bool inside_word = true;
+
+ const char* s = text;
+ while (s < text_end)
+ {
+ unsigned int c;
+ const int bytes_count = ImTextCharFromUtf8(&c, s, text_end);
+ const char* next_s = s + (bytes_count > 0 ? bytes_count : 1);
+
+ if (c == '\n')
+ {
+ line_width = word_width = blank_width = 0.0f;
+ inside_word = true;
+ s = next_s;
+ continue;
+ }
+
+ float char_width = 0.0f;
+ if (c == '\t')
+ {
+ if (const FntGlyph* glyph = FindGlyph((unsigned short)' '))
+ char_width = (glyph->XAdvance + Info->SpacingHoriz) * 4 * scale;
+ }
+ else
+ {
+ if (const FntGlyph* glyph = FindGlyph((unsigned short)c, fallback_glyph))
+ char_width = (glyph->XAdvance + Info->SpacingHoriz) * scale;
+ }
+
+ if (c == ' ' || c == '\t')
+ {
+ if (inside_word)
+ {
+ line_width += blank_width;
+ blank_width = 0.0f;
+ }
+ blank_width += char_width;
+ inside_word = false;
+ }
+ else
+ {
+ word_width += char_width;
+ if (inside_word)
+ {
+ word_end = next_s;
+ }
+ else
+ {
+ prev_word_end = word_end;
+ line_width += word_width + blank_width;
+ word_width = blank_width = 0.0f;
+ }
+
+ // Allow wrapping after punctuation.
+ inside_word = !(c == '.' || c == ',' || c == ';' || c == '!' || c == '?' || c == '\'' || 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;
+ break;
+ }
+
+ s = next_s;
+ }
+
+ return s;
+}
+
+ImVec2 ImBitmapFont::CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end, const char** remaining) const
+{
if (!text_end)
text_end = text_begin + strlen(text_begin);
@@ -5649,40 +5811,70 @@
ImVec2 text_size = ImVec2(0,0);
float line_width = 0.0f;
+ const bool word_wrap_enabled = (wrap_width > 0.0f);
+ const char* word_wrap_eol = NULL;
+
const char* s = text_begin;
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 - line_width, fallback_glyph);
+ 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.y += line_height;
+ line_width = 0.0f;
+ word_wrap_eol = NULL;
+
+ // Wrapping skips upcoming blanks
+ while (s < text_end)
+ {
+ const char c = *s;
+ if (c == ' ' || c == '\t') { s++; } else if (c == '\n') { s++; break; } else { break; }
+ }
+ continue;
+ }
+ }
+
+ // Decode and advance source (handle unlikely UTF-8 decoding failure by skipping to the next byte)
unsigned int c;
const int bytes_count = ImTextCharFromUtf8(&c, s, text_end);
- s += bytes_count > 0 ? bytes_count : 1; // Handle decoding failure by skipping to next byte
-
+ s += bytes_count > 0 ? bytes_count : 1;
+
if (c == '\n')
{
if (text_size.x < line_width)
text_size.x = line_width;
text_size.y += line_height;
- line_width = 0;
+ line_width = 0.0f;
+ continue;
}
- else if (c == '\t')
+
+ float char_width = 0.0f;
+ if (c == '\t')
{
- // FIXME: Better TAB handling needed.
+ // FIXME: Better TAB handling
if (const FntGlyph* glyph = FindGlyph((unsigned short)' '))
- line_width += (glyph->XAdvance + Info->SpacingHoriz) * 4 * scale;
+ char_width = (glyph->XAdvance + Info->SpacingHoriz) * 4 * scale;
}
- else
+ else if (const FntGlyph* glyph = FindGlyph((unsigned short)c, fallback_glyph))
{
- const FntGlyph* glyph = FindGlyph((unsigned short)c);
- if (!glyph)
- glyph = fallback_glyph;
- if (glyph)
- {
- const float char_width = (glyph->XAdvance + Info->SpacingHoriz) * scale;
- //const float char_extend = (glyph->XOffset + glyph->Width * scale);
- if (line_width + char_width >= max_width)
- break;
- line_width += char_width;
- }
+ char_width = (glyph->XAdvance + Info->SpacingHoriz) * scale;
}
+
+ if (line_width + char_width >= max_width)
+ break;
+
+ line_width += char_width;
}
if (line_width > 0 || text_size.y == 0.0f)
@@ -5700,8 +5892,6 @@
ImVec2 ImBitmapFont::CalcTextSizeW(float size, float max_width, const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining) const
{
- if (max_width == 0.0f)
- max_width = FLT_MAX;
if (!text_end)
text_end = text_begin + ImStrlenW(text_begin);
@@ -5722,28 +5912,27 @@
if (text_size.x < line_width)
text_size.x = line_width;
text_size.y += line_height;
- line_width = 0;
+ line_width = 0.0f;
+ continue;
}
- else if (c == '\t')
+
+ float char_width = 0.0f;
+ if (c == '\t')
{
- // FIXME: Better TAB handling needed.
+ // FIXME: Better TAB handling
if (const FntGlyph* glyph = FindGlyph((unsigned short)' '))
- line_width += (glyph->XAdvance + Info->SpacingHoriz) * 4 * scale;
+ char_width = (glyph->XAdvance + Info->SpacingHoriz) * 4 * scale;
}
else
{
- const FntGlyph* glyph = FindGlyph((unsigned short)c);
- if (!glyph)
- glyph = fallback_glyph;
- if (glyph)
- {
- const float char_width = (glyph->XAdvance + Info->SpacingHoriz) * scale;
- //const float char_extend = (glyph->XOffset + glyph->Width * scale);
- if (line_width + char_width >= max_width)
- break;
- line_width += char_width;
- }
+ if (const FntGlyph* glyph = FindGlyph((unsigned short)c, fallback_glyph))
+ char_width = (glyph->XAdvance + Info->SpacingHoriz) * scale;
}
+
+ if (line_width + char_width >= max_width)
+ break;
+
+ line_width += char_width;
}
if (line_width > 0 || text_size.y == 0.0f)
@@ -5759,7 +5948,7 @@
return text_size;
}
-void ImBitmapFont::RenderText(float size, ImVec2 pos, ImU32 col, const ImVec4& clip_rect_ref, const char* text_begin, const char* text_end, ImDrawVert*& out_vertices) const
+void ImBitmapFont::RenderText(float size, ImVec2 pos, ImU32 col, const ImVec4& clip_rect_ref, const char* text_begin, const char* text_end, ImDrawVert*& out_vertices, float wrap_width) const
{
if (!text_end)
text_end = text_begin + strlen(text_begin);
@@ -5775,17 +5964,46 @@
pos.x = (float)(int)pos.x;
pos.y = (float)(int)pos.y + GImGui.IO.FontYOffset;
- const ImVec4 clip_rect = clip_rect_ref;
+ const bool word_wrap_enabled = (wrap_width > 0.0f);
+ const char* word_wrap_eol = NULL;
+ const ImVec4 clip_rect = clip_rect_ref;
float x = pos.x;
float y = pos.y;
- for (const char* s = text_begin; s < text_end; )
+
+ const char* s = text_begin;
+ 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 - pos.x), fallback_glyph);
+ 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)
+ {
+ x = pos.x;
+ y += line_height * scale;
+ word_wrap_eol = NULL;
+
+ // Wrapping skips upcoming blanks
+ while (s < text_end)
+ {
+ const char c = *s;
+ if (c == ' ' || c == '\t') { s++; } else if (c == '\n') { s++; break; } else { break; }
+ }
+ continue;
+ }
+ }
+
+ // Decode and advance source (handle unlikely UTF-8 decoding failure by skipping to the next byte)
unsigned int c;
const int bytes_count = ImTextCharFromUtf8(&c, s, text_end);
- s += bytes_count > 0 ? bytes_count : 1; // Handle decoding failure by skipping to next byte
- if (c >= 0x10000)
- continue;
+ s += bytes_count > 0 ? bytes_count : 1;
if (c == '\n')
{
@@ -5794,67 +6012,59 @@
continue;
}
- const FntGlyph* glyph = FindGlyph((unsigned short)c);
- if (!glyph)
- glyph = fallback_glyph;
- if (glyph)
+ float char_width = 0.0f;
+ if (c == '\t')
{
- const float char_width = (glyph->XAdvance + Info->SpacingHoriz) * scale;
- //const float char_extend = (glyph->XOffset + glyph->Width * scale);
-
- if (c != ' ' && c != '\n')
+ // FIXME: Better TAB handling
+ if (const FntGlyph* glyph = FindGlyph((unsigned short)' '))
+ char_width += (glyph->XAdvance + Info->SpacingHoriz) * 4 * scale;
+ }
+ else if (const FntGlyph* glyph = FindGlyph((unsigned short)c, fallback_glyph))
+ {
+ char_width = (glyph->XAdvance + Info->SpacingHoriz) * scale;
+ if (c != ' ')
{
- // Clipping due to Y limits is more likely
+ // Clipping on Y is more likely
const float y1 = (float)(y + (glyph->YOffset + outline*2) * scale);
const float y2 = (float)(y1 + glyph->Height * scale);
- if (y1 > clip_rect.w || y2 < clip_rect.y)
+ if (y1 <= clip_rect.w && y2 >= clip_rect.y)
{
- x += char_width;
- continue;
+ const float x1 = (float)(x + (glyph->XOffset + outline) * scale);
+ const float x2 = (float)(x1 + glyph->Width * scale);
+ if (x1 <= clip_rect.z && x2 >= clip_rect.x)
+ {
+ // Render a character
+ const float s1 = (glyph->X) * tex_scale_x;
+ const float t1 = (glyph->Y) * tex_scale_y;
+ const float s2 = (glyph->X + glyph->Width) * tex_scale_x;
+ const float t2 = (glyph->Y + glyph->Height) * tex_scale_y;
+
+ out_vertices[0].pos = ImVec2(x1, y1);
+ out_vertices[0].uv = ImVec2(s1, t1);
+ out_vertices[0].col = col;
+
+ out_vertices[1].pos = ImVec2(x2, y1);
+ out_vertices[1].uv = ImVec2(s2, t1);
+ out_vertices[1].col = col;
+
+ out_vertices[2].pos = ImVec2(x2, y2);
+ out_vertices[2].uv = ImVec2(s2, t2);
+ out_vertices[2].col = col;
+
+ out_vertices[3] = out_vertices[0];
+ out_vertices[4] = out_vertices[2];
+
+ out_vertices[5].pos = ImVec2(x1, y2);
+ out_vertices[5].uv = ImVec2(s1, t2);
+ out_vertices[5].col = col;
+
+ out_vertices += 6;
+ }
}
-
- const float x1 = (float)(x + (glyph->XOffset + outline) * scale);
- const float x2 = (float)(x1 + glyph->Width * scale);
- if (x1 > clip_rect.z || x2 < clip_rect.x)
- {
- x += char_width;
- continue;
- }
-
- const float s1 = (glyph->X) * tex_scale_x;
- const float t1 = (glyph->Y) * tex_scale_y;
- const float s2 = (glyph->X + glyph->Width) * tex_scale_x;
- const float t2 = (glyph->Y + glyph->Height) * tex_scale_y;
-
- out_vertices[0].pos = ImVec2(x1, y1);
- out_vertices[0].uv = ImVec2(s1, t1);
- out_vertices[0].col = col;
-
- out_vertices[1].pos = ImVec2(x2, y1);
- out_vertices[1].uv = ImVec2(s2, t1);
- out_vertices[1].col = col;
-
- out_vertices[2].pos = ImVec2(x2, y2);
- out_vertices[2].uv = ImVec2(s2, t2);
- out_vertices[2].col = col;
-
- out_vertices[3] = out_vertices[0];
- out_vertices[4] = out_vertices[2];
-
- out_vertices[5].pos = ImVec2(x1, y2);
- out_vertices[5].uv = ImVec2(s1, t2);
- out_vertices[5].col = col;
-
- out_vertices += 6;
}
+ }
- x += char_width;
- }
- else if (c == '\t')
- {
- if (const FntGlyph* glyph = FindGlyph((unsigned short)' '))
- x += (glyph->XAdvance + Info->SpacingHoriz) * 4 * scale;
- }
+ x += char_width;
}
}
@@ -5948,7 +6158,7 @@
ImGui::BulletText("Mouse Wheel to scroll.");
if (g.IO.FontAllowUserScaling)
ImGui::BulletText("CTRL+Mouse Wheel to zoom window contents.");
- ImGui::BulletText("TAB/SHIFT+TAB to cycle thru keyboard editable fields.");
+ ImGui::BulletText("TAB/SHIFT+TAB to cycle through keyboard editable fields.");
ImGui::BulletText("CTRL+Click on a slider to input text.");
ImGui::BulletText(
"While editing text:\n"
@@ -6114,20 +6324,45 @@
if (ImGui::TreeNode("Colored Text"))
{
- // This is a merely a shortcut, you can use PushStyleColor()/PopStyleColor() for more flexibility.
+ // Using shortcut. You can use PushStyleColor()/PopStyleColor() for more flexibility.
ImGui::TextColored(ImVec4(1.0f,0.0f,1.0f,1.0f), "Pink");
ImGui::TextColored(ImVec4(1.0f,1.0f,0.0f,1.0f), "Yellow");
ImGui::TreePop();
}
+ if (ImGui::TreeNode("Word Wrapping"))
+ {
+ // Using shortcut. You can use PushTextWrapPos()/PopTextWrapPos() for more flexibility.
+ ImGui::TextWrapped("This is a long paragraph. The text should automatically wrap on the edge of the window. The current implementation follows simple rules that works for English and possibly other languages.");
+ ImGui::Spacing();
+
+ static float wrap_width = 200.0f;
+ ImGui::SliderFloat("Wrap width", &wrap_width, -20, 600, "%.0f");
+
+ ImGui::Text("Test paragraph 1:");
+ ImGui::GetWindowDrawList()->AddRectFilled(ImGui::GetCursorScreenPos() + ImVec2(wrap_width, 0.0f), ImGui::GetCursorScreenPos() + ImVec2(wrap_width+10, ImGui::GetTextLineHeight()), 0xFFFF00FF);
+ ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + wrap_width);
+ ImGui::Text("lazy dog. This paragraph is made to fit within %.0f pixels. The quick brown fox jumps over the lazy dog.", wrap_width);
+ ImGui::GetWindowDrawList()->AddRect(ImGui::GetItemBoxMin(), ImGui::GetItemBoxMax(), 0xFF00FFFF);
+ ImGui::PopTextWrapPos();
+
+ ImGui::Text("Test paragraph 2:");
+ ImGui::GetWindowDrawList()->AddRectFilled(ImGui::GetCursorScreenPos() + ImVec2(wrap_width, 0.0f), ImGui::GetCursorScreenPos() + ImVec2(wrap_width+10, ImGui::GetTextLineHeight()), 0xFFFF00FF);
+ ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + wrap_width);
+ ImGui::Text("aaaaaaaa bbbbbbbb, cccccccc,dddddddd. eeeeeeee ffffffff. gggggggg!hhhhhhhh");
+ ImGui::GetWindowDrawList()->AddRect(ImGui::GetItemBoxMin(), ImGui::GetItemBoxMax(), 0xFF00FFFF);
+ ImGui::PopTextWrapPos();
+
+ ImGui::TreePop();
+ }
+
if (ImGui::TreeNode("UTF-8 Text"))
{
// UTF-8 test (need a suitable font, try extra_fonts/mplus* files for example)
// Most compiler appears to support UTF-8 in source code (with Visual Studio you need to save your file as 'UTF-8 without signature')
- // However for the sake for maximum portability here we are *not* including raw UTF-8 character in this source file, instead we encode the string with with hexadecimal constants.
- // In your own application please be reasonable and use UTF-8 in the source or get the data from external files. :)
- //const char* utf8_string = "\xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91\xe3\x81\x93\x20\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e"; // Japanese text for "Kakikukeo" (Hiragana) followed by "Nihongo" (kanji)
- ImGui::Text("(CJK text will only appears if the font supports it. Please check in\nthe extra_fonts/ folder if you intend to use non-ASCII characters.\nNote that characters values are preserved even if the font cannot be\ndisplayed, so you can safely copy & paste garbled characters.)");
+ // However for the sake for maximum portability here we are *not* including raw UTF-8 character in this source file, instead we encode the string with hexadecimal constants.
+ // In your own application please be reasonable and use UTF-8 in the source or get the data from external files! :)
+ ImGui::TextWrapped("(CJK text will only appears if the font supports it. Please check in the extra_fonts/ folder if you intend to use non-ASCII characters. Note that characters values are preserved even if the font cannot be displayed, so you can safely copy & paste garbled characters.)");
ImGui::Text("Hiragana: \xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91\xe3\x81\x93 (kakikukeko)");
ImGui::Text("Kanjis: \xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e (nihongo)");
static char buf[32] = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e";
@@ -6462,15 +6697,26 @@
bool focus_1 = ImGui::Button("Focus on 1"); ImGui::SameLine();
bool focus_2 = ImGui::Button("Focus on 2"); ImGui::SameLine();
bool focus_3 = ImGui::Button("Focus on 3");
+ int has_focus = 0;
static char buf[128] = "click on a button to set focus";
+
if (focus_1) ImGui::SetKeyboardFocusHere();
ImGui::InputText("1", buf, IM_ARRAYSIZE(buf));
+ if (ImGui::IsItemFocused()) has_focus = 1;
+
if (focus_2) ImGui::SetKeyboardFocusHere();
ImGui::InputText("2", buf, IM_ARRAYSIZE(buf));
+ if (ImGui::IsItemFocused()) has_focus = 2;
+
ImGui::PushAllowKeyboardFocus(false);
if (focus_3) ImGui::SetKeyboardFocusHere();
ImGui::InputText("3 (tab skip)", buf, IM_ARRAYSIZE(buf));
+ if (ImGui::IsItemFocused()) has_focus = 3;
ImGui::PopAllowKeyboardFocus();
+ if (has_focus)
+ ImGui::Text("Item with focus: %d", has_focus);
+ else
+ ImGui::Text("Item with focus: <none>");
ImGui::TreePop();
}
}
diff --git a/imgui.h b/imgui.h
index 715a206..05b5d3f 100644
--- a/imgui.h
+++ b/imgui.h
@@ -159,13 +159,16 @@
void SetKeyboardFocusHere(int offset = 0); // focus keyboard on the next widget. Use 'offset' to access sub components of a multiple component widget.
void SetTreeStateStorage(ImGuiStorage* tree); // replace tree state storage with our own (if you want to manipulate it yourself, typically clear subsection of it).
ImGuiStorage* GetTreeStateStorage();
- void PushItemWidth(float item_width);
+
+ void PushItemWidth(float item_width); // width of items for the common item+label case. default to ~2/3 of windows width.
void PopItemWidth();
float GetItemWidth();
void PushAllowKeyboardFocus(bool v); // allow focusing using TAB/Shift-TAB, enabled by default but you can disable it for certain widgets.
void PopAllowKeyboardFocus();
void PushStyleColor(ImGuiCol idx, const ImVec4& col);
void PopStyleColor();
+ void PushTextWrapPos(float wrap_pos_x); // word-wrapping for Text*() commands. < 0.0f: no wrapping; 0.0f: wrap to end of window (or column); > 0.0f: wrap at 'wrap_pos_x' position in window local space.
+ void PopTextWrapPos();
// Tooltip
void SetTooltip(const char* fmt, ...); // set tooltip under mouse-cursor, typically use with ImGui::IsHovered(). last call wins.
@@ -200,10 +203,12 @@
// Widgets
void Text(const char* fmt, ...);
void TextV(const char* fmt, va_list args);
- void TextColored(const ImVec4& col, const char* fmt, ...); // shortcut to doing PushStyleColor(ImGuiCol_Text, col); Text(fmt, ...); PopStyleColor();
+ void TextColored(const ImVec4& col, const char* fmt, ...); // shortcut for PushStyleColor(ImGuiCol_Text, col); Text(fmt, ...); PopStyleColor();
void TextColoredV(const ImVec4& col, const char* fmt, va_list args);
- void TextUnformatted(const char* text, const char* text_end = NULL); // doesn't require null terminated string if 'text_end' is specified. no copy done to any bounded stack buffer, better for long chunks of text.
- void LabelText(const char* label, const char* fmt, ...);
+ void TextWrapped(const char* fmt, ...); // shortcut for PushTextWrapPos(0.0f); Text(fmt, ...); PopTextWrapPos();
+ void TextWrappedV(const char* fmt, va_list args);
+ void TextUnformatted(const char* text, const char* text_end = NULL); // doesn't require null terminated string if 'text_end' is specified. no copy done to any bounded stack buffer, recommended for long chunks of text.
+ void LabelText(const char* label, const char* fmt, ...); // display text+label aligned the same way as value+label widgets
void LabelTextV(const char* label, const char* fmt, va_list args);
void BulletText(const char* fmt, ...);
void BulletTextV(const char* fmt, va_list args);
@@ -265,9 +270,10 @@
// Utilities
void SetNewWindowDefaultPos(const ImVec2& pos); // set position of window that do
bool IsHovered(); // was the last item active area hovered by mouse?
+ bool IsItemFocused(); // was the last item focused for keyboard input?
ImVec2 GetItemBoxMin(); // get bounding box of last item
ImVec2 GetItemBoxMax(); // get bounding box of last item
- bool IsClipped(const ImVec2& item_size); // to perform coarse clipping on user's side (as an optimisation)
+ bool IsClipped(const ImVec2& item_size); // to perform coarse clipping on user's side (as an optimization)
bool IsKeyPressed(int key_index, bool repeat = true); // key_index into the keys_down[512] array, imgui doesn't know the semantic of each entry
bool IsMouseClicked(int button, bool repeat = false);
bool IsMouseDoubleClicked(int button);
@@ -280,7 +286,7 @@
int GetFrameCount();
const char* GetStyleColorName(ImGuiCol idx);
void GetDefaultFontData(const void** fnt_data, unsigned int* fnt_size, const void** png_data, unsigned int* png_size);
- ImVec2 CalcTextSize(const char* text, const char* text_end = NULL, const bool hide_text_after_hash = true);
+ ImVec2 CalcTextSize(const char* text, const char* text_end = NULL, bool hide_text_after_hash = true, float wrap_width = -1.0f);
} // namespace ImGui
@@ -542,6 +548,7 @@
ImVector<char> Buf;
ImGuiTextBuffer() { Buf.push_back(0); }
+ ~ImGuiTextBuffer() { clear(); }
const char* begin() const { return Buf.begin(); }
const char* end() const { return Buf.end()-1; }
size_t size() const { return Buf.size()-1; }
@@ -619,8 +626,8 @@
void AddTriangleFilled(const ImVec2& a, const ImVec2& b, const ImVec2& c, ImU32 col);
void AddCircle(const ImVec2& centre, float radius, ImU32 col, int num_segments = 12);
void AddCircleFilled(const ImVec2& centre, float radius, ImU32 col, int num_segments = 12);
- void AddArc(const ImVec2& center, float rad, ImU32 col, int a_min, int a_max, bool tris=false, const ImVec2& third_point_offset = ImVec2(0,0));
- void AddText(ImFont font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end);
+ void AddArc(const ImVec2& center, float rad, ImU32 col, int a_min, int a_max, bool tris = false, const ImVec2& third_point_offset = ImVec2(0,0));
+ void AddText(ImFont font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end, float wrap_width = 0.0f);
};
// Optional bitmap font data loader & renderer into vertices
@@ -696,11 +703,16 @@
bool LoadFromFile(const char* filename);
void Clear();
void BuildLookupTable();
- const FntGlyph * FindGlyph(unsigned short c) const;
+ const FntGlyph * FindGlyph(unsigned short c, const FntGlyph* fallback = NULL) const;
float GetFontSize() const { return (float)Info->FontSize; }
bool IsLoaded() const { return Info != NULL && Common != NULL && Glyphs != NULL; }
- ImVec2 CalcTextSizeA(float size, float max_width, const char* text_begin, const char* text_end, const char** remaining = NULL) const; // utf8
- ImVec2 CalcTextSizeW(float size, float max_width, const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL) const; // wchar
- void RenderText(float size, ImVec2 pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, ImDrawVert*& out_vertices) const;
+ // '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.
+ ImVec2 CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end, const char** remaining = NULL) const; // utf8
+ ImVec2 CalcTextSizeW(float size, float max_width, const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL) const; // wchar
+ void RenderText(float size, ImVec2 pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, ImDrawVert*& out_vertices, float wrap_width = 0.0f) const;
+
+private:
+ const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width, const FntGlyph* fallback_glyph) const;
};