ECS模式初版
This commit is contained in:
parent
5e39678310
commit
d650fcb558
5
.gitmodules
vendored
5
.gitmodules
vendored
@ -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
|
||||
|
@ -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")
|
||||
|
@ -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();
|
||||
|
@ -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)
|
||||
|
@ -54,16 +54,6 @@ public:
|
||||
*/
|
||||
virtual void cleanup() { }
|
||||
|
||||
//-------------- 渲染操作 --------------
|
||||
|
||||
/**
|
||||
* @brief 更新渲染状态
|
||||
* @param in_delta 自上次更新后的时间间隔
|
||||
*
|
||||
* 根据时间间隔更新渲染状态,执行动画和其他时间相关的操作。
|
||||
*/
|
||||
virtual void tick(const duration_type& in_delta) = 0;
|
||||
|
||||
//-------------- 环境和表面设置 --------------
|
||||
|
||||
/**
|
||||
|
@ -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();
|
||||
|
@ -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_{};
|
||||
};
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
@ -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_{};
|
||||
};
|
||||
|
@ -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 = {
|
||||
|
@ -46,16 +46,6 @@ public:
|
||||
*/
|
||||
void cleanup() override;
|
||||
|
||||
//-------------- 渲染操作 --------------
|
||||
|
||||
/**
|
||||
* @brief 更新渲染状态
|
||||
* @param in_delta 自上次更新后的时间间隔
|
||||
*
|
||||
* 处理时间相关的渲染更新,如动画。
|
||||
*/
|
||||
virtual void tick(const duration_type& in_delta) override;
|
||||
|
||||
//-------------- 环境和表面设置 --------------
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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_;
|
||||
|
@ -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_;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
@ -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_{};
|
||||
};
|
@ -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
1103
src/misc/delegates.h
Normal file
File diff suppressed because it is too large
Load Diff
@ -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_);
|
||||
}
|
||||
|
@ -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_;
|
||||
|
@ -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;
|
||||
|
||||
//-------------- 几何和渲染结构体 --------------
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
};
|
||||
|
@ -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();
|
||||
|
@ -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_;
|
||||
|
@ -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 {};
|
||||
}
|
||||
|
@ -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_;
|
||||
}
|
@ -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}; ///< 缓存是否有效
|
||||
};
|
@ -4,4 +4,3 @@
|
||||
*/
|
||||
|
||||
#include "hit_test_result.h"
|
||||
#include "widget/mwidget.h"
|
||||
|
@ -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; }
|
||||
};
|
@ -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);
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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>()
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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:
|
||||
/** 子组件之间的间距 */
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -25,7 +25,7 @@
|
||||
return me(); \
|
||||
} \
|
||||
protected: \
|
||||
std::shared_ptr<mwidget> widget_{};
|
||||
std::weak_ptr<mwidget> widget_{};
|
||||
|
||||
#define SLOT_OPTIONAL_ATTRIBUTE(type, name) \
|
||||
public: \
|
||||
|
33
src/widget_tree/widget_component.cpp
Normal file
33
src/widget_tree/widget_component.cpp
Normal 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;
|
||||
}
|
94
src/widget_tree/widget_component.h
Normal file
94
src/widget_tree/widget_component.h
Normal 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; }
|
||||
};
|
38
src/widget_tree/widget_manager.cpp
Normal file
38
src/widget_tree/widget_manager.cpp
Normal 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();
|
||||
}
|
||||
}
|
85
src/widget_tree/widget_manager.h
Normal file
85
src/widget_tree/widget_manager.h
Normal 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;
|
||||
};
|
267
src/widget_tree/widget_system.cpp
Normal file
267
src/widget_tree/widget_system.cpp
Normal 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);
|
||||
}
|
||||
}
|
58
src/widget_tree/widget_system.h
Normal file
58
src/widget_tree/widget_system.h
Normal 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
1
third_party/mustache
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit c7951b7029ec6e650c4f15ad9d0c058ea445fec4
|
Loading…
x
Reference in New Issue
Block a user