191 lines
6.3 KiB
C++
191 lines
6.3 KiB
C++
#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;
|
||
} |