Refactor mask widget and renderer for improved functionality and performance

- Removed the MASK_WIDGET_DESIGN.md documentation file as it is no longer needed.
- Updated the example pipeline to modify the corner radius of the rounded rectangle mask.
- Enhanced the mask renderer to correctly calculate UV coordinates based on viewport size for better rendering accuracy.
- Changed the get_mask_params method in circle_mask, rect_mask, and rounded_rect_mask to accept bounds by reference for efficiency.
- Adjusted the mask factory function to ensure correct parameter order.
- Refactored corner_radius method in rounded_rect_mask to accept a rect_corner_radius structure for more flexible corner radius settings.
This commit is contained in:
daiqingshuang
2025-12-01 13:17:38 +08:00
parent a31db1505f
commit b01cf247ec
9 changed files with 27 additions and 2925 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,524 +0,0 @@
# 布局工具重构设计方案
## 1. 概述
本文档描述了将 `v_stack``h_stack``overlay` 容器中重复的布局属性操作封装成通用工具的重构方案。
### 1.1 目标
- 消除重复代码,提高代码可维护性
- 保持与现有代码的完全兼容性
- 使用模板元编程保持类型安全
- 遵循项目现有的命名规范snake_case
- 最小化对现有容器代码的修改
### 1.2 已识别的重复模式
| 重复模式 | 出现位置 | 重复次数 |
|---------|---------|---------|
| `extract_margin()` | v_stack, h_stack, overlay | 3 |
| `extract_stretch()` | v_stack, h_stack | 2 |
| `has_auto_size()` | v_stack, h_stack | 2 |
| `should_stretch()` | v_stack, h_stack | 2 |
| `get_flex_factor()` | v_stack, h_stack | 2 |
| `get_event_children()` 实现 | v_stack, h_stack, overlay | 3 |
| 边距计算模式 | overlay, stack | 多处 |
## 2. 设计方案
### 2.1 新文件结构
```
src/widget/
├── layout_utils.h # 新增:通用布局工具函数
├── slot.h # 现有:保持不变
├── stack.h # 修改:使用 layout_utils
├── overlay.h # 修改:使用 layout_utils
└── ...
```
### 2.2 layout_utils.h 接口设计
```cpp
#pragma once
#include "slot.h"
#include "event_target.h"
#include "dynamic_list.h"
#include <vector>
#include <optional>
#include <type_traits>
namespace mirage {
namespace layout_utils {
// ============================================================================
// 参数提取工具函数
// ============================================================================
/// @brief 从 slot 或普通 widget 中提取 margin 参数
/// @tparam Child 子组件类型
/// @param child 子组件引用
/// @return 提取的 margin如果不存在则返回 margin::zero()
template <typename Child>
constexpr margin extract_margin(const Child& child) {
if constexpr (is_slot_v<Child>) {
if (auto margin_p = child.template get<margin_param>()) {
return margin_p->value;
}
}
return margin::zero();
}
/// @brief 从 slot 中提取 stretch 参数
/// @tparam Child 子组件类型
/// @param child 子组件引用
/// @return 提取的 stretch_param可选
template <typename Child>
constexpr std::optional<stretch_param> extract_stretch(const Child& child) {
if constexpr (is_slot_v<Child>) {
return child.template get<stretch_param>();
}
return std::nullopt;
}
/// @brief 从 slot 中提取 alignment 参数
/// @tparam Child 子组件类型
/// @param child 子组件引用
/// @param default_align 默认对齐方式
/// @return 提取的 alignment
template <typename Child>
constexpr alignment extract_alignment(const Child& child,
alignment default_align = alignment::TOP_LEFT) {
if constexpr (is_slot_v<Child>) {
if (auto align_p = child.template get<alignment_param>()) {
return align_p->value;
}
}
return default_align;
}
/// @brief 检查是否设置了 auto_size 参数
/// @tparam Child 子组件类型
/// @param child 子组件引用
/// @return 如果设置了 auto_size 则返回 true
template <typename Child>
constexpr bool has_auto_size(const Child& child) {
if constexpr (is_slot_v<Child>) {
return child.template get<auto_size_param>().has_value();
}
return false;
}
/// @brief 判断子组件是否应该 stretch
/// @tparam Child 子组件类型
/// @param child 子组件引用
/// @return 如果应该 stretch 则返回 true
template <typename Child>
constexpr bool should_stretch(const Child& child) {
// 如果显式设置了 auto_size不 stretch
if (has_auto_size(child)) return false;
// 如果设置了 stretch 参数,则 stretch
return extract_stretch(child).has_value();
}
/// @brief 获取 stretch 的 flex 因子
/// @tparam Child 子组件类型
/// @param child 子组件引用
/// @return flex 因子,如果不存在则返回 0.0f
template <typename Child>
constexpr float get_flex_factor(const Child& child) {
if (auto sp = extract_stretch(child)) {
return sp->flex_factor;
}
return 0.0f;
}
// ============================================================================
// 边距计算工具函数
// ============================================================================
/// @brief 计算减去 padding/margin 后的内部可用空间
/// @param available_size 可用空间
/// @param padding 边距
/// @return 内部可用空间(保证非负)
inline vec2f_t calculate_inner_available(const vec2f_t& available_size,
const margin& padding) {
return vec2f_t(
std::max(0.0f, available_size.x() - padding.left - padding.right),
std::max(0.0f, available_size.y() - padding.top - padding.bottom)
);
}
/// @brief 计算水平方向的内部可用宽度
/// @param available_width 可用宽度
/// @param padding 边距
/// @return 内部可用宽度(保证非负)
inline float calculate_inner_width(float available_width, const margin& padding) {
return std::max(0.0f, available_width - padding.left - padding.right);
}
/// @brief 计算垂直方向的内部可用高度
/// @param available_height 可用高度
/// @param padding 边距
/// @return 内部可用高度(保证非负)
inline float calculate_inner_height(float available_height, const margin& padding) {
return std::max(0.0f, available_height - padding.top - padding.bottom);
}
/// @brief 计算 stretch 分配的尺寸
/// @param flex_factor 当前组件的 flex 因子
/// @param total_flex 总 flex 因子
/// @param available_stretch 可用于 stretch 的空间
/// @return 分配的尺寸
inline float calculate_stretch_size(float flex_factor, float total_flex,
float available_stretch) {
if (total_flex <= 0.0f) return 0.0f;
return (flex_factor / total_flex) * available_stretch;
}
// ============================================================================
// 事件子组件收集工具
// ============================================================================
/// @brief 从单个子组件中收集 event_target
/// @tparam Child 子组件类型
/// @param child 子组件引用
/// @param event_children 输出的 event_target 列表
template <typename Child>
void collect_event_child(Child& child, std::vector<event_target*>& event_children) {
using ChildType = std::remove_cvref_t<Child>;
// 情况 1: dynamic_list
if constexpr (std::is_same_v<ChildType, dynamic_list>) {
event_children.push_back(const_cast<dynamic_list*>(&child));
}
// 情况 2: slot 类型
else if constexpr (is_slot_v<ChildType>) {
using SlotChildType = slot_child_type_t<ChildType>;
if constexpr (std::is_base_of_v<event_target, SlotChildType>) {
event_children.push_back(const_cast<SlotChildType*>(&child.child()));
}
}
// 情况 3: 继承自 event_target 的普通 widget
else if constexpr (std::is_base_of_v<event_target, ChildType>) {
event_children.push_back(const_cast<ChildType*>(&child));
}
// 情况 4: 其他 widget不处理
}
/// @brief 从 tuple 中收集所有 event_target 子组件
/// @tparam Tuple tuple 类型
/// @param children 子组件 tuple
/// @return event_target 指针列表
template <typename Tuple>
std::vector<event_target*> collect_event_children_from_tuple(Tuple& children) {
std::vector<event_target*> event_children;
std::apply([&](auto&... child) {
(collect_event_child(child, event_children), ...);
}, children);
return event_children;
}
// ============================================================================
// 子组件访问工具
// ============================================================================
/// @brief 获取子组件的实际 widget 引用(解包 slot
/// @tparam Child 子组件类型
/// @param child 子组件引用
/// @return 实际 widget 的引用
template <typename Child>
decltype(auto) get_actual_child(Child& child) {
if constexpr (is_slot_v<std::remove_cvref_t<Child>>) {
return child.child();
} else {
return child;
}
}
/// @brief 获取子组件的实际 widget 引用const 版本)
template <typename Child>
decltype(auto) get_actual_child(const Child& child) {
if constexpr (is_slot_v<std::remove_cvref_t<Child>>) {
return child.child();
} else {
return child;
}
}
} // namespace layout_utils
} // namespace mirage
```
## 3. 重构后的代码示例
### 3.1 v_stack 重构示例
**重构前(当前代码):**
```cpp
// stack.h 中的 v_stack 类
template <typename Child>
margin extract_margin(const Child& child) const {
if constexpr (is_slot_v<Child>) {
if (auto margin_p = child.template get<margin_param>()) {
return margin_p->value;
}
}
return margin::zero();
}
template <typename Child>
std::optional<stretch_param> extract_stretch(const Child& child) const {
if constexpr (is_slot_v<Child>) {
return child.template get<stretch_param>();
}
return std::nullopt;
}
// ... 更多重复方法
```
**重构后:**
```cpp
// stack.h 中的 v_stack 类
#include "layout_utils.h"
template <widget... children>
class v_stack : public event_target, public z_order_mixin<v_stack<children...>> {
public:
// ... 构造函数等保持不变
std::vector<event_target*> get_event_children() const override {
// 使用工具函数简化实现
return layout_utils::collect_event_children_from_tuple(
const_cast<std::tuple<children...>&>(children_)
);
}
private:
// 删除重复的私有方法,直接使用 layout_utils 命名空间中的函数
template <typename Child>
void calculate_flex_info(const Child& child, const vec2f_t& available_size,
float& fixed_height, float& total_flex) const {
const auto child_margin = layout_utils::extract_margin(child);
if constexpr (std::is_same_v<std::decay_t<Child>, dynamic_list>) {
for (const auto& sub_child : child.get_children()) {
const auto child_size = sub_child.measure(available_size);
fixed_height += child_size.y() + child_margin.top + child_margin.bottom;
}
}
else if constexpr (is_slot_v<Child>) {
if (layout_utils::should_stretch(child)) {
fixed_height += child_margin.top + child_margin.bottom;
total_flex += layout_utils::get_flex_factor(child);
} else {
const auto child_size = child.child().measure(available_size);
fixed_height += child_size.y() + child_margin.top + child_margin.bottom;
}
}
else {
const auto child_size = child.measure(available_size);
fixed_height += child_size.y() + child_margin.top + child_margin.bottom;
}
}
// ... 其他方法类似重构
};
```
### 3.2 overlay 重构示例
**重构前:**
```cpp
// overlay.h
template <typename Child>
static margin extract_margin(const Child& child) {
if constexpr (is_slot_v<Child>) {
if (auto margin_p = child.template get<margin_param>()) {
return margin_p->value;
}
}
return margin::zero();
}
// 边距计算
const vec2f_t inner_available = vec2f_t(
std::max(0.0f, available_size.x() - padding.left - padding.right),
std::max(0.0f, available_size.y() - padding.top - padding.bottom)
);
```
**重构后:**
```cpp
// overlay.h
#include "layout_utils.h"
template <widget... Children>
class overlay : public event_target, public z_order_mixin<overlay<Children...>> {
public:
auto measure(const vec2f_t& available_size) const -> vec2f_t {
vec2f_t max_size = vec2f_t::Zero();
// 使用工具函数计算内部可用空间
const vec2f_t inner_available = layout_utils::calculate_inner_available(
available_size, extra_padding_
);
std::apply([&](const auto&... child) {
(process_child_measure(child, inner_available, max_size), ...);
}, children_);
max_size.x() += extra_padding_.left + extra_padding_.right;
max_size.y() += extra_padding_.top + extra_padding_.bottom;
return max_size;
}
std::vector<event_target*> get_event_children() const override {
return layout_utils::collect_event_children_from_tuple(
const_cast<std::tuple<Children...>&>(children_)
);
}
private:
template <typename Child>
alignment extract_alignment(const Child& child) const {
// 使用工具函数,传入默认对齐方式
return layout_utils::extract_alignment(child, default_alignment_);
}
template <typename Child>
void process_child_measure(const Child& child, const vec2f_t& available_size,
vec2f_t& max_size) const {
const auto align = extract_alignment(child);
const auto padding = layout_utils::extract_margin(child);
// 使用工具函数计算内部可用空间
const vec2f_t child_available = layout_utils::calculate_inner_available(
available_size, padding
);
// ... 其余逻辑保持不变
}
};
```
## 4. 重构步骤
### 4.1 阶段一:创建工具文件(低风险)
1. 创建 `src/widget/layout_utils.h`
2. 实现所有工具函数
3. 添加单元测试验证工具函数正确性
### 4.2 阶段二:逐步迁移(中等风险)
1. **v_stack 迁移**
- 引入 `layout_utils.h`
- 替换私有方法调用为工具函数
- 删除重复的私有方法
- 运行测试验证
2. **h_stack 迁移**
- 同 v_stack 步骤
3. **overlay 迁移**
- 引入 `layout_utils.h`
- 替换 `extract_margin` 和边距计算
- 替换 `get_event_children` 实现
- 运行测试验证
### 4.3 阶段三:清理和优化(低风险)
1. 移除所有容器中的重复代码
2. 更新文档
3. 代码审查
## 5. 可行性评估
### 5.1 技术可行性:高
- 所有重复代码模式清晰,易于抽象
- 模板元编程技术成熟,项目已有使用先例
- 不涉及运行时行为变更
### 5.2 兼容性:完全兼容
- 工具函数为纯函数,无副作用
- 不改变任何公共 API
- 不改变任何运行时行为
### 5.3 风险评估
| 风险项 | 风险等级 | 缓解措施 |
|-------|---------|---------|
| 编译错误 | 低 | 逐步迁移,每步验证 |
| 模板实例化问题 | 低 | 充分的单元测试 |
| 性能回归 | 极低 | 内联函数,零开销抽象 |
| 行为变更 | 极低 | 工具函数逻辑与原代码完全一致 |
## 6. 预期收益
### 6.1 代码量减少
| 文件 | 当前行数 | 预计减少 | 减少比例 |
|-----|---------|---------|---------|
| stack.h | ~564 | ~80 | ~14% |
| overlay.h | ~356 | ~40 | ~11% |
| **总计** | ~920 | ~120 | ~13% |
### 6.2 维护性提升
- 单一职责:布局工具函数集中管理
- 易于测试:工具函数可独立测试
- 易于扩展:新容器可直接复用工具函数
### 6.3 一致性提升
- 所有容器使用相同的参数提取逻辑
- 边距计算行为统一
- 事件子组件收集逻辑统一
## 7. 架构图
```mermaid
graph TB
subgraph 重构前
VS1[v_stack] --> |重复代码| M1[extract_margin]
VS1 --> |重复代码| S1[extract_stretch]
VS1 --> |重复代码| E1[get_event_children]
HS1[h_stack] --> |重复代码| M2[extract_margin]
HS1 --> |重复代码| S2[extract_stretch]
HS1 --> |重复代码| E2[get_event_children]
OV1[overlay] --> |重复代码| M3[extract_margin]
OV1 --> |重复代码| E3[get_event_children]
end
subgraph 重构后
LU[layout_utils.h]
LU --> EM[extract_margin]
LU --> ES[extract_stretch]
LU --> SS[should_stretch]
LU --> GF[get_flex_factor]
LU --> CIA[calculate_inner_available]
LU --> CEC[collect_event_children]
VS2[v_stack] --> LU
HS2[h_stack] --> LU
OV2[overlay] --> LU
end
```
## 8. 结论
本重构方案具有高可行性和低风险,预计可减少约 13% 的重复代码,同时提升代码的可维护性和一致性。建议按照分阶段计划执行,每个阶段完成后进行充分测试。

