From b77c6117528f410b3e85d34b81abedae254c7d19 Mon Sep 17 00:00:00 2001 From: nanako <469449812@qq.com> Date: Tue, 1 Jul 2025 18:33:17 +0800 Subject: [PATCH] =?UTF-8?q?TODO=20=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E7=B1=BB=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mirage_app/src/mirage.cpp | 5 +- src/mirage_config/src/config.cpp | 16 +- src/mirage_config/src/config.h | 337 +++++++++++++++++- src/mirage_render/src/font/font_system.h | 5 +- .../src/style/default_style.toml | 3 + src/mirage_widget/src/style/mirage_style.h | 23 +- .../widget/leaf_widget/meditable_text_box.cpp | 26 +- .../widget/leaf_widget/meditable_text_box.h | 4 + src/mirage_widget/src/window/mwindow.h | 1 - 9 files changed, 363 insertions(+), 57 deletions(-) diff --git a/src/mirage_app/src/mirage.cpp b/src/mirage_app/src/mirage.cpp index e97d1f6..7bb3a85 100644 --- a/src/mirage_app/src/mirage.cpp +++ b/src/mirage_app/src/mirage.cpp @@ -59,7 +59,10 @@ void mirage_app::init() { mirage_scoped_duration_timer timer(duration); last_time = get_current_time(); - if (!mirage_style::get().load_config("default_style.toml")) { + + config_options_t o{}; + o.enable_hot_reload = true; + if (!mirage_style::get().load_config("default_style.toml", o)) { log_error("无法加载样式配置"); } render_context = mirage_create_render_context(); diff --git a/src/mirage_config/src/config.cpp b/src/mirage_config/src/config.cpp index e7a2ebe..4046a45 100644 --- a/src/mirage_config/src/config.cpp +++ b/src/mirage_config/src/config.cpp @@ -1,14 +1,4 @@ +// +// Created by 46944 on 25-7-1. +// #include "config.h" - -#include - -bool config_t::load_config(const std::filesystem::path& in_filename) { - try { - tbl = toml::parse_file(in_filename.string()); - } - catch (const toml::parse_error& err) { - println(std::cerr, "config error: {}", err.what()); - return false; - } - return true; -} diff --git a/src/mirage_config/src/config.h b/src/mirage_config/src/config.h index 06507f5..b8b1908 100644 --- a/src/mirage_config/src/config.h +++ b/src/mirage_config/src/config.h @@ -1,14 +1,339 @@ #pragma once -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace config_convert { + // 对于一般类型,返回原值 + template requires (!std::is_pointer_v> || !std::is_same_v< + std::remove_cv_t>>, char>) + decltype(auto) convert(T&& in) noexcept { return std::forward(in); } + + // 对于 const char* 和 char* + template requires std::is_pointer_v> && std::is_same_v< + std::remove_cv_t>>, char> + std::string convert(T&& in) { return in ? std::string(in) : std::string{}; } + + // 对于字符数组 + template + std::string convert(const char (&in)[N]) { return std::string(in); } +} + +struct config_options_t { + bool enable_hot_reload = false; + std::chrono::milliseconds reload_interval{ 1000 }; + bool notify_on_reload = true; +}; + +template class config_t { public: - bool load_config(const std::filesystem::path& in_filename); + using callback_t = std::function; + using clock_t = std::chrono::steady_clock; - [[nodiscard]] const auto& get_config() const { - return tbl; + + + [[nodiscard]] static auto& get() { + static T instance; + return instance; } + + // 析构函数 - 停止监控线程 + ~config_t() { stop_watching(); } + + // 加载配置文件 + [[nodiscard]] auto load_config(const std::filesystem::path& in_filename, + const config_options_t& options = {}) -> std::expected { + std::unique_lock lock(mutex_); + + auto result = load_config_impl(in_filename); + if (!result) { return result; } + + config_path_ = in_filename; + options_ = options; + + if (options.enable_hot_reload) { start_watching(); } + + return {}; + } + + // 注册配置变更回调 + size_t register_callback(callback_t callback) { + std::unique_lock lock(mutex_); + size_t id = next_callback_id_++; + callbacks_[id] = std::move(callback); + return id; + } + + // 注销回调 + void unregister_callback(size_t id) { + std::unique_lock lock(mutex_); + callbacks_.erase(id); + } + + // 手动重新加载配置 + [[nodiscard]] auto reload() -> std::expected { + std::unique_lock lock(mutex_); + + if (config_path_.empty()) { return std::unexpected("No config file loaded"); } + + auto old_table = std::move(tbl_); + auto result = load_config_impl(config_path_); + + if (!result) { + tbl_ = std::move(old_table); // 恢复旧配置 + return result; + } + + if (options_.notify_on_reload) { notify_callbacks(); } + + return {}; + } + + // 线程安全的读取方法 + template requires (std::convertible_to && ...) + [[nodiscard]] auto get(const Ks&... keys) const noexcept -> std::optional> { + std::shared_lock lock(mutex_); + try { + if (auto node = tbl_.at_path(make_path(keys...))) { + return node; + } + return std::nullopt; + } + catch (...) { + return std::nullopt; + } + } + + // 线程安全的带默认值获取 + template + requires (std::convertible_to && ...) + [[nodiscard]] auto get_or(const T_Value& default_value, const Ks&... keys) const + -> decltype(config_convert::convert(default_value)) { + std::shared_lock lock(mutex_); + + using return_t = decltype(config_convert::convert(default_value)); + + if (auto node = get_unlocked(keys...)) { + // 其他类型正常处理 + if (auto value = node->template value()) { + return config_convert::convert(*value); + } + } + + return config_convert::convert(default_value); + } + + // 线程安全的类型获取 + template requires (std::convertible_to && ...) + [[nodiscard]] auto get_as( + const Ks&... keys) const noexcept { + std::shared_lock lock(mutex_); + + if constexpr (std::is_same_v) { + // 对于 const char*,返回 std::optional + if (auto node = get_unlocked(keys...)) { + return node->template value(); + } + return std::nullopt; + } + else { + // 其他类型保持原有逻辑 + if (auto node = get_unlocked(keys...)) { + return node->template value(); + } + return std::nullopt; + } + } + + // 便捷的字符串获取方法 + template requires (std::convertible_to && ...) + [[nodiscard]] auto get_string(const std::string& default_value, const Ks&... keys) const noexcept -> std::string { + return get_or(default_value, keys...); + } + + template requires (std::convertible_to && ...) + [[nodiscard]] auto get_string(const char* default_value, const Ks&... keys) const noexcept -> std::string { + return get_or(default_value, keys...); + } + + // 线程安全的检查键是否存在 + template requires (std::convertible_to && ...) + [[nodiscard]] bool contains(const Ks&... keys) const noexcept { + std::shared_lock lock(mutex_); + return get_unlocked(keys...).has_value(); + } + + // 获取配置的快照(完整拷贝) + [[nodiscard]] toml::table snapshot() const { + std::shared_lock lock(mutex_); + return tbl_; + } + + // 批量读取操作 + template + auto with_config(Func&& func) const { + std::shared_lock lock(mutex_); + return func(tbl_); + } + + // 保存配置 + [[nodiscard]] auto save_config(const std::filesystem::path& in_filename) const -> std::expected { + std::shared_lock lock(mutex_); + try { + std::ofstream file(in_filename); + if (!file) { return std::unexpected(std::format("Cannot open file: {}", in_filename.string())); } + file << tbl_; + return {}; + } + catch (const std::exception& e) { + return std::unexpected(std::format("Failed to save {}: {}", in_filename.string(), e.what())); + } + } + + // 获取配置文件的最后修改时间 + [[nodiscard]] auto last_modified() const noexcept { + std::shared_lock lock(mutex_); + return last_modified_; + } + + // 停止热重载 + void stop_hot_reload() { + stop_watching(); + } + + // 启动热重载 + void start_hot_reload() { + if (!config_path_.empty() && options_.enable_hot_reload) { + start_watching(); + } + } + protected: - toml::table tbl{}; + mutable std::shared_mutex mutex_; + toml::table tbl_; + std::filesystem::path config_path_; + config_options_t options_; + std::filesystem::file_time_type last_modified_; + + // 回调管理 + std::unordered_map callbacks_; + size_t next_callback_id_ = 0; + + // 监控线程 + std::jthread watch_thread_; + std::atomic watching_{ false }; + +private: + // 内部加载实现(不加锁) + [[nodiscard]] auto load_config_impl(const std::filesystem::path& in_filename) -> std::expected { + try { + if (!std::filesystem::exists(in_filename)) { + return std::unexpected(std::format("File not found: {}", in_filename.string())); + } + + tbl_ = toml::parse_file(in_filename.string()); + last_modified_ = std::filesystem::last_write_time(in_filename); + return {}; + } + catch (const toml::parse_error& err) { + std::println(std::cerr, "config error: {}", err.what()); + return std::unexpected(std::format("Failed to parse {}: {}", in_filename.string(), err.what())); + } catch (const std::exception& e) { + return std::unexpected(std::format("Error loading {}: {}", in_filename.string(), e.what())); + } + } + + // 内部获取实现(不加锁) + template + [[nodiscard]] auto get_unlocked( + const Ks&... keys) const noexcept -> std::optional> { + try { + auto node = tbl_.at_path(make_path(keys...)); + if (node) { return node; } + return std::nullopt; + } + catch (...) { return std::nullopt; } + } + + // 通知所有回调 + void notify_callbacks() { + for (const auto& [id, callback]: callbacks_) { + try { + callback(tbl_); + } + catch (const std::exception& e) { + std::println(std::cerr, "Callback {} threw exception: {}", id, e.what()); + } + } + } + + // 开始监控文件变化 + void start_watching() { + stop_watching(); + + watching_ = true; + watch_thread_ = std::jthread([this](std::stop_token stop_token) { watch_file(stop_token); }); + } + + // 停止监控 + void stop_watching() { + watching_ = false; + if (watch_thread_.joinable()) { + watch_thread_.request_stop(); + watch_thread_.join(); + } + } + + // 文件监控循环 + void watch_file(std::stop_token stop_token) { + while (!stop_token.stop_requested() && watching_) { + try { + if (std::filesystem::exists(config_path_)) { + auto current_modified = std::filesystem::last_write_time(config_path_); + + // 检查文件是否被修改 + if (current_modified != last_modified_) { + std::println("Config file changed, reloading..."); + + // 等待一小段时间,确保文件写入完成 + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + if (auto result = reload(); !result) { + std::println(std::cerr, "Failed to reload config: {}", result.error()); + } + else { std::println("Config reloaded successfully"); } + } + } + } + catch (const std::exception& e) { std::println(std::cerr, "Error watching config file: {}", e.what()); } + + // 使用可中断的睡眠 + std::unique_lock lock(mutex_); + std::condition_variable_any cv; + cv.wait_for(lock, stop_token, options_.reload_interval, [] { return false; }); + } + } + + // 构建路径字符串 + template + static std::string make_path(const Ks&... keys) { + std::string path; + auto append = [&path](std::string_view key) { + if (!path.empty()) { path += '.'; } + path += key; + }; + (append(keys), ...); + return path; + } }; diff --git a/src/mirage_render/src/font/font_system.h b/src/mirage_render/src/font/font_system.h index d16e99b..783cb75 100644 --- a/src/mirage_render/src/font/font_system.h +++ b/src/mirage_render/src/font/font_system.h @@ -1,16 +1,13 @@ #pragma once #include #include -#include #include -#include +#include #include -#include #include "atlas/font_atlas.h" #include "atlas/font_atlas_manager.h" -#include "font_layout.h" #include "font_type.h" #include "interface/font_interface.h" diff --git a/src/mirage_widget/src/style/default_style.toml b/src/mirage_widget/src/style/default_style.toml index 0f22368..3afa267 100644 --- a/src/mirage_widget/src/style/default_style.toml +++ b/src/mirage_widget/src/style/default_style.toml @@ -9,3 +9,6 @@ author = "11" hover_color = "rgb(31, 31, 31)" pressed_color = "rgb(31, 31, 31)" normal_color = "rgb(31, 31, 31)" + +[editable_text_box] +round = 5 diff --git a/src/mirage_widget/src/style/mirage_style.h b/src/mirage_widget/src/style/mirage_style.h index 02c5923..0e8e82b 100644 --- a/src/mirage_widget/src/style/mirage_style.h +++ b/src/mirage_widget/src/style/mirage_style.h @@ -1,32 +1,21 @@ #pragma once #include "config.h" -class mirage_style : public config_t { +class mirage_style : public config_t { public: - static auto& get() { - static mirage_style instance; - return instance; - } - [[nodiscard]] auto name() const { - return tbl["info"]["name"].value_or("unknown"); + return get_or("unknown", "info", "name"); } [[nodiscard]] auto version() const { - return tbl["info"]["version"].value_or("unknown"); + return get_or("unknown", "info", "version"); } [[nodiscard]] auto author() const { - return tbl["info"]["author"].value_or("unknown"); + return get_or("unknown", "info", "author"); } [[nodiscard]] auto description() const { - return tbl["info"]["description"].value_or("unknown"); + return get_or("unknown", "info", "description"); } [[nodiscard]] auto license() const { - return tbl["info"]["license"].value_or("unknown"); + return get_or("unknown", "info", "license"); } - - [[nodiscard]] auto get(const std::string& key) const { - return tbl[key]; - } -private: - mirage_style() = default; }; diff --git a/src/mirage_widget/src/widget/leaf_widget/meditable_text_box.cpp b/src/mirage_widget/src/widget/leaf_widget/meditable_text_box.cpp index cbe55e9..5cb6b5b 100644 --- a/src/mirage_widget/src/widget/leaf_widget/meditable_text_box.cpp +++ b/src/mirage_widget/src/widget/leaf_widget/meditable_text_box.cpp @@ -1,6 +1,5 @@ #include "meditable_text_box.h" #include "font/font_system.h" -#include "font/font_utils.h" #include "window/mwindow.h" void meditable_text_box::init() { @@ -9,8 +8,6 @@ void meditable_text_box::init() { set_focusable(true); } -constexpr float round_ = 5; - void meditable_text_box::setup_widget(const editable_text_box_args& in_args) { text_ = in_args.text(); font_ = in_args.font(); @@ -38,6 +35,7 @@ void meditable_text_box::on_tick(const duration_type& in_delta) { } void meditable_text_box::on_paint(mirage_paint_context& in_context) { + const auto round = get_round(); // 绘制底 in_context.drawer().make_rounded_rect( {0, 0}, @@ -45,14 +43,13 @@ void meditable_text_box::on_paint(mirage_paint_context& in_context) { in_context.geo(), draw_effect::none, { { 0.1f, 0.1f, 0.1f, 1.f } }, - { round_ } + { round } ); - auto text_y = round_; // 绘制文本 in_context.drawer().make_text( layout_, - { round_, text_y }, + { round, round }, in_context.geo().get_local_size(), in_context.geo() ); @@ -65,7 +62,7 @@ void meditable_text_box::on_paint(mirage_paint_context& in_context) { // 绘制光标 in_context.drawer().make_rounded_rect( - cursor_pos + Eigen::Vector2f(round_, round_), + cursor_pos + Eigen::Vector2f(round, round), Eigen::Vector2f(2, line_height), in_context.geo(), draw_effect::none, @@ -79,7 +76,7 @@ auto meditable_text_box::compute_desired_size(float in_layout_scale_multiplier) return no_warp_size_ .cwiseMax(layout_.total_size) .cwiseMax(Eigen::Vector2f(0, layout_.get_empty_line_height())) - + Eigen::Vector2f(round_ * 2, round_ * 2); + + Eigen::Vector2f(get_round() * 2, get_round() * 2); } void meditable_text_box::arrange_children(const geometry_t& in_allotted_geometry) { @@ -91,7 +88,7 @@ hit_test_handle meditable_text_box::on_mouse_button_down(const Eigen::Vector2f& set_focus(); // 将鼠标位置转换为文本坐标 - auto text_pos = in_position - Eigen::Vector2f(round_, round_); + const auto text_pos = in_position - Eigen::Vector2f(get_round(), get_round()); // 找到最近的字符位置 int32_t line_index = 0; @@ -289,7 +286,7 @@ void meditable_text_box::update_layout(const geometry_t& in_geometry, bool in_la float max_width = 0; if (warp_text_) { - const auto current_width = in_geometry.get_local_size().x() - round_ * 2; + const auto current_width = in_geometry.get_local_size().x() - get_round() * 2; if (current_width == layout_.total_size.x()) { return; } @@ -318,7 +315,7 @@ void meditable_text_box::update_layout(const geometry_t& in_geometry, bool in_la void meditable_text_box::setup_ime_pos() const { const auto& in_local_pos = layout_.get_glyph_pos(cursor_y_, cursor_x_, true); - const auto& in_window_pos = geometry_.get_window_position() + in_local_pos + Eigen::Vector2f(round_, round_); + const auto& in_window_pos = geometry_.get_window_position() + in_local_pos + Eigen::Vector2f(get_round(), get_round()); const auto& in_pos = in_window_pos.cast(); ime::get().set_cursor_pos(in_pos); ime::get().update_cursor_pos(get_window()->get_platform_handle()); @@ -342,7 +339,7 @@ size_t meditable_text_box::get_text_index_from_cursor() const { // 计算前面所有行的字符数 for (int32_t y = 0; y < cursor_y_; ++y) { - if (auto line = layout_.get_line(y)) { + if (const auto line = layout_.get_line(y)) { index += line->get_glyph_count(); // 如果不是最后一行,加上换行符 if (y < layout_.size() - 1) { @@ -360,7 +357,7 @@ size_t meditable_text_box::get_text_index_from_cursor() const { void meditable_text_box::delete_char_before_cursor() { if (text_.empty()) return; - size_t delete_pos = get_text_index_from_cursor(); + const auto delete_pos = get_text_index_from_cursor(); if (delete_pos > 0) { text_.erase(delete_pos - 1, 1); @@ -372,8 +369,7 @@ void meditable_text_box::delete_char_before_cursor() { } else if (cursor_y_ > 0) { // 需要移到上一行末尾 cursor_y_--; - auto prev_line = layout_.get_line(cursor_y_); - if (prev_line) { + if (const auto prev_line = layout_.get_line(cursor_y_)) { cursor_x_ = static_cast(prev_line->get_glyph_count()); } } diff --git a/src/mirage_widget/src/widget/leaf_widget/meditable_text_box.h b/src/mirage_widget/src/widget/leaf_widget/meditable_text_box.h index 7b80928..85be10b 100644 --- a/src/mirage_widget/src/widget/leaf_widget/meditable_text_box.h +++ b/src/mirage_widget/src/widget/leaf_widget/meditable_text_box.h @@ -1,5 +1,6 @@ #pragma once #include "ime/ime.h" +#include "style/mirage_style.h" #include "widget/mleaf_widget.h" class font_face_interface; @@ -36,6 +37,9 @@ public: void update_layout(const geometry_t& in_geometry, bool in_layout_validate = true); void setup_ime_pos() const; private: + static float get_round() { + return mirage_style::get().get_or(5.f, "editable_text_box", "round"); + } void validate_cursor_position(); size_t get_text_index_from_cursor() const; void delete_char_before_cursor(); diff --git a/src/mirage_widget/src/window/mwindow.h b/src/mirage_widget/src/window/mwindow.h index 928b803..7432bb8 100644 --- a/src/mirage_widget/src/window/mwindow.h +++ b/src/mirage_widget/src/window/mwindow.h @@ -10,7 +10,6 @@ #include "widget/mwidget.h" #include #include -#include #include "misc/delegates.h"