Created
June 23, 2016 17:23
-
-
Save Flix01/3e176c64540f51b99a4ec82cfaf759a5 to your computer and use it in GitHub Desktop.
ImDrawList methods to display vertical text in ImGui
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include <imgui.h> | |
#include <imgui_internal.h> | |
// ImDrawList methods to display vertical text | |
/* | |
// TEST: inside a window: | |
ImGuiWindow* window = ImGui::GetCurrentWindow(); | |
ImDrawList* dl = window->DrawList; | |
const ImVec2 scroll(ImGui::GetScrollX(),ImGui::GetScrollY()); | |
ImVec2 pos = ImGui::GetCursorScreenPos(); | |
pos.x = window->Pos.x + ImGui::GetStyle().WindowPadding.x; // Optional (to ensure we're at the far left of the window) | |
pos.x-=scroll.x;pos.y-=scroll.y; // Not sure this is necessary | |
const ImU32 col = ImGui::ColorConvertFloat4ToU32(ImVec4(1,1,1,1)); | |
static const char testString[] = "Test Text"; // It should work with '\n' inside too | |
// Test 1. clockwise text | |
ImGui::AddTextVertical(dl,pos,col,testString,NULL); // pos is at the top-left corner of the vertical text (text is written from top to bottom) | |
// Test 2. counter clockwise text | |
const ImVec2 textSize = ImGui::CalcVerticalTextSize(testString); // Too bad I need to call this every time (but I'm not sure I can loop UTF8 chars from text_end to text...). Moreover it seems to return a bigger y AFAICS. | |
pos.x= window->Pos.x + window->Size.x - ImGui::GetStyle().WindowPadding.x - scroll.x - textSize.x; // to ensure we're at the far right of the window MINUS the textSize.x) | |
pos.y+= textSize.y; | |
ImGui::AddTextVertical(dl,pos,col,testString,NULL,true); // counter-clockwise. pos is at the bottom-left corner of the vertical text (text is written from bottom to top). | |
*/ | |
namespace ImGui { | |
// It seems to return a bigger y... | |
ImVec2 CalcVerticalTextSize(const char* text, const char* text_end = NULL, bool hide_text_after_double_hash = false, float wrap_width = -1.0f) { | |
const ImVec2 rv = ImGui::CalcTextSize(text,text_end,hide_text_after_double_hash,wrap_width); | |
return ImVec2(rv.y,rv.x); | |
} | |
void RenderTextVertical(const ImFont* font,ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end=NULL, float wrap_width=0.0f, bool cpu_fine_clip=false, bool rotateCCW=false) | |
{ | |
if (!text_end) text_end = text_begin + strlen(text_begin); | |
const float scale = size / font->FontSize; | |
// Align to be pixel perfect | |
pos.x = (float)(int)pos.x;// + (rotateCCW ? (font->FontSize-font->DisplayOffset.y) : 0); // Not sure it's correct | |
pos.y = (float)(int)pos.y + font->DisplayOffset.x; | |
float x = pos.x; | |
float y = pos.y; | |
if (x > clip_rect.z) | |
return; | |
const float line_height = font->FontSize * scale; | |
const bool word_wrap_enabled = (wrap_width > 0.0f); | |
const char* word_wrap_eol = NULL; | |
const float y_dir = rotateCCW ? -1.f : 1.f; | |
// Skip non-visible lines | |
const char* s = text_begin; | |
if (!word_wrap_enabled && y + line_height < clip_rect.y) | |
while (s < text_end && *s != '\n') // Fast-forward to next line | |
s++; | |
// Reserve vertices for remaining worse case (over-reserving is useful and easily amortized) | |
const int vtx_count_max = (int)(text_end - s) * 4; | |
const int idx_count_max = (int)(text_end - s) * 6; | |
const int idx_expected_size = draw_list->IdxBuffer.Size + idx_count_max; | |
draw_list->PrimReserve(idx_count_max, vtx_count_max); | |
ImDrawVert* vtx_write = draw_list->_VtxWritePtr; | |
ImDrawIdx* idx_write = draw_list->_IdxWritePtr; | |
unsigned int vtx_current_idx = draw_list->_VtxCurrentIdx; | |
float x1=0.f,x2=0.f,y1=0.f,y2=0.f; | |
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 = font->CalcWordWrapPositionA(scale, s, text_end, wrap_width - (y - pos.y)); | |
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) | |
{ | |
y = pos.y; | |
x += line_height; | |
word_wrap_eol = NULL; | |
// Wrapping skips upcoming blanks | |
while (s < text_end) | |
{ | |
const char c = *s; | |
if (ImCharIsSpace(c)) { s++; } else if (c == '\n') { s++; break; } else { break; } | |
} | |
continue; | |
} | |
} | |
// Decode and advance source | |
unsigned int c = (unsigned int)*s; | |
if (c < 0x80) | |
{ | |
s += 1; | |
} | |
else | |
{ | |
s += ImTextCharFromUtf8(&c, s, text_end); | |
if (c == 0) | |
break; | |
} | |
if (c < 32) | |
{ | |
if (c == '\n') | |
{ | |
y = pos.y; | |
x += line_height; | |
if (x > clip_rect.z) | |
break; | |
if (!word_wrap_enabled && x + line_height < clip_rect.x) | |
while (s < text_end && *s != '\n') // Fast-forward to next line | |
s++; | |
continue; | |
} | |
if (c == '\r') | |
continue; | |
} | |
float char_width = 0.0f; | |
if (const ImFont::Glyph* glyph = font->FindGlyph((unsigned short)c)) | |
{ | |
char_width = glyph->XAdvance * scale; | |
// Arbitrarily assume that both space and tabs are empty glyphs as an optimization | |
if (c != ' ' && c != '\t') | |
{ | |
// We don't do a second finer clipping test on the Y axis as we've already skipped anything before clip_rect.y and exit once we pass clip_rect.w | |
if (!rotateCCW) { | |
x1 = x + (font->FontSize-glyph->Y1) * scale; | |
x2 = x + (font->FontSize-glyph->Y0) * scale; | |
y1 = y + glyph->X0 * scale; | |
y2 = y + glyph->X1 * scale; | |
} | |
else { | |
x1 = x + glyph->Y0 * scale; | |
x2 = x + glyph->Y1 * scale; | |
y1 = y + glyph->X0 * scale; | |
y2 = y + glyph->X1 * scale; | |
} | |
if (y1 <= clip_rect.w && y2 >= clip_rect.y) | |
{ | |
// Render a character | |
float u1 = glyph->U0; | |
float v1 = glyph->V0; | |
float u2 = glyph->U1; | |
float v2 = glyph->V1; | |
// CPU side clipping used to fit text in their frame when the frame is too small. Only does clipping for axis aligned quads. | |
if (cpu_fine_clip) | |
{ | |
if (x1 < clip_rect.x) | |
{ | |
u1 = u1 + (1.0f - (x2 - clip_rect.x) / (x2 - x1)) * (u2 - u1); | |
x1 = clip_rect.x; | |
} | |
if (y1 < clip_rect.y) | |
{ | |
v1 = v1 + (1.0f - (y2 - clip_rect.y) / (y2 - y1)) * (v2 - v1); | |
y1 = clip_rect.y; | |
} | |
if (x2 > clip_rect.z) | |
{ | |
u2 = u1 + ((clip_rect.z - x1) / (x2 - x1)) * (u2 - u1); | |
x2 = clip_rect.z; | |
} | |
if (y2 > clip_rect.w) | |
{ | |
v2 = v1 + ((clip_rect.w - y1) / (y2 - y1)) * (v2 - v1); | |
y2 = clip_rect.w; | |
} | |
if (x1 >= x2) | |
{ | |
y += char_width*y_dir; | |
continue; | |
} | |
} | |
// We are NOT calling PrimRectUV() here because non-inlined causes too much overhead in a debug build. | |
// Inlined here: | |
{ | |
idx_write[0] = (ImDrawIdx)(vtx_current_idx); idx_write[1] = (ImDrawIdx)(vtx_current_idx+1); idx_write[2] = (ImDrawIdx)(vtx_current_idx+2); | |
idx_write[3] = (ImDrawIdx)(vtx_current_idx); idx_write[4] = (ImDrawIdx)(vtx_current_idx+2); idx_write[5] = (ImDrawIdx)(vtx_current_idx+3); | |
vtx_write[0].col = vtx_write[1].col = vtx_write[2].col = vtx_write[3].col = col; | |
vtx_write[0].pos.x = x1; vtx_write[0].pos.y = y1; | |
vtx_write[1].pos.x = x2; vtx_write[1].pos.y = y1; | |
vtx_write[2].pos.x = x2; vtx_write[2].pos.y = y2; | |
vtx_write[3].pos.x = x1; vtx_write[3].pos.y = y2; | |
if (rotateCCW) { | |
vtx_write[0].uv.x = u2; vtx_write[0].uv.y = v1; | |
vtx_write[1].uv.x = u2; vtx_write[1].uv.y = v2; | |
vtx_write[2].uv.x = u1; vtx_write[2].uv.y = v2; | |
vtx_write[3].uv.x = u1; vtx_write[3].uv.y = v1; | |
} | |
else { | |
vtx_write[0].uv.x = u1; vtx_write[0].uv.y = v2; | |
vtx_write[1].uv.x = u1; vtx_write[1].uv.y = v1; | |
vtx_write[2].uv.x = u2; vtx_write[2].uv.y = v1; | |
vtx_write[3].uv.x = u2; vtx_write[3].uv.y = v2; | |
} | |
vtx_write += 4; | |
vtx_current_idx += 4; | |
idx_write += 6; | |
} | |
} | |
} | |
} | |
y += char_width*y_dir; | |
} | |
// Give back unused vertices | |
draw_list->VtxBuffer.resize((int)(vtx_write - draw_list->VtxBuffer.Data)); | |
draw_list->IdxBuffer.resize((int)(idx_write - draw_list->IdxBuffer.Data)); | |
draw_list->CmdBuffer[draw_list->CmdBuffer.Size-1].ElemCount -= (idx_expected_size - draw_list->IdxBuffer.Size); | |
draw_list->_VtxWritePtr = vtx_write; | |
draw_list->_IdxWritePtr = idx_write; | |
draw_list->_VtxCurrentIdx = (unsigned int)draw_list->VtxBuffer.Size; | |
} | |
void AddTextVertical(ImDrawList* drawList,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,bool rotateCCW = false) | |
{ | |
if ((col >> 24) == 0) | |
return; | |
if (text_end == NULL) | |
text_end = text_begin + strlen(text_begin); | |
if (text_begin == text_end) | |
return; | |
// Note: This is one of the few instance of breaking the encapsulation of ImDrawList, as we pull this from ImGui state, but it is just SO useful. | |
// Might just move Font/FontSize to ImDrawList? | |
if (font == NULL) | |
font = GImGui->Font; | |
if (font_size == 0.0f) | |
font_size = GImGui->FontSize; | |
IM_ASSERT(drawList && font->ContainerAtlas->TexID == drawList->_TextureIdStack.back()); // Use high-level ImGui::PushFont() or low-level ImDrawList::PushTextureId() to change font. | |
ImVec4 clip_rect = drawList->_ClipRectStack.back(); | |
if (cpu_fine_clip_rect) | |
{ | |
clip_rect.x = ImMax(clip_rect.x, cpu_fine_clip_rect->x); | |
clip_rect.y = ImMax(clip_rect.y, cpu_fine_clip_rect->y); | |
clip_rect.z = ImMin(clip_rect.z, cpu_fine_clip_rect->z); | |
clip_rect.w = ImMin(clip_rect.w, cpu_fine_clip_rect->w); | |
} | |
RenderTextVertical(font, drawList, font_size, pos, col, clip_rect, text_begin, text_end, wrap_width, cpu_fine_clip_rect != NULL,rotateCCW); | |
} | |
void AddTextVertical(ImDrawList* drawList,const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end,bool rotateCCW = false) | |
{ | |
AddTextVertical(drawList,GImGui->Font, GImGui->FontSize, pos, col, text_begin, text_end,0.0f,NULL,rotateCCW); | |
} | |
} // namespace ImGui |
Author
Flix01
commented
Jun 23, 2016
Nice idea. But complicated for use :/
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment