diff --git a/example/src/main.cpp b/example/src/main.cpp index fe8ce69..576bcb9 100644 --- a/example/src/main.cpp +++ b/example/src/main.cpp @@ -4,6 +4,7 @@ #include "mirage.h" #include "core/window/render_window.h" +#include "widget/compound_widget/mbutton.h" int main(int argc, char* argv[]) { mirage_app app; @@ -12,7 +13,11 @@ int main(int argc, char* argv[]) { mirage_window window; window.create_window(800, 600, L"Hello, World!"); window.show(); - app.get_render_context()->setup_surface(&window); + + mirage_app::get_render_context()->setup_surface(&window); + + auto button = std::make_shared(); + window.set_content(button); app.run(); return 0; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5dfd1b8..9f488de 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -19,6 +19,7 @@ add_library(${PROJECT_NAME} STATIC ${SRC_FILES}) target_link_libraries(${PROJECT_NAME} PUBLIC Freetype::Freetype Eigen3::Eigen) target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) add_os_definitions(${PROJECT_NAME}) +target_compile_definitions(${PROJECT_NAME} PUBLIC -DNOMINMAX) # 添加编译shader的自定义命令 add_mirage_shader_directory(${CMAKE_CURRENT_SOURCE_DIR}/shaders) diff --git a/src/core/render_elements.cpp b/src/core/render_elements.cpp index ecf512a..7694c60 100644 --- a/src/core/render_elements.cpp +++ b/src/core/render_elements.cpp @@ -39,42 +39,104 @@ void compute_rect_vertices(const Eigen::MatrixBase& in_pos, // 开始新一帧 void render_elements::begin_frame() { - vertices.clear(); - indices.clear(); - batches.clear(); - current_batch_index = -1; - current_key = batch_key{}; - draw_call_count = 0; - total_triangles = 0; + vertices_.clear(); + indices_.clear(); + batches_.clear(); + current_batch_index_ = -1; + 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); + } } // 设置渲染管线 void render_elements::set_pipeline(sg_pipeline pipeline) { - batch_key new_key = current_key; + batch_key new_key = current_key_; new_key.pipeline = pipeline; ensure_batch_compatibility(new_key); } // 设置纹理 void render_elements::set_texture(sg_image image) { - batch_key new_key = current_key; + batch_key new_key = current_key_; new_key.image = image; ensure_batch_compatibility(new_key); } +Eigen::Matrix4f render_elements::create_projection_matrix(const Eigen::Vector2i& in_size) { + Eigen::Matrix4f projection_matrix = Eigen::Matrix4f::Identity(); + + // 缩放因子 + const float scale_x = 2.0f / in_size.x(); + const float scale_y = -2.0f / in_size.y(); // Y轴翻转,因为窗口坐标系Y轴向下 + + // 平移因子 + constexpr float translate_x = -1.0f; + constexpr float translate_y = 1.0f; + + // 设置缩放 + projection_matrix(0, 0) = scale_x; + projection_matrix(1, 1) = scale_y; + + // 设置平移 + projection_matrix(0, 3) = translate_x; + projection_matrix(1, 3) = translate_y; + + return projection_matrix; +} + +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_window_size(const Eigen::Vector2i& in_size) { + window_size_ = 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) { // 如果当前没有批次或者渲染状态改变了,创建新批次 - if (current_batch_index < 0 || !(current_key == key)) { - current_key = key; + if (current_batch_index_ < 0 || current_key_ != key) { + current_key_ = key; draw_batch new_batch; new_batch.key = key; - new_batch.vertex_start = vertices.size(); - new_batch.index_start = indices.size(); + new_batch.vertex_start = vertices_.size(); + new_batch.index_start = indices_.size(); - batches.push_back(new_batch); - current_batch_index = batches.size() - 1; + batches_.push_back(new_batch); + current_batch_index_ = batches_.size() - 1; } } @@ -91,21 +153,24 @@ void render_elements::add_rect_to_batch( const Eigen::Vector2f& in_pivot, const Eigen::Vector2f& in_scale) { // 确保有活跃的批次 - if (current_batch_index < 0) { + if (current_batch_index_ < 0) { set_pipeline(sg_pipeline{}); // 使用默认管线 set_texture(sg_image{}); // 使用默认纹理 } + const auto& transform = get_current_transform(); + const auto& pos = transform.transform_point(in_pos); + // 计算顶点位置 Eigen::Matrix positions; - compute_rect_vertices(in_pos, in_size, positions, in_rotation_radians, in_pivot, in_scale); + compute_rect_vertices(pos, in_size, positions, in_rotation_radians, in_pivot, in_scale); // 记录起始顶点索引 - uint32_t base_index = vertices.size(); + uint32_t base_index = vertices_.size(); // 添加顶点 for (int i = 0; i < 4; ++i) { - auto& vertex = vertices.emplace_back(); + auto& vertex = vertices_.emplace_back(); vertex.position = positions.col(i); vertex.color = in_color[i]; vertex.uv = in_uv[i]; @@ -115,21 +180,21 @@ void render_elements::add_rect_to_batch( } // 添加索引(两个三角形) - indices.push_back(base_index); - indices.push_back(base_index + 1); - indices.push_back(base_index + 2); + indices_.push_back(base_index); + indices_.push_back(base_index + 1); + indices_.push_back(base_index + 2); - indices.push_back(base_index + 1); - indices.push_back(base_index + 3); - indices.push_back(base_index + 2); + indices_.push_back(base_index + 1); + indices_.push_back(base_index + 3); + indices_.push_back(base_index + 2); // 更新当前批次的计数 - draw_batch& batch = batches[current_batch_index]; - batch.vertex_count = vertices.size() - batch.vertex_start; - batch.index_count = indices.size() - batch.index_start; + draw_batch& batch = batches_[current_batch_index_]; + batch.vertex_count = vertices_.size() - batch.vertex_start; + batch.index_count = indices_.size() - batch.index_start; // 更新统计信息 - total_triangles += 2; + total_triangles_ += 2; } // 创建矩形(公开接口) @@ -160,58 +225,58 @@ void render_elements::make_rounded_rect(const Eigen::Vector2f& in_pos, const Eig const rect_color& in_color, const rect_uv& in_uv, const rect_round& in_round, float in_rotation_radians, const Eigen::Vector2f& in_pivot, const Eigen::Vector2f& in_scale) { - set_pipeline(rounded_rect_pipeline); - mirage_vertex_param_t param_a{ in_size }; - mirage_vertex_param_t param_b(in_round); + set_pipeline(rounded_rect_pipeline_); + const mirage_vertex_param_t param_a{ in_size }; + const mirage_vertex_param_t param_b{ in_round }; make_rect(in_pos, in_size, in_color, param_a, param_b, {}, in_uv, in_rotation_radians, in_scale, in_pivot); } // 确保缓冲区容量 void render_elements::ensure_buffer_capacity(uint32_t vertex_count, uint32_t index_count) { // 如果现有缓冲区不够大,重新创建 - if (vertex_count > vertex_buffer_capacity) { + if (vertex_count > vertex_buffer_capacity_) { // 销毁旧缓冲区 - if (vertex_buffer.id != SG_INVALID_ID) { sg_destroy_buffer(vertex_buffer); } + if (vertex_buffer_.id != SG_INVALID_ID) { sg_destroy_buffer(vertex_buffer_); } // 创建新缓冲区,容量翻倍以减少重新分配 - vertex_buffer_capacity = vertex_count * 2; + vertex_buffer_capacity_ = vertex_count * 2; sg_buffer_desc vbuf_desc = {}; - vbuf_desc.size = vertex_buffer_capacity * sizeof(mirage_vertex_t); + vbuf_desc.size = vertex_buffer_capacity_ * sizeof(mirage_vertex_t); vbuf_desc.usage = SG_USAGE_STREAM; - vertex_buffer = sg_make_buffer(&vbuf_desc); + vertex_buffer_ = sg_make_buffer(&vbuf_desc); } - if (index_count > index_buffer_capacity) { - if (index_buffer.id != SG_INVALID_ID) { sg_destroy_buffer(index_buffer); } + if (index_count > index_buffer_capacity_) { + if (index_buffer_.id != SG_INVALID_ID) { sg_destroy_buffer(index_buffer_); } - index_buffer_capacity = index_count * 2; + index_buffer_capacity_ = index_count * 2; sg_buffer_desc ibuf_desc = {}; - ibuf_desc.size = index_buffer_capacity * sizeof(uint32_t); + ibuf_desc.size = index_buffer_capacity_ * sizeof(uint32_t); ibuf_desc.usage = SG_USAGE_STREAM; ibuf_desc.type = SG_BUFFERTYPE_INDEXBUFFER; - index_buffer = sg_make_buffer(&ibuf_desc); + index_buffer_ = sg_make_buffer(&ibuf_desc); } } // 上传并绘制所有批次 void render_elements::flush_batches() { // 如果没有数据,直接返回 - if (vertices.empty() || indices.empty() || batches.empty()) { return; } + if (vertices_.empty() || indices_.empty() || batches_.empty()) { return; } // 确保缓冲区足够大 - ensure_buffer_capacity(vertices.size(), indices.size()); + ensure_buffer_capacity(vertices_.size(), indices_.size()); // 上传顶点和索引数据 - sg_update_buffer(vertex_buffer, sg_range{ vertices.data(), vertices.size() * sizeof(mirage_vertex_t) }); - sg_update_buffer(index_buffer, sg_range{ indices.data(), indices.size() * sizeof(uint32_t) }); + sg_update_buffer(vertex_buffer_, sg_range{ vertices_.data(), vertices_.size() * sizeof(mirage_vertex_t) }); + sg_update_buffer(index_buffer_, sg_range{ indices_.data(), indices_.size() * sizeof(uint32_t) }); // 绑定顶点和索引缓冲区 sg_bindings bind = {}; - bind.vertex_buffers[0] = vertex_buffer; - bind.index_buffer = index_buffer; + bind.vertex_buffers[0] = vertex_buffer_; + bind.index_buffer = index_buffer_; // 渲染每个批次 - for (const auto& batch: batches) { + for (const auto& batch: batches_) { // 设置纹理 bind.images[0] = batch.key.image; @@ -219,11 +284,11 @@ void render_elements::flush_batches() { sg_apply_pipeline(batch.key.pipeline); sg_apply_bindings(&bind); - sg_apply_uniforms(0, SG_RANGE(projection_matrix)); + sg_apply_uniforms(0, SG_RANGE(projection_matrix_)); // 绘制批次 sg_draw(batch.index_start, batch.index_count, 1); - draw_call_count++; + draw_call_count_++; } } @@ -234,30 +299,30 @@ void render_elements::end_frame() { flush_batches(); } render_elements::~render_elements() { if (!sg_isvalid()) return; - if (vertex_buffer.id != SG_INVALID_ID) { sg_destroy_buffer(vertex_buffer); } - if (index_buffer.id != SG_INVALID_ID) { sg_destroy_buffer(index_buffer); } + if (vertex_buffer_.id != SG_INVALID_ID) { sg_destroy_buffer(vertex_buffer_); } + if (index_buffer_.id != SG_INVALID_ID) { sg_destroy_buffer(index_buffer_); } } void render_elements::create_resources() { constexpr uint32_t default_vertex_buffer_size = 512; // 512kb constexpr uint32_t default_index_buffer_size = 256; // 256kb // 计算默认顶点, 使用default_vertex_buffer_size对齐到sizeof(mirage_vertex_t) - vertex_buffer_capacity = default_vertex_buffer_size * 1024 / sizeof(mirage_vertex_t); - index_buffer_capacity = default_index_buffer_size * 1024 / sizeof(uint32_t); + vertex_buffer_capacity_ = default_vertex_buffer_size * 1024 / sizeof(mirage_vertex_t); + index_buffer_capacity_ = default_index_buffer_size * 1024 / sizeof(uint32_t); // 创建顶点缓冲区 sg_buffer_desc vbuf_desc = {}; - vbuf_desc.size = vertex_buffer_capacity * sizeof(mirage_vertex_t); + vbuf_desc.size = vertex_buffer_capacity_ * sizeof(mirage_vertex_t); vbuf_desc.usage = SG_USAGE_STREAM; vbuf_desc.type = SG_BUFFERTYPE_VERTEXBUFFER; - vertex_buffer = sg_make_buffer(&vbuf_desc); + vertex_buffer_ = sg_make_buffer(&vbuf_desc); // 创建索引缓冲区 sg_buffer_desc ibuf_desc = {}; - ibuf_desc.size = index_buffer_capacity * sizeof(mirage_triangle_t); + ibuf_desc.size = index_buffer_capacity_ * sizeof(mirage_triangle_t); ibuf_desc.usage = SG_USAGE_STREAM; ibuf_desc.type = SG_BUFFERTYPE_INDEXBUFFER; - index_buffer = sg_make_buffer(&ibuf_desc); + index_buffer_ = sg_make_buffer(&ibuf_desc); } void render_elements::load_mirage_pipelines() { @@ -270,5 +335,5 @@ void render_elements::load_mirage_pipelines() { auto rounded_rect_pipeline_desc = get_mirage_rounded_rect_pipeline_desc(rounded_rect_shader, format, 1); - rounded_rect_pipeline = sg_make_pipeline(rounded_rect_pipeline_desc); + rounded_rect_pipeline_ = sg_make_pipeline(rounded_rect_pipeline_desc); } diff --git a/src/core/render_elements.h b/src/core/render_elements.h index 158628c..260fea4 100644 --- a/src/core/render_elements.h +++ b/src/core/render_elements.h @@ -1,5 +1,8 @@ #pragma once +#include + +#include "geometry/layout_transform.h" #include "sokol/sokol_header.h" #include "misc/color.h" #include "misc/mirage_type.h" @@ -66,7 +69,15 @@ public: // 设置当前渲染状态 void set_pipeline(sg_pipeline pipeline); void set_texture(sg_image image); - void set_projection_matrix(const Eigen::Matrix4f& in_matrix) { projection_matrix = in_matrix; } + + Eigen::Matrix4f create_projection_matrix(const Eigen::Vector2i& in_size); + + void init_window_size(const Eigen::Vector2i& in_size); + void update_window_size(const Eigen::Vector2i& in_size); + + void push_transform(const transform2d& in_transform); + void pop_transform(); + const auto& get_current_transform() const { return transform_stack_.top(); } // 添加矩形(自动加入当前批次) void make_rect(const Eigen::Vector2f& in_pos, @@ -77,8 +88,8 @@ public: const mirage_vertex_param_t& in_param_c = {}, const rect_uv& in_uv = {}, float in_rotation_radians = 0.0f, - const Eigen::Vector2f& in_pivot = Eigen::Vector2f(1.0f, 1.0f), - const Eigen::Vector2f& in_scale = Eigen::Vector2f(0.5f, 0.5f)); + const Eigen::Vector2f& in_pivot = Eigen::Vector2f(0.5f, 0.5f), + const Eigen::Vector2f& in_scale = Eigen::Vector2f(1.f, 1.f)); // 添加圆角矩形 void make_rounded_rect(const Eigen::Vector2f& in_pos, @@ -87,8 +98,8 @@ public: const rect_uv& in_uv = {}, const rect_round& in_round = {}, float in_rotation_radians = 0.0f, - const Eigen::Vector2f& in_pivot = Eigen::Vector2f(1.0f, 1.0f), - const Eigen::Vector2f& in_scale = Eigen::Vector2f(0.5f, 0.5f)); + const Eigen::Vector2f& in_pivot = Eigen::Vector2f(0.5f, 0.5f), + const Eigen::Vector2f& in_scale = Eigen::Vector2f(1.f, 1.f)); private: // 根据顶点和索引数据创建一个矩形 @@ -113,31 +124,34 @@ private: void ensure_buffer_capacity(uint32_t vertex_count, uint32_t index_count); // 当前渲染状态 - batch_key current_key{}; + batch_key current_key_{}; // 当前批次索引 - int32_t current_batch_index = -1; + int32_t current_batch_index_ = -1; // 所有待渲染的批次 - std::vector batches; + std::vector batches_; // 顶点和索引数据 - std::vector vertices; - std::vector indices; + std::vector vertices_; + std::vector indices_; // GPU缓冲区 - sg_buffer vertex_buffer{}; - sg_buffer index_buffer{}; + sg_buffer vertex_buffer_{}; + sg_buffer index_buffer_{}; // 缓冲区容量追踪 - uint32_t vertex_buffer_capacity = 0; - uint32_t index_buffer_capacity = 0; + uint32_t vertex_buffer_capacity_ = 0; + uint32_t index_buffer_capacity_ = 0; // 统计信息 - uint32_t draw_call_count = 0; - uint32_t total_triangles = 0; + uint32_t draw_call_count_ = 0; + uint32_t total_triangles_ = 0; - Eigen::Matrix4f projection_matrix; + Eigen::Matrix4f projection_matrix_{ Eigen::Matrix4f::Identity() }; + Eigen::Vector2i window_size_{ 0, 0 }; + // 变换栈 + std::stack transform_stack_; - sg_pipeline rounded_rect_pipeline{}; + sg_pipeline rounded_rect_pipeline_{}; }; diff --git a/src/core/window/android/android_render_window.cpp b/src/core/window/android/android_render_window.cpp index 9cd5e8e..c1d53e9 100644 --- a/src/core/window/android/android_render_window.cpp +++ b/src/core/window/android/android_render_window.cpp @@ -2,6 +2,3 @@ // Created by Administrator on 25-3-1. // #include "core/window/render_window.h" - -#include - diff --git a/src/core/window/ios/ios_render_window.cpp b/src/core/window/ios/ios_render_window.cpp index df3b412..4938f41 100644 --- a/src/core/window/ios/ios_render_window.cpp +++ b/src/core/window/ios/ios_render_window.cpp @@ -3,8 +3,6 @@ // #include "core/window/render_window.h" -#include - void* mirage_window::get_window_handle() const { return glfwGetIOSurface(window); } diff --git a/src/core/window/linux/linux_render_window.cpp b/src/core/window/linux/linux_render_window.cpp index 1d3edda..bfd253e 100644 --- a/src/core/window/linux/linux_render_window.cpp +++ b/src/core/window/linux/linux_render_window.cpp @@ -3,8 +3,6 @@ // #include "core/window/render_window.h" -#include - void* mirage_window::get_window_handle() const { return glfwGetX11Window(window); } diff --git a/src/core/window/mac/mac_render_window.mm b/src/core/window/mac/mac_render_window.mm index cfb3b97..28e6e6e 100644 --- a/src/core/window/mac/mac_render_window.mm +++ b/src/core/window/mac/mac_render_window.mm @@ -3,8 +3,6 @@ // #include "core/window/render_window.h" -#include - void* mirage_window::get_window_handle() const { return glfwGetCocoaWindow(window); } diff --git a/src/core/window/render_window.cpp b/src/core/window/render_window.cpp index a6b52f5..616cce6 100644 --- a/src/core/window/render_window.cpp +++ b/src/core/window/render_window.cpp @@ -1,29 +1,45 @@ #include "render_window.h" -Eigen::Matrix4f mirage_window::create_screen_to_dci_matrix(float in_screen_width, float in_screen_height) { - // 创建一个单位矩阵 - Eigen::Matrix4f matrix = Eigen::Matrix4f::Identity(); +#include "widget/mwidget.h" +#include "widget/compound_widget/mborder.h" - // 缩放因子 - const float scale_x = 2.0f / in_screen_width; - const float scale_y = -2.0f / in_screen_height; // Y轴翻转,因为窗口坐标系Y轴向下 +void mirage_window::on_paint() { + if (!root_widget_) + return; - // 平移因子 - constexpr float translate_x = -1.0f; - constexpr float translate_y = 1.0f; - - // 设置缩放 - matrix(0, 0) = scale_x; - matrix(1, 1) = scale_y; - - // 设置平移 - matrix(0, 3) = translate_x; - matrix(1, 3) = translate_y; - - return matrix; + elements_.begin_frame(); + layout_tree_.paint(elements_); + elements_.end_frame(); } -Eigen::Matrix4f mirage_window::create_screen_to_dci_matrix() const { - const auto size = get_window_frame_size(); - return create_screen_to_dci_matrix(size.x(), size.y()); +geometry_t mirage_window::get_window_geometry_in_screen() const { + auto local_to_screen = get_local_to_screen_transform(); + geometry_t root_geometry(get_window_frame_size().cast(), local_to_screen, transform2d()); + return root_geometry; +} + +void mirage_window::set_content(const std::shared_ptr& in_widget) { + root_widget_->set_content(in_widget); + layout_tree_.set_root(root_widget_); + transform2d identity{}; + geometry_t root_geometry(get_window_frame_size().cast(), identity, identity); + layout_tree_.set_root_geometry(root_geometry); + layout_tree_.build_layout(); +} + +void mirage_window::on_resize(int width, int height) { + const Eigen::Vector2i size(width, height); + state_->swapchain.width = width; + state_->swapchain.height = height; + + state_->resize(size); + elements_.update_window_size(size); + + transform2d identity; + geometry_t new_geometry(size.cast(), identity, identity); + layout_tree_.set_root_geometry(new_geometry); + layout_tree_.invalidate_layout(); +} + +void mirage_window::on_move(int x, int y) { } diff --git a/src/core/window/render_window.h b/src/core/window/render_window.h index 6ae0641..e1aa673 100644 --- a/src/core/window/render_window.h +++ b/src/core/window/render_window.h @@ -2,7 +2,13 @@ #include "windows/windows_render_context.h" #include +#include "geometry/dpi_helper.h" +#include "geometry/geometry.h" +#include "geometry/layout_transform.h" +#include "geometry/widget_layout_tree.h" + class mirage_window; +class mborder; struct mirage_window_state { virtual ~mirage_window_state() { @@ -37,9 +43,8 @@ public: [[nodiscard]] Eigen::Vector2i get_window_size() const; [[nodiscard]] Eigen::Vector2i get_window_position() const; [[nodiscard]] Eigen::Vector2i get_window_frame_size() const; - // 创建从屏幕坐标到DCI坐标的正交投影矩阵 - [[nodiscard]] static Eigen::Matrix4f create_screen_to_dci_matrix(float in_screen_width, float in_screen_height); - [[nodiscard]] Eigen::Matrix4f create_screen_to_dci_matrix() const; + [[nodiscard]] auto& get_render_elements() { return elements_; } + [[nodiscard]] float get_dpi_scale() const; [[nodiscard]] void* get_window_handle() const; @@ -54,14 +59,33 @@ public: mirage_window& set_topmost(bool is_topmost); // end style functions - [[nodiscard]] bool close_requested() const { return close_request; } + [[nodiscard]] bool close_requested() const { return close_request_; } static bool poll_events(); static const std::vector& get_windows(); void on_resize(int width, int height); + void on_move(int x, int y); - std::unique_ptr state; + void set_state(std::unique_ptr&& in_state) { state_ = std::move(in_state); } + [[nodiscard]] auto& get_state() const { return *state_; } + + void on_paint(); + + [[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() }); + } + [[nodiscard]] auto get_local_to_window_transform() const { + return transform2d({}, 0, { dpi_helper::get_global_scale() * get_dpi_scale(), dpi_helper::get_global_scale() * get_dpi_scale() }); + } + [[nodiscard]] geometry_t get_window_geometry_in_screen() const; + + void set_content(const std::shared_ptr& in_widget); private: - void* window_handle{}; - bool close_request = false; + void* window_handle_{}; + bool close_request_ = false; + + render_elements elements_; // 渲染元素 + std::shared_ptr root_widget_; + std::unique_ptr state_; + 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 abf3cc7..28ad2b0 100644 --- a/src/core/window/windows/windows_render_context.cpp +++ b/src/core/window/windows/windows_render_context.cpp @@ -155,12 +155,6 @@ bool windows_mirage_render_context::init() { } } -void windows_mirage_render_context::end_init() { - mirage_render_context::end_init(); - elements.create_resources(); - elements.load_mirage_pipelines(); -} - // 资源清理函数 void windows_mirage_render_context::cleanup() { mirage_render_context::cleanup(); @@ -183,7 +177,7 @@ void windows_mirage_render_context::cleanup() { void windows_mirage_render_context::tick(const duration_type& in_delta) { const auto& windows = mirage_window::get_windows(); for (const auto& window: windows) { - auto& window_state = window->state; + 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; @@ -192,41 +186,16 @@ void windows_mirage_render_context::tick(const duration_type& in_delta) { 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; + pass.swapchain = window_state.swapchain; sg_begin_pass(pass); - sg_apply_viewport(0, 0, window_state->swapchain.width, window_state->swapchain.height, true); + sg_apply_viewport(0, 0, window_state.swapchain.width, window_state.swapchain.height, true); - const auto& matrix = window->create_screen_to_dci_matrix(); - elements.set_projection_matrix(matrix); - - elements.begin_frame(); - - // draw some triangles - Eigen::Vector2f pos{ 0, 0 }; - Eigen::Vector2f pos2{ 200, 200 }; - Eigen::Vector2f size{ 200, 200 }; - elements.make_rounded_rect( - pos, - size, - rect_color({ 1, 0, 0, 1 }, { 0, 1, 1, 1 }), - {}, - rect_round({ 10, 20, 30, 40 }), - 45_deg, - { 0, 0 }, - { 1, 1 } - ); - elements.make_rounded_rect( - pos2, - size, - rect_color({ 1, 1, 0, 1 }) - ); - - elements.end_frame(); + window->on_paint(); sg_end_pass(); sg_commit(); - window_state->present(); + window_state.present(); } } @@ -242,8 +211,7 @@ sg_environment windows_mirage_render_context::get_environment() { bool windows_mirage_render_context::setup_surface(mirage_window* in_window) { auto state = std::make_unique(); if (!state->init(device, dxgi_factory, in_window)) { return false; } - - in_window->state = std::move(state); + in_window->set_state(std::move(state)); return true; } diff --git a/src/core/window/windows/windows_render_context.h b/src/core/window/windows/windows_render_context.h index 01a48b4..4294765 100644 --- a/src/core/window/windows/windows_render_context.h +++ b/src/core/window/windows/windows_render_context.h @@ -11,7 +11,6 @@ public: windows_mirage_render_context() = default; bool init() override; - virtual void end_init() override; void cleanup() override; virtual void tick(const duration_type& in_delta) override; @@ -23,6 +22,4 @@ private: IDXGIFactory* dxgi_factory{}; D3D_FEATURE_LEVEL feature_level; - - render_elements elements; }; diff --git a/src/core/window/windows/windows_render_window.cpp b/src/core/window/windows/windows_render_window.cpp index 9914e60..670d4b5 100644 --- a/src/core/window/windows/windows_render_window.cpp +++ b/src/core/window/windows/windows_render_window.cpp @@ -3,21 +3,28 @@ // #include -#include "../render_window.h" +#include "core/window/render_window.h" #include -#define WINDOW_HANDLE static_cast(window_handle) +#include "widget/compound_widget/mborder.h" + +#define WINDOW_HANDLE static_cast(window_handle_) std::vector windows; +mirage_window* get_window_from_hwnd(HWND hwnd) { + for (const auto& window: windows) { + if (window->get_window_handle() == hwnd) { + return window; + } + } + return nullptr; +} LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_CLOSE: - for (const auto& window: windows) { - if (window->get_window_handle() == hwnd) { - window->close(); - break; - } + if (auto window = get_window_from_hwnd(hwnd)) { + window->close(); } std::erase_if(windows, [hwnd](const mirage_window* window) { return window->get_window_handle() == hwnd; }); return 0; @@ -25,11 +32,13 @@ LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) PostQuitMessage(0); return 0; case WM_SIZE: - for (const auto& window: windows) { - if (window->get_window_handle() == hwnd) { - window->on_resize(LOWORD(lParam), HIWORD(lParam)); - break; - } + if (auto window = get_window_from_hwnd(hwnd)) { + window->on_resize(LOWORD(lParam), HIWORD(lParam)); + } + return 0; + case WM_MOVE: + if (auto window = get_window_from_hwnd(hwnd)) { + window->on_move(LOWORD(lParam), HIWORD(lParam)); } return 0; default: @@ -50,7 +59,7 @@ bool mirage_window::create_window(int width, int height, const wchar_t* title) { RECT rect = { 0, 0, width, height }; AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, FALSE); - window_handle = (void*)CreateWindowW( + window_handle_ = (void*)CreateWindowW( L"mirage_window_class", title, WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, @@ -58,11 +67,18 @@ bool mirage_window::create_window(int width, int height, const wchar_t* title) { NULL, NULL, GetModuleHandleW(NULL), NULL ); - if (!window_handle) { + if (!window_handle_) { std::cerr << "Failed to create window" << std::endl; return false; } + elements_.create_resources(); + elements_.load_mirage_pipelines(); + elements_.init_window_size(Eigen::Vector2i(width, height)); + + root_widget_ = std::make_shared(); + root_widget_->set_window(this); + windows.push_back(this); return true; } @@ -71,7 +87,7 @@ void mirage_window::show() { ShowWindow(WINDOW_HANDLE, SW_SHOW); } void mirage_window::hide() { ShowWindow(WINDOW_HANDLE, SW_HIDE); } void mirage_window::close() { - close_request = true; + close_request_ = true; DestroyWindow(WINDOW_HANDLE); } @@ -82,6 +98,8 @@ void mirage_window::move(int x, int y) { RECT rect; GetWindowRect(WINDOW_HANDLE, &rect); MoveWindow(WINDOW_HANDLE, x, y, rect.right - rect.left, rect.bottom - rect.top, TRUE); + + on_move(x, y); } void mirage_window::resize(int width, int height) { @@ -112,13 +130,17 @@ Eigen::Vector2i mirage_window::get_window_frame_size() const { return Eigen::Vector2i(0, 0); } +float mirage_window::get_dpi_scale() const { + return 1.f; +} + Eigen::Vector2i mirage_window::get_window_position() const { RECT rect; if (!GetWindowRect(WINDOW_HANDLE, &rect)) { return {}; } return { rect.left, rect.top }; } -void* mirage_window::get_window_handle() const { return window_handle; } +void* mirage_window::get_window_handle() const { return window_handle_; } mirage_window& mirage_window::set_title(const wchar_t* title) { SetWindowText(WINDOW_HANDLE, title); @@ -186,9 +208,3 @@ bool mirage_window::poll_events() { } const std::vector& mirage_window::get_windows() { return windows; } - -void mirage_window::on_resize(int width, int height) { - state->swapchain.width = width; - state->swapchain.height = height; - state->resize(Eigen::Vector2i(width, height)); -} diff --git a/src/geometry/arranged_children.cpp b/src/geometry/arranged_children.cpp new file mode 100644 index 0000000..690e5be --- /dev/null +++ b/src/geometry/arranged_children.cpp @@ -0,0 +1,11 @@ +#include "arranged_children.h" + +#include "widget/mwidget.h" + +void arranged_children::add_widget(const arranged_widget& in_widget_geometry) { + add_widget(in_widget_geometry.get_widget()->get_visibility(), in_widget_geometry); +} + +void arranged_children::insert_widget(const arranged_widget& in_widget_geometry, size_t in_index) { + insert_widget(in_widget_geometry.get_widget()->get_visibility(), in_widget_geometry, in_index); +} diff --git a/src/geometry/arranged_children.h b/src/geometry/arranged_children.h new file mode 100644 index 0000000..bcdb8d4 --- /dev/null +++ b/src/geometry/arranged_children.h @@ -0,0 +1,58 @@ +#pragma once +#include + +#include "geometry.h" +#include "misc/mirage_type.h" + +class mwidget; + +class arranged_widget { +public: + arranged_widget(geometry_t in_geometry, std::shared_ptr in_widget) : geometry_(std::move(in_geometry)), + widget_(std::move(in_widget)) { + } + + [[nodiscard]] const auto& get_geometry() const { return geometry_; } + [[nodiscard]] const auto& get_widget() const { return widget_; } + auto operator==(const arranged_widget& in_other) const { + return widget_ == in_other.widget_; + } +private: + geometry_t geometry_; + std::shared_ptr widget_; +}; + +class arranged_children { +public: + explicit arranged_children(visibility in_visibility_filter) : visibility_filter_(in_visibility_filter) {} + + void reverse() { + std::ranges::reverse(children_); + } + + void add_widget(visibility in_visibility_override, const arranged_widget& in_widget_geometry) { + if (accepts(in_visibility_override)) + children_.push_back(in_widget_geometry); + } + + void insert_widget(visibility 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); + } + + void add_widget(const arranged_widget& in_widget_geometry); + void insert_widget(const arranged_widget& in_widget_geometry, size_t in_index); + + [[nodiscard]] auto accepts(visibility in_visibility) const { + return in_visibility >= visibility_filter_; + } + + void set_filter(visibility in_visibility_filter) { + visibility_filter_ = in_visibility_filter; + } + + [[nodiscard]] const auto& get_children() const { return children_; } +private: + visibility visibility_filter_; + std::vector children_; +}; diff --git a/src/geometry/dpi_helper.h b/src/geometry/dpi_helper.h new file mode 100644 index 0000000..6a69a5b --- /dev/null +++ b/src/geometry/dpi_helper.h @@ -0,0 +1,50 @@ +#pragma once +#include + +#include "rect.h" + +class dpi_helper { +public: + static void set_global_scale(float in_scale) { + global_scale_ = in_scale; + } + static auto get_global_scale() { + return global_scale_; + } + + static auto physical_to_logical(float in_physical) { + return in_physical / global_scale_; + } + static auto logical_to_physical(float in_logical) { + return in_logical * global_scale_; + } + template + static auto physical_to_logical(const Eigen::Vector2& in_physical) { + return in_physical / global_scale_; + } + template + static auto logical_to_physical(const Eigen::Vector2& in_logical) { + return in_logical * global_scale_; + } + + static auto snap_to_pixel(float in_logical) { + auto physical_value = logical_to_physical(in_logical); + auto snapped_value = std::round(physical_value); + return physical_to_logical(snapped_value); + } + template + static auto snap_to_pixel(const Eigen::Vector2& in_logical) { + auto x = snap_to_pixel(in_logical.x()); + auto y = snap_to_pixel(in_logical.y()); + return Eigen::Vector2(x, y); + } + + template + static auto snap_rect_to_pixels(const rect_t& in_rect) { + auto top_left = snap_to_pixel(in_rect.top_left()); + auto bottom_right = snap_to_pixel(in_rect.bottom_right()); + return rect_t(top_left, bottom_right - top_left); + } +private: + inline static float global_scale_ = 1.f; +}; diff --git a/src/geometry/geometry.cpp b/src/geometry/geometry.cpp new file mode 100644 index 0000000..2a3bd86 --- /dev/null +++ b/src/geometry/geometry.cpp @@ -0,0 +1 @@ +#include "geometry.h" diff --git a/src/geometry/geometry.h b/src/geometry/geometry.h new file mode 100644 index 0000000..33a0945 --- /dev/null +++ b/src/geometry/geometry.h @@ -0,0 +1,196 @@ +#pragma once +#include +#include + +#include "layout_transform.h" +#include "rect.h" + +class geometry_t { +public: + geometry_t() : size_(), local_to_parent_(), local_to_absolute_() {} + + /** + * 构造具有特定大小和变换的几何体 + * + * @param in_size 组件的本地大小 + * @param in_local_to_parent 本地到父级的变换 + * @param in_parent_to_absolute 父级到绝对坐标的变换(屏幕/窗口坐标) + */ + geometry_t(Eigen::Vector2f in_size, transform2d in_local_to_parent, const transform2d& in_parent_to_absolute) + : size_(std::move(in_size)), local_to_parent_(std::move(in_local_to_parent)) { + local_to_absolute_ = in_parent_to_absolute.concatenate(local_to_parent_); + } + + /** + * 创建一个相对于此几何体定位的子几何体 + * + * @param in_local_offset 相对于父级的位置偏移 + * @param in_child_size 子级的大小 + * @param in_scale 子级的缩放 + * @return 新的子几何体 + */ + [[nodiscard]] auto make_child(const Eigen::Vector2f& in_local_offset, const Eigen::Vector2f& in_child_size, float in_scale = 1.f) const { + // 创建从子级到此几何体的变换 + transform2d child_to_parent; + child_to_parent.set_transform(in_local_offset, 0.0f, Eigen::Vector2f(in_scale, in_scale)); + + // 级联变换以获得子级到绝对空间的变换 + const transform2d local_to_absolute = local_to_absolute_.concatenate(child_to_parent); + + return geometry_t(in_child_size, child_to_parent, local_to_absolute); + } + + /** + * 获取此几何体的本地大小 + */ + [[nodiscard]] const auto& get_local_size() const { return size_; } + + /** + * 获取本地坐标系中的矩形 + */ + [[nodiscard]] auto get_local_rect() const { return rect_t({}, size_); } + + /** + * 获取本地到父级的变换 + */ + [[nodiscard]] const auto& get_local_to_parent_transform() const + { + return local_to_parent_; + } + + /** + * 获取本地到绝对的变换 + */ + [[nodiscard]] const auto& get_local_to_absolute_transform() const + { + return local_to_absolute_; + } + + /** + * 将点从本地坐标转换到绝对坐标 + */ + [[nodiscard]] auto local_to_absolute(const Eigen::Vector2f& in_local_point) const + { + return local_to_absolute_.transform_point(in_local_point); + } + + /** + * 将点从绝对坐标转换到本地坐标 + */ + [[nodiscard]] auto absolute_to_local(const Eigen::Vector2f& in_absolute_point) const + { + return local_to_absolute_.inverse_transform_point(in_absolute_point); + } + + /** + * 将点从本地坐标转换到父级坐标 + */ + [[nodiscard]] auto local_to_parent(const Eigen::Vector2f& in_local_point) const + { + return local_to_parent_.transform_point(in_local_point); + } + + /** + * 将点从父级坐标转换到本地坐标 + */ + [[nodiscard]] auto parent_to_local(const Eigen::Vector2f& in_parent_point) const + { + return local_to_parent_.inverse_transform_point(in_parent_point); + } + + /** + * 获取此几何体在绝对坐标中的位置 + */ + [[nodiscard]] auto get_absolute_position() const + { + return local_to_absolute(Eigen::Vector2f(0, 0)); + } + + /** + * 获取几何体的缩放因子 + */ + [[nodiscard]] auto get_scale() const + { + // 从本地到绝对变换中提取缩放分量 + return local_to_absolute_.get_scale(); + } + + /** + * 获取绝对坐标系中的矩形 + */ + [[nodiscard]] auto get_absolute_rect() const + { + // 变换本地矩形的四个角到绝对坐标 + const auto& top_left = local_to_absolute(Eigen::Vector2f(0, 0)); + const auto& bottom_right = local_to_absolute(size_); + + const Eigen::Vector2f& absolute_size = bottom_right - top_left; + + return rect_t(top_left, absolute_size); + } + + /** + * 检查点是否在此几何体内 + * + * @param in_absolute_point 以绝对坐标表示的点 + * @return 如果点在几何体内,则为true + */ + [[nodiscard]] auto is_under_location(const Eigen::Vector2f& in_absolute_point) const + { + // 将点从绝对坐标转换到本地坐标 + auto local_point = absolute_to_local(in_absolute_point); + + // 检查点是否在本地矩形范围内 + return local_point.x() >= 0 && local_point.x() <= size_.x() && + local_point.y() >= 0 && local_point.y() <= size_.y(); + } + + /** + * 创建一个基于当前几何体但应用偏移的新几何体 + * + * @param in_offset 要应用的偏移 + * @return 新的几何体 + */ + [[nodiscard]] auto with_new_offset(const Eigen::Vector2f& in_offset) const + { + // 创建一个新的本地到父级变换,使用相同的缩放但新的偏移 + transform2d local_to_parent; + const auto& scale = local_to_parent_.get_scale(); + local_to_parent.set_transform(in_offset, 0.0f, scale); + + // 计算新的本地到绝对变换 + const transform2d parent_to_absolute = local_to_absolute_.concatenate(local_to_parent_.inverse()); + + return geometry_t(size_, local_to_parent, parent_to_absolute); + } + + /** + * 创建一个缩放版本的几何体 + * + * @param in_scale_amount 要应用的缩放因子 + * @return 新的几何体 + */ + [[nodiscard]] auto with_scale(float in_scale_amount) const + { + // 获取当前变换的组件 + const auto& current_scale = local_to_parent_.get_scale(); + const auto& current_trans = local_to_parent_.get_translation(); + + // 创建新的本地到父级变换 + transform2d new_local_to_parent; + new_local_to_parent.set_transform( + current_trans, + 0.0f, + Eigen::Vector2f(current_scale.x() * in_scale_amount, current_scale.y() * in_scale_amount) + ); + + // 计算新的本地到绝对变换 + const transform2d parent_to_absolute = local_to_absolute_.concatenate(local_to_parent_.inverse()); + + return geometry_t(size_, new_local_to_parent, parent_to_absolute); + } +private: + Eigen::Vector2f size_; + transform2d local_to_parent_; + transform2d local_to_absolute_; +}; diff --git a/src/geometry/geometry_transformer.h b/src/geometry/geometry_transformer.h new file mode 100644 index 0000000..b5ef92a --- /dev/null +++ b/src/geometry/geometry_transformer.h @@ -0,0 +1,39 @@ +#pragma once +#include +#include + +#include "layout_transform.h" +#include "rect.h" + +class geometry_transformer { +public: + template + static auto transform_points(const transform2d& in_transform, const std::vector>& in_points) { + std::vector> result; + result.reserve(in_points.size()); + + for (const auto& point : in_points) { + result.push_back(in_transform.transform_point(point)); + } + return result; + } + + template + static auto transform_rect(const transform2d& in_transform, const rect_t& in_rect) { + // 变换矩形的四个顶点 + auto top_left = in_transform.transform_point(in_rect.top_left()); + auto top_right = in_transform.transform_point(in_rect.top_right()); + auto bottom_left = in_transform.transform_point(in_rect.bottom_left()); + auto bottom_right = in_transform.transform_point(in_rect.bottom_right()); + + // 如果只有平移和缩放(没有旋转),则可以直接计算包围盒 + if (std::abs(in_transform.get_matrix()(0, 1)) < 1e-6f && + std::abs(in_transform.get_matrix()(1, 0)) < 1e-6f + ) { + auto min = top_left.cwiseMin(top_right).cwiseMin(bottom_left).cwiseMin(bottom_right); + auto max = top_left.cwiseMax(top_right).cwiseMax(bottom_left).cwiseMax(bottom_right); + return rect_t(min, max - min); + } + return rect_t::bounding_box({top_left, top_right, bottom_left, bottom_right}); + } +}; diff --git a/src/geometry/layout_transform.cpp b/src/geometry/layout_transform.cpp new file mode 100644 index 0000000..4a3ee86 --- /dev/null +++ b/src/geometry/layout_transform.cpp @@ -0,0 +1,55 @@ +#include "layout_transform.h" + +transform2d::transform2d(const Eigen::Vector2f& in_translation, float in_rotation, const Eigen::Vector2f& in_scale): translation_(in_translation), scale_(in_scale), rotation_(in_rotation) { + update_matrix(); +} + +void transform2d::set_transform(const Eigen::Vector2f& in_translation, float in_rotation, + const Eigen::Vector2f& in_scale) { + translation_ = in_translation; + rotation_ = in_rotation; + scale_ = in_scale; + + update_matrix(); +} + +void transform2d::translate(const Eigen::Vector2f& in_delta) { + translation_ += in_delta; + update_matrix(); +} + +void transform2d::rotate(float in_angle) { + rotation_ += in_angle; + update_matrix(); +} + +void transform2d::scale(const Eigen::Vector2f& in_scale) { + scale_.array() *= in_scale.array(); + update_matrix(); +} + +void transform2d::update_matrix() { + // 构建变换矩阵: 平移 * 旋转 * 缩放 + const float con_theta = std::cos(rotation_); + const float sin_theta = std::sin(rotation_); + + // 缩放矩阵 + Eigen::Matrix3f s = Eigen::Matrix3f::Identity(); + s(0, 0) = scale_.x(); + s(1, 1) = scale_.y(); + + // 旋转矩阵 + Eigen::Matrix3f r = Eigen::Matrix3f::Identity(); + r(0, 0) = con_theta; + r(0, 1) = -sin_theta; + r(1, 0) = sin_theta; + r(1, 1) = con_theta; + + // 平移矩阵 + Eigen::Matrix3f t = Eigen::Matrix3f::Identity(); + t(0, 2) = translation_.x(); + t(1, 2) = translation_.y(); + + // 更新变换矩阵 + matrix_ = t * r * s; +} diff --git a/src/geometry/layout_transform.h b/src/geometry/layout_transform.h new file mode 100644 index 0000000..fcc2c35 --- /dev/null +++ b/src/geometry/layout_transform.h @@ -0,0 +1,130 @@ +#pragma once +#include + +class transform2d { +public: + transform2d() : matrix_(Eigen::Matrix3f::Identity()), + translation_({ 0, 0 }), + scale_({ 1, 1 }), + rotation_(0) { + } + + transform2d(const Eigen::Vector2f& in_translation, float in_rotation, const Eigen::Vector2f& in_scale); + + void set_transform(const Eigen::Vector2f& in_translation, float in_rotation = 0.f, const Eigen::Vector2f& in_scale = {}); + void translate(const Eigen::Vector2f& in_delta); + void rotate(float in_angle); + void scale(const Eigen::Vector2f& in_scale); + [[nodiscard]] const auto& get_scale() const { return scale_; } + [[nodiscard]] const auto& get_translation() const { return translation_; } + + template + auto transform_point(const Eigen::Vector2& in_point) const { + Eigen::Vector3 homogeneous_point(in_point.x(), in_point.y(), 1); + auto transformed = matrix_.cast() * homogeneous_point; + return Eigen::Vector2( + transformed.x() / transformed.z(), + transformed.y() / transformed.z() + ); + } + + template + auto inverse_transform_point(const Eigen::Vector2& in_point) const { + auto inverse = matrix_.inverse(); + Eigen::Vector3 homogeneous_point(in_point.x(), in_point.y(), 1); + auto transformed = inverse.cast() * homogeneous_point; + return Eigen::Vector2( + transformed.x() / transformed.z(), + transformed.y() / transformed.z() + ); + } + + template + auto transform_vector(const Eigen::Vector2& in_vector) const { + Eigen::Vector3 homogeneous_vector(in_vector.x(), in_vector.y(), 0); + auto transformed = matrix_.cast() * homogeneous_vector; + return Eigen::Vector2(transformed.x(), transformed.y()); + } + + template + auto inverse_transform_vector(const Eigen::Vector2& in_vector) const { + auto inverse = matrix_.inverse(); + Eigen::Vector3 homogeneous_vector(in_vector.x(), in_vector.y(), 0); + auto transformed = inverse.cast() * homogeneous_vector; + return Eigen::Vector2(transformed.x(), transformed.y()); + } + + [[nodiscard]] auto combine(const transform2d& in_other) const { + transform2d result; + result.matrix_ = matrix_ * in_other.matrix_; + return result; + } + + [[nodiscard]] auto inverse() const { + transform2d result; + // 逆变换的缩放是原缩放的倒数 + result.scale_ = Eigen::Vector2f(1.0f / scale_.x(), 1.0f / scale_.y()); + + // 逆变换的平移需要考虑缩放 + result.translation_ = Eigen::Vector2f( + -translation_.x() / scale_.x(), + -translation_.y() / scale_.y() + ); + + // 逆变换的旋转是原旋转的负值 + result.rotation_ = -rotation_; + + result.update_matrix(); + return result; + } + + [[nodiscard]] auto is_identity() const { + return matrix_.isIdentity(); + } + + /** + * 连接两个布局变换,实现与UnrealEngine的FSlateLayoutTransform::Concatenate相同的功能。 + * + * 这个函数通过先应用LHS变换然后应用RHS变换来工作。 + * 用矩阵形式表示如下: + * [ Sa 0 0 ] [ Sb 0 0 ] + * [ 0 Sa 0 ] * [ 0 Sb 0 ] + * [ Tax Tay 1 ] [ Tbx Tby 1 ] + * + * @param rhs 右侧变换 + * @return 两个变换的组合结果 + */ + [[nodiscard]] auto concatenate(const transform2d& rhs) const + { + // 新的缩放是两个缩放的元素级乘积 + const Eigen::Vector2f new_scale(scale_.x() * rhs.scale_.x(), + scale_.y() * rhs.scale_.y()); + + // 新的平移是当前平移通过RHS变换后的结果 + // 等价于 RHS.TransformPoint(Translation) + const Eigen::Vector2f new_translation(translation_.x() * rhs.scale_.x() + rhs.translation_.x(), + translation_.y() * rhs.scale_.y() + rhs.translation_.y()); + + // 创建并返回具有这些值的新变换 + transform2d result; + result.scale_ = new_scale; + result.translation_ = new_translation; + + // 布局变换中不考虑旋转 + result.rotation_ = 0.0f; + + // 更新内部矩阵 + result.update_matrix(); + + return result; + } + + [[nodiscard]] const auto& get_matrix() const { return matrix_; } +private: + void update_matrix(); + + Eigen::Matrix3f matrix_; + Eigen::Vector2f translation_; + Eigen::Vector2f scale_; + float rotation_; // 弧度 +}; diff --git a/src/geometry/margin.h b/src/geometry/margin.h new file mode 100644 index 0000000..8f10d57 --- /dev/null +++ b/src/geometry/margin.h @@ -0,0 +1,24 @@ +#pragma once +#include + +struct margin_t { + float left = 0; + float right = 0; + float top = 0; + float bottom = 0; + + margin_t() = default; + margin_t(float in_uniform) : left(in_uniform), right(in_uniform), top(in_uniform), bottom(in_uniform) {} + margin_t(float in_horizontal, float in_vertical) : left(in_horizontal), right(in_horizontal), top(in_vertical), bottom(in_vertical) {} + margin_t(float in_left, float in_right, float in_top, float in_bottom) : left(in_left), right(in_right), top(in_top), bottom(in_bottom) {} + + static auto all(float in_uniform) { return margin_t(in_uniform); } + static auto symmetric(float in_horizontal, float in_vertical) { return margin_t(in_horizontal, in_vertical); } + + auto is_zero() const { + return left == 0 && right == 0 && top == 0 && bottom == 0; + } + auto get_total_spacing() const { + return Eigen::Vector2f(left + right, top + bottom); + } +}; diff --git a/src/geometry/rect.h b/src/geometry/rect.h new file mode 100644 index 0000000..f1799a4 --- /dev/null +++ b/src/geometry/rect.h @@ -0,0 +1,916 @@ +#pragma once +#include +#include +#include +#include +#include + +#include "margin.h" +#include "misc/mirage_type.h" + +/** + * @brief 表示二维空间中的矩形 + * + * 矩形由左上角位置(position_)和大小(size_)表示。 + * 支持各种几何操作如平移、缩放、合并等。 + * + * @tparam T 坐标和尺寸的数据类型,默认为float + */ +template +class rect_t { +public: + /** @brief 默认构造函数 */ + rect_t() = default; + + /** + * @brief 使用位置和大小构造矩形 + * @param in_position 左上角位置 + * @param in_size 矩形大小 + */ + rect_t(const Eigen::Vector2& in_position, const Eigen::Vector2& in_size) : + position_(in_position), size_(in_size) { + } + + /** + * @brief 从两个点构造矩形 + * @param in_point1 第一个点 + * @param in_point2 第二个点 + * @return 新构造的矩形 + */ + static auto from_two_points(const Eigen::Vector2& in_point1, const Eigen::Vector2& in_point2) { + auto min = in_point1.cwiseMin(in_point2); + auto max = in_point1.cwiseMax(in_point2); + return rect_t(min, max - min); + } + + /** + * @brief 从中心点和大小构造矩形 + * @param in_center 中心点 + * @param in_size 矩形大小 + * @return 新构造的矩形 + */ + static auto from_center_and_size(const Eigen::Vector2& in_center, const Eigen::Vector2& in_size) { + return rect_t(in_center - in_size / 2, in_size); + } + + /** + * @brief 从物理像素矩形转换 + * @param in_physical_rect 物理像素矩形 + * @param in_dpi_scale DPI比例 + * @return 转换后的矩形 + */ + static auto from_physical_pixels(const rect_t& in_physical_rect, float in_dpi_scale) { + if (in_dpi_scale == 1.0f) { + return in_physical_rect; + } + return rect_t(in_physical_rect.position_ / in_dpi_scale, in_physical_rect.size() / in_dpi_scale); + } + + /** + * @brief 创建空矩形 + * @return 位置和大小均为零的矩形 + */ + static auto empty() { + return rect_t(Eigen::Vector2(0, 0), Eigen::Vector2(0, 0)); + } + + /** + * @brief 从大小创建矩形 + * @param in_size 矩形大小 + * @return 位置为原点,指定大小的矩形 + */ + static auto from_size(const Eigen::Vector2& in_size) { + return rect_t(Eigen::Vector2(0, 0), in_size); + } + + /** + * @brief 创建单位矩形 + * @return 位置为原点,大小为1的矩形 + */ + static auto unit() { + return rect_t(Eigen::Vector2(0, 0), Eigen::Vector2(1, 1)); + } + + /** + * @brief 创建具有指定宽高比和面积的矩形 + * @param in_aspect_ratio 宽高比(宽/高) + * @param in_area 面积,默认为1 + * @return 具有指定宽高比和面积的矩形 + */ + static auto with_aspect_ratio(float in_aspect_ratio, float in_area = 1.f) { + if (in_aspect_ratio <= 0) { + return empty(); + } + auto width = std::sqrt(in_area * in_aspect_ratio); + auto height = width / in_aspect_ratio; + + return rect_t(Eigen::Vector2(0, 0), Eigen::Vector2(width, height)); + } + + /** + * @brief 计算一组点的包围盒 + * @param in_points 点的集合 + * @return 包含所有点的最小矩形 + */ + template + static auto bounding_box(const std::vector>& in_points) { + if (in_points.empty()) { + return empty(); + } + auto min = in_points[0]; + auto max = in_points[0]; + for (const auto& point : in_points) { + min = min.cwiseMin(point); + max = max.cwiseMax(point); + } + return rect_t(Eigen::Vector2(min), Eigen::Vector2(max - min)); + } + + /** + * @brief 转换矩形的数据类型 + * @tparam U 目标数据类型 + * @return 转换后的矩形 + */ + template + auto cast() const { + return rect_t(position_.template cast(), size_.template cast()); + } + + /** + * @brief 返回规范化的矩形(保证宽高为正) + * @return 规范化后的矩形 + */ + rect_t normalized() const { + auto min = position_.cwiseMin(position_ + size_); + auto max = position_.cwiseMax(position_ + size_); + return rect_t(min, max - min); + } + + /** + * @brief 原地规范化矩形(保证宽高为正) + */ + void normalize() { + auto min = position_.cwiseMin(position_ + size_); + auto max = position_.cwiseMax(position_ + size_); + position_ = min; + size_ = max - min; + } + + /** + * @brief 返回平移后的矩形 + * @param in_offset 偏移量 + * @return 平移后的矩形 + */ + auto translated(const Eigen::Vector2& in_offset) const { + return rect_t(position_ + in_offset, size_); + } + + /** + * @brief 返回调整大小后的矩形(保持左上角位置) + * @param in_size 新大小 + * @return 调整大小后的矩形 + */ + auto resized(const Eigen::Vector2& in_size) const { + return rect_t(position_, in_size); + } + + /** + * @brief 返回从中心调整大小后的矩形 + * @param in_size 新大小 + * @return 调整大小后的矩形 + */ + auto resized_from_center(const Eigen::Vector2& in_size) const { + return rect_t(position_ + (size_ - in_size) / 2, in_size); + } + + /** + * @brief 返回在四个方向上膨胀后的矩形 + * @param in_offset 膨胀量 + * @return 膨胀后的矩形 + */ + auto inflated(T in_offset) const { + return rect_t(position_ - Eigen::Vector2(in_offset, in_offset), + size_ + Eigen::Vector2(in_offset * 2, in_offset * 2)); + } + + /** + * @brief 返回在水平和垂直方向上膨胀后的矩形 + * @param in_horizontal 水平方向膨胀量 + * @param in_vertical 垂直方向膨胀量 + * @return 膨胀后的矩形 + */ + auto inflated(T in_horizontal, T in_vertical) const { + return rect_t(position_ - Eigen::Vector2(in_horizontal, in_vertical), + size_ + Eigen::Vector2(in_horizontal * 2, in_vertical * 2)); + } + + /** + * @brief 返回在四个方向上收缩后的矩形 + * @param in_offset 收缩量 + * @return 收缩后的矩形 + */ + auto deflated(T in_offset) const { + return inflated(-in_offset); + } + + /** + * @brief 返回在水平和垂直方向上收缩后的矩形 + * @param in_horizontal 水平方向收缩量 + * @param in_vertical 垂直方向收缩量 + * @return 收缩后的矩形 + */ + auto deflated(T in_horizontal, T in_vertical) const { + return inflated(-in_horizontal, -in_vertical); + } + + /** + * @brief 原地膨胀矩形 + * @param in_offset 膨胀量 + */ + void inflate(T in_offset) { + position_ -= Eigen::Vector2(in_offset, in_offset); + size_ += Eigen::Vector2(in_offset * 2, in_offset * 2); + } + + /** + * @brief 原地在水平和垂直方向上膨胀矩形 + * @param in_horizontal 水平方向膨胀量 + * @param in_vertical 垂直方向膨胀量 + */ + void inflate(T in_horizontal, T in_vertical) { + position_ -= Eigen::Vector2(in_horizontal, in_vertical); + size_ += Eigen::Vector2(in_horizontal * 2, in_vertical * 2); + } + + /** + * @brief 原地收缩矩形 + * @param in_offset 收缩量 + */ + void deflate(T in_offset) { inflate(-in_offset); } + + /** + * @brief 原地在水平和垂直方向上收缩矩形 + * @param in_horizontal 水平方向收缩量 + * @param in_vertical 垂直方向收缩量 + */ + void deflate(T in_horizontal, T in_vertical) { inflate(-in_horizontal, -in_vertical); } + + /** + * @brief 设置左边界 + * @param in_left 左边界位置 + */ + void set_left(T in_left) { position_.x() = in_left; } + + /** + * @brief 设置右边界 + * @param in_right 右边界位置 + */ + void set_right(T in_right) { position_.x() = in_right - size_.x(); } + + /** + * @brief 设置上边界 + * @param in_top 上边界位置 + */ + void set_top(T in_top) { position_.y() = in_top; } + + /** + * @brief 设置下边界 + * @param in_bottom 下边界位置 + */ + void set_bottom(T in_bottom) { position_.y() = in_bottom - size_.y(); } + + /** + * @brief 设置矩形大小 + * @param in_size 新大小 + */ + void set_size(const Eigen::Vector2& in_size) { size_ = in_size; } + + /** + * @brief 设置矩形位置 + * @param in_position 新位置 + */ + void set_position(const Eigen::Vector2& in_position) { position_ = in_position; } + + /** + * @brief 设置矩形中心点 + * @param in_center 新中心点 + */ + void set_center(const Eigen::Vector2& in_center) { position_ = in_center - size_ / 2; } + + /** + * @brief 获取位置 + * @return 矩形位置 + */ + const auto& position() const { return position_; } + + /** + * @brief 获取大小 + * @return 矩形大小 + */ + const auto& size() const { return size_; } + + /** + * @brief 获取左边界 + * @return 左边界位置 + */ + auto left() const { return position_.x(); } + + /** + * @brief 获取右边界 + * @return 右边界位置 + */ + auto right() const { return position_.x() + size_.x(); } + + /** + * @brief 获取上边界 + * @return 上边界位置 + */ + auto top() const { return position_.y(); } + + /** + * @brief 获取下边界 + * @return 下边界位置 + */ + auto bottom() const { return position_.y() + size_.y(); } + + /** + * @brief 获取宽度 + * @return 矩形宽度 + */ + auto width() const { return size_.x(); } + + /** + * @brief 获取高度 + * @return 矩形高度 + */ + auto height() const { return size_.y(); } + + /** + * @brief 获取左上角点 + * @return 左上角点 + */ + auto top_left() const { return position_; } + + /** + * @brief 获取右上角点 + * @return 右上角点 + */ + auto top_right() const { return position_ + Eigen::Vector2(size_.x(), 0); } + + /** + * @brief 获取左下角点 + * @return 左下角点 + */ + auto bottom_left() const { return position_ + Eigen::Vector2(0, size_.y()); } + + /** + * @brief 获取右下角点 + * @return 右下角点 + */ + auto bottom_right() const { return position_ + size_; } + + /** + * @brief 获取中心点 + * @return 中心点 + */ + auto center() const { return position_ + size_ / 2; } + + /** + * @brief 检查矩形是否有效(宽高均为正) + * @return 若矩形有效则为true,否则为false + */ + auto is_valid() const { return size_.x() > 0 && size_.y() > 0; } + + /** + * @brief 检查矩形是否为空(宽或高不为正) + * @return 若矩形为空则为true,否则为false + */ + auto is_empty() const { return size_.x() <= 0 || size_.y() <= 0; } + + /** + * @brief 计算矩形面积 + * @return 矩形面积 + */ + auto area() const { return size_.x() * size_.y(); } + + /** + * @brief 计算矩形周长 + * @return 矩形周长 + */ + auto perimeter() const { return 2 * (size_.x() + size_.y()); } + + /** + * @brief 计算矩形宽高比 + * @return 宽度/高度比值 + */ + auto aspect_ratio() const { return size_.x() / size_.y(); } + + /** + * @brief 检查矩形是否包含指定点 + * @tparam U 点的坐标类型 + * @param in_point 检查的点 + * @return 若包含则为true,否则为false + */ + template + auto contains(const Eigen::Vector2& in_point) const { + return in_point.x() >= left() && in_point.x() <= right() && + in_point.y() >= top() && in_point.y() <= bottom(); + } + + /** + * @brief 检查矩形是否完全包含另一个矩形 + * @tparam U 另一个矩形的类型 + * @param in_rect 检查的矩形 + * @return 若完全包含则为true,否则为false + */ + template + auto contains(const rect_t& in_rect) const { + return in_rect.left() >= left() && in_rect.right() <= right() && + in_rect.top() >= top() && in_rect.bottom() <= bottom(); + } + + /** + * @brief 检查矩形是否与另一个矩形相交 + * @tparam U 另一个矩形的类型 + * @param in_rect 检查的矩形 + * @return 若相交则为true,否则为false + */ + template + auto intersects(const rect_t& in_rect) const { + return left() < in_rect.right() && right() > in_rect.left() && + top() < in_rect.bottom() && bottom() > in_rect.top(); + } + + /** + * @brief 计算与另一个矩形的并集 + * @tparam U 另一个矩形的类型 + * @param in_rect 另一个矩形 + * @return 包含两个矩形的最小矩形 + */ + template + auto union_with(const rect_t& in_rect) const { + auto min = position_.cwiseMin(in_rect.position_); + auto max = (position_ + size_).cwiseMax(in_rect.position_ + in_rect.size_); + return rect_t(min, max - min); + } + + /** + * @brief 计算与另一个矩形的交集 + * @tparam U 另一个矩形的类型 + * @param in_rect 另一个矩形 + * @return 两个矩形的重叠部分 + */ + template + auto overlap_with(const rect_t& in_rect) const { + auto min = position_.cwiseMax(in_rect.position_); + auto max = (position_ + size_).cwiseMin(in_rect.position_ + in_rect.size_); + return rect_t(min, max - min); + } + + /** + * @brief 计算与另一个矩形的交并比(IOU) + * @tparam U 另一个矩形的类型 + * @param in_rect 另一个矩形 + * @return 交集面积 / 并集面积 + */ + template + auto overlap_ratio(const rect_t& in_rect) const { + auto overlap = overlap_with(in_rect); + return overlap.area() / (area() + in_rect.area() - overlap.area()); + } + + /** + * @brief 根据对齐方式调整内部矩形的位置 + * @tparam U 内部矩形的类型 + * @param in_inner 内部矩形 + * @param in_h_align 水平对齐方式 + * @param in_v_align 垂直对齐方式 + * @param padding 内边距 + * @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 { + U x{}; + U y{}; + U width = in_inner.width(); + U height = in_inner.height(); + + // 水平对齐 + switch (in_h_align) { + case horizontal_alignment::left: + x = left() + padding.x(); + break; + case horizontal_alignment::center: + x = left() + (this->width() - width) / 2; + break; + case horizontal_alignment::right: + x = right() - width - padding.x(); + break; + case horizontal_alignment::stretch: + x = left() + padding.x(); + width = std::max(this->width() - padding.x() * 2, U{}); + break; + } + + // 垂直对齐 + switch (in_v_align) { + case vertical_alignment::top: + y = top() + padding.y(); + break; + case vertical_alignment::center: + y = top() + (this->height() - height) / 2; + break; + case vertical_alignment::bottom: + y = bottom() - height - padding.y(); + break; + case vertical_alignment::stretch: + y = top() + padding.y(); + height = std::max(this->height() - padding.y() * 2, U{}); + break; + } + + return rect_t({ x, y }, { width, height }); + } + + /** + * @brief 应用边距 + * @param in_margin 边距 + * @return 应用边距后的矩形 + */ + auto apply_margin(const margin_t& in_margin) const { + return rect_t(position_ + Eigen::Vector2(in_margin.left, in_margin.top), + size_ - Eigen::Vector2(in_margin.left + in_margin.right, in_margin.top + in_margin.bottom)); + } + + /** + * @brief 根据文本对齐方式获取文本矩形 + * @tparam U 文本大小的类型 + * @param in_h_align 水平文本对齐方式 + * @param in_v_align 垂直文本对齐方式 + * @param in_text_size 文本大小 + * @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 { + rect_t text_rect{{}, in_text_size}; + + switch (in_h_align) { + case horizontal_text_alignment::left: + text_rect.position_.x() = left(); + break; + case horizontal_text_alignment::center: + text_rect.position_.x() = left() + (width() - text_rect.width()) / 2; + break; + case horizontal_text_alignment::right: + text_rect.position_.x() = right() - text_rect.width(); + break; + } + + switch (in_v_align) { + case vertical_text_alignment::top: + text_rect.position_.y() = top(); + break; + case vertical_text_alignment::center: + text_rect.position_.y() = top() + (height() - text_rect.height()) / 2; + break; + case vertical_text_alignment::bottom: + text_rect.position_.y() = bottom() - text_rect.height(); + break; + } + + return text_rect; + } + + /** + * @brief 将矩形裁剪到指定矩形内 + * @tparam U 裁剪矩形的类型 + * @param in_clip_rect 裁剪矩形 + * @return 裁剪后的矩形 + */ + template + auto clip_to(const rect_t& in_clip_rect) const { + auto min = position_.cwiseMax(in_clip_rect.position_); + auto max = (position_ + size_).cwiseMin(in_clip_rect.position_ + in_clip_rect.size_); + return rect_t(min, max - min); + } + + /** + * @brief 水平分割矩形 + * @param in_proportions 分割比例数组 + * @param in_spacing 间距 + * @return 分割后的矩形数组 + */ + auto divide_horizontally(const std::vector& in_proportions, float in_spacing = 0.f) const { + std::vector result; + + if (in_proportions.empty()) { + return result; + } + + float total_proportion = 0.f; + for (auto proportion : in_proportions) { + total_proportion += proportion; + } + if (total_proportion < std::numeric_limits::epsilon()) { + return result; + } + + float total_spacing = in_spacing * (in_proportions.size() - 1); + float available_width = width() - total_spacing; + + result.reserve(in_proportions.size()); + + float x = left(); + for (const float prop : in_proportions) { + if (prop < 0.f) { + result.emplace_back(rect_t()); + continue; + } + + float part_width = available_width * (prop / total_proportion); + result.emplace_back(rect_t({ x, top() }, { part_width, height() })); + x += part_width + in_spacing; + } + return result; + } + + /** + * @brief 垂直分割矩形 + * @param in_proportions 分割比例数组 + * @param in_spacing 间距 + * @return 分割后的矩形数组 + */ + auto divide_vertically(const std::vector& in_proportions, T in_spacing = 0.f) const { + std::vector result; + + if (in_proportions.empty()) { + return result; + } + + float total_proportion = 0.f; + for (auto proportion : in_proportions) { + total_proportion += proportion; + } + if (total_proportion < std::numeric_limits::epsilon()) { + return result; + } + + float total_spacing = in_spacing * (in_proportions.size() - 1); + float available_height = height() - total_spacing; + + result.reserve(in_proportions.size()); + + float y = top(); + for (const float prop : in_proportions) { + if (prop < 0.f) { + result.emplace_back(rect_t()); + continue; + } + + float part_height = available_height * (prop / total_proportion); + result.emplace_back(rect_t({ left(), y }, { width(), part_height })); + y += part_height + in_spacing; + } + return result; + } + + /** + * @brief 创建网格 + * @param in_columns 列数 + * @param in_rows 行数 + * @param in_spacing 间距 + * @return 网格矩形数组 + */ + auto create_grid(int32_t in_columns, int32_t in_rows, const Eigen::Vector2& in_spacing) const { + std::vector grid; + if (in_columns <= 0 || in_rows <= 0) { + return grid; + } + + float total_horizontal_spacing = in_spacing.x() * (in_columns - 1); + float total_vertical_spacing = in_spacing.y() * (in_rows - 1); + + float cell_width = (width() - total_horizontal_spacing) / in_columns; + float cell_height = (height() - total_vertical_spacing) / in_rows; + + grid.reserve(in_columns * in_rows); + + for (int row = 0; row < in_rows; ++row) { + for (int col = 0; col < in_columns; ++col) { + float cell_x = left() + col * (cell_width + in_spacing.x()); + float cell_y = top() + row * (cell_height + in_spacing.y()); + + grid.emplace_back(rect_t({ cell_x, cell_y }, { cell_width, cell_height })); + } + } + return grid; + } + + /** + * @brief 创建内嵌矩形 + * @param in_left 左边距 + * @param in_top 上边距 + * @param in_right 右边距 + * @param in_bottom 下边距 + * @return 内嵌矩形 + */ + auto inset(T in_left, T in_top, T in_right, T in_bottom) const { + return rect_t(position_ + Eigen::Vector2(in_left, in_top), + size_ - Eigen::Vector2(in_left + in_right, in_top + in_bottom)); + } + + /** + * @brief 创建均匀内嵌矩形 + * @param in_amount 内嵌量 + * @return 内嵌矩形 + */ + auto inset(T in_amount) const { + return inset(in_amount, in_amount, in_amount, in_amount); + } + + /** + * @brief 获取矩形的四条边 + * @param in_thickness 边的厚度 + * @return 四条边的矩形数组(上、下、左、右) + */ + auto get_edges(float in_thickness) const { + return std::array{ + rect_t({ left(), top() }, { width(), in_thickness }), // 上边 + rect_t({ left(), bottom() - in_thickness }, { width(), in_thickness }), // 下边 + rect_t({ left(), top() }, { in_thickness, height() }), // 左边 + rect_t({ right() - in_thickness, top() }, { in_thickness, height() }) // 右边 + }; + } + + /** + * @brief 将矩形坐标对齐到整数像素 + * @return 对齐后的矩形 + */ + auto snap_to_pixels() const { + return rect_t(position_.template cast().template cast(), + size_.template cast().template cast()); + } + + /** + * @brief 根据DPI缩放矩形 + * @param in_dpi_scale DPI缩放系数 + * @return 缩放后的矩形 + */ + auto scaled_by_dpi(float in_dpi_scale) const { + return rect_t(position_ * in_dpi_scale, size_ * in_dpi_scale); + } + + /** + * @brief 将矩形转换为字符串表示 + * @return 矩形的字符串表示 + */ + auto to_string() const { + std::stringstream ss; + ss << "rect_t(" << position_.x() << ", " << position_.y() << ", " << size_.x() << ", " << size_.y() << ")"; + return ss.str(); + } + + /** + * @brief 将矩形转换为详细的调试字符串 + * @return 矩形的详细调试字符串 + */ + auto to_debug_string() const { + std::stringstream ss; + ss << "rect{ position: " << position_.x() << ", " << position_.y() << + ", size: " << size_.x() << ", " << size_.y() << + ", bounds: [" << left() << ", " << top() << ", " << right() << ", " << bottom() << " ]}"; + return ss.str(); + } + + /** + * @brief 比较两个矩形是否相等 + * @tparam U 另一个矩形的类型 + * @param in_other 另一个矩形 + * @return 相等为true,不相等为false + */ + template + auto operator==(const rect_t& in_other) const { + return position_ == in_other.position_ && size_ == in_other.size_; + } + + /** + * @brief 比较两个矩形是否不相等 + * @tparam U 另一个矩形的类型 + * @param in_other 另一个矩形 + * @return 不相等为true,相等为false + */ + template + auto operator!=(const rect_t& in_other) const { + return !(*this == in_other); + } + + /** + * @brief 矩形平移运算符 + * @tparam U 偏移向量的类型 + * @param in_offset 偏移向量 + * @return 平移后的矩形 + */ + template + auto operator+(const Eigen::Vector2& in_offset) const { + return translated(in_offset); + } + + /** + * @brief 矩形反方向平移运算符 + * @tparam U 偏移向量的类型 + * @param in_offset 偏移向量 + * @return 平移后的矩形 + */ + template + auto operator-(const Eigen::Vector2& in_offset) const { + return translated(-in_offset); + } + + /** + * @brief 矩形自增平移运算符 + * @tparam U 偏移向量的类型 + * @param in_offset 偏移向量 + * @return 平移后的矩形引用 + */ + template + auto& operator+=(const Eigen::Vector2& in_offset) { + position_ += in_offset; + return *this; + } + + /** + * @brief 矩形自减平移运算符 + * @tparam U 偏移向量的类型 + * @param in_offset 偏移向量 + * @return 平移后的矩形引用 + */ + template + auto& operator-=(const Eigen::Vector2& in_offset) { + position_ -= in_offset; + return *this; + } + + /** + * @brief 矩形缩放运算符 + * @param in_scale 缩放系数 + * @return 缩放后的矩形 + */ + auto operator*(T in_scale) const { + return rect_t(position_ * in_scale, size_ * in_scale); + } + + /** + * @brief 矩形缩小运算符 + * @param in_scale 缩小系数 + * @return 缩小后的矩形 + */ + auto operator/(T in_scale) const { + if (std::abs(in_scale) < std::numeric_limits::epsilon()) { + return rect_t(); + } + return rect_t(position_ / in_scale, size_ / in_scale); + } + + /** + * @brief 矩形自乘缩放运算符 + * @param in_scale 缩放系数 + * @return 缩放后的矩形引用 + */ + auto& operator*=(T in_scale) { + position_ *= in_scale; + size_ *= in_scale; + return *this; + } + + /** + * @brief 矩形自除缩小运算符 + * @param in_scale 缩小系数 + * @return 缩小后的矩形引用 + */ + auto& operator/=(T in_scale) { + if (std::abs(in_scale) < std::numeric_limits::epsilon()) { + position_ = Eigen::Vector2::Zero(); + size_ = Eigen::Vector2::Zero(); + } else { + position_ /= in_scale; + size_ /= in_scale; + } + return *this; + } + +private: + Eigen::Vector2 position_; ///< 矩形左上角位置 + Eigen::Vector2 size_; ///< 矩形大小(宽和高) +}; + +/** + * @brief 矩形输出流运算符 + * @tparam U 矩形的类型 + * @param in_stream 输出流 + * @param in_rect 矩形 + * @return 输出流引用 + */ +template +auto& operator<<(std::ostream& in_stream, const rect_t& in_rect) { + return in_stream << in_rect.to_string(); +} diff --git a/src/geometry/widget_layout_tree.cpp b/src/geometry/widget_layout_tree.cpp new file mode 100644 index 0000000..f29e53a --- /dev/null +++ b/src/geometry/widget_layout_tree.cpp @@ -0,0 +1,231 @@ +#include "widget_layout_tree.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_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_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_layout(); + } +} + +void widget_layout_tree_node::clear_children() { + for (const auto& child : children_) { + child->set_parent(nullptr); + } + children_.clear(); + invalidate_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; + + // 第一阶段: 计算期望尺寸(自下而上) + auto desired_size = widget->compute_desired_size(in_layout_scale_multiplier); + + // 第二阶段: 分配几何区域(自下而上) + // 如果是根节点没有指定大小, 使用期望大小 + 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::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); + } + + // 更新完成,清除脏标记 + is_layout_dirty_ = false; +} + +void widget_layout_tree_node::paint(render_elements& in_drawer, uint32_t in_layer, float in_layout_scale_multiplier) { + auto widget = widget_.lock(); + if (!widget) return; + + // 如果布局需要更新,确保最新状态 + if (is_layout_dirty_) { + update_layout(in_layout_scale_multiplier); + } + + // 绘制当前widget + in_layer = widget->on_paint(in_drawer, geometry_, in_layer); + + // 递归绘制所有子节点 + for (const auto& child_node: children_) { + child_node->paint(in_drawer, in_layer, in_layout_scale_multiplier); + } +} + +void widget_layout_tree_node::invalidate_layout() { + is_layout_dirty_ = true; + for (const auto& child : children_) { + child->invalidate_layout(); + } +} + +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_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 (root_) { + // 首先构建整个树结构 + root_->build_tree(); + + // 然后更新布局 + root_->update_layout(get_dpi_scale()); + } +} + +void widget_layout_tree::update_layout_if_needed() { + if (root_ && root_->is_layout_dirty()) { + root_->update_layout(get_dpi_scale()); + } +} + +void widget_layout_tree::invalidate_layout() { + if (root_) { + root_->invalidate_layout(); + } +} + +void widget_layout_tree::paint(render_elements& in_elements) { + // 确保布局是最新的 + update_layout_if_needed(); + + // 从根节点开始绘制整个UI树 + if (root_) { + uint32_t start_layer = 0; + root_->paint(in_elements, start_layer, get_dpi_scale()); + } +} + +mirage_window* 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; +} diff --git a/src/geometry/widget_layout_tree.h b/src/geometry/widget_layout_tree.h new file mode 100644 index 0000000..fcc3403 --- /dev/null +++ b/src/geometry/widget_layout_tree.h @@ -0,0 +1,60 @@ +#pragma once +#include +#include + +#include "geometry.h" +#include "core/render_elements.h" + +class mirage_window; +class mwidget; + +class widget_layout_tree_node : public std::enable_shared_from_this { +public: + explicit widget_layout_tree_node(const std::shared_ptr& in_widget); + + void set_geometry(const geometry_t& in_geometry); + [[nodiscard]] const auto& get_geometry() const { return geometry_; } + [[nodiscard]] const auto& get_parent_geometry() const { return parent_->get_geometry(); } + + void set_parent(const std::shared_ptr& in_parent) { parent_ = in_parent; } + [[nodiscard]] const auto& get_parent() const { return parent_; } + + [[nodiscard]] auto is_layout_dirty() const { return is_layout_dirty_; } + [[nodiscard]] auto get_widget() const { return widget_.lock(); } + [[nodiscard]] auto get_children() const { return children_; } + + void add_child(const std::shared_ptr& in_child); + void remove_child(const std::shared_ptr& in_child); + void clear_children(); + + void build_tree(); + void update_layout(float in_layout_scale_multiplier); + void paint(render_elements& in_drawer, uint32_t in_layer, float in_layout_scale_multiplier); + + void invalidate_layout(); +private: + std::weak_ptr widget_; + geometry_t geometry_; + std::shared_ptr parent_; + std::vector> children_; + + bool is_layout_dirty_{ true }; +}; + +class widget_layout_tree { +public: + void set_root(const std::shared_ptr& in_root_widget); + void set_root_geometry(const geometry_t& in_geometry); + + [[nodiscard]] const auto& get_root() const { return root_; } + + void build_layout(); + void update_layout_if_needed(); + void invalidate_layout(); + + void paint(render_elements& in_elements); + [[nodiscard]] mirage_window* get_window() const; + [[nodiscard]] float get_dpi_scale() const; +private: + std::shared_ptr root_; +}; diff --git a/src/mirage.h b/src/mirage.h index 856d828..e378134 100644 --- a/src/mirage.h +++ b/src/mirage.h @@ -8,10 +8,10 @@ public: void init(); void run(); - [[nodiscard]] mirage_render_context* get_render_context() const { + [[nodiscard]] static mirage_render_context* get_render_context() { return render_context; } private: - mirage_render_context* render_context{}; + inline static mirage_render_context* render_context{}; time_type last_time = {}; }; diff --git a/src/misc/mirage_type.h b/src/misc/mirage_type.h index a138981..d046379 100644 --- a/src/misc/mirage_type.h +++ b/src/misc/mirage_type.h @@ -9,6 +9,40 @@ using duration_type = decltype(std::chrono::high_resolution_clock::now() - std:: inline time_type get_current_time() { return std::chrono::high_resolution_clock::now(); } +enum class horizontal_alignment { + left, + center, + right, + stretch +}; + +enum class vertical_alignment { + top, + center, + bottom, + stretch +}; + +enum class horizontal_text_alignment { + left, + center, + right +}; + +enum class vertical_text_alignment { + top, + center, + bottom +}; + +enum class visibility { + visible, + hidden, + collapsed, + hit_test_invisible, + self_hit_test_invisible, +}; + // 左上,右上,左下,右下 template struct rect_quad : std::array { diff --git a/src/widget/compound_widget/mborder.cpp b/src/widget/compound_widget/mborder.cpp new file mode 100644 index 0000000..fc45603 --- /dev/null +++ b/src/widget/compound_widget/mborder.cpp @@ -0,0 +1,8 @@ +#include "mborder.h" + +void mborder::arrange_children(const geometry_t& in_allotted_geometry, arranged_children& in_arranged_children) { + if (auto widget = child_slot_.get()) { + arranged_widget child_widget(in_allotted_geometry, widget); + in_arranged_children.add_widget(child_widget); + } +} diff --git a/src/widget/compound_widget/mborder.h b/src/widget/compound_widget/mborder.h new file mode 100644 index 0000000..c7e6a1a --- /dev/null +++ b/src/widget/compound_widget/mborder.h @@ -0,0 +1,22 @@ +#pragma once +#include "mcompound_widget.h" +#include "geometry/margin.h" + +struct mborder_slot : mcompound_widget_slot<> { +public: + mborder_slot() { + h_alignment_ = horizontal_alignment::stretch; + v_alignment_ = vertical_alignment::stretch; + } + auto& margin(const margin_t& in_margin) { + margin_ = in_margin; + return *this; + } +protected: + margin_t margin_{}; +}; + +class mborder : public mcompound_widget { +public: + virtual void arrange_children(const geometry_t& in_allotted_geometry, arranged_children& in_arranged_children) override; +}; diff --git a/src/widget/compound_widget/mbutton.cpp b/src/widget/compound_widget/mbutton.cpp new file mode 100644 index 0000000..e51fd03 --- /dev/null +++ b/src/widget/compound_widget/mbutton.cpp @@ -0,0 +1,25 @@ +#include "mbutton.h" + +#include "geometry/margin.h" + +uint32_t mbutton::on_paint(render_elements& in_drawer, geometry_t& in_transform, uint32_t in_layer) { + in_drawer.make_rounded_rect( + { 0, 0 }, + in_transform.get_local_size(), + { { 0.5f, 0.5f, 0.5f, 1.0f } }, + {}, + { 10 } + ); + return in_layer; +} + +Eigen::Vector2f mbutton::compute_desired_size(float in_layout_scale_multiplier) const { + 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()) { + desired_size += child->compute_desired_size(in_layout_scale_multiplier); + } + + return desired_size; +} diff --git a/src/widget/compound_widget/mbutton.h b/src/widget/compound_widget/mbutton.h new file mode 100644 index 0000000..3379af2 --- /dev/null +++ b/src/widget/compound_widget/mbutton.h @@ -0,0 +1,12 @@ +#pragma once +#include "mborder.h" +#include "mcompound_widget.h" +#include "widget/mwidget.h" + +class mbutton : public mborder { +public: + virtual uint32_t on_paint(render_elements& in_drawer, geometry_t& in_transform, uint32_t in_layer) override; + virtual Eigen::Vector2f compute_desired_size(float in_layout_scale_multiplier) const override; +private: + +}; diff --git a/src/widget/compound_widget/mcompound_widget.cpp b/src/widget/compound_widget/mcompound_widget.cpp new file mode 100644 index 0000000..b94a7fb --- /dev/null +++ b/src/widget/compound_widget/mcompound_widget.cpp @@ -0,0 +1 @@ +#include "mcompound_widget.h" diff --git a/src/widget/compound_widget/mcompound_widget.h b/src/widget/compound_widget/mcompound_widget.h new file mode 100644 index 0000000..c6e1a49 --- /dev/null +++ b/src/widget/compound_widget/mcompound_widget.h @@ -0,0 +1,80 @@ +#pragma once +#include "widget/mwidget.h" + +struct mcompound_widget_slot_args { +public: + auto& h_alignment(horizontal_alignment in_alignment) { + h_alignment_ = in_alignment; + return *this; + } + auto& v_alignment(vertical_alignment in_alignment) { + v_alignment_ = in_alignment; + return *this; + } +protected: + horizontal_alignment h_alignment_{}; + vertical_alignment v_alignment_{}; +}; + +template +struct mcompound_widget_slot : Args { +public: + void set(const std::shared_ptr& in_widget) { + widget_ = in_widget; + } + + const auto& get() const { + return widget_; + } + + auto& operator[](const std::shared_ptr& in_widget) { + set(in_widget); + return *this; + } +protected: + std::shared_ptr widget_{}; +}; + +template +class mcompound_widget : public mwidget { +public: + auto& set_content(const std::shared_ptr& in_widget) { + in_widget->set_window(get_window()); + in_widget->set_parent(shared_from_this()); + return child_slot_[in_widget]; + } + + [[nodiscard]] virtual Eigen::Vector2f compute_desired_size(float in_layout_scale_multiplier) const override; + virtual uint32_t on_paint(render_elements& in_drawer, geometry_t& in_transform, uint32_t in_layer) override; + virtual std::vector> get_children() const override; +protected: + SlotType child_slot_; +}; + +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) { + return child_widget->compute_desired_size(in_layout_scale_multiplier); + } + } + return {}; +} + +template +uint32_t mcompound_widget::on_paint(render_elements& in_drawer, geometry_t& in_transform, uint32_t in_layer) { + if (auto child_widget = child_slot_.get()) { + if (child_widget->get_visibility() != visibility::collapsed) { + return child_widget->on_paint(in_drawer, in_transform, in_layer); + } + } + return in_layer; +} + +template +std::vector> mcompound_widget::get_children() const { + if (auto child_widget = child_slot_.get()) { + return { child_widget }; + } + return {}; +} diff --git a/src/widget/mwidget.cpp b/src/widget/mwidget.cpp new file mode 100644 index 0000000..cb0f8d0 --- /dev/null +++ b/src/widget/mwidget.cpp @@ -0,0 +1,5 @@ +#include "mwidget.h" + +void mwidget::cache_desired_size(float in_layout_scale_multiplier) { + set_desired_size(compute_desired_size(in_layout_scale_multiplier)); +} diff --git a/src/widget/mwidget.h b/src/widget/mwidget.h new file mode 100644 index 0000000..b63137b --- /dev/null +++ b/src/widget/mwidget.h @@ -0,0 +1,45 @@ +#pragma once +#include "core/render_elements.h" +#include "core/window/render_window.h" +#include "geometry/arranged_children.h" + +class mwidget : public std::enable_shared_from_this { +public: + virtual ~mwidget() = default; + + virtual uint32_t on_paint(render_elements& in_drawer, geometry_t& in_transform, uint32_t in_layer) = 0; + + /** + * 获取期望大小 + * @return 期望大小 (像素) + */ + [[nodiscard]] auto get_desired_size() const { return desired_size_.value_or(Eigen::Vector2f{ 0, 0 }); } + + [[nodiscard]] virtual Eigen::Vector2f compute_desired_size(float in_layout_scale_multiplier) const = 0; + + [[nodiscard]] auto get_visibility() const { return visibility_; } + void set_visibility(visibility in_visibility) { visibility_ = in_visibility; } + + virtual void arrange_children(const geometry_t& in_allotted_geometry, arranged_children& in_arranged_children) = 0; + + void set_parent(const std::shared_ptr& in_parent) { parent_ = in_parent; } + virtual std::vector> get_children() const { return {}; } + + auto get_window() const { return window_; } + void set_window(mirage_window* in_window) { window_ = in_window; } +protected: + virtual void cache_desired_size(float in_layout_scale_multiplier); +private: + void set_desired_size(const Eigen::Vector2f& in_size) { desired_size_ = in_size; } + + visibility visibility_{ visibility::self_hit_test_invisible }; + + mirage_window* window_{}; + + std::weak_ptr parent_; + + std::optional desired_size_; +}; + +#define mnew(widget_class, ...) \ + std::make_shared()