diff --git a/source/lib/common/debug/logging.cpp b/source/lib/common/debug/logging.cpp deleted file mode 100644 index a9a151574..000000000 --- a/source/lib/common/debug/logging.cpp +++ /dev/null @@ -1,184 +0,0 @@ -/* - * This source file is a part of the Tactile map editor. - * - * Copyright (C) 2023 Albin Johansson - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "logging.hpp" - -#include // time -#include // move - -#include -#include -#include -#include -#include - -#include "common/debug/panic.hpp" -#include "common/predef.hpp" -#include "common/type/chrono.hpp" -#include "common/type/deque.hpp" -#include "common/type/ptr.hpp" -#include "common/type/set.hpp" -#include "io/directories.hpp" - -namespace tactile { -namespace { - -using LogFilterSet = Set; - -struct LogEntry final { - spdlog::level::level_enum level {}; - String msg; -}; - -[[nodiscard]] auto _to_filter_set(const LogFilter& filter) -> LogFilterSet -{ - LogFilterSet filter_set; - - if (filter.trace) { - filter_set.emplace(LogLevel::trace); - } - - if (filter.debug) { - filter_set.emplace(LogLevel::debug); - } - - if (filter.info) { - filter_set.emplace(LogLevel::info); - } - - if (filter.warn) { - filter_set.emplace(LogLevel::warn); - } - - if (filter.error) { - filter_set.emplace(LogLevel::err); - } - - if (filter.critical) { - filter_set.emplace(LogLevel::critical); - } - - return filter_set; -} - -/// Records logged messages, intended to be displayed in the log dock. -class HistorySink final : public spdlog::sinks::base_sink { - public: - void sink_it_(const spdlog::details::log_msg& msg) override - { - const auto time = fmt::localtime(Clock::to_time_t(msg.time)); - auto processed = fmt::format("[{:%H:%M:%S}] {}", time, msg.payload); - mHistory.push_back({msg.level, std::move(processed)}); - } - - void flush_() override - { - // Do nothing - } - - void clear() { mHistory.clear(); } - - [[nodiscard]] auto count(const LogFilterSet& set) const -> usize - { - usize count = 0; - - for (const auto& [level, str]: mHistory) { - if (set.contains(level)) { - ++count; - } - } - - return count; - } - - /// Complexity: O(NlogN) - void visit_logged_message_range(const LogFilterSet& set, - const usize filtered_begin_index, - const usize filtered_end_index, - const LoggedMessageVisitorFn& fn) const - { - usize filtered_index = 0; - for (const auto& [level, msg]: mHistory) { - if (filtered_index >= filtered_end_index) { - break; - } - - if (set.contains(level)) { - if (filtered_index >= filtered_begin_index) { - fn(level, msg); - } - ++filtered_index; - } - } - } - - private: - Deque mHistory; -}; - -inline Shared gHistorySink; - -} // namespace - -void init_logger() -{ - const auto path = get_persistent_file_dir() / "logs" / "tactile_log.txt"; - - auto cs = std::make_shared(); - auto fs = std::make_shared(path.string(), true); - gHistorySink = std::make_shared(); - - const spdlog::sinks_init_list sinks {cs, fs, gHistorySink}; - - auto logger = std::make_shared("tactile", sinks); - logger->set_pattern("%^[%L][%T.%e]%$ %v"); - logger->flush_on(LogLevel::critical); - - spdlog::set_default_logger(logger); - spdlog::set_level(kIsDebugBuild ? LogLevel::trace : LogLevel::info); - - spdlog::info("Tactile version {} ({} build)", - TACTILE_VERSION_STRING, - TACTILE_DEBUG ? "Unoptimized" : "Optimized"); - - const auto time = fmt::localtime(std::time(nullptr)); - spdlog::info("Today is {:%A %Y-%m-%d}", time); -} - -void clear_log_history() -{ - gHistorySink->clear(); -} - -auto count_matching_log_entries(const LogFilter& filter) -> usize -{ - const auto filter_set = _to_filter_set(filter); - return gHistorySink->count(filter_set); -} - -void visit_logged_message_range(const LogFilter& filter, - const usize begin_index, - const usize end_index, - const LoggedMessageVisitorFn& fn) -{ - const auto filter_set = _to_filter_set(filter); - gHistorySink->visit_logged_message_range(filter_set, begin_index, end_index, fn); -} - -} // namespace tactile diff --git a/source/lib/common/debug/logging.hpp b/source/lib/common/debug/logging.hpp deleted file mode 100644 index f107aeb0d..000000000 --- a/source/lib/common/debug/logging.hpp +++ /dev/null @@ -1,58 +0,0 @@ -/* - * This source file is a part of the Tactile map editor. - * - * Copyright (C) 2023 Albin Johansson - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#pragma once - -#include - -#include "common/primitives.hpp" -#include "common/predef.hpp" -#include "common/type/func.hpp" -#include "common/type/string.hpp" - -namespace tactile { - -struct LogFilter final { - bool trace : 1 {TACTILE_DEBUG}; - bool debug : 1 {TACTILE_DEBUG}; - bool info : 1 {true}; - bool warn : 1 {true}; - bool error : 1 {true}; - bool critical : 1 {true}; -}; - -using LogLevel = spdlog::level::level_enum; -using LoggedMessageVisitorFn = Func; - -/// Initializes the logger, this must be called before any logging takes place. -void init_logger(); - -/// Clears the entire log history. -void clear_log_history(); - -/// Returns the amount of logged messages that satisfy the provided filter. -[[nodiscard]] auto count_matching_log_entries(const LogFilter& filter) -> usize; - -/// Invokes a callback for a range of logged messages matching the provided filter. -void visit_logged_message_range(const LogFilter& filter, - usize begin_index, - usize end_index, - const LoggedMessageVisitorFn& fn); - -} // namespace tactile diff --git a/source/lib/engine/engine.cpp b/source/lib/engine/engine.cpp index a7a45e6ac..3ca2006f9 100644 --- a/source/lib/engine/engine.cpp +++ b/source/lib/engine/engine.cpp @@ -29,11 +29,13 @@ #include "backend/gl/gl_backend.hpp" #include "backend/null/null_backend.hpp" #include "common/debug/assert.hpp" -#include "common/debug/logging.hpp" #include "common/debug/stacktrace.hpp" #include "common/fmt/stacktrace_formatter.hpp" +#include "engine/app_delegate.hpp" #include "engine/platform/window.hpp" #include "io/directories.hpp" +#include "model/locator.hpp" +#include "model/services/logging_service.hpp" namespace tactile { @@ -54,7 +56,11 @@ Engine::Engine(const BackendAPI api) : mAPI {api} { std::set_terminate(&on_terminate); - init_logger(); + + mLoggingService = std::make_unique(); + mLoggingService->install_logger(); + + Locator::reset(mLoggingService.get()); mProtobuf.emplace(); auto& sdl = mSDL.emplace(mAPI); @@ -80,6 +86,8 @@ Engine::Engine(const BackendAPI api) spdlog::debug("[IO] Persistent file directory: {}", get_persistent_file_dir()); } +Engine::~Engine() noexcept = default; + void Engine::start() { TACTILE_ASSERT(mBackend != nullptr); diff --git a/source/lib/engine/engine.hpp b/source/lib/engine/engine.hpp index ef24f5a55..35031d4ca 100644 --- a/source/lib/engine/engine.hpp +++ b/source/lib/engine/engine.hpp @@ -21,22 +21,27 @@ #include -#include "backend/backend.hpp" #include "common/enum/backend_api.hpp" +#include "common/macros.hpp" #include "common/predef.hpp" #include "common/type/maybe.hpp" #include "common/type/ptr.hpp" -#include "engine/app_delegate.hpp" #include "engine/platform/imgui_context.hpp" #include "engine/platform/protobuf_context.hpp" #include "engine/platform/sdl_context.hpp" namespace tactile { +TACTILE_FWD_DECLARE_CLASS(LoggingService) +TACTILE_FWD_DECLARE_CLASS(Backend) +TACTILE_FWD_DECLARE_CLASS(AppDelegate) + class Engine final { public: explicit Engine(BackendAPI api); + ~Engine() noexcept; + void start(); void set_app_delegate(Unique app); @@ -45,6 +50,7 @@ class Engine final { private: BackendAPI mAPI; + Unique mLoggingService; Maybe mProtobuf; Maybe mSDL; Maybe mImGui; diff --git a/source/lib/model/services/logging_service.cpp b/source/lib/model/services/logging_service.cpp new file mode 100644 index 000000000..fadb1bd82 --- /dev/null +++ b/source/lib/model/services/logging_service.cpp @@ -0,0 +1,155 @@ +/* + * This source file is a part of the Tactile map editor. + * + * Copyright (C) 2023 Albin Johansson + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "logging_service.hpp" + +#include // time +#include // move + +#include +#include +#include +#include +#include + +#include "common/debug/panic.hpp" +#include "common/type/chrono.hpp" +#include "io/directories.hpp" + +namespace tactile { +namespace { + +[[nodiscard]] auto _convert_log_level(const spdlog::level::level_enum level) + -> LogLevelBits +{ + switch (level) { + case spdlog::level::trace: + return LOG_LEVEL_TRACE; + + case spdlog::level::debug: + return LOG_LEVEL_DEBUG; + + case spdlog::level::info: + return LOG_LEVEL_INFO; + + case spdlog::level::warn: + return LOG_LEVEL_WARNING; + + case spdlog::level::err: + return LOG_LEVEL_ERROR; + + case spdlog::level::critical: + return LOG_LEVEL_CRITICAL; + + default: + throw TactileError {"Unsupported log level"}; + } +} + +} // namespace + +void LogHistorySink::sink_it_(const spdlog::details::log_msg& msg) +{ + const auto time = fmt::localtime(Clock::to_time_t(msg.time)); + + const auto level = _convert_log_level(msg.level); + auto formatted_msg = fmt::format("[{:%H:%M:%S}] {}", time, msg.payload); + + mMessages.push_back(LogEntry {level, std::move(formatted_msg)}); +} + +void LogHistorySink::flush_() +{ + // Do nothing +} + +void LogHistorySink::clear() +{ + mMessages.clear(); +} + +void LoggingService::install_logger() +{ + using ColorSink = spdlog::sinks::stdout_color_sink_st; + using FileSink = spdlog::sinks::basic_file_sink_st; + + const auto log_file_path = get_persistent_file_dir() / "logs" / "tactile_log.txt"; + + auto color_sink = std::make_shared(); + auto file_sink = std::make_shared(log_file_path.string(), true); + mHistorySink = std::make_shared(); + + const spdlog::sinks_init_list sinks {color_sink, file_sink, mHistorySink}; + + auto logger = std::make_shared("tactile", sinks); + logger->set_pattern("%^[%L][%T.%e]%$ %v"); + logger->flush_on(spdlog::level::critical); + + spdlog::set_default_logger(logger); + spdlog::set_level(kIsDebugBuild ? spdlog::level::trace : spdlog::level::info); + + spdlog::info("Tactile version {} ({} build)", + TACTILE_VERSION_STRING, + TACTILE_DEBUG ? "Unoptimized" : "Optimized"); + + const auto time = fmt::localtime(std::time(nullptr)); + spdlog::info("Today is {:%A %Y-%m-%d}", time); +} + +void LoggingService::clear_history() +{ + mHistorySink->clear(); +} + +void LoggingService::each_logged_message(const usize filtered_begin_index, + const usize filtered_end_index, + const LogLevelFlags log_level_flags, + const LogMessageCallback& callback) const +{ + usize filtered_index = 0; + for (const auto& [level, msg]: mHistorySink->get_messages()) { + if (filtered_index >= filtered_end_index) { + break; + } + + if (level & log_level_flags) { + if (filtered_index >= filtered_begin_index) { + callback(level, msg); + } + + ++filtered_index; + } + } +} + +auto LoggingService::count_logged_messages(const LogLevelFlags log_level_flags) const + -> usize +{ + usize count = 0; + + for (const auto& [level, msg]: mHistorySink->get_messages()) { + if (level & log_level_flags) { + ++count; + } + } + + return count; +} + +} // namespace tactile diff --git a/source/lib/model/services/logging_service.hpp b/source/lib/model/services/logging_service.hpp new file mode 100644 index 000000000..0a1265933 --- /dev/null +++ b/source/lib/model/services/logging_service.hpp @@ -0,0 +1,109 @@ +/* + * This source file is a part of the Tactile map editor. + * + * Copyright (C) 2023 Albin Johansson + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include +#include + +#include "common/predef.hpp" +#include "common/primitives.hpp" +#include "common/type/deque.hpp" +#include "common/type/func.hpp" +#include "common/type/ptr.hpp" +#include "common/type/string.hpp" + +namespace tactile { + +enum LogLevelBits : uint32 { + LOG_LEVEL_TRACE = 1u << 0u, + LOG_LEVEL_DEBUG = 1u << 1u, + LOG_LEVEL_INFO = 1u << 2u, + LOG_LEVEL_WARNING = 1u << 3u, + LOG_LEVEL_ERROR = 1u << 4u, + LOG_LEVEL_CRITICAL = 1u << 5u, +}; + +using LogLevelFlags = uint32; + +inline constexpr LogLevelFlags kAllLogLevels = LOG_LEVEL_TRACE | LOG_LEVEL_DEBUG | + LOG_LEVEL_INFO | LOG_LEVEL_WARNING | + LOG_LEVEL_ERROR | LOG_LEVEL_CRITICAL; + +struct LogEntry final { + LogLevelBits level; + String msg; +}; + +class LogHistorySink final : + public spdlog::sinks::base_sink { + public: + void sink_it_(const spdlog::details::log_msg& msg) override; + + void flush_() override; + + void clear(); + + [[nodiscard]] auto get_messages() const -> const Deque& { return mMessages; } + + private: + Deque mMessages; +}; + +class LoggingService final { + public: + using LogMessageCallback = Func; + + /// Installs the global logger. + void install_logger(); + + /// Clears the in-memory history of logged messages. + void clear_history(); + + /** + * Visits each logged message that satisfies the specified log level filter and range. + * + * \complexity O(N) + * + * \param filtered_begin_index the index of the first passing message to include. + * \param filtered_end_index the index of the last passing message to include. + * \param log_level_flags a bitmask of log levels used as a message filter. + * \param callback a callback invoked for each passing message. + */ + void each_logged_message(usize filtered_begin_index, + usize filtered_end_index, + LogLevelFlags log_level_flags, + const LogMessageCallback& callback) const; + + /** + * Returns the number of messages that match the specified log levels. + * + * \complexity O(N) + * + * \param log_level_flags a bitmask of log levels used as a message filter. + * + * \return the number of matching messages. + */ + [[nodiscard]] auto count_logged_messages(LogLevelFlags log_level_flags) const -> usize; + + private: + Shared mHistorySink; +}; + +} // namespace tactile diff --git a/source/lib/ui/dock/log/log_dock.cpp b/source/lib/ui/dock/log/log_dock.cpp index d4a337478..a189e0da5 100644 --- a/source/lib/ui/dock/log/log_dock.cpp +++ b/source/lib/ui/dock/log/log_dock.cpp @@ -21,72 +21,84 @@ #include -#include "common/debug/logging.hpp" #include "common/type/hash_map.hpp" #include "common/util/lookup.hpp" #include "model/events/setting_events.hpp" #include "model/i18n/language_system.hpp" +#include "model/locator.hpp" #include "ui/widget/scoped.hpp" #include "ui/widget/widgets.hpp" namespace tactile { namespace { -const HashMap kLogLevelColors = { - {LogLevel::trace, ImVec4 {0.93f, 0.51f, 0.93f, 1.00f}}, - {LogLevel::debug, ImVec4 {0.50f, 1.00f, 0.70f, 1.00f}}, - {LogLevel::info, ImVec4 {1.00f, 1.00f, 1.00f, 1.00f}}, - {LogLevel::warn, ImVec4 {1.00f, 1.00f, 0.00f, 1.00f}}, - {LogLevel::err, ImVec4 {1.00f, 0.40f, 0.40f, 1.00f}}, - {LogLevel::critical, ImVec4 {1.00f, 0.00f, 0.00f, 1.00f}}, +inline constexpr auto kLogDockContextMenuID = "##LogDockWindowPopup"; + +const HashMap kLogLevelColors = { + {LOG_LEVEL_TRACE, ImVec4 {0.93f, 0.51f, 0.93f, 1.00f}}, + {LOG_LEVEL_DEBUG, ImVec4 {0.50f, 1.00f, 0.70f, 1.00f}}, + {LOG_LEVEL_INFO, ImVec4 {1.00f, 1.00f, 1.00f, 1.00f}}, + {LOG_LEVEL_WARNING, ImVec4 {1.00f, 1.00f, 0.00f, 1.00f}}, + {LOG_LEVEL_ERROR, ImVec4 {1.00f, 0.40f, 0.40f, 1.00f}}, + {LOG_LEVEL_CRITICAL, ImVec4 {1.00f, 0.00f, 0.00f, 1.00f}}, }; -void _push_message_filter_checkboxes(const Strings& strings, LogDockState& state) +void _push_filter_options(const Strings& strings, LogDockState& state) { - { - bool value = state.log_filter.trace; - ImGui::Checkbox(strings.misc.log_trace_filter.c_str(), &value); - state.log_filter.trace = value; + bool include_trace = state.log_filter & LOG_LEVEL_TRACE; + bool include_debug = state.log_filter & LOG_LEVEL_DEBUG; + bool include_info = state.log_filter & LOG_LEVEL_INFO; + bool include_warning = state.log_filter & LOG_LEVEL_WARNING; + bool include_error = state.log_filter & LOG_LEVEL_ERROR; + bool include_critical = state.log_filter & LOG_LEVEL_CRITICAL; + + if (const ui::Menu filter_menu {strings.misc.filter.c_str()}; filter_menu.is_open()) { + ImGui::MenuItem(strings.misc.log_trace_filter.c_str(), nullptr, &include_trace); + ImGui::MenuItem(strings.misc.log_debug_filter.c_str(), nullptr, &include_debug); + ImGui::MenuItem(strings.misc.log_info_filter.c_str(), nullptr, &include_info); + ImGui::MenuItem(strings.misc.log_warn_filter.c_str(), nullptr, &include_warning); + ImGui::MenuItem(strings.misc.log_error_filter.c_str(), nullptr, &include_error); + ImGui::MenuItem(strings.misc.log_critical_filter.c_str(), nullptr, &include_critical); } - ImGui::SameLine(); + state.log_filter = 0; - { - bool value = state.log_filter.debug; - ImGui::Checkbox(strings.misc.log_debug_filter.c_str(), &value); - state.log_filter.debug = value; + if (include_trace) { + state.log_filter |= LOG_LEVEL_TRACE; } - ImGui::SameLine(); - - { - bool value = state.log_filter.info; - ImGui::Checkbox(strings.misc.log_info_filter.c_str(), &value); - state.log_filter.info = value; + if (include_debug) { + state.log_filter |= LOG_LEVEL_DEBUG; } - ImGui::SameLine(); + if (include_info) { + state.log_filter |= LOG_LEVEL_INFO; + } - { - bool value = state.log_filter.warn; - ImGui::Checkbox(strings.misc.log_warn_filter.c_str(), &value); - state.log_filter.warn = value; + if (include_warning) { + state.log_filter |= LOG_LEVEL_WARNING; } - ImGui::SameLine(); + if (include_error) { + state.log_filter |= LOG_LEVEL_ERROR; + } - { - bool value = state.log_filter.error; - ImGui::Checkbox(strings.misc.log_error_filter.c_str(), &value); - state.log_filter.error = value; + if (include_critical) { + state.log_filter |= LOG_LEVEL_CRITICAL; } +} - ImGui::SameLine(); +void _push_dock_context_menu(const Strings& strings, LogDockState& state) +{ + if (const auto popup = ui::Popup::for_window(kLogDockContextMenuID); popup.is_open()) { + _push_filter_options(strings, state); - { - bool value = state.log_filter.critical; - ImGui::Checkbox(strings.misc.log_critical_filter.c_str(), &value); - state.log_filter.critical = value; + ImGui::Separator(); + + if (ImGui::MenuItem(strings.action.clear_log.c_str())) { + auto& logging_service = Locator::get(); + logging_service.clear_history(); // TODO event + } } } @@ -112,22 +124,22 @@ void _push_logged_message_legend_overlay(const Strings& strings) if (const ui::Window overlay {"##LegendOverlay", overlay_window_flags}; overlay.is_open()) { - ImGui::TextColored(lookup_in(kLogLevelColors, LogLevel::trace), + ImGui::TextColored(lookup_in(kLogLevelColors, LOG_LEVEL_TRACE), "%s", strings.misc.log_trace_filter.c_str()); - ImGui::TextColored(lookup_in(kLogLevelColors, LogLevel::debug), + ImGui::TextColored(lookup_in(kLogLevelColors, LOG_LEVEL_DEBUG), "%s", strings.misc.log_debug_filter.c_str()); - ImGui::TextColored(lookup_in(kLogLevelColors, LogLevel::info), + ImGui::TextColored(lookup_in(kLogLevelColors, LOG_LEVEL_INFO), "%s", strings.misc.log_info_filter.c_str()); - ImGui::TextColored(lookup_in(kLogLevelColors, LogLevel::warn), + ImGui::TextColored(lookup_in(kLogLevelColors, LOG_LEVEL_WARNING), "%s", strings.misc.log_warn_filter.c_str()); - ImGui::TextColored(lookup_in(kLogLevelColors, LogLevel::err), + ImGui::TextColored(lookup_in(kLogLevelColors, LOG_LEVEL_ERROR), "%s", strings.misc.log_error_filter.c_str()); - ImGui::TextColored(lookup_in(kLogLevelColors, LogLevel::critical), + ImGui::TextColored(lookup_in(kLogLevelColors, LOG_LEVEL_CRITICAL), "%s", strings.misc.log_critical_filter.c_str()); } @@ -137,8 +149,9 @@ void _push_logged_message_view(const Strings& strings, const usize message_count, LogDockState& state) { - const ui::StyleColor child_bg {ImGuiCol_ChildBg, {0.1f, 0.1f, 0.1f, 0.75f}}; + const auto& logging_service = Locator::get(); + const ui::StyleColor child_bg {ImGuiCol_ChildBg, {0.1f, 0.1f, 0.1f, 0.75f}}; const auto child_flags = ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_AlwaysAutoResize; @@ -148,13 +161,15 @@ void _push_logged_message_view(const Strings& strings, clipper.Begin(static_cast(message_count)); while (clipper.Step()) { - visit_logged_message_range(state.log_filter, - static_cast(clipper.DisplayStart), - static_cast(clipper.DisplayEnd), - [](const LogLevel level, const String& msg) { - const auto& color = lookup_in(kLogLevelColors, level); - ImGui::TextColored(color, "%s", msg.c_str()); - }); + auto show_colored_message = [](const LogLevelBits level, StringView msg) { + const auto& color = lookup_in(kLogLevelColors, level); + ImGui::TextColored(color, "%s", msg.data()); + }; + + logging_service.each_logged_message(static_cast(clipper.DisplayStart), + static_cast(clipper.DisplayEnd), + state.log_filter, + show_colored_message); } // Makes the log follow new messages, unless the user explicitly scrolls up @@ -189,9 +204,9 @@ void push_log_dock_widget(ModelView& model, LogDockState& state) state.has_focus = dock.has_focus(ImGuiFocusedFlags_RootAndChildWindows); if (dock.is_open()) { - _push_message_filter_checkboxes(strings, state); + const auto& logging_service = Locator::get(); + const auto message_count = logging_service.count_logged_messages(state.log_filter); - const auto message_count = count_matching_log_entries(state.log_filter); if (message_count != 0u) { _push_logged_message_view(strings, message_count, state); } @@ -199,11 +214,11 @@ void push_log_dock_widget(ModelView& model, LogDockState& state) ui::push_centered_label(strings.misc.log_no_messages_match_filter.c_str()); } - if (auto popup = ui::Popup::for_window("##LogDockPopup"); popup.is_open()) { - if (ImGui::MenuItem(strings.action.clear_log.c_str())) { - clear_log_history(); - } + if (dock.is_hovered() && ImGui::IsMouseReleased(ImGuiMouseButton_Right)) { + ImGui::OpenPopup(kLogDockContextMenuID); } + + _push_dock_context_menu(strings, state); } } diff --git a/source/lib/ui/dock/log/log_dock.hpp b/source/lib/ui/dock/log/log_dock.hpp index c8dbda2aa..59644a71e 100644 --- a/source/lib/ui/dock/log/log_dock.hpp +++ b/source/lib/ui/dock/log/log_dock.hpp @@ -19,14 +19,15 @@ #pragma once -#include "common/debug/logging.hpp" +#include "common/primitives.hpp" #include "common/type/ecs.hpp" #include "model/model_view.hpp" +#include "model/services/logging_service.hpp" namespace tactile { struct LogDockState final { - LogFilter log_filter; + uint32 log_filter {kAllLogLevels}; bool has_focus {}; };