diff --git a/.gitmodules b/.gitmodules index 8969bad..f4de11f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "third_party/msdfgen"] path = third_party/msdfgen - url = https://github.com/Chlumsky/msdfgen.git \ No newline at end of file + url = https://github.com/Chlumsky/msdfgen.git +[submodule "third_party/mustache"] + path = third_party/mustache + url = https://github.com/kirillochnev/mustache.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e97ed8..5f639da 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,8 @@ set(MSDFGEN_USE_SKIA OFF CACHE BOOL "Use Skia for MSDFGen" FORCE) set(MSDFGEN_USE_VCPKG OFF CACHE BOOL "Use VCPKG for MSDFGen" FORCE) set(MSDFGEN_USE_OPENMP ON CACHE BOOL "Use OpenMP for MSDFGen" FORCE) set(MSDFGEN_BUILD_STANDALONE ON CACHE BOOL "Build MSDFGen standalone" FORCE) +set(MSDFGEN_BUILD_STANDALONE ON CACHE BOOL "Build MSDFGen standalone" FORCE) +set(MUSTACHE_BUILD_SHARED OFF CACHE BOOL "Build shared libraries?" FORCE) # 配置输出目录 set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) @@ -54,6 +56,7 @@ else () endif () add_subdirectory(third_party/msdfgen) +add_subdirectory(third_party/mustache) add_subdirectory(src) set(BUILD_EXAMPLE FALSE CACHE BOOL "Build example") diff --git a/example/src/main.cpp b/example/src/main.cpp index adc1236..bbcb4ab 100644 --- a/example/src/main.cpp +++ b/example/src/main.cpp @@ -17,62 +17,15 @@ int main(int argc, char* argv[]) { mirage_app::get_render_context()->setup_surface(window.get()); - auto border = std::make_shared(); + widget_manager::get().init_window(window); - auto h_box = std::make_shared(); - window->set_content(h_box); + auto weak_border = widget_manager::get().new_widget(window.get()); + auto border = weak_border.lock(); + auto button = widget_manager::get().new_widget(window.get()); + border->set_content(button.lock()) + .margin({5}); - auto v_box = std::make_shared(); - v_box->add_slot() - .auto_size() - .margin({ 5 }) - [ - std::make_shared() - ]; - v_box->add_slot() - .stretch() - .margin({ 5 }) - [ - std::make_shared() - ]; - v_box->add_slot() - .auto_size() - .margin({ 5 }) - [ - std::make_shared() - ]; - - - h_box->add_slot() - .auto_size() - .margin({ 5 }) - [ - std::make_shared() - ]; - h_box->add_slot() - .auto_size() - .margin({ 5 }) - [ - std::make_shared() - ]; - h_box->add_slot() - .auto_size() - .margin({ 5 }) - [ - std::make_shared() - ]; - h_box->add_slot() - .stretch() - [ - v_box - ]; - - h_box->add_slot() - .stretch() - .margin({ 5 }) - [ - std::make_shared() - ]; + window->set_content(border); app.run(); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c96bc11..9da2bda 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -16,7 +16,7 @@ set(SRC_FILES) retrieve_files(${CMAKE_CURRENT_SOURCE_DIR} SRC_FILES) add_library(${PROJECT_NAME} STATIC ${SRC_FILES}) -target_link_libraries(${PROJECT_NAME} PUBLIC Freetype::Freetype Eigen3::Eigen) +target_link_libraries(${PROJECT_NAME} PUBLIC Freetype::Freetype Eigen3::Eigen mustache) target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) add_os_definitions(${PROJECT_NAME}) target_compile_definitions(${PROJECT_NAME} PUBLIC -DNOMINMAX) diff --git a/src/core/render_context.h b/src/core/render_context.h index a2741c5..2a1dedc 100644 --- a/src/core/render_context.h +++ b/src/core/render_context.h @@ -54,16 +54,6 @@ public: */ virtual void cleanup() { } - //-------------- 渲染操作 -------------- - - /** - * @brief 更新渲染状态 - * @param in_delta 自上次更新后的时间间隔 - * - * 根据时间间隔更新渲染状态,执行动画和其他时间相关的操作。 - */ - virtual void tick(const duration_type& in_delta) = 0; - //-------------- 环境和表面设置 -------------- /** diff --git a/src/core/render_elements.cpp b/src/core/render_elements.cpp index 625d746..63d0075 100644 --- a/src/core/render_elements.cpp +++ b/src/core/render_elements.cpp @@ -46,14 +46,6 @@ void render_elements::begin_frame() { current_key_ = batch_key{}; draw_call_count_ = 0; total_triangles_ = 0; - - assert(transform_stack_.size() == 1); - if (transform_stack_.size() != 1) { - transform_stack_ = std::stack(); - - const transform2d base_transform{ {}, 0.f, { 1, 1 } }; - transform_stack_.push(base_transform); - } } // 设置渲染管线 @@ -95,8 +87,6 @@ Eigen::Matrix4f render_elements::create_projection_matrix(const Eigen::Vector2i& void render_elements::init_window_size(const Eigen::Vector2i& in_size) { window_size_ = in_size; projection_matrix_ = create_projection_matrix(in_size); - const transform2d base_transform{ {0, 0}, 0.f, { 1, 1 } }; - transform_stack_.push(base_transform); } void render_elements::update_projection_matrix(const Eigen::Vector2i& in_size) { @@ -104,26 +94,6 @@ void render_elements::update_projection_matrix(const Eigen::Vector2i& in_size) { projection_matrix_ = create_projection_matrix(in_size); } -void render_elements::push_transform(const transform2d& in_transform) { - // 如果是单位变换,优化处理 - if (in_transform.is_identity()) { - transform_stack_.push(transform_stack_.top()); - return; - } - - // 组合当前变换与新变换 - const transform2d& combined = transform_stack_.top().combine(in_transform); - transform_stack_.push(combined); -} - -void render_elements::pop_transform() { - if (transform_stack_.size() <= 1) { - return; // 保留基础变换 - } - - transform_stack_.pop(); -} - // 确保批次兼容性 void render_elements::ensure_batch_compatibility(const batch_key& key) { // 如果当前没有批次或者渲染状态改变了,创建新批次 @@ -158,12 +128,9 @@ void render_elements::add_rect_to_batch( set_texture(sg_image{}); // 使用默认纹理 } - const auto& transform = get_current_transform(); - const auto& pos = transform.transform_point(in_pos); - // 计算顶点位置 Eigen::Matrix positions; - compute_rect_vertices(pos, in_size, positions, in_rotation_radians, in_pivot, in_scale); + compute_rect_vertices(in_pos, in_size, positions, in_rotation_radians, in_pivot, in_scale); // 记录起始顶点索引 uint32_t base_index = vertices_.size(); diff --git a/src/core/render_elements.h b/src/core/render_elements.h index 43e0f9f..0faceda 100644 --- a/src/core/render_elements.h +++ b/src/core/render_elements.h @@ -200,23 +200,6 @@ public: */ void update_projection_matrix(const Eigen::Vector2i& in_size); - /** - * @brief 压入变换矩阵到变换栈 - * @param in_transform 要压入的变换 - */ - void push_transform(const transform2d& in_transform); - - /** - * @brief 弹出变换栈顶的变换矩阵 - */ - void pop_transform(); - - /** - * @brief 获取当前变换矩阵 - * @return 当前变换矩阵 - */ - const auto& get_current_transform() const { return transform_stack_.top(); } - //-------------- 绘制方法 -------------- /** @@ -359,9 +342,6 @@ private: /** 窗口大小 */ Eigen::Vector2i window_size_{ 0, 0 }; - /** 变换矩阵栈 */ - std::stack transform_stack_; - /** 圆角矩形渲染管线 */ sg_pipeline rounded_rect_pipeline_{}; }; diff --git a/src/core/window/mwindow.cpp b/src/core/window/mwindow.cpp index 7e75f20..ad8278f 100644 --- a/src/core/window/mwindow.cpp +++ b/src/core/window/mwindow.cpp @@ -1,19 +1,6 @@ #include "mwindow.h" -#include "widget/mwidget.h" - -void mwindow::tick() { - layout_tree_.tick(); -} - -void mwindow::paint() { - if (!content_widget_) - return; - - paint_context_.begin_frame(this); - layout_tree_.paint(paint_context_); - paint_context_.end_frame(); -} +#include "widget_tree/widget_system.h" geometry_t mwindow::get_window_geometry_in_screen() const { const auto& local_to_screen = get_local_to_screen_transform(); @@ -25,23 +12,17 @@ geometry_t mwindow::get_window_geometry_in_window() const { return { get_window_frame_size().cast(), local_to_window, local_to_window }; } -void mwindow::arrange_children(const geometry_t& in_allotted_geometry, arranged_children& in_arranged_children) { +void mwindow::arrange_children(const geometry_t& in_allotted_geometry) { if (content_widget_) { auto child_geo = in_allotted_geometry.make_child({0, 0}, get_window_frame_size().cast()); - in_arranged_children.add_widget(arranged_widget(child_geo, content_widget_)); + content_widget_->set_geometry(child_geo); } } void mwindow::set_content(const std::shared_ptr& in_widget) { content_widget_ = in_widget; - in_widget->set_parent(shared_from_this()); - layout_tree_.invalidate(invalidate_reason::all); -} - -std::vector> mwindow::get_children() const { - if (content_widget_) - return { content_widget_ }; - return mwidget::get_children(); + in_widget->set_parent(get_key()); + invalidate(invalidate_reason::all); } void mwindow::on_resize(int width, int height) { @@ -49,12 +30,14 @@ void mwindow::on_resize(int width, int height) { state_->swapchain.width = width; state_->swapchain.height = height; - paint_context_.update_projection_matrix(size); + on_resize_delegate.broadcast(size); transform2d identity; geometry_t new_geometry(size.cast(), identity, identity); - layout_tree_.set_root_geometry(new_geometry); - layout_tree_.invalidate(invalidate_reason::all); + if (content_widget_) { + content_widget_->set_geometry(new_geometry); + } + invalidate(invalidate_reason::all); } void mwindow::rebuild_swapchain() { @@ -64,31 +47,23 @@ void mwindow::rebuild_swapchain() { void mwindow::on_move(int x, int y) { } -void mwindow::init_window() { - layout_tree_.set_root(shared_from_this()); - layout_tree_.set_root_geometry(get_window_geometry_in_window()); -} - void mwindow::on_paint(mirage_paint_context& in_context) { if (content_widget_) - return content_widget_->on_paint(in_context); + content_widget_->on_paint(in_context); } void mwindow::handle_mouse_move(const Eigen::Vector2f& in_window_pos) { - // 执行悬停测试 - layout_tree_.process_mouse_move(in_window_pos); + on_mouse_move_delegate.broadcast(in_window_pos); } -void mwindow::handle_mouse_press(const Eigen::Vector2f& in_window_pos, mouse_button in_button) { - layout_tree_.process_mouse_press(in_window_pos, in_button); +void mwindow::handle_mouse_button_down(const Eigen::Vector2f& in_window_pos, mouse_button in_button) { + on_mouse_button_down_delegate.broadcast(in_window_pos, in_button); } -void mwindow::handle_mouse_release(const Eigen::Vector2f& in_window_pos, mouse_button in_button) { - layout_tree_.process_mouse_release(in_window_pos, in_button); +void mwindow::handle_mouse_button_up(const Eigen::Vector2f& in_window_pos, mouse_button in_button) { + on_mouse_button_up_delegate.broadcast(in_window_pos, in_button); } void mwindow::handle_mouse_leave() { - layout_tree_.on_mouse_leave_window(); + on_mouse_leave_delegate.broadcast(); } - - diff --git a/src/core/window/mwindow.h b/src/core/window/mwindow.h index 65da7d4..4f7a898 100644 --- a/src/core/window/mwindow.h +++ b/src/core/window/mwindow.h @@ -1,6 +1,6 @@ #pragma once /** - * @file render_window.h + * @file mwindow.h * @brief 定义UI系统的窗口类 * * 本文件定义了mirage_window类,作为UI系统的窗口容器,管理窗口的创建、 @@ -13,10 +13,9 @@ #include "geometry/dpi_helper.h" #include "geometry/geometry.h" #include "geometry/layout_transform.h" -#include "geometry/widget_layout_tree.h" +#include "misc/delegates.h" #include "misc/key_type/key_type.h" -#include "widget/compound_widget/mcompound_widget.h" -#include "widget/hit_test/hit_test_manager.h" +#include "widget/mwidget.h" /** * @struct mirage_window_state @@ -87,9 +86,7 @@ struct mirage_window_state { */ class mwindow : public mwidget { public: - //-------------- 组件层级关系 -------------- - virtual void invalidate(invalidate_reason in_reason) override; - + virtual ~mwindow() override { close(); } //-------------- 窗口创建和基本操作 -------------- /** @@ -131,7 +128,6 @@ public: */ bool is_visible() const; - auto get_invalidate_state() const { return layout_tree_.get_invalidate_state(); } //-------------- 窗口位置和大小 -------------- /** @@ -162,8 +158,6 @@ public: //-------------- 窗口属性获取 -------------- - virtual mwindow* get_window() override { return this; } - /** * @brief 获取窗口大小 * @return 窗口大小向量 @@ -186,7 +180,7 @@ public: * @brief 获取DPI缩放系数 * @return DPI缩放系数 */ - [[nodiscard]] float get_dpi_scale() const; + [[nodiscard]] float get_window_dpi_scale() const; /** * @brief 获取窗口句柄 @@ -254,12 +248,6 @@ public: //-------------- 事件处理 -------------- - /** - * @brief 检查窗口是否请求关闭 - * @return 如果窗口请求关闭则返回true - */ - [[nodiscard]] bool close_requested() const { return close_request_; } - /** * @brief 处理鼠标移动事件 * @param in_window_pos 窗口位置 @@ -271,14 +259,14 @@ public: * @param in_window_pos 窗口位置 * @param in_button 按下的按钮 */ - void handle_mouse_press(const Eigen::Vector2f& in_window_pos, mouse_button in_button); + void handle_mouse_button_down(const Eigen::Vector2f& in_window_pos, mouse_button in_button); /** * @brief 处理鼠标释放事件 * @param in_window_pos 窗口位置 * @param in_button 释放的按钮 */ - void handle_mouse_release(const Eigen::Vector2f& in_window_pos, mouse_button in_button); + void handle_mouse_button_up(const Eigen::Vector2f& in_window_pos, mouse_button in_button); /** * @brief 处理光标移出窗口事件 @@ -336,18 +324,6 @@ public: */ [[nodiscard]] auto& get_state() const { return *state_; } - /** - * @brief 更新 - */ - void tick(); - - /** - * @brief 处理窗口绘制 - * - * 触发窗口及其内容的重绘过程。 - */ - void paint(); - //-------------- 坐标变换 -------------- /** @@ -355,7 +331,7 @@ public: * @return 变换矩阵 */ [[nodiscard]] auto get_local_to_screen_transform() const { - return transform2d(get_window_position().cast(), 0, { dpi_helper::get_global_scale() * get_dpi_scale(), dpi_helper::get_global_scale() * get_dpi_scale() }); + return transform2d(get_window_position().cast(), 0, { dpi_helper::get_global_scale() * get_window_dpi_scale(), dpi_helper::get_global_scale() * get_window_dpi_scale() }); } /** @@ -363,7 +339,7 @@ public: * @return 变换矩阵 */ [[nodiscard]] auto get_local_to_window_transform() const { - return transform2d({0, 0}, 0, { dpi_helper::get_global_scale() * get_dpi_scale(), dpi_helper::get_global_scale() * get_dpi_scale() }); + return transform2d({0, 0}, 0, { dpi_helper::get_global_scale() * get_window_dpi_scale(), dpi_helper::get_global_scale() * get_window_dpi_scale() }); } /** @@ -391,11 +367,10 @@ public: /** * @brief 排列子组件 * @param in_allotted_geometry 分配的几何区域 - * @param in_arranged_children 排列好的子组件集合 * * 覆盖基类方法,安排窗口内容的布局。 */ - virtual void arrange_children(const geometry_t& in_allotted_geometry, arranged_children& in_arranged_children) override; + virtual void arrange_children(const geometry_t& in_allotted_geometry) override; /** * @brief 设置窗口内容 @@ -405,13 +380,11 @@ public: */ void set_content(const std::shared_ptr& in_widget); - /** - * @brief 获取子组件列表 - * @return 子组件指针的向量 - * - * 覆盖基类方法,返回窗口包含的组件。 - */ - virtual std::vector> get_children() const override; + /** + * @brief 获取窗口内容 + * @return 窗口内容组件 + */ + [[nodiscard]] auto get_content() const { return content_widget_; } /** * @brief 绘制窗口及其内容 @@ -421,30 +394,20 @@ public: */ virtual void on_paint(mirage_paint_context& in_context) override; -private: - /** - * @brief 初始化窗口 - * - * 执行窗口创建后的初始化操作。 - */ - void init_window(); + multicast_delegate on_resize_delegate; + multicast_delegate on_close_delegate; + multicast_delegate on_mouse_move_delegate; + multicast_delegate on_mouse_button_down_delegate; + multicast_delegate on_mouse_button_up_delegate; + multicast_delegate<> on_mouse_leave_delegate; private: /** 原生窗口句柄 */ void* window_handle_{}; - /** 窗口关闭请求标志 */ - bool close_request_ = false; - - /** 绘制上下文 */ - mirage_paint_context paint_context_; - /** 窗口渲染状态 */ std::unique_ptr state_; /** 窗口内容组件 */ std::shared_ptr content_widget_; - - /** 布局树 */ - widget_layout_tree layout_tree_{}; }; diff --git a/src/core/window/windows/windows_render_context.cpp b/src/core/window/windows/windows_render_context.cpp index 2f5def9..1905283 100644 --- a/src/core/window/windows/windows_render_context.cpp +++ b/src/core/window/windows/windows_render_context.cpp @@ -221,43 +221,6 @@ void windows_mirage_render_context::cleanup() { } } -void windows_mirage_render_context::tick(const duration_type& in_delta) { - const auto& windows = mwindow::get_windows(); - for (const auto& window: windows) { - auto& window_state = window->get_state(); - if (!window->is_visible()) - continue; - - // 狗屎代码, 但是我现在累了, 有空再整理吧 - const auto invalidate_state = window->get_invalidate_state(); - if (!has_any_flag(invalidate_state, invalidate_reason::layout | invalidate_reason::paint)) - return; - - window->tick(); - - // TODO 判断是否需要绘制 - - sg_pass pass{}; - pass.action.colors[0].load_action = SG_LOADACTION_CLEAR; - pass.action.colors[0].store_action = SG_STOREACTION_STORE; - pass.action.colors[0].clear_value = { 0.f, 0.f, 0.f, 1.0f }; - - pass.action.depth.load_action = SG_LOADACTION_CLEAR; - pass.action.depth.store_action = SG_STOREACTION_DONTCARE; - pass.action.depth.clear_value = 1.0f; - pass.swapchain = window_state.swapchain; - - sg_begin_pass(pass); - sg_apply_viewport(0, 0, window_state.swapchain.width, window_state.swapchain.height, true); - - window->paint(); - sg_end_pass(); - sg_commit(); - - window_state.present(); - } -} - sg_environment windows_mirage_render_context::get_environment() { return { .d3d11 = { diff --git a/src/core/window/windows/windows_render_context.h b/src/core/window/windows/windows_render_context.h index f9e4daf..7424e7b 100644 --- a/src/core/window/windows/windows_render_context.h +++ b/src/core/window/windows/windows_render_context.h @@ -46,16 +46,6 @@ public: */ void cleanup() override; - //-------------- 渲染操作 -------------- - - /** - * @brief 更新渲染状态 - * @param in_delta 自上次更新后的时间间隔 - * - * 处理时间相关的渲染更新,如动画。 - */ - virtual void tick(const duration_type& in_delta) override; - //-------------- 环境和表面设置 -------------- /** diff --git a/src/core/window/windows/windows_render_window.cpp b/src/core/window/windows/windows_render_window.cpp index 202c5a0..20bd23b 100644 --- a/src/core/window/windows/windows_render_window.cpp +++ b/src/core/window/windows/windows_render_window.cpp @@ -21,8 +21,13 @@ mwindow* get_window_from_hwnd(const HWND hwnd) { LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { bool processed = false; if (uMsg == WM_CLOSE) { - if (const auto window = get_window_from_hwnd(hwnd)) { window->close(); } - std::erase_if(windows, [hwnd](const mwindow* window) { return window->get_window_handle() == hwnd; }); + std::erase_if(windows, [hwnd](mwindow* window) { + if (window->get_window_handle() == hwnd) { + window->close(); + return true; + } + return false; + }); processed = true; } if (uMsg == WM_DESTROY) { @@ -61,9 +66,9 @@ LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) const auto action = platform_event_to_mouse_action(uMsg, wParam); const auto button = platform_event_to_mouse_button(uMsg, wParam); if (action == mouse_action::press) - window->handle_mouse_press(pos, button); + window->handle_mouse_button_down(pos, button); if (action == mouse_action::release) - window->handle_mouse_release(pos, button); + window->handle_mouse_button_up(pos, button); } processed = true; } @@ -78,10 +83,6 @@ LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) return DefWindowProc(hwnd, uMsg, wParam, lParam); } -void mwindow::invalidate(invalidate_reason in_reason) { - layout_tree_.invalidate(in_reason); -} - bool mwindow::create_window(int width, int height, const wchar_t* title) { WNDCLASS wc = {}; wc.lpfnWndProc = WindowProc; @@ -108,9 +109,6 @@ bool mwindow::create_window(int width, int height, const wchar_t* title) { return false; } - paint_context_.init({ width, height }); - init_window(); - windows.push_back(this); return true; } @@ -119,8 +117,10 @@ void mwindow::show() { ShowWindow(WINDOW_HANDLE, SW_SHOW); } void mwindow::hide() { ShowWindow(WINDOW_HANDLE, SW_HIDE); } void mwindow::close() { - close_request_ = true; + if (!window_handle_) { return; } + on_close_delegate.broadcast(this); DestroyWindow(WINDOW_HANDLE); + window_handle_ = nullptr; } void mwindow::maximize() { ShowWindow(WINDOW_HANDLE, SW_MAXIMIZE); } @@ -167,7 +167,7 @@ Eigen::Vector2i mwindow::get_window_frame_size() const { return Eigen::Vector2i(0, 0); } -float mwindow::get_dpi_scale() const { +float mwindow::get_window_dpi_scale() const { return 1.f; } diff --git a/src/geometry/arranged_children.h b/src/geometry/arranged_children.h index ea23166..4122060 100644 --- a/src/geometry/arranged_children.h +++ b/src/geometry/arranged_children.h @@ -14,7 +14,6 @@ #include "misc/mirage_type.h" class mwidget; - /** * @class arranged_widget * @brief 表示已排列好的单个组件及其几何信息 @@ -90,7 +89,7 @@ public: * * 创建一个具有指定可见性过滤条件的arranged_children实例。 */ - explicit arranged_children(visibility in_visibility_filter = visibility::any_visible) : visibility_filter_(in_visibility_filter) {} + explicit arranged_children(visibility_t in_visibility_filter = visibility_t::any_visible) : visibility_filter_(in_visibility_filter) {} //-------------- 可见性过滤 -------------- @@ -99,7 +98,7 @@ public: * @param in_visibility 要检查的可见性 * @return 如果该可见性级别被接受则返回true */ - [[nodiscard]] auto accepts(visibility in_visibility) const { + [[nodiscard]] auto accepts(visibility_t in_visibility) const { return has_any_flag(in_visibility, visibility_filter_); } @@ -107,7 +106,7 @@ public: * @brief 设置可见性过滤器 * @param in_visibility_filter 新的可见性过滤级别 */ - void set_filter(visibility in_visibility_filter) { + void set_filter(visibility_t in_visibility_filter) { visibility_filter_ = in_visibility_filter; } @@ -115,7 +114,7 @@ public: * @brief 添加标志到筛选器 * @param in_flag 要添加的可见性标志 */ - void add_filter_flag(visibility in_flag) { + void add_filter_flag(visibility_t in_flag) { visibility_filter_ |= in_flag; } @@ -123,7 +122,7 @@ public: * @brief 移除筛选器中的标志 * @param in_flag 要移除的可见性标志 */ - void remove_filter_flag(visibility in_flag) { + void remove_filter_flag(visibility_t in_flag) { visibility_filter_ &= ~in_flag; } @@ -143,7 +142,7 @@ public: * * 根据可见性过滤规则添加组件,只有当可见性满足条件时才会添加。 */ - void add_widget(visibility in_visibility_override, const arranged_widget& in_widget_geometry) { + void add_widget(visibility_t in_visibility_override, const arranged_widget& in_widget_geometry) { if (accepts(in_visibility_override)) children_.push_back(in_widget_geometry); } @@ -156,7 +155,7 @@ public: * * 根据可见性过滤规则在指定位置插入组件,只有当可见性满足条件时才会插入。 */ - void insert_widget(visibility in_visibility_override, const arranged_widget& in_widget_geometry, size_t in_index) { + void insert_widget(visibility_t in_visibility_override, const arranged_widget& in_widget_geometry, size_t in_index) { if (accepts(in_visibility_override)) children_.insert(children_.begin() + in_index, in_widget_geometry); } @@ -194,7 +193,7 @@ public: private: /** 可见性过滤级别 */ - visibility visibility_filter_; + visibility_t visibility_filter_; /** 子组件列表 */ std::vector children_; diff --git a/src/geometry/geometry.h b/src/geometry/geometry.h index 4d8e700..c989765 100644 --- a/src/geometry/geometry.h +++ b/src/geometry/geometry.h @@ -253,23 +253,31 @@ public: /** * @brief 检查点是否在此几何体内 * - * @param in_window_point 以绝对坐标表示的点, 窗口内的点 + * @param in_local_point 以局部坐标表示的点 * @return 如果点在几何体内,则为true + */ + [[nodiscard]] auto is_under_local_location(const Eigen::Vector2f& in_local_point) const { + // 检查点是否在本地矩形范围内 + return in_local_point.x() >= 0 && + in_local_point.x() <= size_.x() && + in_local_point.y() >= 0 && + in_local_point.y() <= size_.y(); + } + + /** + * @brief 检查点是否在此几何体内 + * + * @param in_window_point 以绝对坐标表示的点, 窗口内的点 + * @return 如果点在几何体内,返回局部坐标系中的点 */ [[nodiscard]] auto is_under_location(const Eigen::Vector2f& in_window_point) const { // 将点从绝对坐标转换到本地坐标 - auto local_point = window_to_local(in_window_point); - - // 检查点是否在本地矩形范围内 - if (local_point.x() >= 0 && - local_point.x() <= size_.x() && - local_point.y() >= 0 && - local_point.y() <= size_.y()) - return std::optional(local_point); + const auto& local_point = window_to_local(in_window_point); + if (is_under_local_location(local_point)) + return std::optional(local_point); return std::optional(); } - private: /** 几何体在本地坐标系中的大小 */ Eigen::Vector2f size_; diff --git a/src/geometry/rect.h b/src/geometry/rect.h index 050ef55..0a644ef 100644 --- a/src/geometry/rect.h +++ b/src/geometry/rect.h @@ -581,7 +581,7 @@ public: * @return 调整后的内部矩形 */ template - auto align_rect(const rect_t& in_inner, horizontal_alignment in_h_align, vertical_alignment in_v_align, const Eigen::Vector2& padding = {}) const { + auto align_rect(const rect_t& in_inner, horizontal_alignment_t in_h_align, vertical_alignment_t in_v_align, const Eigen::Vector2& padding = {}) const { U x{}; U y{}; U width = in_inner.width(); @@ -589,16 +589,16 @@ public: // 水平对齐 switch (in_h_align) { - case horizontal_alignment::left: + case horizontal_alignment_t::left: x = left() + padding.x(); break; - case horizontal_alignment::center: + case horizontal_alignment_t::center: x = left() + (this->width() - width) / 2; break; - case horizontal_alignment::right: + case horizontal_alignment_t::right: x = right() - width - padding.x(); break; - case horizontal_alignment::stretch: + case horizontal_alignment_t::stretch: x = left() + padding.x(); width = std::max(this->width() - padding.x() * 2, U{}); break; @@ -606,16 +606,16 @@ public: // 垂直对齐 switch (in_v_align) { - case vertical_alignment::top: + case vertical_alignment_t::top: y = top() + padding.y(); break; - case vertical_alignment::center: + case vertical_alignment_t::center: y = top() + (this->height() - height) / 2; break; - case vertical_alignment::bottom: + case vertical_alignment_t::bottom: y = bottom() - height - padding.y(); break; - case vertical_alignment::stretch: + case vertical_alignment_t::stretch: y = top() + padding.y(); height = std::max(this->height() - padding.y() * 2, U{}); break; @@ -633,29 +633,29 @@ public: * @return 文本矩形 */ template - auto get_text_rect(horizontal_text_alignment in_h_align, vertical_text_alignment in_v_align, const Eigen::Vector2& in_text_size) const { + auto get_text_rect(horizontal_text_alignment_t in_h_align, vertical_text_alignment_t in_v_align, const Eigen::Vector2& in_text_size) const { rect_t text_rect{{0, 0}, in_text_size}; switch (in_h_align) { - case horizontal_text_alignment::left: + case horizontal_text_alignment_t::left: text_rect.position_.x() = left(); break; - case horizontal_text_alignment::center: + case horizontal_text_alignment_t::center: text_rect.position_.x() = left() + (width() - text_rect.width()) / 2; break; - case horizontal_text_alignment::right: + case horizontal_text_alignment_t::right: text_rect.position_.x() = right() - text_rect.width(); break; } switch (in_v_align) { - case vertical_text_alignment::top: + case vertical_text_alignment_t::top: text_rect.position_.y() = top(); break; - case vertical_text_alignment::center: + case vertical_text_alignment_t::center: text_rect.position_.y() = top() + (height() - text_rect.height()) / 2; break; - case vertical_text_alignment::bottom: + case vertical_text_alignment_t::bottom: text_rect.position_.y() = bottom() - text_rect.height(); break; } diff --git a/src/geometry/widget_layout_tree.cpp b/src/geometry/widget_layout_tree.cpp deleted file mode 100644 index 3663653..0000000 --- a/src/geometry/widget_layout_tree.cpp +++ /dev/null @@ -1,417 +0,0 @@ -#include "widget_layout_tree.h" - -#include - -#include "core/window/mwindow.h" -#include "misc/mirage_scoped_duration_timer.h" -#include "widget/mwidget.h" - -widget_layout_tree_node::widget_layout_tree_node(const std::shared_ptr& in_widget) { - widget_ = in_widget; -} - -void widget_layout_tree_node::set_geometry(const geometry_t& in_geometry) { - geometry_ = in_geometry; - invalidate(invalidate_reason::layout); -} - -void widget_layout_tree_node::add_child(const std::shared_ptr& in_child) { - if (!in_child) - return; - - // 如果子节点已经有父节点,先从父节点中移除 - if (auto old_parent = in_child->get_parent()) { - old_parent->remove_child(in_child); - } - - // 设置新的父节点 - in_child->set_parent(shared_from_this()); - children_.push_back(in_child); - - // 通知子节点的父节点已经改变 - invalidate(invalidate_reason::layout); -} - -void widget_layout_tree_node::remove_child(const std::shared_ptr& in_child) { - const auto it = std::ranges::find(children_, in_child); - if (it != children_.end()) { - (*it)->set_parent(nullptr); - children_.erase(it); - invalidate(invalidate_reason::layout); - } -} - -void widget_layout_tree_node::clear_children() { - for (const auto& child : children_) { - child->set_parent(nullptr); - } - children_.clear(); - invalidate(invalidate_reason::layout); -} - -void widget_layout_tree_node::build_tree() { - // 获取当前节点的控件 - auto widget = get_widget(); - if (!widget) - return; - - // 清除现有的子节点 - clear_children(); - // 获取所有子控件 - auto child_widgets = widget->get_children(); - - // 为每个子控件创建一个节点 - for (const auto& child_widget : child_widgets) { - auto child_node = std::make_shared(child_widget); - add_child(child_node); - - // 递归构建子树 - child_node->build_tree(); - } -} - -void widget_layout_tree_node::update_layout(float in_layout_scale_multiplier) { - if (!is_layout_dirty()) - return; - - auto widget = get_widget(); - if (!widget) - return; - - // 第一阶段: 计算期望尺寸(自下而上) - cache_desired_size(in_layout_scale_multiplier); - auto desired_size = widget->get_desired_size(); - - // 第二阶段: 分配几何区域(自下而上) - // 如果是根节点没有指定大小, 使用期望大小 - if (!parent_ && (geometry_.get_local_size().x() == 0 || geometry_.get_local_size().y() == 0)) { - // 创建合适期望尺寸的根几何体 - transform2d identity; - geometry_ = geometry_t{ desired_size, identity, identity }; - } - - // 为子节点分配几何区域 - if (!children_.empty()) { - // 创建arranged_children对象,使用可见性筛选器 - arranged_children arranged_widgets(visibility::any_visible); // 默认只接受可见的widgets - - // 调用widget的arrange_children方法填充arranged_widgets - widget->arrange_children(geometry_, arranged_widgets); - - // 从arranged_widgets提取子widget及其几何体 - const auto& arranged_children_list = arranged_widgets.get_children(); - - // 更新子节点树以匹配排列后的widgets - std::vector> new_children; - - // 为每个排列后的widget找到或创建对应的树节点 - for (const auto& arranged_widget : arranged_children_list) { - const auto& child_widget = arranged_widget.get_widget(); - const auto& child_geometry = arranged_widget.get_geometry(); - - // 在现有子节点中查找对应widget - auto found_it = std::ranges::find_if(children_, - [&child_widget](const auto& node) { - return node->get_widget() == child_widget; - }); - - std::shared_ptr child_node; - - if (found_it != children_.end()) { - // 使用现有节点 - child_node = *found_it; - } else { - // 创建新节点 - child_node = std::make_shared(child_widget); - child_node->set_parent(shared_from_this()); - child_node->build_tree(); // 递归构建子树 - } - - // 更新几何体 - child_node->set_geometry(child_geometry); - - // 添加到新的子节点列表 - new_children.push_back(child_node); - } - - // 更新子节点列表 - children_ = std::move(new_children); - } - - // 递归更新子节点布局 - for (const auto& child : children_) { - child->update_layout(in_layout_scale_multiplier); - } - - // 更新完成,清除脏标记 - clear_flag(invalidate_reason_, invalidate_reason::layout); -} - -void widget_layout_tree_node::paint(mirage_paint_context& in_context, float in_layout_scale_multiplier) { - // 如果既没有绘制也没有布局更新,直接返回 - if (!is_paint_dirty() && !is_layout_dirty()) { - return; - } - - auto widget = widget_.lock(); - if (!widget) return; - - // 如果布局需要更新,确保最新状态 - // if (is_layout_dirty()) { - // update_layout(in_layout_scale_multiplier); - // } - - // 设置几何体 - in_context.set_geometry(geometry_); - - // 绘制当前widget - widget->on_paint(in_context); - - // 递归绘制所有子节点 - for (const auto& child_node: children_) { - child_node->paint(in_context, in_layout_scale_multiplier); - } - clear_flag(invalidate_reason_, invalidate_reason::paint); -} - -hit_test_result widget_layout_tree_node::perform_hit_test( - const Eigen::Vector2f& in_window_pos, - const std::function, const Eigen::Vector2f&)>& in_hit_func) { - - // 首先检查点是否在几何范围内 - auto in_widget_pos = geometry_.is_under_location(in_window_pos); - if (!in_widget_pos) - return {}; - - // 获取widget的强引用,并立即检查是否有效 - auto widget = widget_.lock(); - if (!widget) - return {}; - - // 处理当前widget的命中测试 - if (widget->can_hit_test() && in_hit_func) { - const auto& widget_pos = in_widget_pos.value(); - const hit_test_handle handle = in_hit_func(widget, widget_pos); - if (handle.is_handled()) - return { widget, widget_pos }; // **提前返回命中的widget** - } - - // 递归检查子widget - for (const auto& child : children_) { - if (auto hit_widget = child->perform_hit_test(in_window_pos, in_hit_func)) - return hit_widget; // **立即返回第一个命中的子widget** - } - - return {}; -} - -void widget_layout_tree_node::invalidate(invalidate_reason in_reason) { - set_flag(invalidate_reason_,in_reason); - for (const auto& child : children_) { - child->invalidate(in_reason); - } -} - -void widget_layout_tree_node::cache_desired_size(float in_layout_scale_multiplier) { - auto widget = get_widget(); - if (!widget) return; - - // 递归计算子节点的期望大小 - for (const auto& child : children_) { - child->cache_desired_size(in_layout_scale_multiplier); - } - - // 计算期望大小 - widget->cache_desired_size(in_layout_scale_multiplier); -} - -void widget_layout_tree::set_root(const std::shared_ptr& in_root_widget) { - if (!in_root_widget) return; - - root_ = std::make_shared(in_root_widget); - - // 构建整个布局树 - root_->build_tree(); - - // 标记需要重新计算布局 - invalidate(invalidate_reason::layout); -} - -void widget_layout_tree::set_root_geometry(const geometry_t& in_geometry) { - if (root_) { - root_->set_geometry(in_geometry); - } -} - -void widget_layout_tree::build_layout() { -#if DEBUG - duration_type duration; - { - mirage_scoped_duration_timer timer(duration); -#endif - if (root_) { - // 首先构建整个树结构 - root_->build_tree(); - - // 然后更新布局 - root_->update_layout(get_dpi_scale()); - } -#if DEBUG - } - std::cout << "布局树构建时间: " << std::chrono::duration_cast(duration).count() << "ms" << std::endl; -#endif -} - -void widget_layout_tree::update_layout_if_needed() { - if (root_ && root_->is_layout_dirty()) { - build_layout(); - invalidate(invalidate_reason::paint); - } -} - -void widget_layout_tree::invalidate(invalidate_reason in_reason) { - if (root_) { - root_->invalidate(in_reason); - } -} - -void widget_layout_tree::tick() { - // 确保布局是最新的 - update_layout_if_needed(); -} - -void widget_layout_tree::paint(mirage_paint_context& in_context) { - // 从根节点开始绘制整个UI树 - if (root_) { - root_->paint(in_context, get_dpi_scale()); - } -} - -mwindow* widget_layout_tree::get_window() const { - if (const auto widget = root_->get_widget()) - return widget->get_window(); - return nullptr; -} - -float widget_layout_tree::get_dpi_scale() const { - if (const auto window = get_window()) - return window->get_dpi_scale() * dpi_helper::get_global_scale(); - return 1.0f; -} - -hit_test_result widget_layout_tree::perform_hit_test(const Eigen::Vector2f& in_window_pos, const std::function, const Eigen::Vector2f&)>& in_hit_func) { - if (!root_) - return {}; - - return root_->perform_hit_test(in_window_pos, in_hit_func); -} - -void widget_layout_tree::on_mouse_leave_window() { - if (last_hover_widget_) - last_hover_widget_->on_mouse_leave(); - last_hover_widget_ = nullptr; -} - -void widget_layout_tree::process_mouse_move(const Eigen::Vector2f& in_window_pos) { - auto result = perform_hit_test(in_window_pos, [this](std::shared_ptr in_widget, const Eigen::Vector2f& in_local_pos) { - return in_widget->on_mouse_move(in_local_pos); - }); - - // 如果命中的widget与上次相同,则不需要处理 - if (last_hover_widget_ == result.widget) - return; - - // 如果上次有悬停widget,通知其离开 - if (last_hover_widget_) - last_hover_widget_->on_mouse_leave(); - - // 更新悬停widget - last_hover_widget_ = result; - if (last_hover_widget_) - last_hover_widget_->on_mouse_enter(); -} - -void widget_layout_tree::process_mouse_press(const Eigen::Vector2f& in_window_pos, mouse_button in_button) { - // 执行碰撞检测,找出鼠标位置下的widget - auto result = perform_hit_test(in_window_pos, [in_button](std::shared_ptr in_widget, const Eigen::Vector2f& in_local_pos) { - return in_widget->on_mouse_press(in_local_pos, in_button); - }); - - const auto& hit_widget = result.widget; - - // 更新最后一次点击的widget和时间 - last_hit_widget_ = hit_widget; - last_mouse_press_time_ = get_current_time(); - last_mouse_press_pos_ = in_window_pos; // 存储点击位置,用于空间判断 -} - -void widget_layout_tree::process_mouse_release(const Eigen::Vector2f& in_window_pos, mouse_button in_button) { - // 执行碰撞检测,找出鼠标位置下的widget - auto hit_result = perform_hit_test(in_window_pos, [in_button](std::shared_ptr widget, const Eigen::Vector2f& local_pos) { - return widget->on_mouse_release(local_pos, in_button); - }); - - const auto& hit_widget = hit_result.widget; - const auto& widget_local_pos = hit_result.widget_space_pos; - - // 如果没有点击到任何widget,重置状态并返回 - if (!hit_widget) { - click_count_ = 0; - last_hit_widget_ = nullptr; - return; - } - - // 定义时间和空间阈值 - constexpr auto CLICK_TIME_THRESHOLD = std::chrono::milliseconds(400); // 单次点击最大持续时间 - constexpr float CLICK_DISTANCE_THRESHOLD = 5.0f; // 点击位置容差 - - // 计算从按下到释放的持续时间 - const auto press_duration = get_current_time() - last_mouse_press_time_; - - // 计算与上次点击位置的距离 - const float distance = (in_window_pos - last_mouse_press_pos_).norm(); - - // 判断是否是有效点击(时间短且位置接近) - bool is_valid_click = press_duration < CLICK_TIME_THRESHOLD && distance < CLICK_DISTANCE_THRESHOLD; - - // 处理点击相关逻辑 - if (is_valid_click && last_hit_widget_ == hit_widget) { - // 计算上次点击事件到现在的时间 - const auto time_since_last_click = get_current_time() - last_click_time_; - - // 判断是否是连续点击(在双击时间窗口内) - if (click_count_ > 0 && time_since_last_click < CLICK_TIME_THRESHOLD) { - // 点击次数增加 - click_count_++; - - // 根据点击次数触发不同事件 - if (click_count_ == 2) { - // 双击事件 - hit_widget->on_double_click(widget_local_pos, in_button); - } else { - // 单击事件 - hit_widget->on_click(widget_local_pos, in_button); - } - } else { - // 超过双击时间窗口,视为新的点击序列 - click_count_ = 1; - hit_widget->on_click(widget_local_pos, in_button); - } - - // 更新最后点击时间 - last_click_time_ = get_current_time(); - } else { - // 无效点击或点击了新的widget - if (is_valid_click) { - click_count_ = 1; - hit_widget->on_click(widget_local_pos, in_button); - last_click_time_ = get_current_time(); - } else { - // 不是有效点击,重置点击计数 - click_count_ = 0; - } - } - - // 更新最后点击的widget - last_hit_widget_ = hit_widget; -} diff --git a/src/geometry/widget_layout_tree.h b/src/geometry/widget_layout_tree.h deleted file mode 100644 index 864d9e6..0000000 --- a/src/geometry/widget_layout_tree.h +++ /dev/null @@ -1,296 +0,0 @@ -#pragma once -/** - * @file widget_layout_tree.h - * @brief 定义管理UI组件布局树的结构 - * - * 本文件定义了两个主要类:widget_layout_tree_node表示布局树中的单个节点, - * 以及widget_layout_tree管理整个布局树。这些类负责组件的几何布局、重绘逻辑 - * 和层次结构维护。 - */ - -#include -#include - -#include "geometry.h" -#include "core/render_elements.h" -#include "misc/invalidate_reason.h" -#include "misc/mirage_paint_context.h" -#include "misc/key_type/key_type.h" -#include "widget/hit_test/hit_test_parameters.h" -#include "widget/hit_test/hit_test_result.h" - -class mwindow; -class mwidget; - -/** - * @class widget_layout_tree_node - * @brief 表示布局树中的单个节点 - * - * 每个节点包含一个UI组件的引用,以及它在布局中的几何信息。 - * 节点维护父子关系以形成树结构,并处理布局更新和绘制操作。 - */ -class widget_layout_tree_node : public std::enable_shared_from_this { -public: - //-------------- 构造与结构关系 -------------- - - /** - * @brief 构造函数 - * @param in_widget 节点所对应的UI组件 - */ - explicit widget_layout_tree_node(const std::shared_ptr& in_widget); - - /** - * @brief 设置父节点 - * @param in_parent 父节点指针 - */ - void set_parent(const std::shared_ptr& in_parent) { parent_ = in_parent; } - - /** - * @brief 获取父节点 - * @return 父节点指针 - */ - [[nodiscard]] const auto& get_parent() const { return parent_; } - - /** - * @brief 获取当前节点的UI组件 - * @return 节点关联的UI组件 - */ - [[nodiscard]] auto get_widget() const { return widget_.lock(); } - - /** - * @brief 获取子节点列表 - * @return 子节点指针的向量 - */ - [[nodiscard]] auto get_children() const { return children_; } - - /** - * @brief 添加子节点 - * @param in_child 要添加的子节点 - */ - void add_child(const std::shared_ptr& in_child); - - /** - * @brief 移除子节点 - * @param in_child 要移除的子节点 - */ - void remove_child(const std::shared_ptr& in_child); - - /** - * @brief 清除所有子节点 - */ - void clear_children(); - - //-------------- 几何处理 -------------- - - /** - * @brief 设置节点的几何信息 - * @param in_geometry 新的几何信息 - */ - void set_geometry(const geometry_t& in_geometry); - - /** - * @brief 获取节点的几何信息 - * @return 节点的几何信息 - */ - [[nodiscard]] const auto& get_geometry() const { return geometry_; } - - /** - * @brief 获取父节点的几何信息 - * @return 父节点的几何信息 - */ - [[nodiscard]] const auto& get_parent_geometry() const { return parent_->get_geometry(); } - - /** - * @brief 进行命中测试 - * @return - - //-------------- 布局处理 -------------- - - /** - * @brief 检查布局是否需要更新 - * @return 如果布局需要更新则返回true - */ - [[nodiscard]] auto is_layout_dirty() const { return has_any_flag(invalidate_reason_, invalidate_reason::layout); } - - /** - * @brief 检查绘制是否需要更新 - * @return 如果绘制需要更新则返回true - */ - [[nodiscard]] auto is_paint_dirty() const { return has_any_flag(invalidate_reason_, invalidate_reason::paint); } - - auto get_invalidate_state() const { return invalidate_reason_; } - - /** - * @brief 使布局失效,标记需要更新 - */ - void invalidate(invalidate_reason in_reason); - - /** - * @brief 构建布局树结构 - * - * 递归地从UI组件层次结构构建布局树。 - */ - void build_tree(); - - /** - * @brief 更新节点及其子节点的布局 - * @param in_layout_scale_multiplier 布局缩放系数 - * - * 递归地计算和应用布局更新。 - */ - void update_layout(float in_layout_scale_multiplier); - - /** - * @brief 缓存组件期望的大小 - * @param in_layout_scale_multiplier 布局缩放系数 - */ - void cache_desired_size(float in_layout_scale_multiplier); - - //-------------- 绘制 -------------- - - /** - * @brief 绘制节点及其子节点 - * @param in_context 绘制上下文 - * @param in_layout_scale_multiplier 布局缩放系数 - * - * 递归地绘制整个节点树。 - */ - void paint(mirage_paint_context& in_context, float in_layout_scale_multiplier); - - //----------------- 命中测试 ----------------- - - /** - * @brief 执行命中测试 - * @param in_window_pos 窗口内位置 - * @param in_hit_func 执行的控件相应事件 - */ - hit_test_result perform_hit_test(const Eigen::Vector2f& in_window_pos, const std::function, const Eigen::Vector2f&)>& in_hit_func); -private: - /** 节点对应的UI组件的弱引用 */ - std::weak_ptr widget_; - - /** 节点的几何信息 */ - geometry_t geometry_; - - /** 父节点指针 */ - std::shared_ptr parent_; - - /** 子节点列表 */ - std::vector> children_; - - /** 布局是否需要更新的标志 */ - invalidate_reason invalidate_reason_{ invalidate_reason::none }; -}; - -/** - * @class widget_layout_tree - * @brief 管理整个UI组件布局树 - * - * 控制整个UI组件层次结构的布局处理,包括根节点设置、 - * 布局更新和绘制操作。 - */ -class widget_layout_tree { -public: - //-------------- 根节点管理 -------------- - - /** - * @brief 设置布局树的根组件 - * @param in_root_widget 根UI组件 - */ - void set_root(const std::shared_ptr& in_root_widget); - - /** - * @brief 设置根节点的几何信息 - * @param in_geometry 根节点的几何信息 - */ - void set_root_geometry(const geometry_t& in_geometry); - - /** - * @brief 获取根节点 - * @return 根节点指针 - */ - [[nodiscard]] const auto& get_root() const { return root_; } - - //-------------- 布局操作 -------------- - - /** - * @brief 构建整个布局树 - */ - void build_layout(); - - /** - * @brief 如果需要则更新布局 - * - * 只有当布局被标记为失效时才执行更新。 - */ - void update_layout_if_needed(); - - /** - * @brief 失效 - */ - void invalidate(invalidate_reason in_reason); - - //-------------- 绘制和窗口 -------------- - - /** - * @brief 更新函数 - */ - void tick(); - - /** - * @brief 绘制整个布局树 - * @param in_context 绘制上下文 - */ - void paint(mirage_paint_context& in_context); - - /** - * @brief 获取关联的窗口 - * @return 窗口指针 - */ - [[nodiscard]] mwindow* get_window() const; - - /** - * @brief 获取DPI缩放系数 - * @return DPI缩放系数 - */ - [[nodiscard]] float get_dpi_scale() const; - - //----------------- 命中测试 ----------------- - - /** - * @brief 执行命中测试 - * @param in_window_pos 窗口内位置 - * @param in_hit_func 执行的控件相应事件 - * @return 命中的控件 - */ - hit_test_result perform_hit_test(const Eigen::Vector2f& in_window_pos, const std::function, const Eigen::Vector2f&)>& in_hit_func); - - void on_mouse_leave_window(); - - void process_mouse_move(const Eigen::Vector2f& in_window_pos); - - void process_mouse_press(const Eigen::Vector2f& in_window_pos, mouse_button in_button); - - void process_mouse_release(const Eigen::Vector2f& in_window_pos, mouse_button in_button); - - auto get_invalidate_state() const { - if (root_) - return root_->get_invalidate_state(); - return invalidate_reason::none; - } - -private: - /** 布局树的根节点 */ - std::shared_ptr root_; - - //----------------- 命中测试 ----------------- - std::shared_ptr last_hit_widget_; - std::shared_ptr last_hover_widget_; - Eigen::Vector2f last_hit_position_; - Eigen::Vector2f last_mouse_position_; - int32_t click_count_{}; - - Eigen::Vector2f last_mouse_press_pos_; // 上次鼠标按下的位置 - time_type last_click_time_{}; // 上次点击完成的时间 - time_type last_mouse_press_time_{}; -}; diff --git a/src/mirage.cpp b/src/mirage.cpp index e92c7ac..44f9ee3 100644 --- a/src/mirage.cpp +++ b/src/mirage.cpp @@ -50,7 +50,8 @@ void mirage_app::run() { delta_time = get_current_time() - last_time; mwindow::poll_events(); - render_context->tick(delta_time); + widget_manager::get().update(); + last_time = get_current_time(); // std::this_thread::sleep_for(std::chrono::milliseconds(1)); std::this_thread::yield(); diff --git a/src/misc/delegates.h b/src/misc/delegates.h new file mode 100644 index 0000000..0c3dc6e --- /dev/null +++ b/src/misc/delegates.h @@ -0,0 +1,1103 @@ +/* +DOCUMENTATION + +// CONFIG + +Override default asset +#define DELEGATE_ASSERT(expression, ...) + +Override default static_assert +#define DELEGATE_STATIC_ASSERT(expression, msg) + +Set inline allocator size (default: 32) +#define DELEGATE_INLINE_ALLOCATION_SIZE + +Reassign allocation functions: +Delegates::SetAllocationCallbacks(allocFunction, freeFunc); + + +// USAGE + +## Classes ## +- ```Delegate``` +- ```MulticastDelegate``` + +## Features ## +- Support for: + - Static/Global methods + - Member functions + - Lambda's + - std::shared_ptr +- Delegate object is allocated inline if it is under 32 bytes +- Add payload to delegate during bind-time +- Move operations enable optimization + +## Example Usage ## + +### Delegate ### + +Delegate del; +del.BindLambda([](float a, int payload) +{ + std::cout << "Lambda delegate parameter: " << a << std::endl; + std::cout << "Lambda delegate payload: " << payload << std::endl; + return 10; +}, 50); +std::cout << "Lambda delegate return value: " << del.Execute(20) << std::endl; + +Output: +Lambda delegate parameter: 20 +Lambda delegate payload: 50 +Lambda delegate return value: 10 + +### MulticastDelegate ### + +struct Foo +{ + void Bar(float a, int payload) + { + std::cout << "Raw delegate parameter: " << a << std::endl; + std::cout << "Raw delegate payload: " << payload << std::endl; + } +}; +MulticastDelegate del; +del.AddLambda([](float a, int payload) +{ + std::cout << "Lambda delegate parameter: " << a << std::endl; + std::cout << "Lambda delegate payload: " << payload << std::endl; +}, 90); + +Foo foo; +del.AddRaw(&foo, &Foo::Bar, 10); +del.Broadcast(20); + +Output: +Lambda delegate parameter: 20 +Lambda delegate payload: 90 +Raw delegate parameter: 20 +Raw delegate payload: 10 + +*/ + +#ifndef CPP_DELEGATES +#define CPP_DELEGATES + +#include +#include +#include + +/////////////////////////////////////////////////////////////// +//////////////////// DEFINES SECTION ////////////////////////// +/////////////////////////////////////////////////////////////// + +#ifndef DELEGATE_ASSERT +#include +#define DELEGATE_ASSERT(expression, ...) assert(expression) +#endif + +#ifndef DELEGATE_STATIC_ASSERT +#if __cplusplus <= 199711L +#define DELEGATE_STATIC_ASSERT(expression, msg) static_assert(expression, msg) +#else +#define DELEGATE_STATIC_ASSERT(expression, msg) +#endif +#endif + +//The allocation size of delegate data. +//Delegates larger than this will be heap allocated. +#ifndef DELEGATE_INLINE_ALLOCATION_SIZE +#define DELEGATE_INLINE_ALLOCATION_SIZE 32 +#endif + +#define DECLARE_DELEGATE(name, ...) \ +using name = Delegate + +#define DECLARE_DELEGATE_RET(name, retValue, ...) \ +using name = Delegate + +#define DECLARE_MULTICAST_DELEGATE(name, ...) \ +using name = MulticastDelegate<__VA_ARGS__>; \ +using name ## Delegate = MulticastDelegate<__VA_ARGS__>::DelegateT + +#define DECLARE_EVENT(name, ownerType, ...) \ +class name : public MulticastDelegate<__VA_ARGS__> \ +{ \ +private: \ + friend class ownerType; \ + using MulticastDelegate::Broadcast; \ + using MulticastDelegate::RemoveAll; \ + using MulticastDelegate::Remove; \ +}; + +/////////////////////////////////////////////////////////////// +/////////////////// INTERNAL SECTION ////////////////////////// +/////////////////////////////////////////////////////////////// + +#if __cplusplus >= 201703L +#define NO_DISCARD [[nodiscard]] +#else +#define NO_DISCARD +#endif + +namespace _DelegatesInteral +{ + template + struct MemberFunction; + + template + struct MemberFunction + { + using Type = RetVal(Object::*)(Args...) const; + }; + + template + struct MemberFunction + { + using Type = RetVal(Object::*)(Args...); + }; + + static void* (*Alloc)(size_t size) = [](size_t size) { return malloc(size); }; + static void(*Free)(void* pPtr) = [](void* pPtr) { free(pPtr); }; + template + void DelegateDeleteFunc(T* pPtr) + { + pPtr->~T(); + DelegateFreeFunc(pPtr); + } +} + +namespace Delegates +{ + using AllocateCallback = void* (*)(size_t size); + using FreeCallback = void(*)(void* pPtr); + inline void SetAllocationCallbacks(AllocateCallback allocateCallback, FreeCallback freeCallback) + { + _DelegatesInteral::Alloc = allocateCallback; + _DelegatesInteral::Free = freeCallback; + } +} + +class i_delegate_base +{ +public: + i_delegate_base() = default; + virtual ~i_delegate_base() noexcept = default; + virtual const void* get_owner() const { return nullptr; } + virtual void clone(void* pDestination) = 0; +}; + +//Base type for delegates +template +class i_delegate : public i_delegate_base +{ +public: + virtual RetVal execute(Args&&... args) = 0; +}; + +template +class static_delegate; + +template +class static_delegate : public i_delegate +{ +public: + using DelegateFunction = RetVal(*)(Args..., Args2...); + + static_delegate(DelegateFunction function, Args2&&... payload) + : function_(function), payload_(std::forward(payload)...) + {} + + static_delegate(DelegateFunction function, const std::tuple& payload) + : function_(function), payload_(payload) + {} + + virtual RetVal execute(Args&&... args) override + { + return execute_internal(std::forward(args)..., std::index_sequence_for()); + } + + virtual void clone(void* pDestination) override + { + new (pDestination) static_delegate(function_, payload_); + } + +private: + template + RetVal execute_internal(Args&&... args, std::index_sequence) + { + return function_(std::forward(args)..., std::get(payload_)...); + } + + DelegateFunction function_; + std::tuple payload_; +}; + +template +class raw_delegate; + +template +class raw_delegate : public i_delegate +{ +public: + using DelegateFunction = typename _DelegatesInteral::MemberFunction::Type; + + raw_delegate(T* pObject, DelegateFunction function, Args2&&... payload) + : object_(pObject), function_(function), payload_(std::forward(payload)...) + {} + + raw_delegate(T* pObject, DelegateFunction function, const std::tuple& payload) + : object_(pObject), function_(function), payload_(payload) + {} + + virtual RetVal execute(Args&&... args) override + { + return execute_internal(std::forward(args)..., std::index_sequence_for()); + } + virtual const void* get_owner() const override + { + return object_; + } + + virtual void clone(void* pDestination) override + { + new (pDestination) raw_delegate(object_, function_, payload_); + } + +private: + template + RetVal execute_internal(Args&&... args, std::index_sequence) + { + return (object_->*function_)(std::forward(args)..., std::get(payload_)...); + } + + T* object_; + DelegateFunction function_; + std::tuple payload_; +}; + +template +class lambda_delegate; + +template +class lambda_delegate : public i_delegate +{ +public: + explicit lambda_delegate(TLambda&& lambda, Args2&&... payload) + : lambda_(std::forward(lambda)), + payload_(std::forward(payload)...) + {} + + explicit lambda_delegate(const TLambda& lambda, const std::tuple& payload) + : lambda_(lambda), + payload_(payload) + {} + + RetVal execute(Args&&... args) override + { + return execute_internal(std::forward(args)..., std::index_sequence_for()); + } + + virtual void clone(void* pDestination) override + { + new (pDestination) lambda_delegate(lambda_, payload_); + } + +private: + template + RetVal execute_internal(Args&&... args, std::index_sequence) + { + return (RetVal)((lambda_)(std::forward(args)..., std::get(payload_)...)); + } + + TLambda lambda_; + std::tuple payload_; +}; + +template +class sp_delegate; + +template +class sp_delegate : public i_delegate +{ +public: + using DelegateFunction = typename _DelegatesInteral::MemberFunction::Type; + + sp_delegate(std::shared_ptr pObject, DelegateFunction pFunction, Args2&&... payload) + : object_(pObject), + function_(pFunction), + payload_(std::forward(payload)...) + {} + + sp_delegate(std::weak_ptr pObject, DelegateFunction pFunction, const std::tuple& payload) + : object_(pObject), + function_(pFunction), + payload_(payload) + {} + + virtual RetVal execute(Args&&... args) override + { + return execute_internal(std::forward(args)..., std::index_sequence_for()); + } + + virtual const void* get_owner() const override + { + return object_.expired() ? nullptr : object_.lock().get(); + } + + virtual void clone(void* pDestination) override + { + new (pDestination) sp_delegate(object_, function_, payload_); + } + +private: + template + RetVal execute_internal(Args&&... args, std::index_sequence) + { + if (object_.expired()) + { + return RetVal(); + } + else + { + std::shared_ptr pPinned = object_.lock(); + return (pPinned.get()->*function_)(std::forward(args)..., std::get(payload_)...); + } + } + + std::weak_ptr object_; + DelegateFunction function_; + std::tuple payload_; +}; + +//A handle to a delegate used for a multicast delegate +//Static ID so that every handle is unique +class delegate_handle +{ +public: + constexpr delegate_handle() noexcept + : id_(INVALID_ID) + { + } + + explicit delegate_handle(bool /*generateId*/) noexcept + : id_(get_new_id()) + { + } + + ~delegate_handle() noexcept = default; + delegate_handle(const delegate_handle& other) = default; + delegate_handle& operator=(const delegate_handle& other) = default; + + delegate_handle(delegate_handle&& other) noexcept + : id_(other.id_) + { + other.reset(); + } + + delegate_handle& operator=(delegate_handle&& other) noexcept + { + id_ = other.id_; + other.reset(); + return *this; + } + + operator bool() const noexcept + { + return is_valid(); + } + + bool operator==(const delegate_handle& other) const noexcept + { + return id_ == other.id_; + } + + bool operator<(const delegate_handle& other) const noexcept + { + return id_ < other.id_; + } + + bool is_valid() const noexcept + { + return id_ != INVALID_ID; + } + + void reset() noexcept + { + id_ = INVALID_ID; + } + + constexpr static const unsigned int INVALID_ID = (unsigned int)~0; +private: + unsigned int id_; + inline static unsigned int CURRENT_ID = 0; + + static int get_new_id() + { + unsigned int output = ++CURRENT_ID; + if (CURRENT_ID == INVALID_ID) + { + CURRENT_ID = 0; + } + return output; + } +}; + +template +class inline_allocator +{ +public: + //Constructor + constexpr inline_allocator() noexcept + : size_(0) + { + DELEGATE_STATIC_ASSERT(MaxStackSize > sizeof(void*), "MaxStackSize is smaller or equal to the size of a pointer. This will make the use of an InlineAllocator pointless. Please increase the MaxStackSize."); + } + + //Destructor + ~inline_allocator() noexcept + { + free(); + } + + //Copy constructor + inline_allocator(const inline_allocator& other) + : size_(0) + { + if (other.has_allocation()) + { + memcpy(allocate(other.size_), other.get_allocation(), other.size_); + } + size_ = other.size_; + } + + //Copy assignment operator + inline_allocator& operator=(const inline_allocator& other) + { + if (other.has_allocation()) + { + memcpy(allocate(other.size_), other.get_allocation(), other.size_); + } + size_ = other.size_; + return *this; + } + + //Move constructor + inline_allocator(inline_allocator&& other) noexcept + : size_(other.size_) + { + other.size_ = 0; + if (size_ > MaxStackSize) + { + std::swap(pPtr, other.pPtr); + } + else + { + memcpy(Buffer, other.Buffer, size_); + } + } + + //Move assignment operator + inline_allocator& operator=(inline_allocator&& other) noexcept + { + free(); + size_ = other.size_; + other.size_ = 0; + if (size_ > MaxStackSize) + { + std::swap(pPtr, other.pPtr); + } + else + { + memcpy(Buffer, other.Buffer, size_); + } + return *this; + } + + //Allocate memory of given size + //If the size is over the predefined threshold, it will be allocated on the heap + void* allocate(const size_t size) + { + if (size_ != size) + { + free(); + size_ = size; + if (size > MaxStackSize) + { + pPtr = _DelegatesInteral::Alloc(size); + return pPtr; + } + } + return (void*)Buffer; + } + + //Free the allocated memory + void free() + { + if (size_ > MaxStackSize) + { + _DelegatesInteral::Free(pPtr); + } + size_ = 0; + } + + //Return the allocated memory either on the stack or on the heap + void* get_allocation() const + { + if (has_allocation()) + { + return has_heap_allocation() ? pPtr : (void*)Buffer; + } + else + { + return nullptr; + } + } + + size_t get_size() const + { + return size_; + } + + bool has_allocation() const + { + return size_ > 0; + } + + bool has_heap_allocation() const + { + return size_ > MaxStackSize; + } + +private: + //If the allocation is smaller than the threshold, Buffer is used + //Otherwise pPtr is used together with a separate dynamic allocation + union + { + char Buffer[MaxStackSize]; + void* pPtr; + }; + size_t size_; +}; + +class delegate_base +{ +public: + //Default constructor + constexpr delegate_base() noexcept + : allocator_() + {} + + //Default destructor + virtual ~delegate_base() noexcept + { + release(); + } + + //Copy contructor + delegate_base(const delegate_base& other) + { + if (other.allocator_.has_allocation()) + { + allocator_.allocate(other.allocator_.get_size()); + other.get_delegate()->clone(allocator_.get_allocation()); + } + } + + //Copy assignment operator + delegate_base& operator=(const delegate_base& other) + { + release(); + if (other.allocator_.has_allocation()) + { + allocator_.allocate(other.allocator_.get_size()); + other.get_delegate()->clone(allocator_.get_allocation()); + } + return *this; + } + + //Move constructor + delegate_base(delegate_base&& other) noexcept + : allocator_(std::move(other.allocator_)) + {} + + //Move assignment operator + delegate_base& operator=(delegate_base&& other) noexcept + { + release(); + allocator_ = std::move(other.allocator_); + return *this; + } + + //Gets the owner of the deletage + //Only valid for SPDelegate and RawDelegate. + //Otherwise returns nullptr by default + const void* get_owner() const + { + if (allocator_.has_allocation()) + { + return get_delegate()->get_owner(); + } + return nullptr; + } + + size_t get_size() const + { + return allocator_.get_size(); + } + + //Clear the bound delegate if it is bound to the given object. + //Ignored when pObject is a nullptr + void clear_if_bound_to(void* pObject) + { + if (pObject != nullptr && is_bound_to(pObject)) + { + release(); + } + } + + //Clear the bound delegate if it exists + void clear() + { + release(); + } + + //If the allocator has a size, it means it's bound to something + bool is_bound() const + { + return allocator_.has_allocation(); + } + + bool is_bound_to(void* pObject) const + { + if (pObject == nullptr || allocator_.has_allocation() == false) + { + return false; + } + return get_delegate()->get_owner() == pObject; + } + +protected: + void release() + { + if (allocator_.has_allocation()) + { + get_delegate()->~i_delegate_base(); + allocator_.free(); + } + } + + i_delegate_base* get_delegate() const + { + return static_cast(allocator_.get_allocation()); + } + + //Allocator for the delegate itself. + //Delegate gets allocated when its is smaller or equal than 64 bytes in size. + //Can be changed by preference + inline_allocator allocator_; +}; + +//Delegate that can be bound to by just ONE object +template +class delegate : public delegate_base +{ +private: + template + using ConstMemberFunction = typename _DelegatesInteral::MemberFunction::Type; + template + using NonConstMemberFunction = typename _DelegatesInteral::MemberFunction::Type; + +public: + using IDelegateT = i_delegate; + + //Create delegate using member function + template + NO_DISCARD static delegate create_raw(T* pObj, NonConstMemberFunction pFunction, Args2... args) + { + delegate handler; + handler.bind>(pObj, pFunction, std::forward(args)...); + return handler; + } + + template + NO_DISCARD static delegate create_raw(T* pObj, ConstMemberFunction pFunction, Args2... args) + { + delegate handler; + handler.bind>(pObj, pFunction, std::forward(args)...); + return handler; + } + + //Create delegate using global/static function + template + NO_DISCARD static delegate create_static(RetVal(*pFunction)(Args..., Args2...), Args2... args) + { + delegate handler; + handler.bind>(pFunction, std::forward(args)...); + return handler; + } + + //Create delegate using std::shared_ptr + template + NO_DISCARD static delegate create_sp(const std::shared_ptr& pObject, NonConstMemberFunction pFunction, Args2... args) + { + delegate handler; + handler.bind>(pObject, pFunction, std::forward(args)...); + return handler; + } + + template + NO_DISCARD static delegate create_sp(const std::shared_ptr& pObject, ConstMemberFunction pFunction, Args2... args) + { + delegate handler; + handler.bind>(pObject, pFunction, std::forward(args)...); + return handler; + } + + //Create delegate using a lambda + template + NO_DISCARD static delegate create_lambda(TLambda&& lambda, Args2... args) + { + delegate handler; + handler.bind>(std::forward(lambda), std::forward(args)...); + return handler; + } + + //Bind a member function + template + void bind_raw(T* pObject, NonConstMemberFunction pFunction, Args2&&... args) + { + DELEGATE_STATIC_ASSERT(!std::is_const::value, "Cannot bind a non-const function on a const object"); + *this = create_raw(pObject, pFunction, std::forward(args)...); + } + + template + void bind_raw(T* pObject, ConstMemberFunction pFunction, Args2&&... args) + { + *this = create_raw(pObject, pFunction, std::forward(args)...); + } + + //Bind a static/global function + template + void bind_static(RetVal(*pFunction)(Args..., Args2...), Args2&&... args) + { + *this = create_static(pFunction, std::forward(args)...); + } + + //Bind a lambda + template + void bind_lambda(LambdaType&& lambda, Args2&&... args) + { + *this = create_lambda(std::forward(lambda), std::forward(args)...); + } + + //Bind a member function with a shared_ptr object + template + void bind_sp(std::shared_ptr pObject, NonConstMemberFunction pFunction, Args2&&... args) + { + DELEGATE_STATIC_ASSERT(!std::is_const::value, "Cannot bind a non-const function on a const object"); + *this = create_sp(pObject, pFunction, std::forward(args)...); + } + + template + void bind_sp(std::shared_ptr pObject, ConstMemberFunction pFunction, Args2&&... args) + { + *this = create_sp(pObject, pFunction, std::forward(args)...); + } + + //Execute the delegate with the given parameters + RetVal execute(Args... args) const + { + DELEGATE_ASSERT(allocator_.has_allocation(), "Delegate is not bound"); + return ((IDelegateT*)get_delegate())->execute(std::forward(args)...); + } + + RetVal execute_if_bound(Args... args) const + { + if (is_bound()) + { + return ((IDelegateT*)get_delegate())->execute(std::forward(args)...); + } + return RetVal(); + } + +private: + template + void bind(Args3&&... args) + { + release(); + void* pAlloc = allocator_.allocate(sizeof(T)); + new (pAlloc) T(std::forward(args)...); + } +}; + +//Delegate that can be bound to by MULTIPLE objects +template +class multicast_delegate : public delegate_base +{ +public: + using DelegateT = delegate; + +private: + struct delegate_handler_pair + { + delegate_handle handle; + DelegateT callback; + delegate_handler_pair() : handle(false) {} + delegate_handler_pair(const delegate_handle& handle, const DelegateT& callback) : handle(handle), callback(callback) {} + delegate_handler_pair(const delegate_handle& handle, DelegateT&& callback) : handle(handle), callback(std::move(callback)) {} + }; + template + using ConstMemberFunction = typename _DelegatesInteral::MemberFunction::Type; + template + using NonConstMemberFunction = typename _DelegatesInteral::MemberFunction::Type; + +public: + //Default constructor + constexpr multicast_delegate() + : locks_(0) + { + } + + //Default destructor + ~multicast_delegate() noexcept = default; + + //Default copy constructor + multicast_delegate(const multicast_delegate& other) = default; + + //Defaul copy assignment operator + multicast_delegate& operator=(const multicast_delegate& other) = default; + + //Move constructor + multicast_delegate(multicast_delegate&& other) noexcept + : events_(std::move(other.events_)), + locks_(std::move(other.locks_)) + { + } + + //Move assignment operator + multicast_delegate& operator=(multicast_delegate&& other) noexcept + { + events_ = std::move(other.events_); + locks_ = std::move(other.locks_); + return *this; + } + + template + delegate_handle operator+=(T&& l) + { + return add(DelegateT::create_lambda(std::move(l))); + } + + //Add delegate with the += operator + delegate_handle operator+=(DelegateT&& handler) noexcept + { + return add(std::forward(handler)); + } + + //Remove a delegate using its DelegateHandle + bool operator-=(delegate_handle& handle) + { + return remove(handle); + } + + delegate_handle add(DelegateT&& handler) noexcept + { + //Favour an empty space over a possible array reallocation + for (size_t i = 0; i < events_.size(); ++i) + { + if (events_[i].handle.is_valid() == false) + { + events_[i] = delegate_handler_pair(delegate_handle(true), std::move(handler)); + return events_[i].handle; + } + } + events_.emplace_back(delegate_handle(true), std::move(handler)); + return events_.back().handle; + } + + //Bind a member function + template + delegate_handle add_raw(T* pObject, NonConstMemberFunction pFunction, Args2&&... args) + { + return add(DelegateT::create_raw(pObject, pFunction, std::forward(args)...)); + } + + template + delegate_handle add_raw(T* pObject, ConstMemberFunction pFunction, Args2&&... args) + { + return add(DelegateT::create_raw(pObject, pFunction, std::forward(args)...)); + } + + //Bind a static/global function + template + delegate_handle add_static(void(*pFunction)(Args..., Args2...), Args2&&... args) + { + return add(DelegateT::create_static(pFunction, std::forward(args)...)); + } + + //Bind a lambda + template + delegate_handle add_lambda(LambdaType&& lambda, Args2&&... args) + { + return add(DelegateT::create_lambda(std::forward(lambda), std::forward(args)...)); + } + + //Bind a member function with a shared_ptr object + template + delegate_handle add_sp(std::shared_ptr pObject, NonConstMemberFunction pFunction, Args2&&... args) + { + return add(DelegateT::create_sp(pObject, pFunction, std::forward(args)...)); + } + + template + delegate_handle add_sp(std::shared_ptr pObject, ConstMemberFunction pFunction, Args2&&... args) + { + return add(DelegateT::create_sp(pObject, pFunction, std::forward(args)...)); + } + + //Removes all handles that are bound from a specific object + //Ignored when pObject is null + //Note: Only works on Raw and SP bindings + void remove_object(void* pObject) + { + if (pObject != nullptr) + { + for (size_t i = 0; i < events_.size(); ++i) + { + if (events_[i].callback.get_owner() == pObject) + { + if (is_locked()) + { + events_[i].callback.clear(); + } + else + { + std::swap(events_[i], events_[events_.size() - 1]); + events_.pop_back(); + } + } + } + } + } + + //Remove a function from the event list by the handle + bool remove(delegate_handle& handle) + { + if (handle.is_valid()) + { + for (size_t i = 0; i < events_.size(); ++i) + { + if (events_[i].handle == handle) + { + if (is_locked()) + { + events_[i].callback.clear(); + } + else + { + std::swap(events_[i], events_[events_.size() - 1]); + events_.pop_back(); + } + handle.reset(); + return true; + } + } + } + return false; + } + + bool is_bound_to(const delegate_handle& handle) const + { + if (handle.is_valid()) + { + for (size_t i = 0; i < events_.size(); ++i) + { + if (events_[i].handle == handle) + { + return true; + } + } + } + return false; + } + + //Remove all the functions bound to the delegate + void remove_all() + { + if (is_locked()) + { + for (delegate_handler_pair& handler : events_) + { + handler.callback.clear(); + } + } + else + { + events_.clear(); + } + } + + void compress(const size_t maxSpace = 0) + { + if (is_locked() == false) + { + size_t toDelete = 0; + for (size_t i = 0; i < events_.size() - toDelete; ++i) + { + if (events_[i].handle.is_valid() == false) + { + std::swap(events_[i], events_[toDelete]); + ++toDelete; + } + } + if (toDelete > maxSpace) + { + events_.resize(events_.size() - toDelete); + } + } + } + + //Execute all functions that are bound + void broadcast(Args ...args) + { + lock(); + for (size_t i = 0; i < events_.size(); ++i) + { + if (events_[i].handle.is_valid()) + { + events_[i].callback.execute(std::forward(args)...); + } + } + unlock(); + } + + size_t get_size() const + { + return events_.size(); + } + +private: + void lock() + { + ++locks_; + } + + void unlock() + { + //Unlock() should never be called more than Lock()! + DELEGATE_ASSERT(locks_ > 0); + --locks_; + } + + //Returns true is the delegate is currently broadcasting + //If this is true, the order of the array should not be changed otherwise this causes undefined behaviour + bool is_locked() const + { + return locks_ > 0; + } + + std::vector events_; + unsigned int locks_; +}; + +#endif diff --git a/src/misc/mirage_paint_context.cpp b/src/misc/mirage_paint_context.cpp index 2fd7573..d4fa6c2 100644 --- a/src/misc/mirage_paint_context.cpp +++ b/src/misc/mirage_paint_context.cpp @@ -7,6 +7,7 @@ */ #include "mirage_paint_context.h" + #include "widget/mwidget.h" //-------------- 初始化 -------------- @@ -44,8 +45,7 @@ void mirage_paint_context::reset() { * 每个渲染帧的开始都应调用此函数。 */ void mirage_paint_context::begin_frame(mwidget* in_parent_widget) { - parent_enabled_ = in_parent_widget->is_enabled(); - parent_widget_ = in_parent_widget; + set_parent_widget(in_parent_widget); elements_.begin_frame(); } @@ -59,6 +59,6 @@ void mirage_paint_context::begin_frame(mwidget* in_parent_widget) { * 更新父组件启用状态。这影响子组件的启用状态计算。 */ void mirage_paint_context::set_parent_widget(mwidget* in_widget) { + parent_enabled_ = in_widget->is_enabled(); parent_widget_ = in_widget; - parent_enabled_ = in_widget->should_be_enabled(parent_enabled_); } diff --git a/src/misc/mirage_paint_context.h b/src/misc/mirage_paint_context.h index 2e6ea29..02d023b 100644 --- a/src/misc/mirage_paint_context.h +++ b/src/misc/mirage_paint_context.h @@ -95,7 +95,7 @@ struct mirage_paint_context { * @brief 获取当前几何区域 * @return 当前几何区域的引用 */ - [[nodiscard]] auto& geo() { return *geometry_; } + [[nodiscard]] const auto& geo() { return *geometry_; } /** * @brief 更新投影矩阵 @@ -109,14 +109,14 @@ struct mirage_paint_context { * @brief 设置当前几何区域 * @param in_geometry 要设置的几何区域 */ - void set_geometry(geometry_t& in_geometry) { geometry_ = &in_geometry; } + void set_geometry(const geometry_t& in_geometry) { geometry_ = &in_geometry; } private: /** 当前绘制的父组件 */ mwidget* parent_widget_{}; /** 当前几何区域指针 */ - geometry_t* geometry_{}; + const geometry_t* geometry_{}; /** 渲染元素集合,用于实际绘制操作 */ render_elements elements_; diff --git a/src/misc/mirage_type.h b/src/misc/mirage_type.h index c7a52cb..eb03a75 100644 --- a/src/misc/mirage_type.h +++ b/src/misc/mirage_type.h @@ -36,10 +36,10 @@ inline time_type get_current_time() { return std::chrono::high_resolution_clock: //-------------- 对齐和布局枚举 -------------- /** - * @enum horizontal_alignment + * @enum horizontal_alignment_t * @brief 水平对齐方式 */ -enum class horizontal_alignment { +enum class horizontal_alignment_t { left, ///< 左对齐 center, ///< 居中对齐 right, ///< 右对齐 @@ -47,10 +47,10 @@ enum class horizontal_alignment { }; /** - * @enum vertical_alignment + * @enum vertical_alignment_t * @brief 垂直对齐方式 */ -enum class vertical_alignment { +enum class vertical_alignment_t { top, ///< 顶部对齐 center, ///< 居中对齐 bottom, ///< 底部对齐 @@ -58,30 +58,30 @@ enum class vertical_alignment { }; /** - * @enum horizontal_text_alignment + * @enum horizontal_text_alignment_t * @brief 文本水平对齐方式 */ -enum class horizontal_text_alignment { +enum class horizontal_text_alignment_t { left, ///< 文本左对齐 center, ///< 文本居中对齐 right ///< 文本右对齐 }; /** - * @enum vertical_text_alignment + * @enum vertical_text_alignment_t * @brief 文本垂直对齐方式 */ -enum class vertical_text_alignment { +enum class vertical_text_alignment_t { top, ///< 文本顶部对齐 center, ///< 文本居中对齐 bottom ///< 文本底部对齐 }; /** - * @enum visibility + * @enum visibility_t * @brief 组件可见性和交互行为的位标志 */ -enum class visibility : uint32_t { +enum class visibility_t : uint32_t { none = 0, ///< 无特定可见性设置 collapsed = 1 << 0, ///< 不可见且不占用布局空间 hidden = 1 << 1, ///< 不可见但占用布局空间 @@ -96,13 +96,13 @@ enum class visibility : uint32_t { any_hit_testable = visible, ///< 任何可命中测试状态 }; // 为visibility枚举启用位标志功能 -DEFINE_ENUM_FLAGS(visibility) +DEFINE_ENUM_FLAGS(visibility_t) /** - * @enum orientation + * @enum orientation_t * @brief 组件方向 */ -enum class orientation { +enum class orientation_t { horizontal, ///< 水平方向 vertical ///< 垂直方向 }; @@ -110,19 +110,19 @@ enum class orientation { //-------------- 文本流方向 -------------- /** - * @enum flow_direction + * @enum flow_direction_t * @brief 文本和布局流动方向 */ -enum class flow_direction { +enum class flow_direction_t { left_to_right, ///< 从左到右 right_to_left, ///< 从右到左 }; /** - * @enum flow_direction_preference + * @enum flow_direction_preference_t * @brief 流动方向首选项 */ -enum class flow_direction_preference { +enum class flow_direction_preference_t { inherit, ///< 继承父级设置 culture, ///< 根据当前文化设置 left_to_right, ///< 强制从左到右 @@ -132,7 +132,7 @@ enum class flow_direction_preference { /** * @brief 全局默认流动方向设置 */ -inline static auto g_flow_direction = flow_direction::left_to_right; +inline static auto g_flow_direction = flow_direction_t::left_to_right; //-------------- 几何和渲染结构体 -------------- diff --git a/src/widget/compound_widget/mborder.h b/src/widget/compound_widget/mborder.h index cc14bdf..66d2bd0 100644 --- a/src/widget/compound_widget/mborder.h +++ b/src/widget/compound_widget/mborder.h @@ -24,8 +24,8 @@ struct mborder_slot : mcompound_widget_slot { * 初始化插槽,默认将子组件在水平和垂直方向上都设为拉伸填充模式 */ mborder_slot() { - h_alignment(horizontal_alignment::stretch); - v_alignment(vertical_alignment::stretch); + h_alignment(horizontal_alignment_t::stretch); + v_alignment(vertical_alignment_t::stretch); } /** @@ -46,18 +46,17 @@ public: /** * @brief 排列子组件 * @param in_allotted_geometry 分配给组件的几何区域 - * @param in_arranged_children 排列好的子组件集合 * * 重写基类方法,在排列子组件时考虑margin属性。 * 子组件将根据对齐方式和设置的边距进行布局。 */ - virtual void arrange_children(const geometry_t& in_allotted_geometry, arranged_children& in_arranged_children) override { - if (const std::shared_ptr child_widget = child_slot_.get()) + virtual void arrange_children(const geometry_t& in_allotted_geometry) override { + const auto& slot = get_child_slot(); + if (const auto child_widget = slot.get().lock()) arrange_single_child(in_allotted_geometry, - in_arranged_children, child_widget, - child_slot_.h_alignment(), - child_slot_.v_alignment(), - child_slot_.margin()); + slot.h_alignment(), + slot.v_alignment(), + slot.margin()); } }; diff --git a/src/widget/compound_widget/mbutton.cpp b/src/widget/compound_widget/mbutton.cpp index 78ff3f1..2b5f31f 100644 --- a/src/widget/compound_widget/mbutton.cpp +++ b/src/widget/compound_widget/mbutton.cpp @@ -6,10 +6,14 @@ #include "widget/panel_widget/mbox.h" mbutton::mbutton() { - set_visibility(visibility::visible); color_ = {0.5, 0.5, 0.5, 1}; } +void mbutton::init_component(mustache::EntityManager& in_entity_manager) { + mborder::init_component(in_entity_manager); + set_visibility(visibility_t::visible); +} + void mbutton::on_paint(mirage_paint_context& in_context) { in_context.drawer().make_rounded_rect( { 0, 0 }, @@ -24,7 +28,7 @@ Eigen::Vector2f mbutton::compute_desired_size(float in_layout_scale_multiplier) const margin_t button_padding = { 10, 10, 10, 10 }; // TODO 临时值, 以后会从主题中获取 Eigen::Vector2f desired_size = button_padding.get_total_spacing(); - if (const auto& child = child_slot_.get()) { + if (const auto& child = get_child_slot().get().lock()) { desired_size += child->compute_desired_size(in_layout_scale_multiplier); } @@ -36,22 +40,22 @@ hit_test_handle mbutton::on_click(const Eigen::Vector2f& in_position, mouse_butt std::cout << this << ": Button clicked!" << in_position.x() << ", " << in_position.y() << std::endl; color_ = {0, 0, 1, 1}; auto parent = get_parent(); - if (std::shared_ptr hbox = std::dynamic_pointer_cast(parent)) { - hbox->add_slot() - .stretch() - .margin({ 5 }) - [ - std::make_shared() - ]; - } - if (std::shared_ptr vbox = std::dynamic_pointer_cast(parent)) { - vbox->add_slot() - .stretch() - .margin({ 5 }) - [ - std::make_shared() - ]; - } + // if (std::shared_ptr hbox = std::dynamic_pointer_cast(parent)) { + // hbox->add_slot() + // .stretch() + // .margin({ 5 }) + // [ + // std::make_shared() + // ]; + // } + // if (std::shared_ptr vbox = std::dynamic_pointer_cast(parent)) { + // vbox->add_slot() + // .stretch() + // .margin({ 5 }) + // [ + // std::make_shared() + // ]; + // } return hit_test_handle::handled(); } @@ -84,15 +88,15 @@ hit_test_handle mbutton::on_mouse_move(const Eigen::Vector2f& in_position) { return hit_test_handle::handled(); } -hit_test_handle mbutton::on_mouse_press(const Eigen::Vector2f& in_position, mouse_button in_button) { - mborder::on_mouse_press(in_position, in_button); +hit_test_handle mbutton::on_mouse_button_down(const Eigen::Vector2f& in_position, mouse_button in_button) { + mborder::on_mouse_button_down(in_position, in_button); std::cout << this << ": Mouse pressed!" << in_position.x() << ", " << in_position.y() << std::endl; color_ = {0, 0, 1, 1}; return hit_test_handle::handled(); } -hit_test_handle mbutton::on_mouse_release(const Eigen::Vector2f& in_position, mouse_button in_button) { - mborder::on_mouse_release(in_position, in_button); +hit_test_handle mbutton::on_mouse_button_up(const Eigen::Vector2f& in_position, mouse_button in_button) { + mborder::on_mouse_button_up(in_position, in_button); std::cout << this << ": Mouse released!" << in_position.x() << ", " << in_position.y() << std::endl; color_ = {1, 0, 0, 1}; return hit_test_handle::handled(); diff --git a/src/widget/compound_widget/mbutton.h b/src/widget/compound_widget/mbutton.h index a423c5b..99fac4a 100644 --- a/src/widget/compound_widget/mbutton.h +++ b/src/widget/compound_widget/mbutton.h @@ -1,11 +1,12 @@ #pragma once #include "mborder.h" #include "mcompound_widget.h" -#include "widget/mwidget.h" class mbutton : public mborder { public: mbutton(); + virtual void init_component(mustache::EntityManager& in_entity_manager) override; + virtual void on_paint(mirage_paint_context& in_context) override; virtual Eigen::Vector2f compute_desired_size(float in_layout_scale_multiplier) const override; @@ -14,8 +15,8 @@ public: virtual void on_mouse_enter() override; virtual void on_mouse_leave() override; virtual hit_test_handle on_mouse_move(const Eigen::Vector2f& in_position) override; - virtual hit_test_handle on_mouse_press(const Eigen::Vector2f& in_position, mouse_button in_button) override; - virtual hit_test_handle on_mouse_release(const Eigen::Vector2f& in_position, mouse_button in_button) override; + virtual hit_test_handle on_mouse_button_down(const Eigen::Vector2f& in_position, mouse_button in_button) override; + virtual hit_test_handle on_mouse_button_up(const Eigen::Vector2f& in_position, mouse_button in_button) override; virtual hit_test_handle on_mouse_wheel(const Eigen::Vector2f& in_position, const wheel_event& in_delta) override; private: linear_color color_; diff --git a/src/widget/compound_widget/mcompound_widget.h b/src/widget/compound_widget/mcompound_widget.h index 98aeec6..41a77ca 100644 --- a/src/widget/compound_widget/mcompound_widget.h +++ b/src/widget/compound_widget/mcompound_widget.h @@ -8,7 +8,6 @@ */ #include "widget/layout_utils.h" -#include "widget/mwidget.h" #include "widget/slot_util.h" /** @@ -29,12 +28,12 @@ struct mcompound_widget_slot { auto& me() { return static_cast(*this); } - std::shared_ptr slot_owner{}; + widget_key slot_owner; // 插槽功能宏 - 定义内容管理和对齐属性 SLOT_CONTENT() - SLOT_ATTRIBUTE(horizontal_alignment, h_alignment) - SLOT_ATTRIBUTE(vertical_alignment, v_alignment) + SLOT_ATTRIBUTE(horizontal_alignment_t, h_alignment) + SLOT_ATTRIBUTE(vertical_alignment_t, v_alignment) }; /** @@ -49,6 +48,7 @@ struct mcompound_widget_slot { template class mcompound_widget : public mwidget { public: + virtual void init_component(mustache::EntityManager& in_entity_manager) override; //-------------- 内容设置 -------------- /** @@ -60,9 +60,11 @@ public: */ auto& set_content(const std::shared_ptr& in_widget) { on_set_content(in_widget); - child_slot_.slot_owner = shared_from_this(); invalidate(invalidate_reason::layout); - return child_slot_[in_widget]; + + auto& slot = get_child_slot(); + slot.set(in_widget); + return slot; } //-------------- 尺寸计算 -------------- @@ -90,22 +92,12 @@ public: /** * @brief 排列子组件 * @param in_allotted_geometry 分配给组件的几何区域 - * @param in_arranged_children 排列好的子组件集合 * * 根据插槽中的对齐属性对单个子组件进行排列。 */ - virtual void arrange_children(const geometry_t& in_allotted_geometry, arranged_children& in_arranged_children) override; - - //-------------- 子组件管理 -------------- - - /** - * @brief 获取子组件列表 - * @return 包含子组件的向量 - * - * 如果存在子组件,返回一个只包含该子组件的向量,否则返回空向量。 - */ - virtual std::vector> get_children() const override; + virtual void arrange_children(const geometry_t& in_allotted_geometry) override; + SlotType& get_child_slot() const { return get_component_ref(); } protected: /** * @brief 在设置内容时的回调函数 @@ -115,11 +107,15 @@ protected: * 基类实现为空。 */ virtual void on_set_content(const std::shared_ptr& in_widget) {} - - /** 子组件插槽,管理单个子组件及其属性 */ - SlotType child_slot_; }; +template +void mcompound_widget::init_component(mustache::EntityManager& in_entity_manager) { + mwidget::init_component(in_entity_manager); + auto& slot = add_component(); + slot.slot_owner = get_key(); +} + /** * @brief 计算复合组件的期望大小 * @@ -127,8 +123,8 @@ protected: */ template Eigen::Vector2f mcompound_widget::compute_desired_size(float in_layout_scale_multiplier) const { - if (auto child_widget = child_slot_.get()) { - if (child_widget->get_visibility() != visibility::collapsed) { + if (auto child_widget = get_child_slot().get().lock()) { + if (has_any_flag(child_widget->get_visibility(), visibility_t::any_visible)) { return child_widget->compute_desired_size(in_layout_scale_multiplier); } } @@ -141,20 +137,9 @@ Eigen::Vector2f mcompound_widget::compute_desired_size(float in_layout * 使用layout_utils中的arrange_single_child函数根据对齐属性排列单个子组件。 */ template -void mcompound_widget::arrange_children(const geometry_t& in_allotted_geometry, arranged_children& in_arranged_children) { - if (const auto child_widget = child_slot_.get()) - arrange_single_child(in_allotted_geometry, in_arranged_children, child_widget, child_slot_.h_alignment(), child_slot_.v_alignment(), {}); -} - -/** - * @brief 获取复合组件的子组件列表 - * - * 如果插槽中有子组件,返回包含该子组件的向量,否则返回空向量。 - */ -template -std::vector> mcompound_widget::get_children() const { - if (auto child_widget = child_slot_.get()) { - return { child_widget }; +void mcompound_widget::arrange_children(const geometry_t& in_allotted_geometry) { + auto& slot = get_child_slot(); + if (const auto child_widget = slot.get().lock()) { + arrange_single_child(in_allotted_geometry, child_widget, slot.h_alignment(), slot.v_alignment(), {}); } - return {}; } diff --git a/src/widget/hit_test/hit_test_manager.cpp b/src/widget/hit_test/hit_test_manager.cpp deleted file mode 100644 index f527df3..0000000 --- a/src/widget/hit_test/hit_test_manager.cpp +++ /dev/null @@ -1,32 +0,0 @@ -/** - * @file hit_test_manager.cpp - * @brief 命中测试管理器的实现 - */ - -#include "hit_test_manager.h" -#include "core/window/mwindow.h" -#include "widget/mwidget.h" -#include "geometry/widget_layout_tree.h" -#include "geometry/arranged_children.h" - -//-------------- 构造函数 -------------- - -hit_test_manager::hit_test_manager(mwindow* in_window) - : window_(in_window) -{ -} - -void hit_test_manager::cursor_leave() { -} - -void hit_test_manager::invalidate_cache() { - cache_valid_ = false; -} - -const std::shared_ptr& hit_test_manager::get_last_hit_widget() const { - return last_hit_widget_; -} - -const std::shared_ptr& hit_test_manager::get_last_hover_widget() const { - return last_hover_widget_; -} diff --git a/src/widget/hit_test/hit_test_manager.h b/src/widget/hit_test/hit_test_manager.h deleted file mode 100644 index d908fc6..0000000 --- a/src/widget/hit_test/hit_test_manager.h +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once -/** - * @file hit_test_manager.h - * @brief 定义UI命中测试管理器 - */ - -#include "hit_test_result.h" -#include "hit_test_parameters.h" - -class mwindow; -class mwidget; -class geometry_t; - -/** - * @class hit_test_manager - * @brief 管理UI命中测试流程 - */ -class hit_test_manager { -public: - // 构造函数 - explicit hit_test_manager(mwindow* in_window); - - void cursor_leave(); - - // 缓存控制 - void invalidate_cache(); - - // 访问方法 - [[nodiscard]] const std::shared_ptr& get_last_hit_widget() const; - [[nodiscard]] const std::shared_ptr& get_last_hover_widget() const; - -private: - // 成员变量 - mwindow* window_{nullptr}; ///< 关联的窗口 - std::shared_ptr last_hit_widget_{nullptr}; ///< 最近一次点击命中的组件 - std::shared_ptr last_hover_widget_{nullptr}; ///< 最近一次悬停的组件 - bool cache_valid_{false}; ///< 缓存是否有效 -}; diff --git a/src/widget/hit_test/hit_test_result.cpp b/src/widget/hit_test/hit_test_result.cpp index 1125132..a49822d 100644 --- a/src/widget/hit_test/hit_test_result.cpp +++ b/src/widget/hit_test/hit_test_result.cpp @@ -4,4 +4,3 @@ */ #include "hit_test_result.h" -#include "widget/mwidget.h" diff --git a/src/widget/hit_test/hit_test_result.h b/src/widget/hit_test/hit_test_result.h index 2aef261..34b8f8d 100644 --- a/src/widget/hit_test/hit_test_result.h +++ b/src/widget/hit_test/hit_test_result.h @@ -8,7 +8,7 @@ #include #include -class mwidget; +#include "widget_tree/widget_component.h" /** * @enum hit_test_result_behavior @@ -40,11 +40,11 @@ private: }; struct hit_test_result { - std::shared_ptr widget; + widget_key widget; Eigen::Vector2f widget_space_pos; operator bool() const { - return widget != nullptr; + return widget; } - operator std::shared_ptr() const { return widget; } + operator widget_key() const { return widget; } }; \ No newline at end of file diff --git a/src/widget/layout_utils.cpp b/src/widget/layout_utils.cpp index 7fc2c55..0c30a16 100644 --- a/src/widget/layout_utils.cpp +++ b/src/widget/layout_utils.cpp @@ -3,77 +3,72 @@ // #include "layout_utils.h" -#include "mwidget.h" +void arrange_single_child(const geometry_t& in_allotted_geometry, const std::shared_ptr& child_widget, + horizontal_alignment_t h_alignment, vertical_alignment_t v_alignment, const margin_t& margin) { + if (!child_widget) return; -void arrange_single_child( - const geometry_t& in_allotted_geometry, - arranged_children& in_arranged_children, - const std::shared_ptr& child_widget, - horizontal_alignment h_alignment, - vertical_alignment v_alignment, - const margin_t& margin -) { - if (!child_widget) return; + const auto& local_size = in_allotted_geometry.get_local_size(); - const auto& local_size = in_allotted_geometry.get_local_size(); - Eigen::Vector2f child_desired_size = child_widget->get_desired_size(); + child_widget->cache_desired_size(child_widget->get_dpi_scale()); + const auto& child_desired_size_opt = child_widget->get_desired_size(); + const auto& child_desired_size = child_desired_size_opt.value(); - // 计算考虑边距后的有效可用空间 - Eigen::Vector2f available_size{ - local_size.x() - margin.horizontal(), - local_size.y() - margin.vertical() - }; + // 计算考虑边距后的有效可用空间 + Eigen::Vector2f available_size{ + local_size.x() - margin.horizontal(), + local_size.y() - margin.vertical() + }; - // 确保可用空间不为负 - available_size = available_size.cwiseMax(Eigen::Vector2f::Zero()); + // 确保可用空间不为负 + available_size = available_size.cwiseMax(Eigen::Vector2f::Zero()); - // 计算子部件的位置和大小 - Eigen::Vector2f child_size = child_desired_size; // 默认使用期望尺寸 - Eigen::Vector2f child_pos = margin.top_left(); // 初始位置包含左上边距 + // 计算子部件的位置和大小 + Eigen::Vector2f child_size = child_desired_size; // 默认使用期望尺寸 + Eigen::Vector2f child_pos = margin.top_left(); // 初始位置包含左上边距 - // 水平对齐计算 - 考虑margin后的可用空间 - switch (h_alignment) { - case horizontal_alignment::left: - // 左对齐 - 已包含左边距 - break; - case horizontal_alignment::center: - // 中心对齐 - 位置居中加左边距 - child_pos.x() = margin.left + (available_size.x() - child_desired_size.x()) / 2; - break; - case horizontal_alignment::right: - // 右对齐 - 考虑右边距 - child_pos.x() = local_size.x() - child_desired_size.x() - margin.right; - break; - case horizontal_alignment::stretch: - // 拉伸 - 使用全部可用宽度 - child_size.x() = available_size.x(); - break; - default: - break; - } + // 水平对齐计算 - 考虑margin后的可用空间 + switch (h_alignment) { + case horizontal_alignment_t::left: + // 左对齐 - 已包含左边距 + break; + case horizontal_alignment_t::center: + // 中心对齐 - 位置居中加左边距 + child_pos.x() = margin.left + (available_size.x() - child_desired_size.x()) / 2; + break; + case horizontal_alignment_t::right: + // 右对齐 - 考虑右边距 + child_pos.x() = local_size.x() - child_desired_size.x() - margin.right; + break; + case horizontal_alignment_t::stretch: + // 拉伸 - 使用全部可用宽度 + child_size.x() = available_size.x(); + break; + default: + break; + } - // 垂直对齐计算 - 考虑margin后的可用空间 - switch (v_alignment) { - case vertical_alignment::top: - // 顶部对齐 - 已包含上边距 - break; - case vertical_alignment::center: - // 中心对齐 - 位置居中加上边距 - child_pos.y() = margin.top + (available_size.y() - child_desired_size.y()) / 2; - break; - case vertical_alignment::bottom: - // 底部对齐 - 考虑下边距 - child_pos.y() = local_size.y() - child_desired_size.y() - margin.bottom; - break; - case vertical_alignment::stretch: - // 拉伸 - 使用全部可用高度 - child_size.y() = available_size.y(); - break; - default: - break; - } + // 垂直对齐计算 - 考虑margin后的可用空间 + switch (v_alignment) { + case vertical_alignment_t::top: + // 顶部对齐 - 已包含上边距 + break; + case vertical_alignment_t::center: + // 中心对齐 - 位置居中加上边距 + child_pos.y() = margin.top + (available_size.y() - child_desired_size.y()) / 2; + break; + case vertical_alignment_t::bottom: + // 底部对齐 - 考虑下边距 + child_pos.y() = local_size.y() - child_desired_size.y() - margin.bottom; + break; + case vertical_alignment_t::stretch: + // 拉伸 - 使用全部可用高度 + child_size.y() = available_size.y(); + break; + default: + break; + } - // 创建子几何体并添加到排列的子元素中 - const auto child_geo = in_allotted_geometry.make_child(child_pos, child_size); - in_arranged_children.add_widget(arranged_widget(child_geo, child_widget)); + // 创建子几何体并添加到排列的子元素中 + const auto child_geo = in_allotted_geometry.make_child(child_pos, child_size); + child_widget->set_geometry(child_geo); } diff --git a/src/widget/layout_utils.h b/src/widget/layout_utils.h index d8405ed..e067646 100644 --- a/src/widget/layout_utils.h +++ b/src/widget/layout_utils.h @@ -1,4 +1,5 @@ #pragma once +#include "mwidget.h" #include "geometry/arranged_children.h" #include "geometry/geometry.h" #include "misc/mirage_type.h" @@ -7,7 +8,6 @@ * @brief 为单个子部件布局的通用函数 * * @param in_allotted_geometry 分配给容器的几何区域 - * @param in_arranged_children 用于存储布局结果的容器 * @param child_widget 子部件 * @param h_alignment 水平对齐方式 * @param v_alignment 垂直对齐方式 @@ -15,284 +15,166 @@ */ void arrange_single_child( const geometry_t& in_allotted_geometry, - arranged_children& in_arranged_children, const std::shared_ptr& child_widget, - horizontal_alignment h_alignment, - vertical_alignment v_alignment, + horizontal_alignment_t h_alignment, + vertical_alignment_t v_alignment, const margin_t& margin = margin_t() // 默认为空边距 ); -#if 0 -// 封装的布局函数,可被其他布局类使用 -template +template void arrange_box_children( - const geometry_t& in_allotted_geometry, - arranged_children& in_arranged_children, - const std::vector& child_slots, - flow_direction flow_dir = flow_direction::left_to_right) -{ - Eigen::Vector2f container_size = in_allotted_geometry.get_local_size(); - if (container_size.x() <= 0 || container_size.y() <= 0) { - return; - } + const geometry_t& in_allotted_geometry, + float in_layout_scale_multiplier, + visibility_t in_visibility_filter, + const std::vector& child_slots, + flow_direction_t flow_dir = flow_direction_t::left_to_right +){ + const auto& container_size = in_allotted_geometry.get_local_size(); + if (container_size.x() <= 0 || container_size.y() <= 0) { + return; + } - // 确定主轴方向和是否反向 - constexpr bool is_horizontal = LayoutOrientation == orientation::horizontal; - const bool is_reversed = flow_dir == flow_direction::right_to_left; + // 确定主轴方向和是否反向 + constexpr bool is_horizontal = LayoutOrientation == orientation_t::horizontal; + const bool is_reversed = flow_dir == flow_direction_t::right_to_left; - // 第一轮:收集可见部件信息并计算自动尺寸 - struct slot_widget_info { - std::shared_ptr widget; - const SlotType* slot; - float size; - }; + // 第一轮:收集可见部件信息并计算自动尺寸 + struct slot_widget_info { + std::shared_ptr widget; + const SlotType* slot; + float size; + float margin_start; // 主轴起始边缘的margin + float margin_end; // 主轴结束边缘的margin + float margin_cross_start; // 交叉轴起始边缘的margin + float margin_cross_end; // 交叉轴结束边缘的margin + }; - std::vector visible_widgets; - float total_auto_size = 0.0f; - float total_stretch_factor = 0.0f; + std::vector visible_widgets; + float total_auto_size = 0.0f; + float total_stretch_factor = 0.0f; + float total_margins = 0.0f; // 主轴方向上所有边距的总和 - for (const auto& slot : child_slots) { - auto widget = slot.get(); - if (widget && widget->get_visibility() != visibility::collapsed) { - slot_widget_info info{widget, &slot, 0.0f}; + for (const auto& child_slot : child_slots) { + std::weak_ptr weak_widget = child_slot->get(); + const auto widget = weak_widget.lock(); + // 无效部件或不可见部件跳过 + if (!widget) + continue; - const auto& size_attr = slot.size(); - if (size_attr.is_auto_size()) { - // 自动尺寸部件 - 使用所需尺寸 - Eigen::Vector2f desired_size = widget->get_desired_size(); - info.size = is_horizontal ? desired_size.x() : desired_size.y(); - total_auto_size += info.size; - } else { - // 拉伸部件 - 累计拉伸因子 - total_stretch_factor += size_attr.stretch(); - } + if (!has_any_flag(widget->get_visibility(), in_visibility_filter)) + continue; - visible_widgets.push_back(info); - } - } + const auto& margin = child_slot->margin(); + const float margin_left = margin.left; + const float margin_right = margin.right; + const float margin_top = margin.top; + const float margin_bottom = margin.bottom; - if (visible_widgets.empty()) { - return; - } + float margin_start, margin_end, margin_cross_start, margin_cross_end; + if (is_horizontal) { + margin_start = margin_left; + margin_end = margin_right; + margin_cross_start = margin_top; + margin_cross_end = margin_bottom; + } else { + margin_start = margin_top; + margin_end = margin_bottom; + margin_cross_start = margin_left; + margin_cross_end = margin_right; + } - // 计算拉伸部件的可用空间 - float container_primary_size = is_horizontal ? container_size.x() : container_size.y(); - float remaining_space = std::max(0.0f, container_primary_size - total_auto_size); + total_margins += margin_start + margin_end; + slot_widget_info info{ widget, + child_slot, + 0.0f, + margin_start, + margin_end, + margin_cross_start, + margin_cross_end + }; - // 第二轮:计算拉伸部件的最终尺寸 - for (auto& info : visible_widgets) { - const auto& size_attr = info.slot->size(); - if (!size_attr.is_auto_size()) { - // 计算拉伸部件尺寸 - const float stretch_factor = size_attr.stretch(); - if (total_stretch_factor > 0.0f) { - info.size = remaining_space * (stretch_factor / total_stretch_factor); - } else { - info.size = 0.0f; - } - } - } + const auto& size_attr = child_slot->size(); + if (size_attr.is_auto_size()) { + // 缓存部件的期望大小 + widget->cache_desired_size(in_layout_scale_multiplier); + const auto& desired_size = widget->get_desired_size().value(); - // 第三轮:排列部件 - float position = 0.0f; + info.size = is_horizontal ? desired_size.x() : desired_size.y(); + total_auto_size += info.size; + } else { + total_stretch_factor += size_attr.stretch(); + } - // 对于从右到左布局,反转部件顺序 - if (is_reversed) { - std::reverse(visible_widgets.begin(), visible_widgets.end()); - } + visible_widgets.push_back(info); + } - for (const auto& info : visible_widgets) { - Eigen::Vector2f pos = Eigen::Vector2f::Zero(); - Eigen::Vector2f size = container_size; // 从容器完整尺寸开始 + if (visible_widgets.empty()) + return; - if (is_horizontal) { - // 水平框:竖直方向拉伸 - if (is_reversed) { - pos.x() = container_primary_size - position - info.size; - } else { - pos.x() = position; - } - size.x() = info.size; - // size.y 已经是容器完整高度(拉伸) - } else { - // 垂直框:水平方向拉伸 - if (is_reversed) { - pos.y() = container_primary_size - position - info.size; - } else { - pos.y() = position; - } - size.y() = info.size; - // size.x 已经是容器完整宽度(拉伸) - } + // 计算拉伸部件的可用空间(需要减去所有margin) + const float container_primary_size = is_horizontal ? container_size.x() : container_size.y(); + const float remaining_space = std::max(0.0f, container_primary_size - total_auto_size - total_margins); - auto widget_geo = in_allotted_geometry.make_child(pos, size); + // 第二轮:计算拉伸部件的最终尺寸 + for (auto& info : visible_widgets) { + const auto& size_attr = info.slot->size(); + if (!size_attr.is_auto_size()) { + const float stretch_factor = size_attr.stretch(); + if (total_stretch_factor > 0.0f) { + info.size = remaining_space * (stretch_factor / total_stretch_factor); + } else { + info.size = 0.0f; + } + } + } - in_arranged_children.add_widget({ widget_geo, info.widget }); - position += info.size; - } -} -#endif + // 第三轮:排列部件 + float position = 0.0f; -template -void arrange_box_children( - const geometry_t& in_allotted_geometry, - arranged_children& in_arranged_children, - const std::vector& child_slots, - flow_direction flow_dir = flow_direction::left_to_right) -{ - Eigen::Vector2f container_size = in_allotted_geometry.get_local_size(); - if (container_size.x() <= 0 || container_size.y() <= 0) { - return; - } + if (is_reversed) + std::reverse(visible_widgets.begin(), visible_widgets.end()); - // 确定主轴方向和是否反向 - constexpr bool is_horizontal = LayoutOrientation == orientation::horizontal; - const bool is_reversed = flow_dir == flow_direction::right_to_left; + for (const auto& info : visible_widgets) { + position += info.margin_start; - // 第一轮:收集可见部件信息并计算自动尺寸 - struct slot_widget_info { - std::shared_ptr widget; - const SlotType* slot; - float size; - float margin_start; // 主轴起始边缘的margin - float margin_end; // 主轴结束边缘的margin - float margin_cross_start; // 交叉轴起始边缘的margin - float margin_cross_end; // 交叉轴结束边缘的margin - }; + Eigen::Vector2f pos = Eigen::Vector2f::Zero(); + Eigen::Vector2f size = container_size; - std::vector visible_widgets; - float total_auto_size = 0.0f; - float total_stretch_factor = 0.0f; - float total_margins = 0.0f; // 主轴方向上所有边距的总和 + if (is_horizontal) { + if (is_reversed) + pos.x() = container_primary_size - position - info.size; + else + pos.x() = position; - for (const auto& slot : child_slots) { - auto widget = slot.get(); - if (widget && has_any_flag(widget->get_visibility(), visibility::any_visible)) { - // 获取margin值 - const auto& margin = slot.margin(); - const float margin_left = margin.left; - const float margin_right = margin.right; - const float margin_top = margin.top; - const float margin_bottom = margin.bottom; + pos.y() += info.margin_cross_start; + size.x() = info.size; + } else { + if (is_reversed) + pos.y() = container_primary_size - position - info.size; + else + pos.y() = position; - // 根据布局方向确定主轴和交叉轴的margin - float margin_start, margin_end, margin_cross_start, margin_cross_end; - if (is_horizontal) { - margin_start = margin_left; - margin_end = margin_right; - margin_cross_start = margin_top; - margin_cross_end = margin_bottom; - } else { - margin_start = margin_top; - margin_end = margin_bottom; - margin_cross_start = margin_left; - margin_cross_end = margin_right; - } + pos.x() += info.margin_cross_start; + size.y() = info.size; + } - // 累加主轴方向上的margin - total_margins += margin_start + margin_end; + info.widget->set_geometry(in_allotted_geometry.make_child(pos, size)); - widget->cache_desired_size(1); - slot_widget_info info{widget, &slot, 0.0f, margin_start, margin_end, margin_cross_start, margin_cross_end}; - - const auto& size_attr = slot.size(); - if (size_attr.is_auto_size()) { - // 自动尺寸部件 - 使用所需尺寸 - Eigen::Vector2f desired_size = widget->get_desired_size(); - info.size = is_horizontal ? desired_size.x() : desired_size.y(); - total_auto_size += info.size; - } else { - // 拉伸部件 - 累计拉伸因子 - total_stretch_factor += size_attr.stretch(); - } - - visible_widgets.push_back(info); - } - } - - if (visible_widgets.empty()) { - return; - } - - // 计算拉伸部件的可用空间(需要减去所有margin) - float container_primary_size = is_horizontal ? container_size.x() : container_size.y(); - float remaining_space = std::max(0.0f, container_primary_size - total_auto_size - total_margins); - - // 第二轮:计算拉伸部件的最终尺寸 - for (auto& info : visible_widgets) { - const auto& size_attr = info.slot->size(); - if (!size_attr.is_auto_size()) { - // 计算拉伸部件尺寸 - const float stretch_factor = size_attr.stretch(); - if (total_stretch_factor > 0.0f) { - info.size = remaining_space * (stretch_factor / total_stretch_factor); - } else { - info.size = 0.0f; - } - } - } - - // 第三轮:排列部件 - float position = 0.0f; - - // 对于从右到左布局,反转部件顺序 - if (is_reversed) { - std::reverse(visible_widgets.begin(), visible_widgets.end()); - } - - for (const auto& info : visible_widgets) { - // 应用起始margin - position += info.margin_start; - - Eigen::Vector2f pos = Eigen::Vector2f::Zero(); - Eigen::Vector2f size = container_size; // 从容器完整尺寸开始 - - // 应用交叉轴margin减少size - if (is_horizontal) { - size.y() -= info.margin_cross_start + info.margin_cross_end; - } else { - size.x() -= info.margin_cross_start + info.margin_cross_end; - } - - if (is_horizontal) { - // 水平框:竖直方向拉伸,但考虑margin - if (is_reversed) { - pos.x() = container_primary_size - position - info.size; - } else { - pos.x() = position; - } - pos.y() += info.margin_cross_start; // 应用顶部margin - size.x() = info.size; - } else { - // 垂直框:水平方向拉伸,但考虑margin - if (is_reversed) { - pos.y() = container_primary_size - position - info.size; - } else { - pos.y() = position; - } - pos.x() += info.margin_cross_start; // 应用左侧margin - size.y() = info.size; - } - - auto widget_geo = in_allotted_geometry.make_child(pos, size); - - in_arranged_children.add_widget({ widget_geo, info.widget }); - position += info.size + info.margin_end; // 移动位置并考虑结束margin - } + position += info.size + info.margin_end; + } } // 计算水平或垂直框的期望大小 -template -Eigen::Vector2f compute_box_desired_size( - const std::vector& child_slots -) { +template +Eigen::Vector2f compute_box_desired_size(const std::vector& child_slots) { Eigen::Vector2f desired_size = Eigen::Vector2f::Zero(); for (const auto& slot : child_slots) { - auto widget = slot.get(); - if (widget && has_any_flag(widget->get_visibility(), visibility::any_visible)) { - Eigen::Vector2f widget_desired_size = widget->get_desired_size(); - if (LayoutOrientation == orientation::horizontal) { + auto widget = slot->get().lock(); + if (widget && has_any_flag(widget->get_visibility(), visibility_t::any_visible)) { + widget->cache_desired_size(widget->get_dpi_scale()); + Eigen::Vector2f widget_desired_size = widget->get_desired_size().value(); + if (LayoutOrientation == orientation_t::horizontal) { desired_size.x() += widget_desired_size.x(); desired_size.y() = std::max(desired_size.y(), widget_desired_size.y()); } else { diff --git a/src/widget/mwidget.cpp b/src/widget/mwidget.cpp index 76ad11e..a4cd7ef 100644 --- a/src/widget/mwidget.cpp +++ b/src/widget/mwidget.cpp @@ -1,45 +1,83 @@ +// +// Created by 46944 on 25-3-23. +// + #include "mwidget.h" #include "core/window/mwindow.h" +#include "geometry/dpi_helper.h" +#include "widget_tree/widget_manager.h" -//-------------- 尺寸计算 -------------- - -void mwidget::cache_desired_size(float in_layout_scale_multiplier) { - set_desired_size(compute_desired_size(in_layout_scale_multiplier)); +mwidget::~mwidget() { + widget_manager::get().delete_widget(key_); } -//-------------- 命中测试 -------------- +void mwidget::init_component(mustache::EntityManager& in_entity_manager) { + in_entity_manager.assign(key_, widget_ptr{ shared_from_this() }); + in_entity_manager.assign(key_); + in_entity_manager.assign(key_); + in_entity_manager.assign(key_); + in_entity_manager.assign(key_); +} + +void mwidget::cache_desired_size(float in_layout_scale_multiplier, bool in_force) { + if (auto* layout = get_component()) { + if (!in_force && layout->desired_size.has_value()) + return; + layout->desired_size = compute_desired_size(in_layout_scale_multiplier); + } +} + +auto mwidget::get_dpi_scale() const -> float { + if (const auto window = get_window()) + return window->get_window_dpi_scale() * dpi_helper::get_global_scale(); + return 1.f; +} + +std::weak_ptr mwidget::get_parent_widget() const { + if (const auto parent = get_parent()) + return widget_manager::get().get_component(parent)->widget; + return {}; +} + +void mwidget::set_parent(const widget_key& in_parent) { + auto& manager = widget_manager::get(); + auto* parent_hierarchy = manager.get_component(in_parent); + auto* hierarchy = manager.get_component(key_); + + // 1. 如果当前有父节点,先从父节点中移除 + if (hierarchy->parent) { + auto* old_parent_hierarchy = manager.get_component(hierarchy->parent); + old_parent_hierarchy->remove_child(key_); + } + + // 2. 设置新的父节点 + hierarchy->parent = in_parent; + + // 3. 更新父子关系 + parent_hierarchy->add_child(key_); + + // 4. 通知父节点需要重新计算布局 + if (const auto parent = get_parent_widget().lock()) { + parent->invalidate(invalidate_reason::layout); + } +} void mwidget::invalidate(invalidate_reason in_reason) { - if (auto window = get_window()) { - window->invalidate(in_reason); + // 设置失效标记 + if (auto* invalidate = get_component()) { + invalidate->set(in_reason); + } + + // 通知父组件 + if (const auto parent = get_parent_widget().lock()) { + parent->invalidate(in_reason); + } + + // 如果是布局失效, 重置期望大小 + if (in_reason == invalidate_reason::layout) { + if (auto* layout = get_component()) { + layout->desired_size.reset(); + } } } - -mwindow* mwidget::get_window() { - if (auto parent = get_parent()) - if (auto window = parent->get_window()) - return window; - return nullptr; -} - -bool mwidget::can_hit_test() const { - const auto vis = get_visibility(); - - // 不可见组件不参与命中测试 - if (has_any_flag(vis, visibility::any_invisible)) { - return false; - } - - // 如果整个组件树都不可点击 - if (has_flag(vis, visibility::hit_test_invisible)) { - return false; - } - - // 如果组件自身不可点击(但子组件可以) - if (has_flag(vis, visibility::self_hit_test_invisible)) { - return false; - } - - return true; -} diff --git a/src/widget/mwidget.h b/src/widget/mwidget.h index 2f7d851..b1daeba 100644 --- a/src/widget/mwidget.h +++ b/src/widget/mwidget.h @@ -1,244 +1,130 @@ #pragma once -/** - * @file mwidget.h - * @brief 定义UI框架的基础组件类mwidget - * - * mwidget是UI组件的基类,提供了组件绘制、布局、层级关系等基本功能。 - * 所有可视化组件都应继承自此类。 - */ - -#include "core/render_elements.h" -#include "geometry/arranged_children.h" #include "hit_test/hit_test_result.h" -#include "misc/invalidate_reason.h" #include "misc/mirage_paint_context.h" -#include "misc/key_type/key_type.h" +#include "mustache/ecs/entity_manager.hpp" +#include "widget_tree/widget_manager.h" class mwindow; /** * @class mwidget - * @brief UI组件基类 + * @brief UI组件包装器 * - * 提供UI组件的基本功能,包括绘制、布局计算、层级管理等。 - * 继承自enable_shared_from_this以支持安全地从this指针创建shared_ptr。 + * 对widget entity的封装,提供了组件的基本功能。 */ class mwidget : public std::enable_shared_from_this { public: - //-------------- 构造和析构 -------------- + virtual ~mwidget(); - /** - * @brief 虚析构函数 - */ - virtual ~mwidget() = default; + template + auto& add_component() { + return widget_manager::get().add_component(key_); + } + template + auto& add_component(Args&&... args) { + return widget_manager::get().add_component(key_, std::forward(args)...); + } + template + auto* get_component() const { + return widget_manager::get().get_component(key_); + } + template + auto& get_component_ref() const { + return *widget_manager::get().get_component(key_); + } - //-------------- 绘制和布局 -------------- + void set_key(const widget_key& in_key) { key_ = in_key; } - /** - * @brief 组件绘制函数 - * @param in_context 绘制上下文 - * - * 当组件需要重绘时被调用,派生类必须实现此函数 - */ - virtual void on_paint(mirage_paint_context& in_context) = 0; + virtual void init_component(mustache::EntityManager& in_entity_manager); + virtual void on_paint(mirage_paint_context& in_context) = 0; - /** - * @brief 排列子组件 - * @param in_allotted_geometry 分配给该组件的几何区域 - * @param in_arranged_children 排列后的子组件集合 - * - * 根据给定的几何区域排列子组件,派生类必须实现此函数 - */ - virtual void arrange_children(const geometry_t& in_allotted_geometry, arranged_children& in_arranged_children) = 0; + void set_geometry(const geometry_t& in_geometry) { get_component_ref().geometry = in_geometry; } + const auto& get_geometry() const { return get_component_ref().geometry; } + virtual auto compute_desired_size(float in_layout_scale_multiplier) const -> Eigen::Vector2f = 0; + void cache_desired_size(float in_layout_scale_multiplier, bool in_force = false); + const auto& get_desired_size() const { return get_component_ref().desired_size; } + virtual void arrange_children(const geometry_t& in_allotted_geometry) = 0; - //-------------- 尺寸计算 -------------- + const auto& get_key() const { return key_; } + auto get_window() const { return key_.get_window(); } + auto get_entity() const { return key_.get_entity(); } + auto get_dpi_scale() const -> float; - /** - * @brief 获取组件期望大小 - * @return 组件期望大小(像素) - */ - [[nodiscard]] auto get_desired_size() const { return desired_size_.value_or(Eigen::Vector2f{ 0, 0 }); } - - /** - * @brief 缓存组件期望大小 - * @param in_layout_scale_multiplier 布局缩放系数 - * - * 计算并缓存组件的期望大小,避免重复计算 - */ - virtual void cache_desired_size(float in_layout_scale_multiplier); - - /** - * @brief 计算组件期望大小 - * @param in_layout_scale_multiplier 布局缩放系数 - * @return 计算得到的期望大小 - * - * 派生类需要实现此函数来计算组件的期望大小 - */ - [[nodiscard]] virtual Eigen::Vector2f compute_desired_size(float in_layout_scale_multiplier) const = 0; - - //-------------- 可见性相关 -------------- - - /** - * @brief 获取组件可见性 - * @return 当前可见性状态 - */ - [[nodiscard]] auto get_visibility() const { return visibility_; } - - /** - * @brief 设置组件可见性 - * @param in_visibility 新的可见性状态 - */ - void set_visibility(visibility in_visibility) { visibility_ = in_visibility; } - - //-------------- 组件层级关系 -------------- - - /** - * @brief 设置父组件 - * @param in_parent 父组件的共享指针 - */ - void set_parent(const std::shared_ptr& in_parent) { parent_ = in_parent; } - - /** - * @brief 获取父组件 - */ - auto get_parent() const { return parent_.lock(); } - - /** - * @brief 获取子组件列表 - * @return 子组件的共享指针数组 - * - * 默认实现返回空列表,有子组件的类需要重写此方法 - */ - virtual std::vector> get_children() const { return {}; } + auto get_parent() const { return get_component_ref().parent; } + auto get_children() const { return get_component_ref().children; } + auto get_parent_widget() const -> std::weak_ptr; + void set_parent(const widget_key& in_parent); virtual void invalidate(invalidate_reason in_reason); - //-------------- 窗口关联 -------------- - /** - * @brief 获取关联的窗口 - * @return 关联的窗口指针 - */ - virtual mwindow* get_window(); + auto is_enabled() const { return get_component_ref().is_enabled(); } + void set_enabled(bool in_enabled) { get_component_ref().set_enabled(in_enabled); } + auto get_visibility() const { return get_component_ref().get_visibility(); } + void set_visibility(visibility_t in_visibility) { get_component_ref().set_visibility(in_visibility); } - //-------------- 启用状态 -------------- + bool can_hit_test() const { return get_component_ref().can_hit_test(); } - /** - * @brief 设置组件启用状态 - * @param in 是否启用 - */ - void set_enabled(bool in) { enable_ = in; } + //-------------- 鼠标事件处理 -------------- - /** - * @brief 获取组件启用状态 - * @return 是否启用 - */ - auto is_enabled() const { return enable_; } + /** + * @brief 处理鼠标进入事件 + * @result 命中测试结果 + */ + virtual void on_mouse_enter() {} - /** - * @brief 计算组件是否应该被启用 - * @param in_parent_enabled 父组件是否启用 - * @return 组件是否应该被启用 - * - * 组件只有在自身启用且父组件启用的情况下才应该被启用 - */ - auto should_be_enabled(bool in_parent_enabled) const { return in_parent_enabled && is_enabled(); } + /** + * @brief 处理鼠标离开事件 + * @result 命中测试结果 + */ + virtual void on_mouse_leave() {} - //-------------- 命中测试 -------------- + /** + * @brief 处理鼠标移动事件 + * @param in_position 本地坐标系中的鼠标位置 + * @result 命中测试结果 + */ + virtual hit_test_handle on_mouse_move(const Eigen::Vector2f& in_position) { return hit_test_handle::unhandled(); } - /** - * @brief 检查组件是否可以参与命中测试 - * @return 如果可以参与命中测试则为true - */ - [[nodiscard]] bool can_hit_test() const; + /** + * @brief 处理鼠标按下事件 + * @param in_position 本地坐标系中的鼠标位置 + * @param in_button 按下的鼠标按钮 + * @result 命中测试结果 + */ + virtual hit_test_handle on_mouse_button_down(const Eigen::Vector2f& in_position, mouse_button in_button) { return hit_test_handle::unhandled(); } - //-------------- 鼠标事件处理 -------------- + /** + * @brief 处理鼠标释放事件 + * @param in_position 本地坐标系中的鼠标位置 + * @param in_button 释放的鼠标按钮 + * @result 命中测试结果 + */ + virtual hit_test_handle on_mouse_button_up(const Eigen::Vector2f& in_position, mouse_button in_button) { return hit_test_handle::unhandled(); } - /** - * @brief 处理鼠标进入事件 - * @result 命中测试结果 - */ - virtual void on_mouse_enter() {} + /** + * @brief 处理鼠标点击事件 + * @param in_position 本地坐标系中的鼠标位置 + * @param in_button 点击的鼠标按钮 + * @result 命中测试结果 + */ + virtual hit_test_handle on_click(const Eigen::Vector2f& in_position, mouse_button in_button) { return hit_test_handle::unhandled(); } - /** - * @brief 处理鼠标离开事件 - * @result 命中测试结果 - */ - virtual void on_mouse_leave() {} + /** + * @brief 处理鼠标双击事件 + * @param in_position 本地坐标系中的鼠标位置 + * @param in_button 双击的鼠标按钮 + * @result 命中测试结果 + */ + virtual hit_test_handle on_double_click(const Eigen::Vector2f& in_position, mouse_button in_button) { return hit_test_handle::unhandled(); } - /** - * @brief 处理鼠标移动事件 - * @param in_position 本地坐标系中的鼠标位置 - * @result 命中测试结果 - */ - virtual hit_test_handle on_mouse_move(const Eigen::Vector2f& in_position) { return hit_test_handle::unhandled(); } - - /** - * @brief 处理鼠标按下事件 - * @param in_position 本地坐标系中的鼠标位置 - * @param in_button 按下的鼠标按钮 - * @result 命中测试结果 - */ - virtual hit_test_handle on_mouse_press(const Eigen::Vector2f& in_position, mouse_button in_button) { return hit_test_handle::unhandled(); } - - /** - * @brief 处理鼠标释放事件 - * @param in_position 本地坐标系中的鼠标位置 - * @param in_button 释放的鼠标按钮 - * @result 命中测试结果 - */ - virtual hit_test_handle on_mouse_release(const Eigen::Vector2f& in_position, mouse_button in_button) { return hit_test_handle::unhandled(); } - - /** - * @brief 处理鼠标点击事件 - * @param in_position 本地坐标系中的鼠标位置 - * @param in_button 点击的鼠标按钮 - * @result 命中测试结果 - */ - virtual hit_test_handle on_click(const Eigen::Vector2f& in_position, mouse_button in_button) { return hit_test_handle::unhandled(); } - - /** - * @brief 处理鼠标双击事件 - * @param in_position 本地坐标系中的鼠标位置 - * @param in_button 双击的鼠标按钮 - * @result 命中测试结果 - */ - virtual hit_test_handle on_double_click(const Eigen::Vector2f& in_position, mouse_button in_button) { return hit_test_handle::unhandled(); } - - /** - * @brief 处理鼠标滚轮事件 - * @param in_position 本地坐标系中的鼠标位置 - * @param in_delta 滚轮增量 - * @result 命中测试结果 - */ - virtual hit_test_handle on_mouse_wheel(const Eigen::Vector2f& in_position, const wheel_event& in_delta) { return hit_test_handle::unhandled(); } -private: - /** - * @brief 设置组件期望大小 - * @param in_size 新的期望大小 - */ - void set_desired_size(const Eigen::Vector2f& in_size) { desired_size_ = in_size; } - - //-------------- 成员变量 -------------- - - /** 组件可见性状态 */ - visibility visibility_{ visibility::self_hit_test_invisible }; - - /** 父组件的弱引用 */ - std::weak_ptr parent_; - - /** 缓存的期望大小 */ - std::optional desired_size_; - - /** 组件启用状态 */ - bool enable_{ true }; + /** + * @brief 处理鼠标滚轮事件 + * @param in_position 本地坐标系中的鼠标位置 + * @param in_delta 滚轮增量 + * @result 命中测试结果 + */ + virtual hit_test_handle on_mouse_wheel(const Eigen::Vector2f& in_position, const wheel_event& in_delta) { return hit_test_handle::unhandled(); } +protected: + widget_key key_ = widget_key::invalid(); }; - -/** - * @brief 创建组件的宏 - * @param widget_class 组件类名 - * @param ... 额外参数(未使用) - * @return 新创建的组件的共享指针 - */ -#define mnew(widget_class, ...) \ - std::make_shared() diff --git a/src/widget/panel_widget/mbox.cpp b/src/widget/panel_widget/mbox.cpp index a3d8c3e..d5540d0 100644 --- a/src/widget/panel_widget/mbox.cpp +++ b/src/widget/panel_widget/mbox.cpp @@ -2,18 +2,26 @@ #include "widget/layout_utils.h" -void mh_box::arrange_children(const geometry_t& in_allotted_geometry, arranged_children& in_arranged_children) { - arrange_box_children(in_allotted_geometry, in_arranged_children, child_slots_); +void mh_box::arrange_children(const geometry_t& in_allotted_geometry) { + arrange_box_children(in_allotted_geometry, + get_dpi_scale(), + visibility_t::any_visible, + get_slots()); } Eigen::Vector2f mh_box::compute_desired_size(float in_layout_scale_multiplier) const { - return compute_box_desired_size(child_slots_); + cache_all_children_desired_size(in_layout_scale_multiplier, false); + return compute_box_desired_size(get_slots()); } -void mv_box::arrange_children(const geometry_t& in_allotted_geometry, arranged_children& in_arranged_children) { - arrange_box_children(in_allotted_geometry, in_arranged_children, child_slots_); +void mv_box::arrange_children(const geometry_t& in_allotted_geometry) { + arrange_box_children(in_allotted_geometry, + get_dpi_scale(), + visibility_t::any_visible, + get_slots()); } Eigen::Vector2f mv_box::compute_desired_size(float in_layout_scale_multiplier) const { - return compute_box_desired_size(child_slots_); + cache_all_children_desired_size(in_layout_scale_multiplier, false); + return compute_box_desired_size(get_slots()); } diff --git a/src/widget/panel_widget/mbox.h b/src/widget/panel_widget/mbox.h index 82d7da1..e209823 100644 --- a/src/widget/panel_widget/mbox.h +++ b/src/widget/panel_widget/mbox.h @@ -1,6 +1,6 @@ #pragma once /** - * @file mlayout_boxes.h + * @file mbox.h * @brief 定义水平和垂直布局盒子组件 * * 本文件定义了两种基本的布局容器:水平盒子(mh_box)和垂直盒子(mv_box), @@ -8,6 +8,7 @@ */ #include "mpanel_widget.h" +#include "geometry/margin.h" #include "misc/widget_size.h" /** @@ -36,11 +37,10 @@ public: /** * @brief 排列子组件 * @param in_allotted_geometry 分配给组件的几何区域 - * @param in_arranged_children 排列好的子组件集合 * * 在水平方向上排列所有子组件,考虑各自的大小、边距和间距。 */ - virtual void arrange_children(const geometry_t& in_allotted_geometry, arranged_children& in_arranged_children) override; + virtual void arrange_children(const geometry_t& in_allotted_geometry) override; /** * @brief 计算组件的期望大小 @@ -49,7 +49,7 @@ public: * * 基于所有子组件的期望大小、边距和间距,计算水平盒子组件的总体期望大小。 */ - [[nodiscard]] virtual Eigen::Vector2f compute_desired_size(float in_layout_scale_multiplier) const override; + [[nodiscard]] virtual auto compute_desired_size(float in_layout_scale_multiplier) const -> Eigen::Vector2f override; private: /** 子组件之间的间距 */ @@ -86,7 +86,7 @@ public: * * 在垂直方向上排列所有子组件,考虑各自的大小、边距和间距。 */ - virtual void arrange_children(const geometry_t& in_allotted_geometry, arranged_children& in_arranged_children) override; + virtual void arrange_children(const geometry_t& in_allotted_geometry) override; /** * @brief 计算组件的期望大小 @@ -95,7 +95,7 @@ public: * * 基于所有子组件的期望大小、边距和间距,计算垂直盒子组件的总体期望大小。 */ - [[nodiscard]] virtual Eigen::Vector2f compute_desired_size(float in_layout_scale_multiplier) const override; + [[nodiscard]] virtual auto compute_desired_size(float in_layout_scale_multiplier) const -> Eigen::Vector2f override; private: /** 子组件之间的间距 */ diff --git a/src/widget/panel_widget/mpanel_widget.h b/src/widget/panel_widget/mpanel_widget.h index 5091727..0b165b1 100644 --- a/src/widget/panel_widget/mpanel_widget.h +++ b/src/widget/panel_widget/mpanel_widget.h @@ -9,6 +9,7 @@ #include "widget/mwidget.h" #include "widget/slot_util.h" +#include "widget_tree/widget_manager.h" /** * @struct mpanel_widget_slot @@ -28,7 +29,7 @@ struct mpanel_widget_slot { auto& me() { return static_cast(*this); } - std::shared_ptr slot_owner{}; + widget_key slot_owner; // 插槽功能宏 - 定义内容管理 SLOT_CONTENT() @@ -56,48 +57,64 @@ public: */ void on_paint(mirage_paint_context& in_context) override {} + void cache_all_children_desired_size(float in_layout_scale_multiplier, bool in_force = false) const; + //-------------- 子项管理 -------------- + // 1. 创建一个新的entity + // 2. 给entity添加一个slot component + // 3. 更新父子关系 + // 4. 通知父组件需要重新计算布局 + // 5. 返回slot component的引用,便于链式调用设置属性 + /** * @brief 添加新的子组件插槽 * @return 新添加的插槽引用,允许链式调用设置插槽属性 * * 创建并添加一个新的子组件插槽到面板中。 */ - auto& add_slot() { - auto& slot = child_slots_.emplace_back(); - slot.slot_owner = shared_from_this(); - invalidate(invalidate_reason::layout); + auto add_slot() -> SlotType& { + auto& manager = widget_manager::get(); + auto widget_entity = manager.new_widget(get_window()); + auto w = widget_entity.lock(); + auto& slot = w->template add_component(); + slot.slot_owner = key_; return slot; } - //-------------- 子组件查询 -------------- - - /** - * @brief 获取子组件列表 - * @return 包含所有有效子组件的向量 - * - * 遍历所有插槽,收集其中的有效子组件(非空)。 - */ - virtual std::vector> get_children() const override; - -protected: - /** 子组件插槽列表,管理多个子组件 */ - std::vector child_slots_; + auto get_slots_ref() { + std::vector children; + if (auto hierarchy = get_component()) { + auto& manager = widget_manager::get(); + for (const auto& child : hierarchy->children) { + if (auto slot = manager.get_component(child)) { + children.push_back(*slot); + } + } + } + return children; + } + auto get_slots() const { + std::vector children; + if (auto hierarchy = get_component()) { + auto& manager = widget_manager::get(); + for (const auto& child : hierarchy->children) { + if (auto slot = manager.get_component(child)) { + children.push_back(slot); + } + } + } + return children; + } }; -/** - * @brief 获取面板组件的所有子组件 - * - * 遍历插槽列表,收集所有非空插槽中的子组件。 - */ template -std::vector> mpanel_widget::get_children() const { - std::vector> children; - for (const auto& slot : child_slots_) { - if (slot.get()) { - children.push_back(slot.get()); - } +void mpanel_widget::cache_all_children_desired_size(float in_layout_scale_multiplier, bool in_force) const { + auto& manager = widget_manager::get(); + const auto& hierarchy = get_component_ref(); + + for (const auto& child: hierarchy.children) { + const auto& ptr = manager.get_component_ref(child); + ptr->cache_desired_size(in_layout_scale_multiplier, in_force); } - return children; } diff --git a/src/widget/slot_util.h b/src/widget/slot_util.h index cda1295..895e83f 100644 --- a/src/widget/slot_util.h +++ b/src/widget/slot_util.h @@ -25,7 +25,7 @@ return me(); \ } \ protected: \ - std::shared_ptr widget_{}; + std::weak_ptr widget_{}; #define SLOT_OPTIONAL_ATTRIBUTE(type, name) \ public: \ diff --git a/src/widget_tree/widget_component.cpp b/src/widget_tree/widget_component.cpp new file mode 100644 index 0000000..7e746ce --- /dev/null +++ b/src/widget_tree/widget_component.cpp @@ -0,0 +1,33 @@ +#include "widget_component.h" + +#include "widget_manager.h" + +void widget_hierarchy::add_child(const widget_key& in_child) { + children.push_back(in_child); +} + +void widget_hierarchy::remove_child(const widget_key& in_child) { + const auto find = std::ranges::find(children, in_child); + if (find != children.end()) { + children.erase(find); + } +} + +bool widget_visibility::can_hit_test() const { + // 不可见组件不参与命中测试 + if (has_any_flag(visibility, visibility_t::any_invisible)) { + return false; + } + + // 如果整个组件树都不可点击 + if (has_flag(visibility, visibility_t::hit_test_invisible)) { + return false; + } + + // 如果组件自身不可点击(但子组件可以) + if (has_flag(visibility, visibility_t::self_hit_test_invisible)) { + return false; + } + + return true; +} diff --git a/src/widget_tree/widget_component.h b/src/widget_tree/widget_component.h new file mode 100644 index 0000000..4f9d6e4 --- /dev/null +++ b/src/widget_tree/widget_component.h @@ -0,0 +1,94 @@ +#pragma once +#include "geometry/arranged_children.h" +#include "geometry/geometry.h" +#include "misc/invalidate_reason.h" +#include "misc/key_type/key_type.h" +#include "mustache/ecs/entity.hpp" + +class mwindow; +class mwidget; + +struct widget_key { + widget_key() = default; + + widget_key(mwindow* in_window, mustache::Entity const& in_entity) : + window_(in_window), + entity_(in_entity) {} + + widget_key(const widget_key& in_other) : + window_(in_other.window_), + entity_(in_other.entity_) {} + auto& operator=(const widget_key& in_other) { + if (this != &in_other) { + window_ = in_other.window_; + entity_ = in_other.entity_; + } + return *this; + } + + operator bool() const { return window_ && !entity_.isNull(); } + operator mustache::Entity() const { return entity_; } + operator mwindow*() const { return window_; } + + bool operator==(const widget_key& in_other) const { return window_ == in_other.window_ && entity_ == in_other.entity_; } + bool operator!=(const widget_key& in_other) const { return !(*this == in_other); } + + static auto invalid() { return widget_key(nullptr, mustache::Entity()); } + + [[nodiscard]] auto get_window() const { return window_; } + [[nodiscard]] auto get_entity() const { return entity_; } +private: + mwindow* window_; + mustache::Entity entity_; +}; + +struct widget_ptr { + std::shared_ptr widget; + + operator std::shared_ptr() const { return widget; } + auto operator->() const { return widget.get(); } + operator bool() const { return widget != nullptr; } +}; + +struct widget_layout { + geometry_t geometry; // 几何信息 + std::optional desired_size; // 期望大小 + + auto is_under_location(const Eigen::Vector2f& in_window_location) const { + return geometry.is_under_location(in_window_location); + } + auto is_under_local_location(const Eigen::Vector2f& in_local_location) const { + return geometry.is_under_local_location(in_local_location); + } +}; + +struct widget_hierarchy { + widget_key parent; + std::vector children; + uint32_t depth; + + void add_child(const widget_key& in_child); + void remove_child(const widget_key& in_child); +}; + +struct widget_visibility { + bool enabled = true; + visibility_t visibility = visibility_t::self_hit_test_invisible; + + [[nodiscard]] auto is_enabled() const { return enabled; } + [[nodiscard]] auto get_visibility() const { return visibility; } + void set_enabled(const bool in_enabled) { enabled = in_enabled; } + void set_visibility(const visibility_t in_visibility) { visibility = in_visibility; } + + bool can_hit_test() const; +}; + +struct widget_invalidate { + invalidate_reason invalidate; + + void set(const invalidate_reason in_reason) { set_flag(invalidate, in_reason); } + void unset(const invalidate_reason in_reason) { clear_flag(invalidate, in_reason); } + [[nodiscard]] auto has(const invalidate_reason in_reason) const { return has_any_flag(invalidate, in_reason); } + + void clear() { invalidate = invalidate_reason::none; } +}; diff --git a/src/widget_tree/widget_manager.cpp b/src/widget_tree/widget_manager.cpp new file mode 100644 index 0000000..b4be330 --- /dev/null +++ b/src/widget_tree/widget_manager.cpp @@ -0,0 +1,38 @@ +#include "widget_manager.h" + +#include "widget_system.h" +#include "core/window/mwindow.h" + +void widget_manager::init_window(const std::shared_ptr& in_window) { + auto& registry = registries[in_window.get()]; + auto& system_manager = registry.systems(); + auto& entity_manager = registry.entities(); + + const auto layout_system = std::make_shared(); + const auto hit_test_system = std::make_shared(); + const auto render_system = std::make_shared(); + + layout_system->set_window(in_window); + hit_test_system->set_window(in_window); + render_system->set_window(in_window); + + system_manager.addSystem(layout_system); + system_manager.addSystem(hit_test_system); + system_manager.addSystem(render_system); + + registry.init(); + + in_window->on_close_delegate.add_raw(this, &widget_manager::on_window_close); + + const auto& entity = entity_manager.create<>(); + widget_key key { in_window.get(), entity }; + in_window->set_key(key); + in_window->init_component(entity_manager); +} + +void widget_manager::update() { + for (auto& pair : registries) { + auto& registry = pair.second; + registry.systems().update(); + } +} diff --git a/src/widget_tree/widget_manager.h b/src/widget_tree/widget_manager.h new file mode 100644 index 0000000..9e0cfe5 --- /dev/null +++ b/src/widget_tree/widget_manager.h @@ -0,0 +1,85 @@ +#pragma once +#include + +#include + +#include "widget_component.h" + +class mwidget; +class mwindow; + +class widget_manager { +public: + static widget_manager& get() { + static widget_manager instance; + return instance; + } + void init_window(const std::shared_ptr& in_window); + + auto& get_registry(mwindow* in_window) { + return registries[in_window]; + } + + template + auto new_widget(mwindow* in_window) { + auto& entity_manager = registries[in_window].entities(); + const auto entity = entity_manager.create<>(); + widget_key key{ in_window, entity }; + + auto widget = std::make_shared(); + widget->set_key(key); + widget->init_component(entity_manager); + return std::weak_ptr{ widget }; + } + + template + auto new_widget(mwindow* in_window, Args&&... args) { + auto& entity_manager = registries[in_window].entities(); + const auto entity = entity_manager.create<>(); + widget_key key{ in_window, entity }; + + auto widget = std::make_shared(std::forward(args)...); + widget->set_key(key); + widget->init_component(entity_manager); + return widget; + } + + auto delete_widget(const widget_key& in_key) { + return registries[in_key].entities().destroy(in_key); + } + + template + auto& add_component(const widget_key& in_key) { + return registries[in_key].entities().assign(in_key); + } + template + auto& add_component(const widget_key& in_key, Args&&... args) { + return registries[in_key].entities().assign(in_key, std::forward(args)...); + } + + template + auto* get_component(const widget_key& in_key) { + return registries[in_key].entities().getComponent(in_key); + } + template + auto& get_component_ref(const widget_key& in_key) { + return *registries[in_key].entities().getComponent(in_key); + } + + template + auto get_system(mwindow* in_window) { + auto shared = registries[in_window].systems().findSystem(); + return std::static_pointer_cast(shared); + } + + void update(); +private: + widget_manager() = default; + widget_manager(const widget_manager&) = delete; + + void on_window_close(mwindow* in_window) { + registries.erase(in_window); + } + + std::map registries; +}; diff --git a/src/widget_tree/widget_system.cpp b/src/widget_tree/widget_system.cpp new file mode 100644 index 0000000..a421cfe --- /dev/null +++ b/src/widget_tree/widget_system.cpp @@ -0,0 +1,267 @@ +// +// Created by 46944 on 25-3-23. +// + +#include "widget_system.h" + +#include "mustache/ecs/world.hpp" +#include "widget/mwidget.h" +#include "core/window/mwindow.h" + +auto widget_layout_system::get_dpi_scale() const -> float { + if (const auto window = window_.lock()) + return window->get_window_dpi_scale() * dpi_helper::get_global_scale(); + return 1.f; +} + +void widget_layout_system::onUpdate(mustache::World& in_world) { + // 首先更新所需大小 + in_world.entities().forEach([&](const widget_invalidate& in_invalidate, const widget_ptr& in_ptr) { + // 如果布局有效则返回 + if (!in_invalidate.has(invalidate_reason::layout)) + return; + in_ptr->cache_desired_size(get_dpi_scale()); + }); + + // 然后更新布局 + in_world.entities().forEach([&](const widget_layout& in_layout, widget_invalidate& in_invalidate, const widget_ptr& in_ptr) { + // 如果布局有效则返回 + if (!in_invalidate.has(invalidate_reason::layout)) + return; + + in_ptr->arrange_children(in_layout.geometry); + + in_invalidate.unset(invalidate_reason::layout); + }); +} + +widget_hit_test_system::~widget_hit_test_system() { + if (const auto window = window_.lock()) { + window->on_mouse_move_delegate.remove_object(this); + window->on_mouse_button_up_delegate.remove_object(this); + window->on_mouse_button_down_delegate.remove_object(this); + window->on_mouse_leave_delegate.remove_object(this); + } +} + +void widget_hit_test_system::set_window(std::shared_ptr in_window) { + window_ = in_window; + + in_window->on_mouse_move_delegate.add_raw(this, &widget_hit_test_system::process_mouse_move); + in_window->on_mouse_button_up_delegate.add_raw(this, &widget_hit_test_system::process_mouse_button_up); + in_window->on_mouse_button_down_delegate.add_raw(this, &widget_hit_test_system::process_mouse_button_down); + in_window->on_mouse_leave_delegate.add_raw(this, &widget_hit_test_system::process_mouse_leave); +} + +void widget_hit_test_system::process_mouse_move(const Eigen::Vector2f& in_window_pos) { + const auto& result = perform_hit_test(in_window_pos, [this](const std::shared_ptr& in_widget, const Eigen::Vector2f& in_local_pos) { + return in_widget->on_mouse_move(in_local_pos); + }); + + if (last_hover_widget_ == result.widget) + return; + + if (auto ptr = widget_manager::get().get_component(last_hover_widget_)) + ptr->widget->on_mouse_leave(); + + last_hit_widget_ = result; + if (auto ptr = widget_manager::get().get_component(last_hit_widget_)) + ptr->widget->on_mouse_enter(); +} + +void widget_hit_test_system::process_mouse_button_up(const Eigen::Vector2f& in_window_pos, mouse_button in_button) { + // 执行碰撞检测,找出鼠标位置下的widget + const auto& hit_result = perform_hit_test(in_window_pos, [in_button](const std::shared_ptr& widget, const Eigen::Vector2f& local_pos) { + return widget->on_mouse_button_up(local_pos, in_button); + }); + + const auto& hit_widget = hit_result.widget; + const auto& widget_local_pos = hit_result.widget_space_pos; + + // 如果没有点击到任何widget,重置状态并返回 + if (!hit_widget) { + click_count_ = 0; + last_hit_widget_ = widget_key::invalid(); + return; + } + + auto& ptr = widget_manager::get().get_component_ref(last_hit_widget_); + + // 定义时间和空间阈值 + constexpr auto CLICK_TIME_THRESHOLD = std::chrono::milliseconds(400); // 单次点击最大持续时间 + constexpr float CLICK_DISTANCE_THRESHOLD = 5.0f; // 点击位置容差 + + // 计算从按下到释放的持续时间 + const auto press_duration = get_current_time() - last_mouse_press_time_; + + // 计算与上次点击位置的距离 + const float distance = (in_window_pos - last_mouse_press_pos_).norm(); + + // 判断是否是有效点击(时间短且位置接近) + bool is_valid_click = press_duration < CLICK_TIME_THRESHOLD && distance < CLICK_DISTANCE_THRESHOLD; + + // 处理点击相关逻辑 + if (is_valid_click && last_hit_widget_ == hit_widget) { + // 计算上次点击事件到现在的时间 + const auto time_since_last_click = get_current_time() - last_click_time_; + + // 判断是否是连续点击(在双击时间窗口内) + if (click_count_ > 0 && time_since_last_click < CLICK_TIME_THRESHOLD) { + // 点击次数增加 + click_count_++; + + // 根据点击次数触发不同事件 + if (click_count_ == 2) { + // 双击事件 + ptr->on_double_click(widget_local_pos, in_button); + } else { + // 单击事件 + ptr->on_click(widget_local_pos, in_button); + } + } else { + // 超过双击时间窗口,视为新的点击序列 + click_count_ = 1; + ptr->on_click(widget_local_pos, in_button); + } + + // 更新最后点击时间 + last_click_time_ = get_current_time(); + } else { + // 无效点击或点击了新的widget + if (is_valid_click) { + click_count_ = 1; + ptr->on_click(widget_local_pos, in_button); + last_click_time_ = get_current_time(); + } else { + // 不是有效点击,重置点击计数 + click_count_ = 0; + } + } + + // 更新最后点击的widget + last_hit_widget_ = hit_widget; +} + +void widget_hit_test_system::process_mouse_button_down(const Eigen::Vector2f& in_window_pos, mouse_button in_button) { + // 执行碰撞检测,找出鼠标位置下的widget + const auto& result = perform_hit_test(in_window_pos, [in_button](const std::shared_ptr& in_widget, const Eigen::Vector2f& in_local_pos) { + return in_widget->on_mouse_button_down(in_local_pos, in_button); + }); + + // 更新最后一次点击的widget和时间 + last_hit_widget_ = result; + last_mouse_press_time_ = get_current_time(); + last_mouse_press_pos_ = in_window_pos; // 存储点击位置,用于空间判断 +} + +void widget_hit_test_system::process_mouse_leave() { + + if (auto last_hover_widget = widget_manager::get().get_component(last_hover_widget_)) { + last_hover_widget->widget->on_mouse_leave(); + } + last_hover_widget_ = widget_key::invalid(); +} + +hit_test_result widget_hit_test_system::perform_hit_test(const Eigen::Vector2f& in_window_pos, + const std::function&, + const Eigen::Vector2f&)>& in_hit_func) { + auto window = window_.lock(); + if (!window) + return {}; + + auto window_content = window->get_content(); + if (!window_content) + return {}; + + return perform_hit_test(window_content->get_key(), in_window_pos, in_hit_func); +} + +hit_test_result widget_hit_test_system::perform_hit_test(const widget_key& in_key, const Eigen::Vector2f& in_window_pos, + const std::function&, const Eigen::Vector2f&)>& in_hit_func) { + const auto& layout = widget_manager::get().get_component_ref(in_key); + + auto widget_local_pos = layout.is_under_location(in_window_pos); + if (!widget_local_pos) + return {}; + + auto& widget = widget_manager::get().get_component_ref(in_key); + if (widget->can_hit_test() && in_hit_func) { + const auto& widget_pos = widget_local_pos.value(); + const auto& handle = in_hit_func(widget, widget_pos); + if (handle.is_handled()) + return { in_key, widget_pos }; + } + + const auto& children = widget_manager::get().get_component_ref(in_key).children; + for (const auto& child : children) { + if (const auto& result = perform_hit_test(child, in_window_pos, in_hit_func)) + return result; + } + + return {}; +} + +widget_render_system::~widget_render_system() { + if (auto window = window_.lock()) { + window->on_resize_delegate.remove_object(this); + } +} + +void widget_render_system::set_window(std::shared_ptr in_window) { + window_ = in_window; + context.init(in_window->get_window_frame_size()); + in_window->on_resize_delegate.add_raw(this, &widget_render_system::on_resize); +} + +void widget_render_system::on_resize(const Eigen::Vector2i& in_size) { + context.update_projection_matrix(in_size); +} + +void widget_render_system::onUpdate(mustache::World& in_world) { + const auto window = window_.lock(); + if (!window) + return; + if (!window->is_visible()) + return; + + auto& window_state = window->get_state(); + + sg_pass pass{}; + pass.action.colors[0].load_action = SG_LOADACTION_CLEAR; + pass.action.colors[0].store_action = SG_STOREACTION_STORE; + pass.action.colors[0].clear_value = { 0.f, 0.f, 0.f, 1.0f }; + + pass.action.depth.load_action = SG_LOADACTION_CLEAR; + pass.action.depth.store_action = SG_STOREACTION_DONTCARE; + pass.action.depth.clear_value = 1.0f; + pass.swapchain = window_state.swapchain; + + sg_begin_pass(pass); + sg_apply_viewport(0, 0, window_state.swapchain.width, window_state.swapchain.height, true); + + context.begin_frame(window.get()); + paint(window->get_key()); + context.end_frame(); + + sg_end_pass(); + sg_commit(); + + window_state.present(); +} + +void widget_render_system::paint(const widget_key& in_key) { + const auto& ptr = widget_manager::get().get_component_ref(in_key); + if (!ptr) + return; + + const auto& geo = ptr->get_geometry(); + + context.set_geometry(geo); + ptr->on_paint(context); + + const auto& children = widget_manager::get().get_component_ref(in_key).children; + for (const auto& child : children) { + paint(child); + } +} diff --git a/src/widget_tree/widget_system.h b/src/widget_tree/widget_system.h new file mode 100644 index 0000000..0816f64 --- /dev/null +++ b/src/widget_tree/widget_system.h @@ -0,0 +1,58 @@ +#pragma once +#include "widget_component.h" +#include "misc/mirage_paint_context.h" +#include "mustache/ecs/system.hpp" +#include "widget/hit_test/hit_test_result.h" + +class widget_layout_system : public mustache::System { +public: + void set_window(std::shared_ptr in_window) { window_ = in_window; } + + auto get_dpi_scale() const -> float; +protected: + virtual void onUpdate(mustache::World& in_world) override; + + std::weak_ptr window_{}; +}; + +class widget_hit_test_system : public mustache::System { +public: + virtual ~widget_hit_test_system() override; + void set_window(std::shared_ptr in_window); + +protected: + void process_mouse_move(const Eigen::Vector2f& in_window_pos); + void process_mouse_button_up(const Eigen::Vector2f& in_window_pos, mouse_button in_button); + void process_mouse_button_down(const Eigen::Vector2f& in_window_pos, mouse_button in_button); + void process_mouse_leave(); + + virtual void onUpdate(mustache::World& in_world) override {} + hit_test_result perform_hit_test(const Eigen::Vector2f& in_window_pos, const std::function&, const Eigen::Vector2f&)>& in_hit_func); + + static hit_test_result perform_hit_test(const widget_key& in_key, const Eigen::Vector2f& in_window_pos, const std::function&, const Eigen::Vector2f&)>& in_hit_func); + + std::weak_ptr window_{}; + widget_key last_hit_widget_{}; + widget_key last_hover_widget_{}; + + int32_t click_count_{}; + Eigen::Vector2f last_mouse_press_pos_; // 上次鼠标按下的位置 + time_type last_click_time_{}; // 上次点击完成的时间 + time_type last_mouse_press_time_{}; +}; + +class widget_render_system : public mustache::System { +public: + virtual ~widget_render_system() override; + void set_window(std::shared_ptr in_window); + +protected: + virtual void onUpdate(mustache::World& in_world) override; + + void paint(const widget_key& in_key); + + void on_resize(const Eigen::Vector2i& in_size); + + mirage_paint_context context; + std::weak_ptr window_{}; +}; diff --git a/third_party/mustache b/third_party/mustache new file mode 160000 index 0000000..c7951b7 --- /dev/null +++ b/third_party/mustache @@ -0,0 +1 @@ +Subproject commit c7951b7029ec6e650c4f15ad9d0c058ea445fec4