Files
mirage/docs/LAYOUT_UTILS_REFACTORING.md
nanako 3a7d810a15 Refactor widget event handling and layout system
- 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.
2025-11-30 14:15:04 +08:00

16 KiB
Raw Blame History

布局工具重构设计方案

1. 概述

本文档描述了将 v_stackh_stackoverlay 容器中重复的布局属性操作封装成通用工具的重构方案。

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 阶段一:创建工具文件(低风险)

  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. 架构图

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% 的重复代码,同时提升代码的可维护性和一致性。建议按照分阶段计划执行,每个阶段完成后进行充分测试。