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.
This commit is contained in:
omar 2014-11-07 14:45:56 +09:00
parent 74363c5a43
commit 78645a7dba
2 changed files with 396 additions and 138 deletions

496
imgui.cpp
View File

@ -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 @@ namespace ImGui
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 @@ struct ImGuiDrawContext
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 @@ struct ImGuiDrawContext
TreeDepth = 0;
LastItemAabb = ImGuiAabb(0.0f,0.0f,0.0f,0.0f);
LastItemHovered = false;
LastItemFocused = true;
StateStorage = NULL;
OpenNextNode = -1;
@ -1068,6 +1071,9 @@ bool ImGuiWindow::FocusItemRegister(bool is_active)
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 void LogText(const ImVec2& ref_pos, const char* text, const char* text_en
}
}
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 @@ static void RenderText(ImVec2 pos, const char* text, const char* text_end, const
}
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 @@ static void RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border,
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 @@ static void RenderCollapseTriangle(ImVec2 p_min, bool open, float scale, bool sh
// 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 @@ ImVec2 CalcTextSize(const char* text, const char* text_end, const bool hide_text
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 @@ bool IsHovered()
return window->DC.LastItemHovered;
}
bool IsItemFocused()
{
ImGuiWindow* window = GetCurrentWindow();
return window->DC.LastItemFocused;
}
ImVec2 GetItemBoxMin()
{
ImGuiWindow* window = GetCurrentWindow();
@ -2299,6 +2326,8 @@ bool Begin(const char* name, bool* open, ImVec2 size, float fill_alpha, ImGuiWin
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 @@ void PopAllowKeyboardFocus()
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 @@ ImVec2 GetWindowContentRegionMin()
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 @@ void TextColored(const ImVec4& col, const char* fmt, ...)
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 @@ void TextUnformatted(const char* text, const char* text_end)
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 @@ void TextUnformatted(const char* text, const char* text_end)
}
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 @@ void TextUnformatted(const char* text, const char* text_end)
// 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 @@ bool SliderFloat(const char* label, float* v, float v_min, float v_max, const ch
}
}
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 @@ bool SliderFloat(const char* label, float* v, float v_min, float v_max, const ch
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 @@ bool RadioButton(const char* label, int* v, int v_button)
// 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 @@ void ImGuiTextEditState::UpdateScrollOffset()
{
// 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 @@ const char* ImGuiTextEditState::GetTextPointerClippedA(ImFont font, float font_s
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 @@ static bool ClipAdvance(const ImGuiAabb& bb)
{
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::AddCircleFilled(const ImVec2& centre, float radius, ImU32 col,
}
}
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 @@ void ImDrawList::AddText(ImFont font, float font_size, const ImVec2& pos, ImU32
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 @@ void ImBitmapFont::BuildLookupTable()
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 @@ const ImBitmapFont::FntGlyph* ImBitmapFont::FindGlyph(unsigned short c) const
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 @@ static ptrdiff_t ImTextStrToUtf8(char* buf, size_t buf_size, const ImWchar* in_t
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
{
// 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 (max_width == 0.0f)
max_width = FLT_MAX;
if (!text_end)
text_end = text_begin + strlen(text_begin);
@ -5649,40 +5811,70 @@ ImVec2 ImBitmapFont::CalcTextSizeA(float size, float max_width, const char* text
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::CalcTextSizeA(float size, float max_width, const char* text
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 @@ ImVec2 ImBitmapFont::CalcTextSizeW(float size, float max_width, const ImWchar* t
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 @@ ImVec2 ImBitmapFont::CalcTextSizeW(float size, float max_width, const ImWchar* t
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 @@ void ImBitmapFont::RenderText(float size, ImVec2 pos, ImU32 col, const ImVec4& c
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 @@ void ImBitmapFont::RenderText(float size, ImVec2 pos, ImU32 col, const ImVec4& c
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 @@ void ShowUserGuide()
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 @@ void ShowTestWindow(bool* open)
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 @@ void ShowTestWindow(bool* open)
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();
}
}

36
imgui.h
View File

@ -159,13 +159,16 @@ namespace ImGui
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 @@ namespace ImGui
// 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 @@ namespace ImGui
// 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 @@ namespace ImGui
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 @@ struct ImGuiTextBuffer
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 @@ struct ImDrawList
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 @@ struct ImBitmapFont
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;
};