#ifdef ENABLE_EDITOR
#define IMGUI_DEFINE_MATH_OPERATORS
#include "editor.h"
#include <imgui.h>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
#include <plog/Appenders/IAppender.h>
#include <plog/Log.h>
#include <plog/Record.h>
#include <plog/Util.h>
#include <mutex>
#include <string>
namespace editor {
namespace {
#ifdef _WIN32
std::string log_message_to_utf8(const std::wstring &wstr) {
if (wstr.empty()) {
return {};
}
int len = plog::WideCharToMultiByte(CP_UTF8, 0, wstr.data(), (int)wstr.size(), nullptr, 0, nullptr, nullptr);
std::string result(len, '\0');
plog::WideCharToMultiByte(CP_UTF8, 0, wstr.data(), (int)wstr.size(), result.data(), len, nullptr, nullptr);
return result;
}
#else
std::string log_message_to_utf8(const std::string &str) {
return str;
}
#endif
constexpr int MAX_LOG_ENTRIES = 1000;
struct LogEntry {
plog::Severity severity;
std::string text;
int repeat_count;
};
struct LogBuffer : plog::IAppender {
LogEntry entries[MAX_LOG_ENTRIES];
int head = 0;
int count = 0;
std::mutex mtx;
void write(const plog::Record &record) override {
std::lock_guard<std::mutex> lock(mtx);
std::string msg = log_message_to_utf8(record.getMessage());
if (count > 0) {
LogEntry &last = entries[(head - 1 + MAX_LOG_ENTRIES) % MAX_LOG_ENTRIES];
if (last.severity == record.getSeverity() && last.text == msg) {
++last.repeat_count;
return;
}
}
LogEntry &e = entries[head];
e.severity = record.getSeverity();
e.text = std::move(msg);
e.repeat_count = 0;
head = (head + 1) % MAX_LOG_ENTRIES;
if (count < MAX_LOG_ENTRIES) {
++count;
}
}
};
LogBuffer &log_buffer() {
static LogBuffer buf;
return buf;
}
struct LogViewerUIState {
bool show_trace = false, show_debug = true, show_info = true, show_warn = true, show_error = true,
show_critical = true;
bool auto_scroll = true, initialized = false;
char filter[256] = "";
};
LogViewerUIState &ui_state() {
static LogViewerUIState s;
return s;
}
bool severity_visible(plog::Severity severity) {
const LogViewerUIState &s = ui_state();
switch (severity) {
case plog::verbose:
return s.show_trace;
case plog::debug:
return s.show_debug;
case plog::info:
return s.show_info;
case plog::warning:
return s.show_warn;
case plog::error:
return s.show_error;
case plog::fatal:
return s.show_critical;
default:
return true;
}
}
ImU32 severity_color(plog::Severity severity) {
switch (severity) {
case plog::verbose:
return IM_COL32(130, 130, 130, 255);
case plog::debug:
return IM_COL32(180, 180, 180, 255);
case plog::info:
return IM_COL32(200, 220, 255, 255);
case plog::warning:
return IM_COL32(255, 200, 80, 255);
case plog::error:
return IM_COL32(255, 80, 80, 255);
case plog::fatal:
return IM_COL32(255, 40, 40, 255);
default:
return IM_COL32(255, 255, 255, 255);
}
}
const char *severity_label(plog::Severity severity) {
switch (severity) {
case plog::verbose:
return "TRACE";
case plog::debug:
return "DEBUG";
case plog::info:
return "INFO";
case plog::warning:
return "WARN";
case plog::error:
return "ERROR";
case plog::fatal:
return "CRITICAL";
default:
return "?";
}
}
} // anonymous namespace
void init_log_viewer() {
LogViewerUIState &s = ui_state();
if (s.initialized) {
return;
}
plog::get()->addAppender(&log_buffer());
s.initialized = true;
}
void draw_log_window(ImFont *mono_font) {
LogViewerUIState &s = ui_state();
if (ImGui::Begin("Log", nullptr)) {
if (ImGui::Button("Clear")) {
std::lock_guard<std::mutex> lock(log_buffer().mtx);
log_buffer().head = 0;
log_buffer().count = 0;
}
ImGui::SameLine();
ImGui::Checkbox("Trace", &s.show_trace);
ImGui::SameLine();
ImGui::Checkbox("Debug", &s.show_debug);
ImGui::SameLine();
ImGui::Checkbox("Info", &s.show_info);
ImGui::SameLine();
ImGui::Checkbox("Warn", &s.show_warn);
ImGui::SameLine();
ImGui::Checkbox("Error", &s.show_error);
ImGui::SameLine();
ImGui::Checkbox("Fatal", &s.show_critical);
ImGui::SameLine();
ImGui::Checkbox("Auto-scroll", &s.auto_scroll);
ImGui::SameLine();
ImGui::SetNextItemWidth(180);
ImGui::InputText("##filter", s.filter, sizeof(s.filter));
ImGui::Separator();
ImGui::BeginChild("LogMessages", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar);
bool filter_active = s.filter[0] != '\0';
int visible = 0;
{
std::lock_guard<std::mutex> lock(log_buffer().mtx);
LogBuffer &buf = log_buffer();
int start = buf.count < MAX_LOG_ENTRIES ? 0 : buf.head;
int end = start + buf.count;
for (int i = start; i < end; ++i) {
const LogEntry &e = buf.entries[i % MAX_LOG_ENTRIES];
if (!severity_visible(e.severity)) {
continue;
}
if (filter_active && e.text.find(s.filter) == std::string::npos) {
continue;
}
++visible;
ImGui::PushFont(mono_font);
ImGui::PushStyleColor(ImGuiCol_Text, severity_color(e.severity));
if (e.repeat_count > 0) {
ImGui::Text("[%s] %s (x%d)", severity_label(e.severity), e.text.c_str(), e.repeat_count + 1);
} else {
ImGui::Text("[%s] %s", severity_label(e.severity), e.text.c_str());
}
ImGui::PopStyleColor();
ImGui::PopFont();
}
}
if (visible == 0) {
ImGui::TextUnformatted("(no messages)");
}
if (s.auto_scroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY() - 6.0f) {
ImGui::SetScrollHereY(1.0f);
}
ImGui::EndChild();
}
ImGui::End();
}
} // namespace editor
#endif // ENABLE_EDITOR