log_viewer.cpp

cross platform rendering playground

src/editor/log_viewer.cpp

6.29 KB
#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