Refine console panel to match Unity
This commit is contained in:
BIN
editor/resources/Icons/console__info_icon.png
Normal file
BIN
editor/resources/Icons/console__info_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 862 B |
BIN
editor/resources/Icons/console_error_button_icon.png
Normal file
BIN
editor/resources/Icons/console_error_button_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 351 B |
BIN
editor/resources/Icons/console_error_icon.png
Normal file
BIN
editor/resources/Icons/console_error_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 432 B |
BIN
editor/resources/Icons/console_info_button_icon.png
Normal file
BIN
editor/resources/Icons/console_info_button_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 511 B |
BIN
editor/resources/Icons/console_warn_button_icon.png
Normal file
BIN
editor/resources/Icons/console_warn_button_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 486 B |
BIN
editor/resources/Icons/console_warn_icon.png
Normal file
BIN
editor/resources/Icons/console_warn_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 624 B |
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user