#include "cru/ui/render/FlexLayoutRenderObject.h" #include "cru/common/Logger.h" #include "cru/platform/graphics/util/Painter.h" #include "cru/ui/Base.h" #include "cru/ui/render/LayoutHelper.h" #include "cru/ui/render/MeasureRequirement.h" #include #include #include namespace cru::ui::render { std::u16string_view FlexLayoutRenderObject::GetName() const { return u"FlexLayoutRenderObject"; } struct tag_horizontal_t {}; struct tag_vertical_t {}; template constexpr auto GetMain(const TSize& size, tag_horizontal_t) { return size.width; } template constexpr auto GetCross(const TSize& size, tag_horizontal_t) { return size.height; } template constexpr auto GetMain(const TSize& size, tag_vertical_t) { return size.height; } template constexpr auto GetCross(const TSize& size, tag_vertical_t) { return size.width; } template constexpr auto& GetMain(TSize& size, tag_horizontal_t) { return size.width; } template constexpr auto& GetCross(TSize& size, tag_horizontal_t) { return size.height; } template constexpr auto& GetMain(TSize& size, tag_vertical_t) { return size.height; } template constexpr auto& GetCross(TSize& size, tag_vertical_t) { return size.width; } template constexpr TSize CreateTSize(decltype(std::declval().width) main, decltype(std::declval().height) cross, tag_horizontal_t) { return TSize{main, cross}; } template constexpr TSize CreateTSize(decltype(std::declval().width) main, decltype(std::declval().height) cross, tag_vertical_t) { return TSize{cross, main}; } enum class FlexLayoutAdjustType { None, Expand, Shrink }; namespace { void Remove(std::vector& v, const std::vector& to_remove_v) { Index current = 0; for (auto to_remove : to_remove_v) { while (v[current] != to_remove) { current++; } v.erase(v.cbegin() + current); } } template || std::is_same_v>> Size FlexLayoutMeasureContentImpl( const MeasureRequirement& requirement, const MeasureSize& preferred_size, const std::vector& children, const std::vector& layout_data, Alignment item_cross_align, StringView log_tag) { Expects(children.size() == layout_data.size()); direction_tag_t direction_tag; const Index child_count = children.size(); MeasureLength preferred_main_length = GetMain(preferred_size, direction_tag); MeasureLength preferred_cross_length = GetCross(preferred_size, direction_tag); MeasureLength max_main_length = GetMain(requirement.max, direction_tag); MeasureLength max_cross_length = GetCross(requirement.max, direction_tag); MeasureLength min_main_length = GetMain(requirement.min, direction_tag); MeasureLength min_cross_length = GetCross(requirement.min, direction_tag); // step 1. for (Index i = 0; i < child_count; i++) { const auto child = children[i]; child->Measure(MeasureRequirement{CreateTSize( MeasureLength::NotSpecified(), max_cross_length, direction_tag), MeasureSize::NotSpecified()}, MeasureSize::NotSpecified()); } float total_length = 0.f; for (auto child : children) { total_length += GetMain(child->GetSize(), direction_tag); } // step 2. FlexLayoutAdjustType adjust_type = FlexLayoutAdjustType::None; float target_length = -1.f; // Use a strange value to indicate error. This value should be // assigned before usage. Or program has a bug. if (preferred_main_length.IsSpecified()) { const float preferred_main_length_value = preferred_main_length.GetLengthOrUndefined(); target_length = preferred_main_length_value; if (total_length > preferred_main_length_value) { adjust_type = FlexLayoutAdjustType::Shrink; } else if (total_length < preferred_main_length_value) { adjust_type = FlexLayoutAdjustType::Expand; } } else { if (max_main_length.IsSpecified()) { const float max_main_length_value = max_main_length.GetLengthOrUndefined(); if (max_main_length_value < total_length) { adjust_type = FlexLayoutAdjustType::Shrink; target_length = max_main_length_value; } else if (max_main_length_value > total_length) { for (auto data : layout_data) { if (data.expand_factor > 0) { adjust_type = FlexLayoutAdjustType::Expand; target_length = max_main_length_value; break; } } } } // Do not use else here. if (adjust_type == FlexLayoutAdjustType::None && min_main_length.IsSpecified() && min_main_length.GetLengthOrUndefined() > total_length) { adjust_type = FlexLayoutAdjustType::Expand; target_length = min_main_length.GetLengthOrUndefined(); } } // step 3. if (adjust_type == FlexLayoutAdjustType::Shrink) { std::vector shrink_list; for (Index i = 0; i < child_count; i++) { if (layout_data[i].shrink_factor > 0) { shrink_list.push_back(i); } } while (!shrink_list.empty()) { const float total_shrink_length = total_length - target_length; float total_shrink_factor = 0.f; std::vector to_remove; for (Index i : shrink_list) { total_shrink_factor += layout_data[i].shrink_factor; } for (Index i : shrink_list) { const auto child = children[i]; const float shrink_length = layout_data[i].shrink_factor / total_shrink_factor * total_shrink_length; float new_measure_length = GetMain(child->GetSize(), direction_tag) - shrink_length; MeasureLength child_min_main_length = GetMain(child->GetMinSize(), direction_tag); if (child_min_main_length.IsSpecified() && new_measure_length < child_min_main_length.GetLengthOrUndefined()) { new_measure_length = child_min_main_length.GetLengthOrUndefined(); } else if (new_measure_length < 0.f) { new_measure_length = 0.f; } child->Measure(MeasureRequirement{CreateTSize( new_measure_length, max_cross_length, direction_tag), MeasureSize::NotSpecified()}, CreateTSize(new_measure_length, MeasureLength::NotSpecified(), direction_tag)); const Size new_size = child->GetSize(); const float new_main_length = GetMain(new_size, direction_tag); if (new_main_length > new_measure_length) { to_remove.push_back(i); } } total_length = 0.f; for (auto child : children) { total_length += GetMain(child->GetSize(), direction_tag); } if (total_length <= target_length) break; Remove(shrink_list, to_remove); } } else if (adjust_type == FlexLayoutAdjustType::Expand) { std::vector expand_list; for (Index i = 0; i < child_count; i++) { if (layout_data[i].expand_factor > 0) { expand_list.push_back(i); } } while (!expand_list.empty()) { const float total_expand_length = target_length - total_length; float total_expand_factor = 0.f; std::vector to_remove; for (Index i : expand_list) { total_expand_factor += layout_data[i].expand_factor; } for (Index i : expand_list) { const auto child = children[i]; const float expand_length = layout_data[i].expand_factor / total_expand_factor * total_expand_length; float new_measure_length = GetMain(child->GetSize(), direction_tag) + expand_length; MeasureLength child_max_main_length = GetMain(child->GetMaxSize(), direction_tag); if (child_max_main_length.IsSpecified() && new_measure_length > child_max_main_length.GetLengthOrUndefined()) { new_measure_length = child_max_main_length.GetLengthOrUndefined(); } child->Measure( MeasureRequirement{ CreateTSize(MeasureLength::NotSpecified(), max_cross_length, direction_tag), CreateTSize(new_measure_length, MeasureLength::NotSpecified(), direction_tag)}, CreateTSize(new_measure_length, MeasureLength::NotSpecified(), direction_tag)); const Size new_size = child->GetSize(); const float new_main_length = GetMain(new_size, direction_tag); if (new_main_length < new_measure_length) { to_remove.push_back(i); } } total_length = 0.f; for (auto child : children) { total_length += GetMain(child->GetSize(), direction_tag); } if (total_length >= target_length) break; Remove(expand_list, to_remove); } } float child_max_cross_length = 0.f; for (auto child : children) { const float cross_length = GetCross(child->GetSize(), direction_tag); if (cross_length > child_max_cross_length) { child_max_cross_length = cross_length; } } if (max_main_length.IsSpecified() && total_length > max_main_length.GetLengthOrUndefined()) { log::TagWarn( log_tag, u"(Measure) Children's main axis length exceeds required max length."); total_length = max_main_length.GetLengthOrUndefined(); } else if (min_main_length.IsSpecified() && total_length < min_main_length.GetLengthOrUndefined()) { total_length = min_main_length.GetLengthOrUndefined(); } child_max_cross_length = std::max(preferred_cross_length.GetLengthOr0(), child_max_cross_length); child_max_cross_length = std::max(min_cross_length.GetLengthOr0(), child_max_cross_length); for (Index i = 0; i < child_count; i++) { auto child_layout_data = layout_data[i]; auto child = children[i]; if (child_layout_data.cross_alignment.value_or(item_cross_align) == Alignment::Stretch) { auto size = child->GetSize(); GetCross(size, direction_tag) = child_max_cross_length; child->Measure({size, size}, MeasureSize::NotSpecified()); } } return CreateTSize(total_length, child_max_cross_length, direction_tag); } } // namespace Size FlexLayoutRenderObject::OnMeasureContent( const MeasureRequirement& requirement, const MeasureSize& preferred_size) { const bool horizontal = (direction_ == FlexDirection::Horizontal || direction_ == FlexDirection::HorizontalReverse); if (horizontal) { return FlexLayoutMeasureContentImpl( requirement, preferred_size, GetChildren(), GetChildLayoutDataList(), item_cross_align_, log_tag); } else { return FlexLayoutMeasureContentImpl( requirement, preferred_size, GetChildren(), GetChildLayoutDataList(), item_cross_align_, log_tag); } } void FlexLayoutRenderObject::OnLayoutContent(const Rect& content_rect) { const auto& children = GetChildren(); const Index child_count = children.size(); if (direction_ == FlexDirection::Horizontal) { float current_main_offset = 0; for (Index i = 0; i < child_count; i++) { const auto child = children[i]; const auto size = child->GetSize(); const auto cross_align = GetChildLayoutDataList()[i].cross_alignment.value_or( GetItemCrossAlign()); child->Layout( Point{content_rect.left + current_main_offset, CalculateAnchorByAlignment(cross_align, content_rect.top, content_rect.height, size.height)}); current_main_offset += size.width; } } else if (direction_ == FlexDirection::HorizontalReverse) { float current_main_offset = 0; for (Index i = 0; i < child_count; i++) { const auto child = children[i]; const auto size = child->GetSize(); const auto cross_align = GetChildLayoutDataList()[i].cross_alignment.value_or( GetItemCrossAlign()); child->Layout( Point{content_rect.GetRight() - current_main_offset, CalculateAnchorByAlignment(cross_align, content_rect.top, content_rect.height, size.height)}); current_main_offset += size.width; } } else if (direction_ == FlexDirection::Vertical) { float current_main_offset = 0; for (Index i = 0; i < child_count; i++) { const auto child = children[i]; const auto size = child->GetSize(); const auto cross_align = GetChildLayoutDataList()[i].cross_alignment.value_or( GetItemCrossAlign()); child->Layout(Point{ CalculateAnchorByAlignment(cross_align, content_rect.left, content_rect.width, size.width), content_rect.top + current_main_offset, }); current_main_offset += size.height; } } else { float current_main_offset = 0; for (Index i = 0; i < child_count; i++) { const auto child = children[i]; const auto size = child->GetSize(); const auto cross_align = GetChildLayoutDataList()[i].cross_alignment.value_or( GetItemCrossAlign()); child->Layout( Point{CalculateAnchorByAlignment(cross_align, content_rect.left, content_rect.width, size.width), content_rect.GetBottom() - current_main_offset}); current_main_offset += size.height; } } } } // namespace cru::ui::render