Log/Capture: Fixes for handling \n in strings. Improve the look of various widgets. Added LogSetNextTextDecoration helper. Fixup/amend dbaf74d75.
For now removed LogRenderedTextNewLine() - it is eventually desirable but currently carries too much ambiguities, so reverted until we have a better system and test suite.
diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt
index 58b8f9e..6020873 100644
--- a/docs/CHANGELOG.txt
+++ b/docs/CHANGELOG.txt
@@ -30,15 +30,6 @@
and API updates have been a little more frequent lately. They are documented below and in imgui.cpp and should not affect all users.
- Please report any issue!
------------------------------------------------------------------------
- VERSION 1.81 (In Progress)
------------------------------------------------------------------------
-
-Other Changes:
-
-- Log/Capture: Fix various new line/spacing issue by using same render text position when there are both
- RenderText and LogRenderedText call in widget code.
- Also Buttons are now enclosed in bracket. [@Xipiryon]
-----------------------------------------------------------------------
VERSION 1.81 WIP (In Progress)
@@ -71,6 +62,8 @@
to have enough space when provided width precisely calculated with CalcTextSize().x. (#3776)
Note that the rounding of either positions and widths are technically undesirable (e.g. #3437, #791) but
variety of code is currently on it so we are first fixing current behavior before we'll eventually change it.
+- Log/Capture: Fix various new line/spacing issue when logging widgets. [@Xipiryon, @ocornut]
+- Log/Capture: Improved the ascii look of various widgets, making large dumps more easily human readable.
- ImDrawList: Fixed AddCircle()/AddCircleFilled() with (rad > 0.0f && rad < 1.0f && num_segments == 0). (#3738)
Would lead to a buffer read overflow.
- Backends: Win32: Dynamically loading XInput DLL instead of linking with it, facilite compiling with
diff --git a/docs/TODO.txt b/docs/TODO.txt
index f9b9456..93f00c3 100644
--- a/docs/TODO.txt
+++ b/docs/TODO.txt
@@ -247,12 +247,14 @@
- style: gradients fill (#1223) ~ 2 bg colors for each fill? tricky with rounded shapes and using textures for corners.
- style editor: color child window height expressed in multiple of line height.
+ - log: improve logging of ArrowButton, ListBox, TabItem
+ - log: carry on indent / tree depth when opening a child window
+ - log: enabling log ends up pushing and growing vertices buffers because we don't distinguish layout vs render clipping
- log: have more control over the log scope (e.g. stop logging when leaving current tree node scope)
- log: be able to log anything (e.g. right-click on a window/tree-node, shows context menu? log into tty/file/clipboard)
- log: let user copy any window content to clipboard easily (CTRL+C on windows? while moving it? context menu?). code is commented because it fails with multiple Begin/End pairs.
- log: obsolete LogButtons() all together.
- log: LogButtons() options for specifying depth and/or hiding depth slider
- - log: enabling log ends up pushing and growing vertices buffersbecause we don't distinguish layout vs render clipping
- filters: set a current filter that tree node can automatically query to hide themselves
- filters: handle wild-cards (with implicit leading/trailing *), reg-exprs
diff --git a/imgui.cpp b/imgui.cpp
index f1e05c0..e41ee39 100644
--- a/imgui.cpp
+++ b/imgui.cpp
@@ -4944,6 +4944,7 @@
}
}
g.WithinEndChild = false;
+ g.LogLinePosY = -FLT_MAX; // To enforce a carriage return
}
// Helper to create a child window / scrolling region that looks like a normal widget frame.
@@ -7572,7 +7573,7 @@
window->DC.CursorMaxPos = window->DC.CursorPos;
window->DC.CurrLineSize = ImVec2(0.0f, 0.0f);
if (g.LogEnabled)
- LogRenderedTextNewLine();
+ g.LogLinePosY = -FLT_MAX; // To enforce a carriage return
}
void ImGui::EndGroup()
@@ -7593,7 +7594,7 @@
window->DC.CurrLineSize = group_data.BackupCurrLineSize;
window->DC.CurrLineTextBaseOffset = group_data.BackupCurrLineTextBaseOffset;
if (g.LogEnabled)
- LogRenderedTextNewLine();
+ g.LogLinePosY = -FLT_MAX; // To enforce a carriage return
if (!group_data.EmitItem)
{
@@ -9859,11 +9860,16 @@
// 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)
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
+ const char* prefix = g.LogNextPrefix;
+ const char* suffix = g.LogNextSuffix;
+ g.LogNextPrefix = g.LogNextSuffix = NULL;
+
if (!text_end)
text_end = FindRenderedTextEnd(text, text_end);
@@ -9871,52 +9877,46 @@
if (ref_pos)
g.LogLinePosY = ref_pos->y;
if (log_new_line)
+ {
+ LogText(IM_NEWLINE);
g.LogLineFirstItem = true;
+ }
- const char* text_remaining = text;
- if (g.LogDepthRef > window->DC.TreeDepth) // Re-adjust padding if we have popped out of our starting depth
+ if (prefix)
+ LogRenderedText(ref_pos, prefix, prefix + strlen(prefix)); // Calculate end ourself to ensure "##" are included here.
+
+ // Re-adjust padding if we have popped out of our starting depth
+ if (g.LogDepthRef > window->DC.TreeDepth)
g.LogDepthRef = window->DC.TreeDepth;
const int tree_depth = (window->DC.TreeDepth - g.LogDepthRef);
+
+ const char* text_remaining = text;
for (;;)
{
- // Split the string. Each new line (after a '\n') is followed by spacing corresponding to the current depth of our log entry.
- // We don't add a trailing \n to allow a subsequent item on the same line to be captured.
+ // 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_first_line = (line_start == text);
const bool is_last_line = (line_end == text_end);
- if (!is_last_line || (line_start != line_end))
+ if (line_start != line_end || !is_last_line)
{
- const int char_count = (int)(line_end - line_start);
- if (log_new_line || !is_first_line)
- LogText(IM_NEWLINE "%*s%.*s", tree_depth * 4, "", char_count, line_start);
- else if (g.LogLineFirstItem)
- LogText("%*s%.*s", tree_depth * 4, "", char_count, line_start);
- else
- LogText(" %.*s", char_count, line_start);
+ const int line_length = (int)(line_end - line_start);
+ const int indentation = g.LogLineFirstItem ? tree_depth * 4 : 1;
+ LogText("%*s%.*s", indentation, "", line_length, line_start);
g.LogLineFirstItem = false;
-
if (*line_end == '\n')
- LogRenderedTextNewLine();
+ {
+ LogText(IM_NEWLINE);
+ g.LogLineFirstItem = true;
+ }
}
- else if (log_new_line)
- {
- // An empty "" string at a different Y position should output a carriage return.
- LogText(IM_NEWLINE);
- break;
- }
-
if (is_last_line)
break;
text_remaining = line_end + 1;
}
-}
-void ImGui::LogRenderedTextNewLine()
-{
- // To enforce Log carriage return
- ImGuiContext& g = *GImGui;
- g.LogLinePosY = -FLT_MAX;
+ if (suffix)
+ LogRenderedText(ref_pos, suffix, suffix + strlen(suffix));
}
// Start logging/capturing text output
@@ -9929,12 +9929,21 @@
IM_ASSERT(g.LogBuffer.empty());
g.LogEnabled = true;
g.LogType = type;
+ g.LogNextPrefix = g.LogNextSuffix = NULL;
g.LogDepthRef = window->DC.TreeDepth;
g.LogDepthToExpand = ((auto_open_depth >= 0) ? auto_open_depth : g.LogDepthToExpandDefault);
g.LogLinePosY = FLT_MAX;
g.LogLineFirstItem = true;
}
+// Important: doesn't copy underlying data, use carefully (prefix/suffix must be in scope at the time of the next LogRenderedText)
+void ImGui::LogSetNextTextDecoration(const char* prefix, const char* suffix)
+{
+ ImGuiContext& g = *GImGui;
+ g.LogNextPrefix = prefix;
+ g.LogNextSuffix = suffix;
+}
+
void ImGui::LogToTTY(int auto_open_depth)
{
ImGuiContext& g = *GImGui;
diff --git a/imgui_internal.h b/imgui_internal.h
index 779c629..0745df3 100644
--- a/imgui_internal.h
+++ b/imgui_internal.h
@@ -1469,6 +1469,8 @@
ImGuiLogType LogType; // Capture target
ImFileHandle LogFile; // If != NULL log to stdout/ file
ImGuiTextBuffer LogBuffer; // Accumulation buffer when log to clipboard. This is pointer so our GImGui static constructor doesn't call heap allocators.
+ const char* LogNextPrefix;
+ const char* LogNextSuffix;
float LogLinePosY;
bool LogLineFirstItem;
int LogDepthRef;
@@ -1620,6 +1622,7 @@
LogEnabled = false;
LogType = ImGuiLogType_None;
+ LogNextPrefix = LogNextSuffix = NULL;
LogFile = NULL;
LogLinePosY = FLT_MAX;
LogLineFirstItem = false;
@@ -2253,6 +2256,8 @@
// Logging/Capture
IMGUI_API void LogBegin(ImGuiLogType type, int auto_open_depth); // -> BeginCapture() when we design v2 api, for now stay under the radar by using the old name.
IMGUI_API void LogToBuffer(int auto_open_depth = -1); // Start logging/capturing to internal buffer
+ IMGUI_API void LogRenderedText(const ImVec2* ref_pos, const char* text, const char* text_end = NULL);
+ IMGUI_API void LogSetNextTextDecoration(const char* prefix, const char* suffix);
// Popups, Modals, Tooltips
IMGUI_API bool BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags flags);
@@ -2391,8 +2396,6 @@
IMGUI_API void RenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list, ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, float grid_step, ImVec2 grid_off, float rounding = 0.0f, int rounding_corners_flags = ~0);
IMGUI_API void RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavHighlightFlags flags = ImGuiNavHighlightFlags_TypeDefault); // Navigation highlight
IMGUI_API const char* FindRenderedTextEnd(const char* text, const char* text_end = NULL); // Find the optional ## from which we stop displaying text.
- IMGUI_API void LogRenderedText(const ImVec2* ref_pos, const char* text, const char* text_end = NULL);
- IMGUI_API void LogRenderedTextNewLine();
// Render helpers (those functions don't access any ImGui state!)
IMGUI_API void RenderArrow(ImDrawList* draw_list, ImVec2 pos, ImU32 col, ImGuiDir dir, float scale = 1.0f);
diff --git a/imgui_tables.cpp b/imgui_tables.cpp
index 9681e72..6799d29 100644
--- a/imgui_tables.cpp
+++ b/imgui_tables.cpp
@@ -1654,6 +1654,10 @@
if (table->CurrentColumn != -1)
TableEndCell(table);
+ // Logging
+ if (g.LogEnabled)
+ LogRenderedText(NULL, "|");
+
// Position cursor at the bottom of our row so it can be used for e.g. clipping calculation. However it is
// likely that the next call to TableBeginCell() will reposition the cursor to take account of vertical padding.
window->DC.CursorPos.y = table->RowPosY2;
@@ -1890,6 +1894,14 @@
SetWindowClipRectBeforeSetChannel(window, column->ClipRect);
table->DrawSplitter.SetCurrentChannel(window->DrawList, column->DrawChannelCurrent);
}
+
+ // Logging
+ ImGuiContext& g = *GImGui;
+ if (g.LogEnabled && !column->IsSkipItems)
+ {
+ LogRenderedText(&window->DC.CursorPos, "|");
+ g.LogLinePosY = FLT_MAX;
+ }
}
// [Internal] Called by TableNextRow()/TableSetColumnIndex()/TableNextColumn()
diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp
index cea0fb4..d159188 100644
--- a/imgui_widgets.cpp
+++ b/imgui_widgets.cpp
@@ -693,12 +693,9 @@
RenderNavHighlight(bb, id);
RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding);
- ImRect render_text_pos = ImRect(bb.Min + style.FramePadding, bb.Max - style.FramePadding);
if (g.LogEnabled)
- LogRenderedText(&render_text_pos.Min, "[");
- RenderTextClipped(render_text_pos.Min, render_text_pos.Max ,label, NULL, &label_size, style.ButtonTextAlign, &bb);
- if (g.LogEnabled)
- LogRenderedText(&render_text_pos.Min, "]");
+ LogSetNextTextDecoration("[", "]");
+ RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb);
// Automatically close popups
//if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup))
@@ -1103,12 +1100,11 @@
RenderCheckMark(window->DrawList, check_bb.Min + ImVec2(pad, pad), check_col, square_sz - pad * 2.0f);
}
-
- ImVec2 render_text_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y);
+ ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y);
if (g.LogEnabled)
- LogRenderedText(&render_text_pos, mixed_value ? "[~]" : *v ? "[x]" : "[ ]");
+ LogRenderedText(&label_pos, mixed_value ? "[~]" : *v ? "[x]" : "[ ]");
if (label_size.x > 0.0f)
- RenderText(render_text_pos, label);
+ RenderText(label_pos, label);
IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0));
return pressed;
@@ -1206,11 +1202,11 @@
window->DrawList->AddCircle(center, radius, GetColorU32(ImGuiCol_Border), 16, style.FrameBorderSize);
}
- ImVec2 render_text_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y);
+ ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y);
if (g.LogEnabled)
- LogRenderedText(&render_text_pos, active ? "(x)" : "( )");
+ LogRenderedText(&label_pos, active ? "(x)" : "( )");
if (label_size.x > 0.0f)
- RenderText(render_text_pos, label);
+ RenderText(label_pos, label);
IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags);
return pressed;
@@ -1394,10 +1390,7 @@
// Draw
window->DrawList->AddLine(bb.Min, ImVec2(bb.Max.x, bb.Min.y), GetColorU32(ImGuiCol_Separator));
if (g.LogEnabled)
- {
- LogRenderedText(&bb.Min, "--------------------------------");
- LogRenderedTextNewLine(); // Separator isn't tall enough to trigger a new line automatically in LogRenderText
- }
+ LogRenderedText(&bb.Min, "--------------------------------\n");
}
if (columns)
@@ -1589,7 +1582,12 @@
}
RenderFrameBorder(frame_bb.Min, frame_bb.Max, style.FrameRounding);
if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview))
- RenderTextClipped(frame_bb.Min + style.FramePadding, ImVec2(value_x2, frame_bb.Max.y), preview_value, NULL, NULL, ImVec2(0.0f, 0.0f));
+ {
+ ImVec2 preview_pos = frame_bb.Min + style.FramePadding;
+ if (g.LogEnabled)
+ LogSetNextTextDecoration("{", "}");
+ RenderTextClipped(preview_pos, ImVec2(value_x2, frame_bb.Max.y), preview_value, NULL, NULL, ImVec2(0.0f, 0.0f));
+ }
if (label_size.x > 0)
RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
@@ -2339,6 +2337,8 @@
// Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
char value_buf[64];
const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format);
+ if (g.LogEnabled)
+ LogSetNextTextDecoration("{", "}");
RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f));
if (label_size.x > 0.0f)
@@ -2951,6 +2951,8 @@
// Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
char value_buf[64];
const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format);
+ if (g.LogEnabled)
+ LogSetNextTextDecoration("{", "}");
RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f));
if (label_size.x > 0.0f)
@@ -4602,7 +4604,10 @@
// Log as text
if (g.LogEnabled && (!is_password || is_displaying_hint))
+ {
+ LogSetNextTextDecoration("{", "}");
LogRenderedText(&draw_pos, buf_display, buf_display_end);
+ }
if (label_size.x > 0)
RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
@@ -5809,18 +5814,10 @@
text_pos.x -= text_offset_x;
if (flags & ImGuiTreeNodeFlags_ClipLabelForTrailingButton)
frame_bb.Max.x -= g.FontSize + style.FramePadding.x;
+
if (g.LogEnabled)
- {
- // NB: '##' is normally used to hide text (as a library-wide feature), so we need to specify the text range to make sure the ## aren't stripped out here.
- const char log_prefix[] = "##";
- LogRenderedText(&text_pos, log_prefix, log_prefix + 2);
- RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size);
- LogRenderedText(&text_pos, log_prefix, log_prefix + 2);
- }
- else
- {
- RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size);
- }
+ LogSetNextTextDecoration("###", "###");
+ RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size);
}
else
{
@@ -5836,7 +5833,7 @@
else if (!is_leaf)
RenderArrow(window->DrawList, ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y + g.FontSize * 0.15f), text_col, is_open ? ImGuiDir_Down : ImGuiDir_Right, 0.70f);
if (g.LogEnabled)
- LogRenderedText(&text_pos, ">");
+ LogSetNextTextDecoration(">", NULL);
RenderText(text_pos, label, label_end, false);
}