ECS模式初版

This commit is contained in:
Nanako 2025-03-24 00:48:27 +08:00
parent 5e39678310
commit d650fcb558
45 changed files with 2304 additions and 1799 deletions

5
.gitmodules vendored
View File

@ -1,3 +1,6 @@
[submodule "third_party/msdfgen"]
path = third_party/msdfgen
url = https://github.com/Chlumsky/msdfgen.git
url = https://github.com/Chlumsky/msdfgen.git
[submodule "third_party/mustache"]
path = third_party/mustache
url = https://github.com/kirillochnev/mustache.git

View File

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

View File

@ -17,62 +17,15 @@ int main(int argc, char* argv[]) {
mirage_app::get_render_context()->setup_surface(window.get());
auto border = std::make_shared<mborder>();
widget_manager::get().init_window(window);
auto h_box = std::make_shared<mh_box>();
window->set_content(h_box);
auto weak_border = widget_manager::get().new_widget<mborder>(window.get());
auto border = weak_border.lock();
auto button = widget_manager::get().new_widget<mbutton>(window.get());
border->set_content(button.lock())
.margin({5});
auto v_box = std::make_shared<mv_box>();
v_box->add_slot()
.auto_size()
.margin({ 5 })
[
std::make_shared<mbutton>()
];
v_box->add_slot()
.stretch()
.margin({ 5 })
[
std::make_shared<mbutton>()
];
v_box->add_slot()
.auto_size()
.margin({ 5 })
[
std::make_shared<mbutton>()
];
h_box->add_slot()
.auto_size()
.margin({ 5 })
[
std::make_shared<mbutton>()
];
h_box->add_slot()
.auto_size()
.margin({ 5 })
[
std::make_shared<mbutton>()
];
h_box->add_slot()
.auto_size()
.margin({ 5 })
[
std::make_shared<mbutton>()
];
h_box->add_slot()
.stretch()
[
v_box
];
h_box->add_slot()
.stretch()
.margin({ 5 })
[
std::make_shared<mbutton>()
];
window->set_content(border);
app.run();

View File

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

View File

@ -54,16 +54,6 @@ public:
*/
virtual void cleanup() { }
//-------------- 渲染操作 --------------
/**
* @brief
* @param in_delta
*
*
*/
virtual void tick(const duration_type& in_delta) = 0;
//-------------- 环境和表面设置 --------------
/**

View File

@ -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<transform2d>();
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<float, 2, 4> 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();

View File

@ -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<transform2d> transform_stack_;
/** 圆角矩形渲染管线 */
sg_pipeline rounded_rect_pipeline_{};
};

View File

@ -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<float>(), 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<float>());
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<mwidget>& in_widget) {
content_widget_ = in_widget;
in_widget->set_parent(shared_from_this());
layout_tree_.invalidate(invalidate_reason::all);
}
std::vector<std::shared_ptr<mwidget>> 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<float>(), 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();
}

View File

@ -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<float>(), 0, { dpi_helper::get_global_scale() * get_dpi_scale(), dpi_helper::get_global_scale() * get_dpi_scale() });
return transform2d(get_window_position().cast<float>(), 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<mwidget>& in_widget);
/**
* @brief
* @return
*
*
*/
virtual std::vector<std::shared_ptr<mwidget>> 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<const Eigen::Vector2i&> on_resize_delegate;
multicast_delegate<mwindow*> on_close_delegate;
multicast_delegate<const Eigen::Vector2f&> on_mouse_move_delegate;
multicast_delegate<const Eigen::Vector2f&, mouse_button> on_mouse_button_down_delegate;
multicast_delegate<const Eigen::Vector2f&, mouse_button> 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<mirage_window_state> state_;
/** 窗口内容组件 */
std::shared_ptr<mwidget> content_widget_;
/** 布局树 */
widget_layout_tree layout_tree_{};
};

View File

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

View File

@ -46,16 +46,6 @@ public:
*/
void cleanup() override;
//-------------- 渲染操作 --------------
/**
* @brief
* @param in_delta
*
*
*/
virtual void tick(const duration_type& in_delta) override;
//-------------- 环境和表面设置 --------------
/**

View File

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

View File

@ -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<arranged_widget> children_;

View File

@ -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<Eigen::Vector2f>();
}
private:
/** 几何体在本地坐标系中的大小 */
Eigen::Vector2f size_;

View File