File diff suppressed because it is too large Load Diff

View File

@@ -432,22 +432,22 @@ private:
},
// 使用 | 管道操作符
mask_widget{
rounded_rect_mask{}.corner_radius(10.f),
rounded_rect_mask{}.corner_radius(30.f),
overlay{
// 使用管道语法设置图片居中对齐和边距
imager{}
.texture_id(texture_id_)
.fit(image_fit::contain)
.set_texture_size(texture_size_)
| align(alignment::CENTER)
| padding(10.f),
| align(alignment::CENTER),
// | padding(10.f),
// 普通后效控件
post_effect_widget{
blur_effect{20.f},
fill_box{}
},
}
} | stretch()
} | stretch()
}
};
layout_state state;

View File

@@ -494,6 +494,13 @@ namespace mirage {
float right = bounds.max().x();
float bottom = bounds.max().y();
// 计算 UV 坐标(基于视口大小)
// UV 坐标需要映射到离屏目标中子控件的实际位置
float uv_left = left / viewport_size_.x();
float uv_top = top / viewport_size_.y();
float uv_right = right / viewport_size_.x();
float uv_bottom = bottom / viewport_size_.y();
// 使用实际边界框尺寸,而不是遮罩参数中的尺寸
// 这样着色器才能正确计算非正方形区域中的圆形遮罩
float width = bounds.sizes().x();
@@ -514,8 +521,8 @@ namespace mirage {
vertices[0].color[1] = 1.0f;
vertices[0].color[2] = 1.0f;
vertices[0].color[3] = 1.0f;
vertices[0].uv[0] = 0.0f;
vertices[0].uv[1] = 0.0f;
vertices[0].uv[0] = uv_left;
vertices[0].uv[1] = uv_top;
vertices[0].data_a[0] = mask_type_f;
vertices[0].data_a[1] = params.softness;
vertices[0].data_a[2] = width;
@@ -532,8 +539,8 @@ namespace mirage {
vertices[1].color[1] = 1.0f;
vertices[1].color[2] = 1.0f;
vertices[1].color[3] = 1.0f;
vertices[1].uv[0] = 1.0f;
vertices[1].uv[1] = 0.0f;
vertices[1].uv[0] = uv_right;
vertices[1].uv[1] = uv_top;
vertices[1].data_a[0] = mask_type_f;
vertices[1].data_a[1] = params.softness;
vertices[1].data_a[2] = width;
@@ -550,8 +557,8 @@ namespace mirage {
vertices[2].color[1] = 1.0f;
vertices[2].color[2] = 1.0f;
vertices[2].color[3] = 1.0f;
vertices[2].uv[0] = 1.0f;
vertices[2].uv[1] = 1.0f;
vertices[2].uv[0] = uv_right;
vertices[2].uv[1] = uv_bottom;
vertices[2].data_a[0] = mask_type_f;
vertices[2].data_a[1] = params.softness;
vertices[2].data_a[2] = width;
@@ -568,8 +575,8 @@ namespace mirage {
vertices[3].color[1] = 1.0f;
vertices[3].color[2] = 1.0f;
vertices[3].color[3] = 1.0f;
vertices[3].uv[0] = 0.0f;
vertices[3].uv[1] = 1.0f;
vertices[3].uv[0] = uv_left;
vertices[3].uv[1] = uv_bottom;
vertices[3].data_a[0] = mask_type_f;
vertices[3].data_a[1] = params.softness;
vertices[3].data_a[2] = width;

View File

@@ -44,7 +44,7 @@ namespace mirage {
/// @brief 根据边界框计算遮罩参数
/// @param bounds 边界框
/// @return 遮罩参数
[[nodiscard]] auto get_mask_params(aabb2d_t bounds) const -> mask_params {
[[nodiscard]] auto get_mask_params(const aabb2d_t& bounds) const -> mask_params {
const vec2f_t center = bounds.center();
const vec2f_t size = bounds.sizes();

View File

@@ -136,8 +136,8 @@ namespace mirage {
template <widget Child, mask_shape Mask>
auto mask(Child&& child, Mask&& m) {
return mask_widget<std::decay_t<Child>, std::decay_t<Mask>>(
std::forward<Child>(child),
std::forward<Mask>(m)
std::forward<Mask>(m),
std::forward<Child>(child)
);
}
} // namespace mirage

View File

@@ -43,7 +43,7 @@ namespace mirage {
/// @brief 根据边界框计算遮罩参数
/// @param bounds 边界框
/// @return 遮罩参数
[[nodiscard]] auto get_mask_params(aabb2d_t bounds) const -> mask_params {
[[nodiscard]] auto get_mask_params(const aabb2d_t& bounds) const -> mask_params {
const vec2f_t center = bounds.center();
const vec2f_t size = bounds.sizes();

View File

@@ -30,19 +30,8 @@ namespace mirage {
/// @brief 设置统一圆角半径(四个角相同)
/// @param r 圆角半径
/// @return 返回自身引用,支持链式调用(包括临时对象)
auto&& corner_radius(this auto&& self, float r) {
self.corner_radii_ = vec4f_t(r, r, r, r);
return std::forward<decltype(self)>(self);
}
/// @brief 设置四个角分别的圆角半径
/// @param tl 左上角半径
/// @param tr 右上角半径
/// @param br 右下角半径
/// @param bl 左下角半径
/// @return 返回自身引用,支持链式调用(包括临时对象)
auto&& corner_radius(this auto&& self, float tl, float tr, float br, float bl) {
self.corner_radii_ = vec4f_t(tl, tr, br, bl);
auto&& corner_radius(this auto&& self, const rect_corner_radius r) {
self.corner_radii_ = vec4f_t(r.top_left, r.top_right, r.bottom_right, r.bottom_left);
return std::forward<decltype(self)>(self);
}
@@ -75,7 +64,7 @@ namespace mirage {
/// @brief 根据边界框计算遮罩参数
/// @param bounds 边界框
/// @return 遮罩参数
[[nodiscard]] auto get_mask_params(aabb2d_t bounds) const -> mask_params {
[[nodiscard]] auto get_mask_params(const aabb2d_t& bounds) const -> mask_params {
const vec2f_t center = bounds.center();
const vec2f_t size = bounds.sizes();