mirage/src/widget/layout_utils.h
2025-03-25 14:48:20 +08:00

191 lines
6.3 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#pragma once
#include "mwidget.h"
#include "geometry/geometry.h"
#include "misc/mirage_type.h"
/**
* @brief 为单个子部件布局的通用函数
*
* @param in_allotted_geometry 分配给容器的几何区域
* @param child_widget 子部件
* @param h_alignment 水平对齐方式
* @param v_alignment 垂直对齐方式
* @param margin 边距
*/
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 = margin_t() // 默认为空边距
);
template<orientation_t LayoutOrientation, typename SlotType>
void arrange_box_children(
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_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;
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;
float total_margins = 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;
if (!has_any_flag(widget->get_visibility(), in_visibility_filter))
continue;
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;
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;
}
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
};
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();
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
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);
// 第二轮:计算拉伸部件的最终尺寸
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) {
position += info.margin_start;
Eigen::Vector2f pos = Eigen::Vector2f::Zero();
Eigen::Vector2f size = container_size;
if (is_horizontal) {
if (is_reversed)
pos.x() = container_primary_size - position - info.size;
else
pos.x() = position;
pos.y() += info.margin_cross_start;
size.x() = info.size;
size.y() -= info.margin_start + info.margin_end;
} else {
if (is_reversed)
pos.y() = container_primary_size - position - info.size;
else
pos.y() = position;
pos.x() += info.margin_cross_start;
size.x() -= info.margin_start + info.margin_end;
size.y() = info.size;
}
const auto& child_geometry = in_allotted_geometry.make_child(pos, size);
info.widget->set_geometry(child_geometry);
info.widget->arrange_children(child_geometry);
position += info.size + info.margin_end;
}
}
// 计算水平或垂直框的期望大小
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().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 {
desired_size.x() = std::max(desired_size.x(), widget_desired_size.x());
desired_size.y() += widget_desired_size.y();
}
}
}
return desired_size;
}