@ -581,7 +581,7 @@ public:
* @return
*/
template<typename U>
auto align_rect(const rect_t<U>& in_inner, horizontal_alignment in_h_align, vertical_alignment in_v_align, const Eigen::Vector2<U>& padding = {}) const {
auto align_rect(const rect_t<U>& in_inner, horizontal_alignment_t in_h_align, vertical_alignment_t in_v_align, const Eigen::Vector2<U>& 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<typename U>
auto get_text_rect(horizontal_text_alignment in_h_align, vertical_text_alignment in_v_align, const Eigen::Vector2<U>& 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<U>& in_text_size) const {
rect_t<U> 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;
}

View File

@ -1,417 +0,0 @@
#include "widget_layout_tree.h"
#include <iostream>
#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<mwidget>& 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<widget_layout_tree_node>& 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<widget_layout_tree_node>& 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<widget_layout_tree_node>(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<std::shared_ptr<widget_layout_tree_node>> 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<widget_layout_tree_node> child_node;
if (found_it != children_.end()) {
// 使用现有节点
child_node = *found_it;
} else {
// 创建新节点
child_node = std::make_shared<widget_layout_tree_node>(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<hit_test_handle(std::shared_ptr<mwidget>, 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<mwidget>& in_root_widget) {
if (!in_root_widget) return;
root_ = std::make_shared<widget_layout_tree_node>(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<std::chrono::milliseconds>(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<hit_test_handle(std::shared_ptr<mwidget>, 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<mwidget> 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<mwidget> 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<mwidget> 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;
}

View File

@ -1,296 +0,0 @@
#pragma once
/**
* @file widget_layout_tree.h
* @brief UI组件布局树的结构
*
* widget_layout_tree_node表示布局树中的单个节点
* widget_layout_tree管理整个布局树
*
*/
#include <memory>
#include <vector>
#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<widget_layout_tree_node> {
public:
//-------------- 构造与结构关系 --------------
/**
* @brief
* @param in_widget UI组件
*/
explicit widget_layout_tree_node(const std::shared_ptr<mwidget>& in_widget);
/**
* @brief
* @param in_parent
*/
void set_parent(const std::shared_ptr<widget_layout_tree_node>& 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<widget_layout_tree_node>& in_child);
/**
* @brief
* @param in_child
*/
void remove_child(const std::shared_ptr<widget_layout_tree_node>& 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<hit_test_handle(std::shared_ptr<mwidget>, const Eigen::Vector2f&)>& in_hit_func);
private:
/** 节点对应的UI组件的弱引用 */
std::weak_ptr<mwidget> widget_;
/** 节点的几何信息 */
geometry_t geometry_;
/** 父节点指针 */
std::shared_ptr<widget_layout_tree_node> parent_;
/** 子节点列表 */
std::vector<std::shared_ptr<widget_layout_tree_node>> 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<mwidget>& 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<hit_test_handle(std::shared_ptr<mwidget>, 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<widget_layout_tree_node> root_;
//----------------- 命中测试 -----------------
std::shared_ptr<mwidget> last_hit_widget_;
std::shared_ptr<mwidget> 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_{};
};

View File

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

1103
src/misc/delegates.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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_);
}

View File

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

View File

@ -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;
//-------------- 几何和渲染结构体 --------------

View File

@ -24,8 +24,8 @@ struct mborder_slot : mcompound_widget_slot<mborder_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<mwidget> 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());
}
};

View File

@ -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<mh_box> hbox = std::dynamic_pointer_cast<mh_box>(parent)) {
hbox->add_slot()
.stretch()
.margin({ 5 })
[
std::make_shared<mbutton>()
];
}
if (std::shared_ptr<mv_box> vbox = std::dynamic_pointer_cast<mv_box>(parent)) {
vbox->add_slot()
.stretch()
.margin({ 5 })
[
std::make_shared<mbutton>()
];
}
// if (std::shared_ptr<mh_box> hbox = std::dynamic_pointer_cast<mh_box>(parent)) {
// hbox->add_slot()
// .stretch()
// .margin({ 5 })
// [
// std::make_shared<mbutton>()
// ];
// }
// if (std::shared_ptr<mv_box> vbox = std::dynamic_pointer_cast<mv_box>(parent)) {
// vbox->add_slot()
// .stretch()
// .margin({ 5 })
// [
// std::make_shared<mbutton>()
// ];
// }
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();

View File

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

View File

@ -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<T&>(*this);
}
std::shared_ptr<mwidget> 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<typename SlotType>
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<mwidget>& 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<std::shared_ptr<mwidget>> get_children() const override;
virtual void arrange_children(const geometry_t& in_allotted_geometry) override;
SlotType& get_child_slot() const { return get_component_ref<SlotType>(); }
protected:
/**
* @brief
@ -115,11 +107,15 @@ protected:
*
*/
virtual void on_set_content(const std::shared_ptr<mwidget>& in_widget) {}
/** 子组件插槽,管理单个子组件及其属性 */
SlotType child_slot_;
};
template<typename SlotType>
void mcompound_widget<SlotType>::init_component(mustache::EntityManager& in_entity_manager) {
mwidget::init_component(in_entity_manager);
auto& slot = add_component<SlotType>();
slot.slot_owner = get_key();
}
/**
* @brief
*
@ -127,8 +123,8 @@ protected:
*/
template<typename SlotType>
Eigen::Vector2f mcompound_widget<SlotType>::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<SlotType>::compute_desired_size(float in_layout
* 使layout_utils中的arrange_single_child函数根据对齐属性排列单个子组件
*/
template<typename SlotType>
void mcompound_widget<SlotType>::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<typename SlotType>
std::vector<std::shared_ptr<mwidget>> mcompound_widget<SlotType>::get_children() const {
if (auto child_widget = child_slot_.get()) {
return { child_widget };
void mcompound_widget<SlotType>::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 {};
}

View File

@ -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<mwidget>& hit_test_manager::get_last_hit_widget() const {
return last_hit_widget_;
}
const std::shared_ptr<mwidget>& hit_test_manager::get_last_hover_widget() const {
return last_hover_widget_;
}

View File

@ -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<mwidget>& get_last_hit_widget() const;
[[nodiscard]] const std::shared_ptr<mwidget>& get_last_hover_widget() const;
private:
// 成员变量
mwindow* window_{nullptr}; ///< 关联的窗口
std::shared_ptr<mwidget> last_hit_widget_{nullptr}; ///< 最近一次点击命中的组件
std::shared_ptr<mwidget> last_hover_widget_{nullptr}; ///< 最近一次悬停的组件
bool cache_valid_{false}; ///< 缓存是否有效
};

View File

@ -4,4 +4,3 @@
*/
#include "hit_test_result.h"
#include "widget/mwidget.h"

View File

@ -8,7 +8,7 @@
#include <vector>
#include <Eigen/Core>
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<mwidget> widget;
widget_key widget;
Eigen::Vector2f widget_space_pos;
operator bool() const {
return widget != nullptr;
return widget;
}
operator std::shared_ptr<mwidget>() const { return widget; }
operator widget_key() const { return widget; }
};

