TODO 配置文件类修改

This commit is contained in:
2025-07-01 18:33:17 +08:00
parent c7bc99e041
commit b77c611752
9 changed files with 363 additions and 57 deletions

View File

@@ -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();

View File

@@ -1,14 +1,4 @@
//
// Created by 46944 on 25-7-1.
//
#include "config.h"
#include <iostream>
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;
}

View File

@@ -1,14 +1,339 @@
#pragma once
#include <toml++/toml.hpp>
#include <filesystem>
#include <filesystem>
#include <print>
#include <string_view>
#include <expected>
#include <toml++/toml.h>
#include <shared_mutex>
#include <atomic>
#include <thread>
#include <chrono>
#include <functional>
#include <iostream>
#include <stop_token>
namespace config_convert {
// 对于一般类型,返回原值
template<typename T> requires (!std::is_pointer_v<std::decay_t<T>> || !std::is_same_v<
std::remove_cv_t<std::remove_pointer_t<std::decay_t<T>>>, char>)
decltype(auto) convert(T&& in) noexcept { return std::forward<T>(in); }
// 对于 const char* 和 char*
template<typename T> requires std::is_pointer_v<std::decay_t<T>> && std::is_same_v<
std::remove_cv_t<std::remove_pointer_t<std::decay_t<T>>>, char>
std::string convert(T&& in) { return in ? std::string(in) : std::string{}; }
// 对于字符数组
template<size_t N>
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<typename T>
class config_t {
public:
bool load_config(const std::filesystem::path& in_filename);
using callback_t = std::function<void(const toml::table&)>;
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<void, std::string> {
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<void, std::string> {
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<typename... Ks> requires (std::convertible_to<Ks, std::string_view> && ...)
[[nodiscard]] auto get(const Ks&... keys) const noexcept -> std::optional<toml::node_view<const toml::node>> {
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<typename T_Value, typename... Ks>
requires (std::convertible_to<Ks, std::string_view> && ...)
[[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_t>()) {
return config_convert::convert(*value);
}
}
return config_convert::convert(default_value);
}
// 线程安全的类型获取
template<typename T_Value, typename... Ks> requires (std::convertible_to<Ks, std::string_view> && ...)
[[nodiscard]] auto get_as(
const Ks&... keys) const noexcept {
std::shared_lock lock(mutex_);
if constexpr (std::is_same_v<T_Value, const char[]>) {
// 对于 const char*,返回 std::optional<std::string>
if (auto node = get_unlocked(keys...)) {
return node->template value<std::string>();
}
return std::nullopt;
}
else {
// 其他类型保持原有逻辑
if (auto node = get_unlocked(keys...)) {
return node->template value<T_Value>();
}
return std::nullopt;
}
}
// 便捷的字符串获取方法
template<typename... Ks> requires (std::convertible_to<Ks, std::string_view> && ...)
[[nodiscard]] auto get_string(const std::string& default_value, const Ks&... keys) const noexcept -> std::string {
return get_or<std::string>(default_value, keys...);
}
template<typename... Ks> requires (std::convertible_to<Ks, std::string_view> && ...)
[[nodiscard]] auto get_string(const char* default_value, const Ks&... keys) const noexcept -> std::string {
return get_or<const char*>(default_value, keys...);
}
// 线程安全的检查键是否存在
template<typename... Ks> requires (std::convertible_to<Ks, std::string_view> && ...)
[[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<typename Func>
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<void, std::string> {
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<size_t, callback_t> callbacks_;
size_t next_callback_id_ = 0;
// 监控线程
std::jthread watch_thread_;
std::atomic<bool> watching_{ false };
private:
// 内部加载实现(不加锁)
[[nodiscard]] auto load_config_impl(const std::filesystem::path& in_filename) -> std::expected<void, std::string> {
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<typename... Ks>
[[nodiscard]] auto get_unlocked(
const Ks&... keys) const noexcept -> std::optional<toml::node_view<const toml::node>> {
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<typename... Ks>
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;
}
};

View File

@@ -1,16 +1,13 @@
#pragma once
#include <memory>
#include <string>
#include <vector>
#include <unordered_map>
#include <optional>
#include <vector>
#include <filesystem>
#include <ranges>
#include "atlas/font_atlas.h"
#include "atlas/font_atlas_manager.h"
#include "font_layout.h"
#include "font_type.h"
#include "interface/font_interface.h"

View File

@@ -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

View File

@@ -1,32 +1,21 @@
#pragma once
#include "config.h"
class mirage_style : public config_t {
class mirage_style : public config_t<mirage_style> {
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;
};

View File

@@ -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<int32_t>();
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<int32_t>(prev_line->get_glyph_count());
}
}

View File

@@ -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();

View File

@@ -10,7 +10,6 @@
#include "widget/mwidget.h"
#include <memory>
#include <vector>
#include <functional>
#include "misc/delegates.h"