TODO 配置文件类修改
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include "widget/mwidget.h"
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
#include "misc/delegates.h"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user