View File

@ -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<mwidget>& 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<mwidget>& 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);
}

View File

@ -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<mwidget>& 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<orientation LayoutOrientation, typename SlotType>
template<orientation_t LayoutOrientation, typename SlotType>
void arrange_box_children(
const geometry_t& in_allotted_geometry,
arranged_children& in_arranged_children,
const std::vector<SlotType>& 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<SlotType*>& 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<mwidget> widget;
const SlotType* slot;
float size;
};
// 第一轮:收集可见部件信息并计算自动尺寸
struct slot_widget_info {
std::shared_ptr<mwidget> 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<slot_widget_info> visible_widgets;
float total_auto_size = 0.0f;
float total_stretch_factor = 0.0f;
std::vector<slot_widget_info> 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<mwidget> 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<orientation LayoutOrientation, typename SlotType>
void arrange_box_children(
const geometry_t& in_allotted_geometry,
arranged_children& in_arranged_children,
const std::vector<SlotType>& 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<mwidget> 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<slot_widget_info> 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<orientation LayoutOrientation, typename SlotType>
Eigen::Vector2f compute_box_desired_size(
const std::vector<SlotType>& child_slots
) {
template<orientation_t LayoutOrientation, typename SlotType>
Eigen::Vector2f compute_box_desired_size(const std::vector<SlotType*>& 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 {

View File

@ -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<widget_ptr>(key_, widget_ptr{ shared_from_this() });
in_entity_manager.assign<widget_layout>(key_);
in_entity_manager.assign<widget_visibility>(key_);
in_entity_manager.assign<widget_hierarchy>(key_);
in_entity_manager.assign<widget_invalidate>(key_);
}
void mwidget::cache_desired_size(float in_layout_scale_multiplier, bool in_force) {
if (auto* layout = get_component<widget_layout>()) {
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> mwidget::get_parent_widget() const {
if (const auto parent = get_parent())
return widget_manager::get().get_component<widget_ptr>(parent)->widget;
return {};
}
void mwidget::set_parent(const widget_key& in_parent) {
auto& manager = widget_manager::get();
auto* parent_hierarchy = manager.get_component<widget_hierarchy>(in_parent);
auto* hierarchy = manager.get_component<widget_hierarchy>(key_);
// 1. 如果当前有父节点,先从父节点中移除
if (hierarchy->parent) {
auto* old_parent_hierarchy = manager.get_component<widget_hierarchy>(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<widget_invalidate>()) {
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<widget_layout>()) {
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;
}

View File

@ -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<mwidget> {
public:
//-------------- 构造和析构 --------------
virtual ~mwidget();
/**
* @brief
*/
virtual ~mwidget() = default;
template<typename T>
auto& add_component() {
return widget_manager::get().add_component<T>(key_);
}
template<typename T, typename... Args>
auto& add_component(Args&&... args) {
return widget_manager::get().add_component<T>(key_, std::forward<Args>(args)...);
}
template<typename T>
auto* get_component() const {
return widget_manager::get().get_component<T>(key_);
}
template<typename T>
auto& get_component_ref() const {
return *widget_manager::get().get_component<T>(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<widget_layout>().geometry = in_geometry; }
const auto& get_geometry() const { return get_component_ref<widget_layout>().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<widget_layout>().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<mwidget>& in_parent) { parent_ = in_parent; }
/**
* @brief
*/
auto get_parent() const { return parent_.lock(); }
/**
* @brief
* @return
*
*
*/
virtual std::vector<std::shared_ptr<mwidget>> get_children() const { return {}; }
auto get_parent() const { return get_component_ref<widget_hierarchy>().parent; }
auto get_children() const { return get_component_ref<widget_hierarchy>().children; }
auto get_parent_widget() const -> std::weak_ptr<mwidget>;
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<widget_visibility>().is_enabled(); }
void set_enabled(bool in_enabled) { get_component_ref<widget_visibility>().set_enabled(in_enabled); }
auto get_visibility() const { return get_component_ref<widget_visibility>().get_visibility(); }
void set_visibility(visibility_t in_visibility) { get_component_ref<widget_visibility>().set_visibility(in_visibility); }
//-------------- 启用状态 --------------
bool can_hit_test() const { return get_component_ref<widget_visibility>().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<mwidget> parent_;
/** 缓存的期望大小 */
std::optional<Eigen::Vector2f> 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<widget_class>()

View File

@ -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<orientation::horizontal, hbox_slot>(in_allotted_geometry, in_arranged_children, child_slots_);
void mh_box::arrange_children(const geometry_t& in_allotted_geometry) {
arrange_box_children<orientation_t::horizontal>(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<orientation::horizontal, hbox_slot>(child_slots_);
cache_all_children_desired_size(in_layout_scale_multiplier, false);
return compute_box_desired_size<orientation_t::horizontal>(get_slots());
}
void mv_box::arrange_children(const geometry_t& in_allotted_geometry, arranged_children& in_arranged_children) {
arrange_box_children<orientation::vertical, vbox_slot>(in_allotted_geometry, in_arranged_children, child_slots_);
void mv_box::arrange_children(const geometry_t& in_allotted_geometry) {
arrange_box_children<orientation_t::vertical>(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<orientation::vertical, vbox_slot>(child_slots_);
cache_all_children_desired_size(in_layout_scale_multiplier, false);
return compute_box_desired_size<orientation_t::vertical>(get_slots());
}

View File

@ -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:
/** 子组件之间的间距 */

View File

@ -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<T&>(*this);
}
std::shared_ptr<mwidget> 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<SlotType>(get_window());
auto w = widget_entity.lock();
auto& slot = w->template add_component<SlotType>();
slot.slot_owner = key_;
return slot;
}
//-------------- 子组件查询 --------------
/**
* @brief
* @return
*
*
*/
virtual std::vector<std::shared_ptr<mwidget>> get_children() const override;
protected:
/** 子组件插槽列表,管理多个子组件 */
std::vector<SlotType> child_slots_;
auto get_slots_ref() {
std::vector<SlotType&> children;
if (auto hierarchy = get_component<widget_hierarchy>()) {
auto& manager = widget_manager::get();
for (const auto& child : hierarchy->children) {
if (auto slot = manager.get_component<SlotType>(child)) {
children.push_back(*slot);
}
}
}
return children;
}
auto get_slots() const {
std::vector<SlotType*> children;
if (auto hierarchy = get_component<widget_hierarchy>()) {
auto& manager = widget_manager::get();
for (const auto& child : hierarchy->children) {
if (auto slot = manager.get_component<SlotType>(child)) {
children.push_back(slot);
}
}
}
return children;
}
};
/**
* @brief
*
*
*/
template<typename SlotType>
std::vector<std::shared_ptr<mwidget>> mpanel_widget<SlotType>::get_children() const {
std::vector<std::shared_ptr<mwidget>> children;
for (const auto& slot : child_slots_) {
if (slot.get()) {
children.push_back(slot.get());
}
void mpanel_widget<SlotType>::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<widget_hierarchy>();
for (const auto& child: hierarchy.children) {
const auto& ptr = manager.get_component_ref<widget_ptr>(child);
ptr->cache_desired_size(in_layout_scale_multiplier, in_force);
}
return children;
}

View File

@ -25,7 +25,7 @@
return me(); \
} \
protected: \
std::shared_ptr<mwidget> widget_{};
std::weak_ptr<mwidget> widget_{};
#define SLOT_OPTIONAL_ATTRIBUTE(type, name) \
public: \

View File

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

View File

@ -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<mwidget> widget;
operator std::shared_ptr<mwidget>() const { return widget; }
auto operator->() const { return widget.get(); }
operator bool() const { return widget != nullptr; }
};
struct widget_layout {
geometry_t geometry; // 几何信息
std::optional<Eigen::Vector2f> 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<widget_key> 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; }
};

View File

@ -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<mwindow>& 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<widget_layout_system>();
const auto hit_test_system = std::make_shared<widget_hit_test_system>();
const auto render_system = std::make_shared<widget_render_system>();
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();
}
}

View File

@ -0,0 +1,85 @@
#pragma once
#include <map>
#include <mustache/ecs/ecs.hpp>
#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<mwindow>& in_window);
auto& get_registry(mwindow* in_window) {
return registries[in_window];
}
template<typename T>
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<T>();
widget->set_key(key);
widget->init_component(entity_manager);
return std::weak_ptr{ widget };
}
template<typename T, typename... Args>
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<T>(std::forward<Args>(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<typename T>
auto& add_component(const widget_key& in_key) {
return registries[in_key].entities().assign<T>(in_key);
}
template<typename T, typename... Args>
auto& add_component(const widget_key& in_key, Args&&... args) {
return registries[in_key].entities().assign<T>(in_key, std::forward<Args>(args)...);
}
template<typename T>
auto* get_component(const widget_key& in_key) {
return registries[in_key].entities().getComponent<T>(in_key);
}
template<typename T>
auto& get_component_ref(const widget_key& in_key) {
return *registries[in_key].entities().getComponent<T>(in_key);
}
template<typename T>
auto get_system(mwindow* in_window) {
auto shared = registries[in_window].systems().findSystem<T>();
return std::static_pointer_cast<T>(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<mwindow*, mustache::World> registries;
};

View File

@ -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<mwindow> 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<mwidget>& 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<widget_ptr>(last_hover_widget_))
ptr->widget->on_mouse_leave();
last_hit_widget_ = result;
if (auto ptr = widget_manager::get().get_component<widget_ptr>(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<mwidget>& 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<widget_ptr>(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<mwidget>& 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<widget_ptr>(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<hit_test_handle(
const std::shared_ptr<mwidget>&,
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<hit_test_handle(const std::shared_ptr<mwidget>&, const Eigen::Vector2f&)>& in_hit_func) {
const auto& layout = widget_manager::get().get_component_ref<widget_layout>(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<widget_ptr>(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<widget_hierarchy>(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<mwindow> 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<widget_ptr>(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<widget_hierarchy>(in_key).children;
for (const auto& child : children) {
paint(child);
}
}

View File

@ -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<widget_layout_system> {
public:
void set_window(std::shared_ptr<mwindow> in_window) { window_ = in_window; }
auto get_dpi_scale() const -> float;
protected:
virtual void onUpdate(mustache::World& in_world) override;
std::weak_ptr<mwindow> window_{};
};
class widget_hit_test_system : public mustache::System<widget_hit_test_system> {
public:
virtual ~widget_hit_test_system() override;
void set_window(std::shared_ptr<mwindow> 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<hit_test_handle(const std::shared_ptr<mwidget>&, 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<hit_test_handle(const std::shared_ptr<mwidget>&, const Eigen::Vector2f&)>& in_hit_func);
std::weak_ptr<mwindow> 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<widget_render_system> {
public:
virtual ~widget_render_system() override;
void set_window(std::shared_ptr<mwindow> 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<mwindow> window_{};
};

1
third_party/mustache vendored Submodule

@ -0,0 +1 @@
Subproject commit c7951b7029ec6e650c4f15ad9d0c058ea445fec4