ImStrv: Step 2 - change ImStrv typedef to struct, perform all other logic conversion.

Squashed commits (initially a commit from rokups + many rework by ocornut. keeping them separate commits made rebasing unnecessarily tricking so merged from 2024/02)
ImStrv: many fixes (see details), added imconfig class extension example, added natvis description.
ImStrv: rework toward ensuring End is always set to constant can be compile time calculated
ImStrv: using length(), fix ambiguous empty() function, fix altered behaviors, removed unused operators.
ImStrv: various tweaks and fixes. removed ImGuiTextRange from ImGuiTextFilter, fix test engine hooks, removed constructor only used twice.
This commit is contained in:
ocornut
2020-08-24 11:37:01 +03:00
committed by ocornut
parent 327ae2b2e2
commit 9a1c0ce98c
9 changed files with 551 additions and 426 deletions

368
imgui.cpp
View File

@@ -2046,6 +2046,15 @@ ImVec2 ImTriangleClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c,
// [SECTION] MISC HELPERS/UTILITIES (String, Format, Hash functions)
//-----------------------------------------------------------------------------
int ImStrcmp(ImStrv str1, ImStrv str2)
{
size_t str1_len = str1.length();
size_t str2_len = str2.length();
if (str1_len != str2_len)
return (int)str1_len - (int)str2_len;
return memcmp(str1.Begin, str2.Begin, str1_len);
}
// Consider using _stricmp/_strnicmp under Windows or strcasecmp/strncasecmp. We don't actually use either ImStricmp/ImStrnicmp in the codebase any more.
int ImStricmp(const char* str1, const char* str2)
{
@@ -2070,6 +2079,13 @@ void ImStrncpy(char* dst, const char* src, size_t count)
dst[count - 1] = 0;
}
void ImStrncpy(char* dst, ImStrv src, size_t count)
{
// Even though src does not necessarily include \0 terminator it is ok to include it. ImStrncpy above does not
// actually include that in a copy operation and inserts zero terminator manually.
ImStrncpy(dst, src.Begin, ImMin(count, src.length() + 1));
}
char* ImStrdup(const char* str)
{
size_t len = ImStrlen(str);
@@ -2077,16 +2093,20 @@ char* ImStrdup(const char* str)
return (char*)memcpy(buf, (const void*)str, len + 1);
}
void* ImMemdup(const void* src, size_t size)
char* ImStrdup(ImStrv str)
{
void* dst = IM_ALLOC(size);
return memcpy(dst, src, size);
size_t len = str.length();
void* buf = IM_ALLOC(len + 1);
*((char*)buf + len) = 0; // str may not contain \0, it must be inserted manually.
if (len > 0)
return (char*)memcpy(buf, (const void*)str.Begin, len);
return (char*)buf;
}
char* ImStrdupcpy(char* dst, size_t* p_dst_size, const char* src)
char* ImStrdupcpy(char* dst, size_t* p_dst_size, ImStrv src)
{
size_t dst_buf_size = p_dst_size ? *p_dst_size : ImStrlen(dst) + 1;
size_t src_size = ImStrlen(src) + 1;
size_t src_size = src.length() + 1;
if (dst_buf_size < src_size)
{
IM_FREE(dst);
@@ -2094,7 +2114,21 @@ char* ImStrdupcpy(char* dst, size_t* p_dst_size, const char* src)
if (p_dst_size)
*p_dst_size = src_size;
}
return (char*)memcpy(dst, (const void*)src, src_size);
dst[src_size - 1] = 0; // str may not contain \0, it must be inserted manually.
if (src_size > 1)
return (char*)memcpy(dst, (const void*)src.Begin, src_size - 1);
return dst;
}
char* ImStrdupcpy(char* dst, size_t* p_dst_size, const char* src)
{
return ImStrdupcpy(dst, p_dst_size, ImStrv(src));
}
void* ImMemdup(const void* src, size_t size)
{
void* dst = IM_ALLOC(size);
return memcpy(dst, src, size);
}
const char* ImStrchrRange(const char* str, const char* str_end, char c)
@@ -2148,6 +2182,26 @@ const char* ImStristr(const char* haystack, const char* haystack_end, const char
return NULL;
}
// FIXME-IMSTR: probably unneeded.
const char* ImStrstr(ImStrv haystack, ImStrv needle)
{
const char un0 = (char)*needle.Begin;
while ((!haystack.End && *haystack.Begin) || (haystack.End && haystack.Begin < haystack.End))
{
if (*haystack.Begin == un0)
{
const char* b = needle.Begin + 1;
for (const char* a = haystack.Begin + 1; b < needle.End; a++, b++)
if (*a != *b)
break;
if (b == needle.End)
return haystack.Begin;
}
haystack.Begin++;
}
return NULL;
}
// Trim str by offsetting contents when there's leading data + writing a \0 at the trailing position. We use this in situation where the cost is negligible.
void ImStrTrimBlanks(char* buf)
{
@@ -2229,11 +2283,11 @@ int ImFormatStringV(char* buf, size_t buf_size, const char* fmt, va_list args)
}
#endif // #ifdef IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS
void ImFormatStringToTempBuffer(const char** out_buf, const char** out_buf_end, const char* fmt, ...)
void ImFormatStringToTempBuffer(ImStrv* out_buf, const char* fmt, ...)
{
va_list args;
va_start(args, fmt);
ImFormatStringToTempBufferV(out_buf, out_buf_end, fmt, args);
ImFormatStringToTempBufferV(out_buf, fmt, args);
va_end(args);
}
@@ -2241,7 +2295,7 @@ void ImFormatStringToTempBuffer(const char** out_buf, const char** out_buf_end,
// by making the caller acquire a temp buffer token, with either explicit or destructor release, e.g.
// ImGuiTempBufferToken token;
// ImFormatStringToTempBuffer(token, ...);
void ImFormatStringToTempBufferV(const char** out_buf, const char** out_buf_end, const char* fmt, va_list args)
void ImFormatStringToTempBufferV(ImStrv* out_buf, const char* fmt, va_list args)
{
ImGuiContext& g = *GImGui;
if (fmt[0] == '%' && fmt[1] == 's' && fmt[2] == 0)
@@ -2249,8 +2303,8 @@ void ImFormatStringToTempBufferV(const char** out_buf, const char** out_buf_end,
const char* buf = va_arg(args, const char*); // Skip formatting when using "%s"
if (buf == NULL)
buf = "(null)";
*out_buf = buf;
if (out_buf_end) { *out_buf_end = buf + ImStrlen(buf); }
out_buf->Begin = buf;
out_buf->End = buf + ImStrlen(buf);
}
else if (fmt[0] == '%' && fmt[1] == '.' && fmt[2] == '*' && fmt[3] == 's' && fmt[4] == 0)
{
@@ -2261,14 +2315,14 @@ void ImFormatStringToTempBufferV(const char** out_buf, const char** out_buf_end,
buf = "(null)";
buf_len = ImMin(buf_len, 6);
}
*out_buf = buf;
*out_buf_end = buf + buf_len; // Disallow not passing 'out_buf_end' here. User is expected to use it.
out_buf->Begin = buf;
out_buf->End = buf + buf_len;
}
else
{
int buf_len = ImFormatStringV(g.TempBuffer.Data, g.TempBuffer.Size, fmt, args);
*out_buf = g.TempBuffer.Data;
if (out_buf_end) { *out_buf_end = g.TempBuffer.Data + buf_len; }
out_buf->Begin = g.TempBuffer.Data;
out_buf->End = g.TempBuffer.Data + buf_len;
}
}
@@ -2349,16 +2403,17 @@ ImGuiID ImHashData(const void* data_p, size_t data_size, ImGuiID seed)
// - If we reach ### in the string we discard the hash so far and reset to the seed.
// - We don't do 'current += 2; continue;' after handling ### to keep the code smaller/faster (measured ~10% diff in Debug build)
// FIXME-OPT: Replace with e.g. FNV1a hash? CRC32 pretty much randomly access 1KB. Need to do proper measurements.
ImGuiID ImHashStr(const char* data_p, size_t data_size, ImGuiID seed)
ImGuiID ImHashStr(ImStrv str, ImGuiID seed)
{
seed = ~seed;
ImU32 crc = seed;
const unsigned char* data = (const unsigned char*)data_p;
const unsigned char* data = (const unsigned char*)str.Begin;
#ifndef IMGUI_ENABLE_SSE4_2_CRC
const ImU32* crc32_lut = GCrc32LookupTable;
#endif
if (data_size != 0)
if (str.End != NULL)
{
size_t data_size = str.length();
while (data_size-- != 0)
{
unsigned char c = *data++;
@@ -2389,12 +2444,12 @@ ImGuiID ImHashStr(const char* data_p, size_t data_size, ImGuiID seed)
// Skip to the "###" marker if any. We don't skip past to match the behavior of GetID()
// FIXME-OPT: This is not designed to be optimal. Use with care.
const char* ImHashSkipUncontributingPrefix(const char* label)
const char* ImHashSkipUncontributingPrefix(ImStrv label)
{
const char* result = label;
while (unsigned char c = *label++)
if (c == '#' && label[0] == '#' && label[1] == '#')
result = label - 1;
const char* result = label.Begin;
for (const char* p = label.Begin; p < label.End; p++)
if (p[0] == '#' && p[1] == '#' && p[2] == '#')
result = p;
return result;
}
@@ -2410,8 +2465,8 @@ ImFileHandle ImFileOpen(ImStrv filename, ImStrv mode)
#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) && (defined(__MINGW32__) || (!defined(__CYGWIN__) && !defined(__GNUC__)))
// We need a fopen() wrapper because MSVC/Windows fopen doesn't handle UTF-8 filenames.
// Previously we used ImTextCountCharsFromUtf8/ImTextStrFromUtf8 here but we now need to support ImWchar16 and ImWchar32!
const int filename_wsize = ::MultiByteToWideChar(CP_UTF8, 0, filename, -1, NULL, 0);
const int mode_wsize = ::MultiByteToWideChar(CP_UTF8, 0, mode, -1, NULL, 0);
const int filename_wsize = ::MultiByteToWideChar(CP_UTF8, 0, filename.Begin, (int)filename.length() + 1, NULL, 0);
const int mode_wsize = ::MultiByteToWideChar(CP_UTF8, 0, mode.Begin, (int)mode.length() + 1, NULL, 0);
// Use stack buffer if possible, otherwise heap buffer. Sizes include zero terminator.
// We don't rely on current ImGuiContext as this is implied to be a helper function which doesn't depend on it (see #7314).
@@ -2421,11 +2476,19 @@ ImFileHandle ImFileOpen(ImStrv filename, ImStrv mode)
local_temp_heap.resize(filename_wsize + mode_wsize);
wchar_t* filename_wbuf = local_temp_heap.Data ? local_temp_heap.Data : local_temp_stack;
wchar_t* mode_wbuf = filename_wbuf + filename_wsize;
::MultiByteToWideChar(CP_UTF8, 0, filename, -1, filename_wbuf, filename_wsize);
::MultiByteToWideChar(CP_UTF8, 0, mode, -1, mode_wbuf, mode_wsize);
::MultiByteToWideChar(CP_UTF8, 0, filename.Begin, (int)filename.length(), filename_wbuf, filename_wsize);
::MultiByteToWideChar(CP_UTF8, 0, mode.Begin, (int)mode.length(), mode_wbuf, mode_wsize);
filename_wbuf[filename_wsize - 1] = mode_wbuf[mode_wsize - 1] = 0;
return ::_wfopen(filename_wbuf, mode_wbuf);
#else
return fopen(filename, mode);
// ImStrv is not guaranteed to be zero-terminated.
// FIXME-IMSTR: Use TempBuffer to avoid needlessly allocating.
ImStrv filename_0 = ImStrdup(filename);
ImStrv mode_0 = ImStrdup(mode);
ImFileHandle handle = fopen(filename_0.Begin, mode_0.Begin);
IM_FREE((char*)(void*)filename_0.Begin);
IM_FREE((char*)(void*)mode_0.Begin);
return handle;
#endif
}
@@ -2667,16 +2730,13 @@ const char* ImTextFindPreviousUtf8Codepoint(const char* in_text_start, const cha
return in_text_start;
}
int ImTextCountLines(const char* in_text, const char* in_text_end)
int ImTextCountLines(ImStrv in_text)
{
if (in_text_end == NULL)
in_text_end = in_text + ImStrlen(in_text); // FIXME-OPT: Not optimal approach, discourage use for now.
int count = 0;
while (in_text < in_text_end)
for (const char* p = in_text.Begin, * p_end = in_text.End; p < p_end; count++)
{
const char* line_end = (const char*)ImMemchr(in_text, '\n', in_text_end - in_text);
in_text = line_end ? line_end + 1 : in_text_end;
count++;
const char* line_end = (const char*)ImMemchr(p, '\n', p_end - p);
p = line_end ? line_end + 1 : p_end;
}
return count;
}
@@ -2934,66 +2994,64 @@ bool ImGuiTextFilter::Draw(ImStrv label, float width)
return value_changed;
}
void ImGuiTextFilter::ImGuiTextRange::split(char separator, ImVector<ImGuiTextRange>* out) const
static void ImStrplit(ImStrv in, char separator, ImVector<ImStrv>* out)
{
out->resize(0);
const char* wb = b;
const char* wb = in.Begin;
const char* we = wb;
while (we < e)
while (we < in.End)
{
if (*we == separator)
{
out->push_back(ImGuiTextRange(wb, we));
out->push_back(ImStrv(wb, we));
wb = we + 1;
}
we++;
}
if (wb != we)
out->push_back(ImGuiTextRange(wb, we));
out->push_back(ImStrv(wb, we));
}
void ImGuiTextFilter::Build()
{
Filters.resize(0);
ImGuiTextRange input_range(InputBuf, InputBuf + ImStrlen(InputBuf));
input_range.split(',', &Filters);
ImStrplit(ImStrv(InputBuf, InputBuf + ImStrlen(InputBuf)), ',', &Filters);
CountGrep = 0;
for (ImGuiTextRange& f : Filters)
for (ImStrv& f : Filters)
{
while (f.b < f.e && ImCharIsBlankA(f.b[0]))
f.b++;
while (f.e > f.b && ImCharIsBlankA(f.e[-1]))
f.e--;
while (f.Begin < f.End && ImCharIsBlankA(f.Begin[0]))
f.Begin++;
while (f.End > f.Begin && ImCharIsBlankA(f.End[-1]))
f.End--;
if (f.empty())
continue;
if (f.b[0] != '-')
if (f.Begin[0] != '-')
CountGrep += 1;
}
}
bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const
bool ImGuiTextFilter::PassFilter(ImStrv text) const
{
if (Filters.Size == 0)
return true;
if (text == NULL)
text = text_end = "";
if (!text)
text = "";
for (const ImGuiTextRange& f : Filters)
for (const ImStrv& f : Filters)
{
if (f.b == f.e)
if (f.Begin == f.End)
continue;
if (f.b[0] == '-')
if (f.Begin[0] == '-')
{
// Subtract
if (ImStristr(text, text_end, f.b + 1, f.e) != NULL)
if (ImStristr(text.Begin, text.End, f.Begin + 1, f.End) != NULL)
return false;
}
else
{
// Grep
if (ImStristr(text, text_end, f.b, f.e) != NULL)
if (ImStristr(text.Begin, text.End, f.Begin, f.End) != NULL)
return true;
}
}
@@ -3021,9 +3079,11 @@ bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const
char ImGuiTextBuffer::EmptyString[1] = { 0 };
void ImGuiTextBuffer::append(const char* str, const char* str_end)
void ImGuiTextBuffer::append(ImStrv str)
{
int len = str_end ? (int)(str_end - str) : (int)ImStrlen(str);
int len = (int)str.length();
if (len == 0)
return;
// Add zero-terminator the first time
const int write_off = (Buf.Size != 0) ? Buf.Size : 1;
@@ -3035,7 +3095,7 @@ void ImGuiTextBuffer::append(const char* str, const char* str_end)
}
Buf.resize(needed_sz);
memcpy(&Buf[write_off - 1], str, (size_t)len);
memcpy(&Buf[write_off - 1], str.Begin, (size_t)len);
Buf[write_off - 1 + len] = 0;
}
@@ -3708,58 +3768,43 @@ const char* ImGui::GetStyleColorName(ImGuiCol idx)
// Also see imgui_draw.cpp for some more which have been reworked to not rely on ImGui:: context.
//-----------------------------------------------------------------------------
const char* ImGui::FindRenderedTextEnd(const char* text, const char* text_end)
const char* ImGui::FindRenderedTextEnd(ImStrv text)
{
const char* text_display_end = text;
if (!text_end)
text_end = (const char*)-1;
while (text_display_end < text_end && *text_display_end != '\0' && (text_display_end[0] != '#' || text_display_end[1] != '#'))
const char* text_display_end = text.Begin;
while (text_display_end < text.End && (text_display_end[0] != '#' || text_display_end[1] != '#'))
text_display_end++;
return text_display_end;
}
// Internal ImGui functions to render text
// RenderText***() functions calls ImDrawList::AddText() calls ImBitmapFont::RenderText()
void ImGui::RenderText(ImVec2 pos, const char* text, const char* text_end, bool hide_text_after_hash)
void ImGui::RenderText(ImVec2 pos, ImStrv text, bool hide_text_after_hash)
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
// Hide anything after a '##' string
const char* text_display_end;
if (hide_text_after_hash)
{
text_display_end = FindRenderedTextEnd(text, text_end);
}
else
{
if (!text_end)
text_end = text + ImStrlen(text); // FIXME-OPT
text_display_end = text_end;
}
text.End = FindRenderedTextEnd(text);
if (text != text_display_end)
if (text.Begin != text.End)
{
window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end);
window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text);
if (g.LogEnabled)
LogRenderedText(&pos, text, text_display_end);
LogRenderedText(&pos, text);
}
}
void ImGui::RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end, float wrap_width)
void ImGui::RenderTextWrapped(ImVec2 pos, ImStrv text, float wrap_width)
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
if (!text_end)
text_end = text + ImStrlen(text); // FIXME-OPT
if (text != text_end)
if (text.Begin != text.End)
{
window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_end, wrap_width);
window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, wrap_width);
if (g.LogEnabled)
LogRenderedText(&pos, text, text_end);
LogRenderedText(&pos, text);
}
}
@@ -3768,11 +3813,11 @@ void ImGui::RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end
// FIXME-OPT: Since we have or calculate text_size we could coarse clip whole block immediately, especially for text above draw_list->DrawList.
// Effectively as this is called from widget doing their own coarse clipping it's not very valuable presently. Next time function will take
// better advantage of the render function taking size into account for coarse clipping.
void ImGui::RenderTextClippedEx(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_display_end, const ImVec2* text_size_if_known, const ImVec2& align, const ImRect* clip_rect)
void ImGui::RenderTextClippedEx(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, ImStrv text, const ImVec2* text_size_if_known, const ImVec2& align, const ImRect* clip_rect)
{
// Perform CPU side clipping for single clipped element to avoid using scissor state
ImVec2 pos = pos_min;
const ImVec2 text_size = text_size_if_known ? *text_size_if_known : CalcTextSize(text, text_display_end, false, 0.0f);
const ImVec2 text_size = text_size_if_known ? *text_size_if_known : CalcTextSize(text, false, 0.0f);
const ImVec2* clip_min = clip_rect ? &clip_rect->Min : &pos_min;
const ImVec2* clip_max = clip_rect ? &clip_rect->Max : &pos_max;
@@ -3788,39 +3833,37 @@ void ImGui::RenderTextClippedEx(ImDrawList* draw_list, const ImVec2& pos_min, co
if (need_clipping)
{
ImVec4 fine_clip_rect(clip_min->x, clip_min->y, clip_max->x, clip_max->y);
draw_list->AddText(NULL, 0.0f, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, &fine_clip_rect);
draw_list->AddText(NULL, 0.0f, pos, GetColorU32(ImGuiCol_Text), text, 0.0f, &fine_clip_rect);
}
else
{
draw_list->AddText(NULL, 0.0f, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, NULL);
draw_list->AddText(NULL, 0.0f, pos, GetColorU32(ImGuiCol_Text), text, 0.0f, NULL);
}
}
void ImGui::RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align, const ImRect* clip_rect)
void ImGui::RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, ImStrv text, const ImVec2* text_size_if_known, const ImVec2& align, const ImRect* clip_rect)
{
// Hide anything after a '##' string
const char* text_display_end = FindRenderedTextEnd(text, text_end);
const int text_len = (int)(text_display_end - text);
if (text_len == 0)
// FIXME-IMSTR: This is not new but should be moved out of there.
text.End = FindRenderedTextEnd(text);
if (text.Begin == text.End)
return;
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
RenderTextClippedEx(window->DrawList, pos_min, pos_max, text, text_display_end, text_size_if_known, align, clip_rect);
RenderTextClippedEx(window->DrawList, pos_min, pos_max, text, text_size_if_known, align, clip_rect);
if (g.LogEnabled)
LogRenderedText(&pos_min, text, text_display_end);
LogRenderedText(&pos_min, text);
}
// 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) from 'ellipsis_max_x' which may be beyond it.
// 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.
// (BREAKING) On 2025/04/16 we removed the 'float clip_max_x' parameters which was preceeding 'float ellipsis_max' and was the same value for 99% of users.
void ImGui::RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, 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 ellipsis_max_x, ImStrv text, const ImVec2* text_size_if_known)
{
ImGuiContext& g = *GImGui;
if (text_end_full == NULL)
text_end_full = FindRenderedTextEnd(text);
const ImVec2 text_size = text_size_if_known ? *text_size_if_known : CalcTextSize(text, text_end_full, false, 0.0f);
const ImVec2 text_size = text_size_if_known ? *text_size_if_known : CalcTextSize(text, false, 0.0f);
//draw_list->AddLine(ImVec2(pos_max.x, pos_min.y - 4), ImVec2(pos_max.x, pos_max.y + 6), IM_COL32(0, 0, 255, 255));
//draw_list->AddLine(ImVec2(ellipsis_max_x, pos_min.y - 2), ImVec2(ellipsis_max_x, pos_max.y + 3), IM_COL32(0, 255, 0, 255));
@@ -3842,27 +3885,27 @@ void ImGui::RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, con
// We can now claim the space between pos_max.x and ellipsis_max.x
const float text_avail_width = ImMax((ImMax(pos_max.x, ellipsis_max_x) - ellipsis_width) - pos_min.x, 1.0f);
float text_size_clipped_x = font->CalcTextSizeA(font_size, text_avail_width, 0.0f, text, text_end_full, &text_end_ellipsis).x;
while (text_end_ellipsis > text && ImCharIsBlankA(text_end_ellipsis[-1]))
float text_size_clipped_x = font->CalcTextSizeA(font_size, text_avail_width, 0.0f, text, &text_end_ellipsis).x;
while (text_end_ellipsis > text.Begin && ImCharIsBlankA(text_end_ellipsis[-1]))
{
// Trim trailing space before ellipsis (FIXME: Supporting non-ascii blanks would be nice, for this we need a function to backtrack in UTF-8 text)
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
text_size_clipped_x -= font->CalcTextSizeA(font_size, FLT_MAX, 0.0f, ImStrv(text_end_ellipsis, text_end_ellipsis + 1)).x; // Ascii blanks are always 1 byte
}
// Render text, render ellipsis
RenderTextClippedEx(draw_list, pos_min, pos_max, text, text_end_ellipsis, &text_size, ImVec2(0.0f, 0.0f));
RenderTextClippedEx(draw_list, pos_min, pos_max, ImStrv(text.Begin, text_end_ellipsis), &text_size, ImVec2(0.0f, 0.0f));
ImVec4 cpu_fine_clip_rect(pos_min.x, pos_min.y, pos_max.x, pos_max.y);
ImVec2 ellipsis_pos = ImTrunc(ImVec2(pos_min.x + text_size_clipped_x, pos_min.y));
font->RenderChar(draw_list, font_size, ellipsis_pos, GetColorU32(ImGuiCol_Text), font->EllipsisChar, &cpu_fine_clip_rect);
}
else
{
RenderTextClippedEx(draw_list, pos_min, pos_max, text, text_end_full, &text_size, ImVec2(0.0f, 0.0f));
RenderTextClippedEx(draw_list, pos_min, pos_max, text, &text_size, ImVec2(0.0f, 0.0f));
}
if (g.LogEnabled)
LogRenderedText(&pos_min, text, text_end_full);
LogRenderedText(&pos_min, text);
}
// Render a rectangle shaped with optional rounding and borders
@@ -4027,7 +4070,7 @@ static const ImGuiLocEntry GLocalizationEntriesEnUS[] =
{ ImGuiLocKey_WindowingMainMenuBar, "(Main menu bar)" },
{ ImGuiLocKey_WindowingPopup, "(Popup)" },
{ ImGuiLocKey_WindowingUntitled, "(Untitled)" },
{ ImGuiLocKey_OpenLink_s, "Open '%s'" },
{ ImGuiLocKey_OpenLink_s, "Open '%.*s'" },
{ ImGuiLocKey_CopyLink, "Copy Link###CopyLink" },
};
@@ -4444,7 +4487,7 @@ ImGuiWindow::ImGuiWindow(ImGuiContext* ctx, ImStrv name) : DrawListInst(NULL)
memset(this, 0, sizeof(*this));
Ctx = ctx;
Name = ImStrdup(name);
NameBufLen = (int)ImStrlen(name) + 1;
NameBufLen = (int)name.length() + 1;
ID = ImHashStr(name);
IDStack.push_back(ID);
MoveId = GetID("#MOVE");
@@ -5000,7 +5043,15 @@ void ImGui::SetClipboardText(ImStrv text)
{
ImGuiContext& g = *GImGui;
if (g.PlatformIO.Platform_SetClipboardTextFn != NULL)
g.PlatformIO.Platform_SetClipboardTextFn(&g, text);
{
int len = (int)text.length();
char* text_p = (char*)IM_ALLOC(len + 1);
if (len > 0)
memcpy(text_p, text.Begin, len);
text_p[len] = 0; // text may not contain \0, it must be inserted manually.
g.PlatformIO.Platform_SetClipboardTextFn(&g, text_p);
IM_FREE(text_p);
}
}
const char* ImGui::GetVersion()
@@ -5993,21 +6044,18 @@ void ImGui::Render()
// Calculate text size. Text can be multi-line. Optionally ignore text after a ## marker.
// CalcTextSize("") should return ImVec2(0.0f, g.FontSize)
ImVec2 ImGui::CalcTextSize(const char* text, const char* text_end, bool hide_text_after_double_hash, float wrap_width)
ImVec2 ImGui::CalcTextSize(ImStrv text, bool hide_text_after_double_hash, float wrap_width)
{
ImGuiContext& g = *GImGui;
const char* text_display_end;
if (hide_text_after_double_hash)
text_display_end = FindRenderedTextEnd(text, text_end); // Hide anything after a '##' string
else
text_display_end = text_end;
text.End = FindRenderedTextEnd(text); // Hide anything after a '##' string
ImFont* font = g.Font;
const float font_size = g.FontSize;
if (text == text_display_end)
if (text.Begin == text.End)
return ImVec2(0.0f, font_size);
ImVec2 text_size = font->CalcTextSizeA(font_size, FLT_MAX, wrap_width, text, text_display_end, NULL);
ImVec2 text_size = font->CalcTextSizeA(font_size, FLT_MAX, wrap_width, text, NULL);
// Round
// FIXME: This has been here since Dec 2015 (7b0bf230) but down the line we want this out.
@@ -6330,14 +6378,14 @@ bool ImGui::BeginChildEx(ImStrv name, ImGuiID id, const ImVec2& size_arg, ImGuiC
// Build up name. If you need to append to a same child from multiple location in the ID stack, use BeginChild(ImGuiID id) with a stable value.
// FIXME: 2023/11/14: commented out shorted version. We had an issue with multiple ### in child window path names, which the trailing hash helped workaround.
// e.g. "ParentName###ParentIdentifier/ChildName###ChildIdentifier" would get hashed incorrectly by ImHashStr(), trailing _%08X somehow fixes it.
const char* temp_window_name;
ImStrv temp_window_name;
/*if (name && parent_window->IDStack.back() == parent_window->ID)
ImFormatStringToTempBuffer(&temp_window_name, NULL, "%s/%s", parent_window->Name, name); // May omit ID if in root of ID stack
ImFormatStringToTempBuffer(&temp_window_name, "%s/%.*s", parent_window->Name, (int)name.length(), name.Begin); // May omit ID if in root of ID stack
else*/
if (name)
ImFormatStringToTempBuffer(&temp_window_name, NULL, "%s/%s_%08X", parent_window->Name, name, id);
ImFormatStringToTempBuffer(&temp_window_name, "%s/%.*s_%08X", parent_window->Name, (int)name.length(), name.Begin, id);
else
ImFormatStringToTempBuffer(&temp_window_name, NULL, "%s/%08X", parent_window->Name, id);
ImFormatStringToTempBuffer(&temp_window_name, "%s/%08X", parent_window->Name, id);
// Set style
const float backup_border_size = g.Style.ChildBorderSize;
@@ -7140,7 +7188,7 @@ void ImGui::RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& titl
// Title bar text (with: horizontal alignment, avoiding collapse/close button, optional "unsaved document" marker)
// FIXME: Refactor text alignment facilities along with RenderText helpers, this is WAY too much messy code..
const float marker_size_x = (flags & ImGuiWindowFlags_UnsavedDocument) ? button_sz * 0.80f : 0.0f;
const ImVec2 text_size = CalcTextSize(name, NULL, true) + ImVec2(marker_size_x, 0.0f);
const ImVec2 text_size = CalcTextSize(name, true) + ImVec2(marker_size_x, 0.0f);
// As a nice touch we try to ensure that centered title text doesn't get affected by visibility of Close/Collapse button,
// while uncentered title text will still reach edges correctly.
@@ -7171,7 +7219,7 @@ void ImGui::RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& titl
}
//if (g.IO.KeyShift) window->DrawList->AddRect(layout_r.Min, layout_r.Max, IM_COL32(255, 128, 0, 255)); // [DEBUG]
//if (g.IO.KeyCtrl) window->DrawList->AddRect(clip_r.Min, clip_r.Max, IM_COL32(255, 128, 0, 255)); // [DEBUG]
RenderTextClipped(layout_r.Min, layout_r.Max, name, NULL, &text_size, style.WindowTitleAlign, &clip_r);
RenderTextClipped(layout_r.Min, layout_r.Max, name, &text_size, style.WindowTitleAlign, &clip_r);
}
void ImGui::UpdateWindowParentAndRootLinks(ImGuiWindow* window, ImGuiWindowFlags flags, ImGuiWindow* parent_window)
@@ -7239,7 +7287,7 @@ bool ImGui::Begin(ImStrv name, bool* p_open, ImGuiWindowFlags flags)
{
ImGuiContext& g = *GImGui;
const ImGuiStyle& style = g.Style;
IM_ASSERT(name != NULL && name[0] != '\0'); // Window name required
IM_ASSERT(name.Begin != name.End); // Window name required
IM_ASSERT(g.WithinFrameScope); // Forgot to call ImGui::NewFrame()
IM_ASSERT(g.FrameCountEnded != g.FrameCount); // Called ImGui::Render() or ImGui::EndFrame() and haven't called ImGui::NewFrame() again yet
@@ -7427,7 +7475,7 @@ bool ImGui::Begin(ImStrv name, bool* p_open, ImGuiWindowFlags flags)
window_title_visible_elsewhere = true;
if (flags & ImGuiWindowFlags_ChildMenu)
window_title_visible_elsewhere = true;
if (window_title_visible_elsewhere && !window_just_created && strcmp(name, window->Name) != 0)
if (window_title_visible_elsewhere && !window_just_created && ImStrcmp(name, window->Name) != 0)
{
size_t buf_len = (size_t)window->NameBufLen;
window->Name = ImStrdupcpy(window->Name, &buf_len, name);
@@ -8974,14 +9022,14 @@ void ImGui::PopFont()
// This is one of the very rare legacy case where we use ImGuiWindow methods,
// it should ideally be flattened at some point but it's been used a lots by widgets.
IM_MSVC_RUNTIME_CHECKS_OFF
ImGuiID ImGuiWindow::GetID(const char* str, const char* str_end)
ImGuiID ImGuiWindow::GetID(ImStrv str)
{
ImGuiID seed = IDStack.back();
ImGuiID id = ImHashStr(str, str_end ? (str_end - str) : 0, seed);
ImGuiID id = ImHashStr(str, seed);
#ifndef IMGUI_DISABLE_DEBUG_TOOLS
ImGuiContext& g = *Ctx;
if (g.DebugHookIdInfoId == id)
ImGui::DebugHookIdInfo(id, ImGuiDataType_String, str, str_end);
ImGui::DebugHookIdInfo(id, ImGuiDataType_String, str.Begin, str.End);
#endif
return id;
}
@@ -9041,7 +9089,7 @@ void ImGui::PushID(const char* str_id_begin, const char* str_id_end)
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
ImGuiID id = window->GetID(str_id_begin, str_id_end);
ImGuiID id = window->GetID(ImStrv(str_id_begin, str_id_end));
window->IDStack.push_back(id);
}
@@ -9118,7 +9166,7 @@ ImGuiID ImGui::GetID(ImStrv str_id)
ImGuiID ImGui::GetID(const char* str_id_begin, const char* str_id_end)
{
ImGuiWindow* window = GImGui->CurrentWindow;
return window->GetID(str_id_begin, str_id_end);
return window->GetID(ImStrv(str_id_begin, str_id_end));
}
ImGuiID ImGui::GetID(const void* ptr_id)
@@ -10612,7 +10660,7 @@ bool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags, ImGuiID own
bool ImGui::DebugCheckVersionAndDataLayout(ImStrv version, size_t sz_io, size_t sz_style, size_t sz_vec2, size_t sz_vec4, size_t sz_vert, size_t sz_idx)
{
bool error = false;
if (strcmp(version, IMGUI_VERSION) != 0) { error = true; IM_ASSERT(strcmp(version, IMGUI_VERSION) == 0 && "Mismatched version string!"); }
if (ImStrcmp(version, IMGUI_VERSION) != 0) { error = true; IM_ASSERT(0 && "Mismatched version string!"); }
if (sz_io != sizeof(ImGuiIO)) { error = true; IM_ASSERT(sz_io == sizeof(ImGuiIO) && "Mismatched struct layout!"); }
if (sz_style != sizeof(ImGuiStyle)) { error = true; IM_ASSERT(sz_style == sizeof(ImGuiStyle) && "Mismatched struct layout!"); }
if (sz_vec2 != sizeof(ImVec2)) { error = true; IM_ASSERT(sz_vec2 == sizeof(ImVec2) && "Mismatched struct layout!"); }
@@ -12016,7 +12064,7 @@ void ImGui::OpenPopup(ImStrv str_id, ImGuiPopupFlags popup_flags)
{
ImGuiContext& g = *GImGui;
ImGuiID id = g.CurrentWindow->GetID(str_id);
IMGUI_DEBUG_LOG_POPUP("[popup] OpenPopup(\"%s\" -> 0x%08X)\n", str_id, id);
IMGUI_DEBUG_LOG_POPUP("[popup] OpenPopup(\"%.*s\" -> 0x%08X\n", (int)(str_id.End - str_id.Begin), str_id, id);
OpenPopupEx(id, popup_flags);
}
@@ -12233,7 +12281,7 @@ bool ImGui::BeginPopupMenuEx(ImGuiID id, ImStrv label, ImGuiWindowFlags extra_wi
char name[128];
IM_ASSERT(extra_window_flags & ImGuiWindowFlags_ChildMenu);
ImFormatString(name, IM_ARRAYSIZE(name), "%s###Menu_%02d", label, g.BeginMenuDepth); // Recycle windows based on depth
ImFormatString(name, IM_ARRAYSIZE(name), "%.*s###Menu_%02d", (int)label.length(), label.Begin, g.BeginMenuDepth); // Recycle windows based on depth
bool is_open = Begin(name, NULL, extra_window_flags | ImGuiWindowFlags_Popup);
if (!is_open) // NB: Begin can return false when the popup is completely clipped (e.g. zero size display)
EndPopup();
@@ -14651,8 +14699,8 @@ bool ImGui::SetDragDropPayload(ImStrv type, const void* data, size_t data_size,
if (cond == 0)
cond = ImGuiCond_Always;
IM_ASSERT(type != NULL);
IM_ASSERT(ImStrlen(type) < IM_ARRAYSIZE(payload.DataType) && "Payload type can be at most 32 characters long");
IM_ASSERT(type.length() > 0 && "Payload type can not be empty");
IM_ASSERT(type.length() < IM_ARRAYSIZE(payload.DataType) && "Payload type can be at most 32 characters long");
IM_ASSERT((data != NULL && data_size > 0) || (data == NULL && data_size == 0));
IM_ASSERT(cond == ImGuiCond_Always || cond == ImGuiCond_Once);
IM_ASSERT(payload.SourceId != 0); // Not called between BeginDragDropSource() and EndDragDropSource()
@@ -14759,7 +14807,7 @@ const ImGuiPayload* ImGui::AcceptDragDropPayload(ImStrv type, ImGuiDragDropFlags
ImGuiPayload& payload = g.DragDropPayload;
IM_ASSERT(g.DragDropActive); // Not called between BeginDragDropTarget() and EndDragDropTarget() ?
IM_ASSERT(payload.DataFrameCount != -1); // Forgot to call EndDragDropTarget() ?
if (type != NULL && !payload.IsDataType(type))
if (type && !payload.IsDataType(type))
return NULL;
// Accept smallest drag target bounding box, this allows us to nest drag targets conveniently without ordering constraints.
@@ -14874,7 +14922,7 @@ void ImGui::LogTextV(const char* fmt, va_list args)
// Internal version that takes a position to decide on newline placement and pad items according to their depth.
// We split text into individual lines to add current tree level padding
// FIXME: This code is a little complicated perhaps, considering simplifying the whole system.
void ImGui::LogRenderedText(const ImVec2* ref_pos, const char* text, const char* text_end)
void ImGui::LogRenderedText(const ImVec2* ref_pos, ImStrv text)
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
@@ -14883,8 +14931,8 @@ void ImGui::LogRenderedText(const ImVec2* ref_pos, const char* text, const char*
const char* suffix = g.LogNextSuffix;
g.LogNextPrefix = g.LogNextSuffix = NULL;
if (!text_end)
text_end = FindRenderedTextEnd(text, text_end);
if (!text.End)
text.End = FindRenderedTextEnd(text);
const bool log_new_line = ref_pos && (ref_pos->y > g.LogLinePosY + g.Style.FramePadding.y + 1);
if (ref_pos)
@@ -14903,14 +14951,14 @@ void ImGui::LogRenderedText(const ImVec2* ref_pos, const char* text, const char*
g.LogDepthRef = window->DC.TreeDepth;
const int tree_depth = (window->DC.TreeDepth - g.LogDepthRef);
const char* text_remaining = text;
const char* text_remaining = text.Begin;
for (;;)
{
// Split the string. Each new line (after a '\n') is followed by indentation corresponding to the current depth of our log entry.
// We don't add a trailing \n yet to allow a subsequent item on the same line to be captured.
const char* line_start = text_remaining;
const char* line_end = ImStreolRange(line_start, text_end);
const bool is_last_line = (line_end == text_end);
const char* line_end = ImStreolRange(line_start, text.End);
const bool is_last_line = (line_end == text.End);
if (line_start != line_end || !is_last_line)
{
const int line_length = (int)(line_end - line_start);
@@ -14983,7 +15031,7 @@ void ImGui::LogToFile(int auto_open_depth, ImStrv filename)
// By opening the file in binary mode "ab" we have consistent output everywhere.
if (!filename)
filename = g.IO.LogFilename;
if (!filename || !filename[0])
if (filename.empty())
return;
ImFileHandle f = ImFileOpen(filename, "ab");
if (!f)
@@ -15179,13 +15227,12 @@ void ImGui::LoadIniSettingsFromDisk(ImStrv ini_filename)
if (!file_data)
return;
if (file_data_size > 0)
LoadIniSettingsFromMemory(file_data, (size_t)file_data_size);
LoadIniSettingsFromMemory(ImStrv(file_data, file_data + file_data_size));
IM_FREE(file_data);
}
// Zero-tolerance, no error reporting, cheap .ini parsing
// Set ini_size==0 to let us use strlen(ini_data). Do not call this function with a 0 if your buffer is actually empty!
void ImGui::LoadIniSettingsFromMemory(ImStrv ini_data, size_t ini_size)
void ImGui::LoadIniSettingsFromMemory(ImStrv ini_data)
{
ImGuiContext& g = *GImGui;
IM_ASSERT(g.Initialized);
@@ -15194,12 +15241,11 @@ void ImGui::LoadIniSettingsFromMemory(ImStrv ini_data, size_t ini_size)
// For user convenience, we allow passing a non zero-terminated string (hence the ini_size parameter).
// For our convenience and to make the code simpler, we'll also write zero-terminators within the buffer. So let's create a writable copy..
if (ini_size == 0)
ini_size = ImStrlen(ini_data);
const int ini_size = (int)ini_data.length();
g.SettingsIniData.Buf.resize((int)ini_size + 1);
char* const buf = g.SettingsIniData.Buf.Data;
char* const buf_end = buf + ini_size;
memcpy(buf, ini_data, ini_size);
memcpy(buf, ini_data.Begin, ini_size);
buf_end[0] = 0;
// Call pre-read handlers
@@ -15247,7 +15293,7 @@ void ImGui::LoadIniSettingsFromMemory(ImStrv ini_data, size_t ini_size)
g.SettingsLoaded = true;
// [DEBUG] Restore untouched copy so it can be browsed in Metrics (not strictly necessary)
memcpy(buf, ini_data, ini_size);
memcpy(buf, ini_data.Begin, ini_size);
// Call post-read handlers
for (ImGuiSettingsHandler& handler : g.SettingsHandlers)
@@ -15291,15 +15337,21 @@ ImGuiWindowSettings* ImGui::CreateNewWindowSettings(ImStrv name)
// Preserve the full string when ConfigDebugVerboseIniSettings is set to make .ini inspection easier.
if (g.IO.ConfigDebugIniSettings == false)
name = ImHashSkipUncontributingPrefix(name);
const size_t name_len = ImStrlen(name);
name.Begin = ImHashSkipUncontributingPrefix(name);
const size_t name_len = name.length();
if (name_len == 0)
{
IM_ASSERT(false && "Name must not be empty.");
return NULL;
}
// Allocate chunk
const size_t chunk_size = sizeof(ImGuiWindowSettings) + name_len + 1;
ImGuiWindowSettings* settings = g.SettingsWindows.alloc_chunk(chunk_size);
IM_PLACEMENT_NEW(settings) ImGuiWindowSettings();
settings->ID = ImHashStr(name, name_len);
memcpy(settings->GetName(), name, name_len + 1); // Store with zero terminator
settings->ID = ImHashStr(name);
memcpy(settings->GetName(), name.Begin, name_len);
settings->GetName()[name_len] = 0; // name may not contain \0, it must be inserted manually.
return settings;
}
@@ -15920,12 +15972,12 @@ void ImGui::DebugTextEncoding(ImStrv str)
TableSetupColumn("Glyph");
TableSetupColumn("Codepoint");
TableHeadersRow();
for (const char* p = str; *p != 0; )
for (const char* p = str.Begin; p != str.End; )
{
unsigned int c;
const int c_utf8_len = ImTextCharFromUtf8(&c, p, NULL);
const int c_utf8_len = ImTextCharFromUtf8(&c, p, str.End);
TableNextColumn();
Text("%d", (int)(p - str));
Text("%d", (int)(size_t)(p - str));
TableNextColumn();
for (int byte_index = 0; byte_index < c_utf8_len; byte_index++)
{
@@ -16275,7 +16327,7 @@ void ImGui::ShowMetricsWindow(bool* p_open)
{
// Debug Break features
// The Item Picker tool is super useful to visually select an item and break into the call-stack of where it was submitted.
SeparatorTextEx(0, "Debug breaks", NULL, CalcTextSize("(?)").x + g.Style.SeparatorTextPadding.x);
SeparatorTextEx(0, "Debug breaks", CalcTextSize("(?)").x + g.Style.SeparatorTextPadding.x);
SameLine();
MetricsHelpMarker("Will call the IM_DEBUG_BREAK() macro to break in debugger.\nWarning: If you don't have a debugger attached, this will probably crash.");
if (Checkbox("Show Item Picker", &g.DebugItemPickerActive) && g.DebugItemPickerActive)