Refine console panel to match Unity

This commit is contained in:
2026-03-31 23:05:52 +08:00
parent 6a38230f3f
commit 7ff5cd4cf2
7 changed files with 503 additions and 112 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 511 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 486 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 624 B

View File

@@ -16,6 +16,7 @@
#include <cmath>
#include <ctime>
#include <filesystem>
#include <functional>
#include <string>
#include <unordered_map>
#include <vector>
@@ -52,6 +53,65 @@ struct ConsoleRowInteraction {
bool doubleClicked = false;
};
bool CanOpenSourceLocation(const LogEntry& entry);
constexpr float kConsoleToolbarHeight = 26.0f;
constexpr float kConsoleToolbarButtonHeight = 20.0f;
constexpr float kConsoleToolbarRowPaddingY = 3.0f;
constexpr float kConsoleCounterWidth = 36.0f;
constexpr float kConsoleSearchWidth = 220.0f;
constexpr float kConsoleRowHeight = 20.0f;
constexpr float kConsoleDetailsHeaderHeight = 24.0f;
constexpr float kConsoleToolbarItemSpacing = 4.0f;
constexpr ImVec4 kConsoleToolbarBackgroundColor = ImVec4(0.23f, 0.23f, 0.23f, 1.0f);
std::filesystem::path ResolveConsoleIconPath(const wchar_t* fileName) {
const std::filesystem::path exeDir(XCEngine::Editor::Platform::Utf8ToWide(XCEngine::Editor::Platform::GetExecutableDirectoryUtf8()));
return (exeDir / L".." / L".." / L"resources" / L"Icons" / fileName).lexically_normal();
}
const std::string& ConsoleInfoRowIconPath() {
static const std::string path = XCEngine::Editor::Platform::WideToUtf8(ResolveConsoleIconPath(L"console__info_icon.png").wstring());
return path;
}
const std::string& ConsoleWarnRowIconPath() {
static const std::string path = XCEngine::Editor::Platform::WideToUtf8(ResolveConsoleIconPath(L"console_warn_icon.png").wstring());
return path;
}
const std::string& ConsoleErrorRowIconPath() {
static const std::string path = XCEngine::Editor::Platform::WideToUtf8(ResolveConsoleIconPath(L"console_error_icon.png").wstring());
return path;
}
const std::string& ConsoleInfoButtonIconPath() {
static const std::string path = XCEngine::Editor::Platform::WideToUtf8(ResolveConsoleIconPath(L"console_info_button_icon.png").wstring());
return path;
}
const std::string& ConsoleWarnButtonIconPath() {
static const std::string path = XCEngine::Editor::Platform::WideToUtf8(ResolveConsoleIconPath(L"console_warn_button_icon.png").wstring());
return path;
}
const std::string& ConsoleErrorButtonIconPath() {
static const std::string path = XCEngine::Editor::Platform::WideToUtf8(ResolveConsoleIconPath(L"console_error_button_icon.png").wstring());
return path;
}
const std::string& IconPathForSeverity(ConsoleSeverityVisual severity, bool toolbarButton) {
switch (severity) {
case ConsoleSeverityVisual::Warning:
return toolbarButton ? ConsoleWarnButtonIconPath() : ConsoleWarnRowIconPath();
case ConsoleSeverityVisual::Error:
return toolbarButton ? ConsoleErrorButtonIconPath() : ConsoleErrorRowIconPath();
case ConsoleSeverityVisual::Log:
default:
return toolbarButton ? ConsoleInfoButtonIconPath() : ConsoleInfoRowIconPath();
}
}
ConsoleSeverityVisual ResolveSeverity(LogLevel level) {
switch (level) {
case LogLevel::Warning:
@@ -257,7 +317,22 @@ bool SelectRelativeRow(const std::vector<ConsoleRowData>& rows, uint64_t& select
return true;
}
void DrawSeverityIcon(ImDrawList* drawList, const ImVec2& center, float radius, ConsoleSeverityVisual severity) {
void DrawSeverityIcon(
ImDrawList* drawList,
const ImVec2& center,
float radius,
ConsoleSeverityVisual severity,
bool toolbarButton = false) {
const std::string& iconPath = IconPathForSeverity(severity, toolbarButton);
if (!iconPath.empty() &&
XCEngine::Editor::UI::DrawTextureAssetPreview(
drawList,
ImVec2(center.x - radius, center.y - radius),
ImVec2(center.x + radius, center.y + radius),
iconPath)) {
return;
}
const ImU32 color = ImGui::GetColorU32(SeverityColor(severity));
switch (severity) {
case ConsoleSeverityVisual::Warning:
@@ -283,22 +358,256 @@ void DrawSeverityIcon(ImDrawList* drawList, const ImVec2& center, float radius,
}
}
void DrawSearchGlyph(ImDrawList* drawList, const ImVec2& center, ImU32 color) {
drawList->AddCircle(center, 4.0f, color, 16, 1.5f);
drawList->AddLine(
ImVec2(center.x + 3.0f, center.y + 3.0f),
ImVec2(center.x + 7.0f, center.y + 7.0f),
color,
1.5f);
}
bool DrawToolbarDropdownButton(
const char* id,
const char* label,
float width,
const std::function<void()>& drawPopup,
bool active = false) {
const ImVec2 size(width, kConsoleToolbarButtonHeight);
ImGui::InvisibleButton(id, size);
const bool hovered = ImGui::IsItemHovered();
const bool held = ImGui::IsItemActive();
const bool pressed = ImGui::IsItemClicked(ImGuiMouseButton_Left);
const ImVec2 min = ImGui::GetItemRectMin();
const ImVec2 max = ImGui::GetItemRectMax();
ImDrawList* drawList = ImGui::GetWindowDrawList();
drawList->AddRectFilled(
min,
max,
ImGui::GetColorU32(
held ? XCEngine::Editor::UI::ToolbarButtonActiveColor()
: hovered ? XCEngine::Editor::UI::ToolbarButtonHoveredColor(active)
: XCEngine::Editor::UI::ToolbarButtonColor(active)),
2.0f);
const ImVec2 textSize = ImGui::CalcTextSize(label);
drawList->AddText(
ImVec2(min.x + 8.0f, min.y + (size.y - textSize.y) * 0.5f),
ImGui::GetColorU32(ImGuiCol_Text),
label);
const float arrowCenterX = max.x - 9.0f;
const float arrowCenterY = min.y + size.y * 0.5f + 1.0f;
drawList->AddTriangleFilled(
ImVec2(arrowCenterX - 3.0f, arrowCenterY - 2.0f),
ImVec2(arrowCenterX + 3.0f, arrowCenterY - 2.0f),
ImVec2(arrowCenterX, arrowCenterY + 2.0f),
ImGui::GetColorU32(ImGuiCol_Text));
if (pressed) {
ImGui::OpenPopup(id);
}
if (XCEngine::Editor::UI::BeginContextMenu(id, ImGuiWindowFlags_AlwaysAutoResize)) {
drawPopup();
XCEngine::Editor::UI::EndContextMenu();
}
return pressed;
}
void DrawToolbarOverflowButton(
const char* id,
const std::function<void()>& drawPopup) {
const ImVec2 size(18.0f, kConsoleToolbarButtonHeight);
ImGui::InvisibleButton(id, size);
const bool hovered = ImGui::IsItemHovered();
const bool held = ImGui::IsItemActive();
const bool pressed = ImGui::IsItemClicked(ImGuiMouseButton_Left);
const ImVec2 min = ImGui::GetItemRectMin();
const ImVec2 max = ImGui::GetItemRectMax();
ImDrawList* drawList = ImGui::GetWindowDrawList();
drawList->AddRectFilled(
min,
max,
ImGui::GetColorU32(
held ? XCEngine::Editor::UI::ToolbarButtonActiveColor()
: hovered ? XCEngine::Editor::UI::ToolbarButtonHoveredColor(false)
: XCEngine::Editor::UI::ToolbarButtonColor(false)),
2.0f);
const float cx = (min.x + max.x) * 0.5f;
const float startY = min.y + size.y * 0.5f - 4.0f;
for (int i = 0; i < 3; ++i) {
drawList->AddCircleFilled(
ImVec2(cx, startY + i * 4.0f),
1.2f,
ImGui::GetColorU32(ImGuiCol_Text),
8);
}
if (pressed) {
ImGui::OpenPopup(id);
}
if (XCEngine::Editor::UI::BeginContextMenu(id, ImGuiWindowFlags_AlwaysAutoResize)) {
drawPopup();
XCEngine::Editor::UI::EndContextMenu();
}
}
bool DrawToolbarToggleButton(
const char* id,
const char* label,
bool& active,
float width = 0.0f) {
const ImVec2 textSize = ImGui::CalcTextSize(label);
const ImVec2 size(
width > 0.0f ? width : textSize.x + 18.0f,
kConsoleToolbarButtonHeight);
ImGui::InvisibleButton(id, size);
const bool hovered = ImGui::IsItemHovered();
const bool held = ImGui::IsItemActive();
const bool pressed = ImGui::IsItemClicked(ImGuiMouseButton_Left);
if (pressed) {
active = !active;
}
const ImVec2 min = ImGui::GetItemRectMin();
const ImVec2 max = ImGui::GetItemRectMax();
ImDrawList* drawList = ImGui::GetWindowDrawList();
drawList->AddRectFilled(
min,
max,
ImGui::GetColorU32(
held ? XCEngine::Editor::UI::ToolbarButtonActiveColor()
: hovered ? XCEngine::Editor::UI::ToolbarButtonHoveredColor(active)
: XCEngine::Editor::UI::ToolbarButtonColor(active)),
2.0f);
drawList->AddText(
ImVec2(min.x + (size.x - textSize.x) * 0.5f, min.y + (size.y - textSize.y) * 0.5f),
ImGui::GetColorU32(ImGuiCol_Text),
label);
return pressed;
}
bool DrawToolbarButton(
const char* id,
const char* label,
float width = 0.0f,
bool active = false) {
const ImVec2 textSize = ImGui::CalcTextSize(label);
const ImVec2 size(
width > 0.0f ? width : textSize.x + 18.0f,
kConsoleToolbarButtonHeight);
ImGui::InvisibleButton(id, size);
const bool hovered = ImGui::IsItemHovered();
const bool held = ImGui::IsItemActive();
const bool pressed = ImGui::IsItemClicked(ImGuiMouseButton_Left);
const ImVec2 min = ImGui::GetItemRectMin();
const ImVec2 max = ImGui::GetItemRectMax();
ImDrawList* drawList = ImGui::GetWindowDrawList();
drawList->AddRectFilled(
min,
max,
ImGui::GetColorU32(
held ? XCEngine::Editor::UI::ToolbarButtonActiveColor()
: hovered ? XCEngine::Editor::UI::ToolbarButtonHoveredColor(active)
: XCEngine::Editor::UI::ToolbarButtonColor(active)),
2.0f);
drawList->AddText(
ImVec2(min.x + (size.x - textSize.x) * 0.5f, min.y + (size.y - textSize.y) * 0.5f),
ImGui::GetColorU32(ImGuiCol_Text),
label);
return pressed;
}
void DrawToolbarArrowDropdownButton(
const char* id,
float width,
const std::function<void()>& drawPopup,
bool active = false) {
const ImVec2 size(width, kConsoleToolbarButtonHeight);
ImGui::InvisibleButton(id, size);
const bool hovered = ImGui::IsItemHovered();
const bool held = ImGui::IsItemActive();
const bool pressed = ImGui::IsItemClicked(ImGuiMouseButton_Left);
const ImVec2 min = ImGui::GetItemRectMin();
const ImVec2 max = ImGui::GetItemRectMax();
ImDrawList* drawList = ImGui::GetWindowDrawList();
drawList->AddRectFilled(
min,
max,
ImGui::GetColorU32(
held ? XCEngine::Editor::UI::ToolbarButtonActiveColor()
: hovered ? XCEngine::Editor::UI::ToolbarButtonHoveredColor(active)
: XCEngine::Editor::UI::ToolbarButtonColor(active)),
2.0f);
const float arrowCenterX = (min.x + max.x) * 0.5f;
const float arrowCenterY = min.y + size.y * 0.5f + 1.0f;
drawList->AddTriangleFilled(
ImVec2(arrowCenterX - 3.0f, arrowCenterY - 2.0f),
ImVec2(arrowCenterX + 3.0f, arrowCenterY - 2.0f),
ImVec2(arrowCenterX, arrowCenterY + 2.0f),
ImGui::GetColorU32(ImGuiCol_Text));
if (pressed) {
ImGui::OpenPopup(id);
}
if (XCEngine::Editor::UI::BeginContextMenu(id, ImGuiWindowFlags_AlwaysAutoResize)) {
drawPopup();
XCEngine::Editor::UI::EndContextMenu();
}
}
bool DrawConsoleSearchField(const char* id, char* buffer, size_t bufferSize) {
const float originalCursorY = ImGui::GetCursorPosY();
ImGui::SetCursorPosY((std::max)(0.0f, originalCursorY - 1.0f));
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(22.0f, 1.0f));
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 2.0f);
ImGui::PushStyleColor(ImGuiCol_FrameBg, XCEngine::Editor::UI::ToolbarButtonColor(false));
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, XCEngine::Editor::UI::ToolbarButtonHoveredColor(false));
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, XCEngine::Editor::UI::ToolbarButtonActiveColor());
ImGui::SetNextItemWidth((std::max)(0.0f, ImGui::GetContentRegionAvail().x));
const bool changed = ImGui::InputTextWithHint(id, "Search", buffer, bufferSize);
const ImVec2 min = ImGui::GetItemRectMin();
const ImVec2 max = ImGui::GetItemRectMax();
ImDrawList* drawList = ImGui::GetWindowDrawList();
DrawSearchGlyph(
drawList,
ImVec2(min.x + 11.0f, (min.y + max.y) * 0.5f),
ImGui::GetColorU32(XCEngine::Editor::UI::ConsoleSecondaryTextColor()));
ImGui::PopStyleColor(3);
ImGui::PopStyleVar(2);
return changed;
}
bool DrawSeverityToggleButton(
const char* id,
ConsoleSeverityVisual severity,
size_t count,
bool& active,
const char* tooltip) {
const float originalCursorY = ImGui::GetCursorPosY();
ImGui::SetCursorPosY((std::max)(0.0f, originalCursorY - 1.0f));
const std::string countText = std::to_string(count);
const ImVec2 countSize = ImGui::CalcTextSize(countText.c_str());
const ImVec2 padding = XCEngine::Editor::UI::ConsoleSeverityButtonPadding();
const float iconRadius = 5.0f;
const float gap = 8.0f;
const ImVec2 padding(6.0f, 3.0f);
const float iconRadius = 6.0f;
const float gap = 4.0f;
const float buttonHeight = kConsoleToolbarButtonHeight + 2.0f;
const ImVec2 size(
(std::max)(
XCEngine::Editor::UI::ConsoleSeverityButtonMinWidth(),
kConsoleCounterWidth,
padding.x * 2.0f + iconRadius * 2.0f + gap + countSize.x),
ImGui::GetFrameHeight());
buttonHeight);
ImGui::InvisibleButton(id, size);
const bool hovered = ImGui::IsItemHovered();
@@ -318,7 +627,7 @@ bool DrawSeverityToggleButton(
drawList->AddRectFilled(min, max, backgroundColor, 2.0f);
const ImVec2 iconCenter(min.x + padding.x + iconRadius, min.y + size.y * 0.5f);
DrawSeverityIcon(drawList, iconCenter, iconRadius, severity);
DrawSeverityIcon(drawList, iconCenter, iconRadius, severity, true);
drawList->AddText(
ImVec2(iconCenter.x + iconRadius + gap, min.y + (size.y - countSize.y) * 0.5f),
ImGui::GetColorU32(ImGuiCol_Text),
@@ -331,10 +640,21 @@ bool DrawSeverityToggleButton(
return pressed;
}
float CalculateSeverityToggleButtonWidth(size_t count) {
const std::string countText = std::to_string(count);
const ImVec2 countSize = ImGui::CalcTextSize(countText.c_str());
const ImVec2 padding(6.0f, 3.0f);
const float iconRadius = 6.0f;
const float gap = 4.0f;
return (std::max)(
kConsoleCounterWidth,
padding.x * 2.0f + iconRadius * 2.0f + gap + countSize.x);
}
ConsoleRowInteraction DrawConsoleRow(const ConsoleRowData& row, bool selected) {
ConsoleRowInteraction interaction;
const float rowHeight = XCEngine::Editor::UI::ConsoleRowHeight();
const float rowHeight = kConsoleRowHeight;
const float availableWidth = (std::max)(ImGui::GetContentRegionAvail().x, 1.0f);
ImGui::InvisibleButton("##ConsoleRow", ImVec2(availableWidth, rowHeight));
interaction.clicked = ImGui::IsItemClicked(ImGuiMouseButton_Left);
@@ -350,48 +670,37 @@ ConsoleRowInteraction DrawConsoleRow(const ConsoleRowData& row, bool selected) {
} else if (hovered) {
drawList->AddRectFilled(min, max, ImGui::GetColorU32(XCEngine::Editor::UI::ConsoleRowHoverFillColor()));
}
drawList->AddLine(
ImVec2(min.x, max.y - 0.5f),
ImVec2(max.x, max.y - 0.5f),
ImGui::GetColorU32(ImVec4(0.0f, 0.0f, 0.0f, 0.28f)));
const ConsoleSeverityVisual severity = ResolveSeverity(row.entry.level);
const ImVec2 iconCenter(min.x + 12.0f, min.y + rowHeight * 0.5f);
DrawSeverityIcon(drawList, iconCenter, 5.0f, severity);
const ImVec2 iconCenter(min.x + 10.0f, min.y + rowHeight * 0.5f);
DrawSeverityIcon(drawList, iconCenter, 4.5f, severity);
float rightEdge = max.x - 8.0f;
float rightEdge = max.x - 6.0f;
if (row.count > 1) {
const std::string countText = std::to_string(row.count);
const ImVec2 countSize = ImGui::CalcTextSize(countText.c_str());
const ImVec2 badgeMin(rightEdge - countSize.x - 10.0f, min.y + 3.0f);
const ImVec2 badgeMin(rightEdge - countSize.x - 8.0f, min.y + 3.0f);
const ImVec2 badgeMax(rightEdge, max.y - 3.0f);
drawList->AddRectFilled(
badgeMin,
badgeMax,
ImGui::GetColorU32(XCEngine::Editor::UI::ConsoleCountBadgeBackgroundColor()),
XCEngine::Editor::UI::ConsoleBadgeRounding());
2.0f);
drawList->AddText(
ImVec2(
badgeMin.x + (badgeMax.x - badgeMin.x - countSize.x) * 0.5f,
badgeMin.y + (badgeMax.y - badgeMin.y - countSize.y) * 0.5f),
ImGui::GetColorU32(XCEngine::Editor::UI::ConsoleCountBadgeTextColor()),
countText.c_str());
rightEdge = badgeMin.x - 8.0f;
}
const std::string sourceText = XCEngine::Editor::UI::BuildConsoleSourceText(row.entry);
if (!sourceText.empty()) {
const ImVec2 sourceSize = ImGui::CalcTextSize(sourceText.c_str());
const float sourceWidth = (std::min)(sourceSize.x, availableWidth * 0.32f);
const ImVec2 sourceMin(rightEdge - sourceWidth, min.y);
const ImVec2 sourceMax(rightEdge, max.y);
drawList->PushClipRect(sourceMin, sourceMax, true);
drawList->AddText(
ImVec2(sourceMin.x, min.y + (rowHeight - sourceSize.y) * 0.5f),
ImGui::GetColorU32(XCEngine::Editor::UI::ConsoleSecondaryTextColor()),
sourceText.c_str());
drawList->PopClipRect();
rightEdge = sourceMin.x - 10.0f;
rightEdge = badgeMin.x - 6.0f;
}
const std::string summary = XCEngine::Editor::UI::BuildConsoleSummaryText(row.entry);
const ImVec2 textMin(min.x + 24.0f, min.y);
const ImVec2 textMin(min.x + 22.0f, min.y);
const ImVec2 textMax((std::max)(textMin.x, rightEdge), max.y);
drawList->PushClipRect(textMin, textMax, true);
drawList->AddText(
@@ -400,6 +709,32 @@ ConsoleRowInteraction DrawConsoleRow(const ConsoleRowData& row, bool selected) {
summary.c_str());
drawList->PopClipRect();
if (hovered) {
const std::string sourceText = XCEngine::Editor::UI::BuildConsoleSourceText(row.entry);
XCEngine::Editor::UI::BeginTitledTooltip(XCEngine::Editor::UI::ConsoleSeverityLabel(row.entry.level));
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 28.0f);
ImGui::TextUnformatted(summary.c_str());
ImGui::PopTextWrapPos();
if (!sourceText.empty() || row.count > 1 || CanOpenSourceLocation(row.entry)) {
ImGui::Separator();
if (!sourceText.empty()) {
ImGui::TextColored(XCEngine::Editor::UI::ConsoleSecondaryTextColor(), "%s", sourceText.c_str());
}
if (row.count > 1) {
ImGui::TextColored(
XCEngine::Editor::UI::ConsoleSecondaryTextColor(),
"Occurrences: %zu",
row.count);
}
if (CanOpenSourceLocation(row.entry)) {
ImGui::TextColored(
XCEngine::Editor::UI::ConsoleSecondaryTextColor(),
"Double-click to open source");
}
}
XCEngine::Editor::UI::EndTitledTooltip();
}
return interaction;
}
@@ -475,6 +810,56 @@ void CopyToClipboard(const ConsoleRowData& row) {
ImGui::SetClipboardText(copyText.c_str());
}
std::string BuildDetailsTraceText(const ConsoleRowData& row) {
std::string traceText;
const LogEntry& entry = row.entry;
const std::string fileText = entry.file.CStr();
const std::string functionText = entry.function.CStr();
const std::string timeText = FormatTimestamp(entry.timestamp);
if (!functionText.empty() || !fileText.empty()) {
traceText += "at ";
traceText += functionText.empty() ? "<unknown>" : functionText;
if (!fileText.empty()) {
traceText += " (";
traceText += fileText;
if (entry.line > 0) {
traceText += ":";
traceText += std::to_string(entry.line);
}
traceText += ")";
}
traceText += "\n";
} else {
traceText += "No source location captured.\n";
}
const char* category = XCEngine::Debug::LogCategoryToString(entry.category);
if (category && category[0] != '\0') {
traceText += "category: ";
traceText += category;
traceText += "\n";
}
traceText += "thread: ";
traceText += std::to_string(entry.threadId);
traceText += "\n";
if (!timeText.empty()) {
traceText += "time: ";
traceText += timeText;
traceText += "\n";
}
if (row.count > 1) {
traceText += "occurrences: ";
traceText += std::to_string(row.count);
traceText += "\n";
}
return traceText;
}
} // namespace
namespace XCEngine {
@@ -640,14 +1025,20 @@ void ConsolePanel::Render() {
}
}
const float logFilterWidth = CalculateSeverityToggleButtonWidth(counts.logCount);
const float warningFilterWidth = CalculateSeverityToggleButtonWidth(counts.warningCount);
const float errorFilterWidth = CalculateSeverityToggleButtonWidth(counts.errorCount);
const float severityGroupWidth = logFilterWidth + warningFilterWidth + errorFilterWidth;
UI::PanelToolbarScope toolbar(
"ConsoleToolbar",
UI::StandardPanelToolbarHeight(),
kConsoleToolbarHeight,
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse,
true,
UI::ToolbarPadding(),
UI::ToolbarItemSpacing(),
UI::ToolbarBackgroundColor());
ImVec2(6.0f, kConsoleToolbarRowPaddingY),
ImVec2(kConsoleToolbarItemSpacing, 0.0f),
kConsoleToolbarBackgroundColor);
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(0.0f, 0.0f));
if (toolbar.IsOpen() &&
ImGui::BeginTable(
"##ConsoleToolbarLayout",
@@ -655,45 +1046,49 @@ void ConsolePanel::Render() {
ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_SizingStretchProp))
{
ImGui::TableSetupColumn("##Primary", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn("##Search", ImGuiTableColumnFlags_WidthFixed, 240.0f);
ImGui::TableSetupColumn("##Severity", ImGuiTableColumnFlags_WidthFixed, 250.0f);
ImGui::TableSetupColumn("##Search", ImGuiTableColumnFlags_WidthFixed, kConsoleSearchWidth);
ImGui::TableSetupColumn("##Severity", ImGuiTableColumnFlags_WidthFixed, severityGroupWidth);
ImGui::TableNextRow();
ImGui::TableNextColumn();
if (Actions::DrawToolbarAction(Actions::MakeClearConsoleAction())) {
if (DrawToolbarButton("##ConsoleClearButton", "Clear", 42.0f)) {
sink->Clear();
m_selectedSerial = 0;
m_selectedEntryKey.clear();
}
ImGui::SameLine();
Actions::DrawToolbarToggleAction(
Actions::MakeConsoleCollapseAction(m_filterState.Collapse()),
m_filterState.Collapse());
ImGui::SameLine();
Actions::DrawToolbarToggleAction(
Actions::MakeConsoleClearOnPlayAction(m_filterState.ClearOnPlay()),
m_filterState.ClearOnPlay());
ImGui::SameLine();
Actions::DrawToolbarToggleAction(
Actions::MakeConsoleErrorPauseAction(m_filterState.ErrorPause()),
m_filterState.ErrorPause());
ImGui::SameLine(0.0f, 1.0f);
DrawToolbarArrowDropdownButton("##ConsoleClearOptions", 16.0f, [&]() {
if (ImGui::MenuItem("Clear on Play", nullptr, m_filterState.ClearOnPlay())) {
m_filterState.ClearOnPlay() = !m_filterState.ClearOnPlay();
}
});
ImGui::SameLine(0.0f, kConsoleToolbarItemSpacing);
DrawToolbarToggleButton("##ConsoleCollapseToggle", "Collapse", m_filterState.Collapse(), 64.0f);
ImGui::SameLine(0.0f, kConsoleToolbarItemSpacing);
DrawToolbarToggleButton("##ConsoleErrorPauseToggle", "Error Pause", m_filterState.ErrorPause(), 82.0f);
ImGui::SameLine(0.0f, kConsoleToolbarItemSpacing);
DrawToolbarDropdownButton("##ConsoleSourceDropdown", "Editor", 58.0f, [&]() {
ImGui::MenuItem("Editor", nullptr, true, true);
ImGui::MenuItem("Player", nullptr, false, false);
});
ImGui::TableNextColumn();
if (m_requestSearchFocus) {
ImGui::SetKeyboardFocusHere();
m_requestSearchFocus = false;
}
UI::ToolbarSearchField("##ConsoleSearch", "Search", m_searchBuffer, sizeof(m_searchBuffer));
DrawConsoleSearchField("##ConsoleSearch", m_searchBuffer, sizeof(m_searchBuffer));
ImGui::TableNextColumn();
DrawSeverityToggleButton("##ConsoleLogFilter", ConsoleSeverityVisual::Log, counts.logCount, m_filterState.ShowLog(), "Log");
ImGui::SameLine(0.0f, 6.0f);
ImGui::SameLine(0.0f, 0.0f);
DrawSeverityToggleButton("##ConsoleWarningFilter", ConsoleSeverityVisual::Warning, counts.warningCount, m_filterState.ShowWarning(), "Warnings");
ImGui::SameLine(0.0f, 6.0f);
ImGui::SameLine(0.0f, 0.0f);
DrawSeverityToggleButton("##ConsoleErrorFilter", ConsoleSeverityVisual::Error, counts.errorCount, m_filterState.ShowError(), "Errors");
ImGui::EndTable();
}
ImGui::PopStyleVar();
UI::PanelContentScope content("ConsoleRoot", ImVec2(0.0f, 0.0f));
if (!content.IsOpen()) {
@@ -709,6 +1104,7 @@ void ConsolePanel::Render() {
m_detailsHeight = std::clamp(m_detailsHeight, minDetailsHeight, maxDetailsHeight);
const float listHeight = (std::max)(minListHeight, totalHeight - m_detailsHeight - splitterThickness);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f));
ImGui::PushStyleColor(ImGuiCol_ChildBg, UI::ConsoleListBackgroundColor());
const bool listOpen = ImGui::BeginChild(
"ConsoleLogList",
@@ -720,7 +1116,7 @@ void ConsolePanel::Render() {
const bool wasAtBottom = ImGui::GetScrollY() >= ImGui::GetScrollMaxY() - 4.0f;
bool openSelectedSource = false;
ImGuiListClipper clipper;
clipper.Begin(static_cast<int>(rows.size()), UI::ConsoleRowHeight());
clipper.Begin(static_cast<int>(rows.size()), kConsoleRowHeight);
while (clipper.Step()) {
for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; ++i) {
ConsoleRowData& row = rows[static_cast<size_t>(i)];
@@ -757,7 +1153,7 @@ void ConsolePanel::Render() {
m_selectedSerial = 0;
m_selectedEntryKey.clear();
});
UI::EndContextMenu();
XCEngine::Editor::UI::EndContextMenu();
}
ImGui::PopID();
@@ -781,7 +1177,7 @@ void ConsolePanel::Render() {
m_selectedSerial = 0;
m_selectedEntryKey.clear();
});
UI::EndContextMenu();
XCEngine::Editor::UI::EndContextMenu();
}
if (hasNewRevision && wasAtBottom) {
@@ -789,6 +1185,7 @@ void ConsolePanel::Render() {
}
}
ImGui::EndChild();
ImGui::PopStyleVar();
const UI::SplitterResult splitter = UI::DrawSplitter("##ConsoleDetailsSplitter", UI::SplitterAxis::Horizontal, splitterThickness);
if (splitter.active) {
@@ -807,90 +1204,84 @@ void ConsolePanel::Render() {
ImGui::PushStyleColor(ImGuiCol_ChildBg, UI::ConsoleDetailsHeaderBackgroundColor());
const bool headerOpen = ImGui::BeginChild(
"ConsoleDetailsHeader",
ImVec2(0.0f, 28.0f),
ImVec2(0.0f, kConsoleDetailsHeaderHeight),
false,
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
ImGui::PopStyleColor();
if (headerOpen) {
if (selectedRow) {
const char* severityLabel = UI::ConsoleSeverityLabel(selectedRow->entry.level);
const std::string sourceText = UI::BuildConsoleSourceText(selectedRow->entry);
ImGui::Text("%s", severityLabel);
const bool canOpenSource = CanOpenSourceLocation(selectedRow->entry);
std::string headerText = UI::ConsoleSeverityLabel(selectedRow->entry.level);
if (!sourceText.empty()) {
ImGui::SameLine();
ImGui::TextColored(UI::ConsoleSecondaryTextColor(), "%s", sourceText.c_str());
headerText += " ";
headerText += sourceText;
}
if (selectedRow->count > 1) {
const std::string countLabel = "x" + std::to_string(selectedRow->count);
ImGui::SameLine();
UI::DrawRightAlignedText(countLabel.c_str(), UI::ConsoleSecondaryTextColor(), 8.0f);
if (ImGui::BeginTable(
"##ConsoleDetailsHeaderLayout",
2,
ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_SizingStretchProp))
{
ImGui::TableSetupColumn("##Left", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn("##Right", ImGuiTableColumnFlags_WidthFixed, 128.0f);
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted(headerText.c_str());
ImGui::TableNextColumn();
if (selectedRow->count > 1) {
ImGui::AlignTextToFramePadding();
ImGui::TextColored(UI::ConsoleSecondaryTextColor(), "x%zu", selectedRow->count);
ImGui::SameLine(0.0f, 6.0f);
}
if (ImGui::SmallButton("Copy")) {
CopyToClipboard(*selectedRow);
}
ImGui::SameLine(0.0f, 4.0f);
ImGui::BeginDisabled(!canOpenSource);
if (ImGui::SmallButton("Open")) {
OpenSourceLocation(selectedRow->entry);
}
ImGui::EndDisabled();
ImGui::EndTable();
}
} else {
ImGui::TextColored(UI::ConsoleSecondaryTextColor(), "Details");
ImGui::TextColored(UI::ConsoleSecondaryTextColor(), "Stack Trace");
}
}
ImGui::EndChild();
if (selectedRow) {
const std::string message = selectedRow->entry.message.CStr();
const std::string fileText = selectedRow->entry.file.CStr();
const std::string functionText = selectedRow->entry.function.CStr();
const std::string timeText = FormatTimestamp(selectedRow->entry.timestamp);
const std::string sourceText = UI::BuildConsoleSourceText(selectedRow->entry);
const std::string threadText = std::to_string(selectedRow->entry.threadId);
const std::string lineText =
selectedRow->entry.line > 0 ? std::to_string(selectedRow->entry.line) : std::string();
const std::string traceText = BuildDetailsTraceText(*selectedRow);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f, 6.0f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(10.0f, 8.0f));
const bool bodyOpen = ImGui::BeginChild("ConsoleDetailsBody", ImVec2(0.0f, 0.0f), false);
ImGui::PopStyleVar(2);
const bool bodyOpen = ImGui::BeginChild(
"ConsoleDetailsBody",
ImVec2(0.0f, 0.0f),
false,
ImGuiWindowFlags_HorizontalScrollbar);
ImGui::PopStyleVar();
if (bodyOpen) {
ImGui::TextWrapped("%s", message.c_str());
ImGui::PushTextWrapPos(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x);
ImGui::TextUnformatted(message.c_str());
ImGui::PopTextWrapPos();
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
if (ImGui::BeginTable("##ConsoleDetailsMeta", 2, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoSavedSettings)) {
auto drawMetaRow = [](const char* label, const std::string& value) {
if (value.empty()) {
return;
}
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::TextColored(XCEngine::Editor::UI::ConsoleSecondaryTextColor(), "%s", label);
ImGui::TableNextColumn();
ImGui::TextWrapped("%s", value.c_str());
};
drawMetaRow("Type", UI::ConsoleSeverityLabel(selectedRow->entry.level));
drawMetaRow("Category", XCEngine::Debug::LogCategoryToString(selectedRow->entry.category));
drawMetaRow("Source", sourceText);
drawMetaRow("File", fileText);
drawMetaRow("Line", lineText);
drawMetaRow("Function", functionText);
drawMetaRow("Time", timeText);
drawMetaRow("Thread", threadText);
ImGui::EndTable();
}
ImGui::Spacing();
if (ImGui::Button("Copy", ImVec2(72.0f, 0.0f))) {
CopyToClipboard(*selectedRow);
}
ImGui::SameLine();
ImGui::BeginDisabled(!CanOpenSourceLocation(selectedRow->entry));
if (ImGui::Button("Open Source", ImVec2(96.0f, 0.0f))) {
OpenSourceLocation(selectedRow->entry);
}
ImGui::EndDisabled();
ImGui::PushStyleColor(ImGuiCol_Text, UI::ConsoleSecondaryTextColor());
ImGui::TextUnformatted(traceText.c_str());
ImGui::PopStyleColor();
}
ImGui::EndChild();
} else {
UI::PanelContentScope detailsContent("ConsoleDetailsEmpty", ImVec2(10.0f, 8.0f));
if (detailsContent.IsOpen()) {
UI::DrawEmptyState("No message selected");
UI::DrawEmptyState("No message selected", "Select a console entry to inspect its stack trace");
}
}
}