- Removed the slot.h file, consolidating layout parameter definitions and slot template class. - Updated widget concept to improve readability and maintainability. - Introduced widget_event_adapter to connect window event dispatcher with widget event router. - Implemented widget_event_router for handling mouse events, including capturing and hover state management. - Created widget_event_types.h to define mouse event structures for widget interactions. - Enhanced event handling logic to support mouse button, movement, and scroll events.
16 KiB
16 KiB
布局工具重构设计方案
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 接口设计
#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 重构示例
重构前(当前代码):
// 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;
}
// ... 更多重复方法
重构后:
// 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 重构示例
重构前:
// 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)
);
重构后:
// 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 阶段一:创建工具文件(低风险)
- 创建
src/widget/layout_utils.h - 实现所有工具函数
- 添加单元测试验证工具函数正确性
4.2 阶段二:逐步迁移(中等风险)
-
v_stack 迁移
- 引入
layout_utils.h - 替换私有方法调用为工具函数
- 删除重复的私有方法
- 运行测试验证
- 引入
-
h_stack 迁移
- 同 v_stack 步骤
-
overlay 迁移
- 引入
layout_utils.h - 替换
extract_margin和边距计算 - 替换
get_event_children实现 - 运行测试验证
- 引入
4.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. 架构图
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% 的重复代码,同时提升代码的可维护性和一致性。建议按照分阶段计划执行,每个阶段完成后进行充分测试。