#include "ui/views/layout/flex_layout.h"
#include <algorithm>
#include <functional>
#include <numeric>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
#include "base/check_op.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/notimplemented.h"
#include "base/numerics/safe_conversions.h"
#include "ui/base/class_property.h"
#include "ui/events/event_target.h"
#include "ui/events/event_target_iterator.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/views/controls/tabbed_pane/tabbed_pane.h"
#include "ui/views/layout/flex_layout_types.h"
#include "ui/views/layout/normalized_geometry.h"
#include "ui/views/layout/proposed_layout.h"
#include "ui/views/view.h"
#include "ui/views/view_class_properties.h"
namespace views {
namespace {
struct FlexChildData {
explicit FlexChildData(const FlexSpecification& flex) : flex(flex) {}
FlexChildData(const FlexChildData&) = delete;
FlexChildData& operator=(const FlexChildData&) = delete;
FlexChildData(FlexChildData&& other) = default;
std::string ToString() const {
std::ostringstream oss;
oss << "{ preferred " << preferred_size.ToString() << " current "
<< current_size.ToString() << " min " << minimum_size.ToString()
<< " base " << flex_base_content_size.ToString() << " max "
<< maximum_size.ToString() << " margins " << margins.ToString()
<< (using_default_margins ? " (using default)" : "") << " padding "
<< internal_padding.ToString() << " bounds " << actual_bounds.ToString()
<< " }";
return oss.str();
}
NormalizedSize preferred_size;
NormalizedSize current_size;
NormalizedSize pending_size;
NormalizedSize minimum_size;
NormalizedSize maximum_size;
NormalizedSize flex_base_content_size;
NormalizedInsets margins;
bool using_default_margins = true;
NormalizedInsets internal_padding;
NormalizedRect actual_bounds;
FlexSpecification flex;
};
template <typename T>
T GetViewProperty(const View* view,
const ui::PropertyHandler& defaults,
const ui::ClassProperty<T*>* property,
bool* is_default = nullptr) {
T* found_value = view->GetProperty(property);
if (found_value) {
if (is_default) {
*is_default = false;
}
return *found_value;
}
if (is_default) {
*is_default = true;
}
found_value = defaults.GetProperty(property);
if (found_value) {
return *found_value;
}
return T();
}
template <typename T>
T MaybeReverse(const T& list, FlexAllocationOrder order) {
return order == FlexAllocationOrder::kReverse ? T(list.rbegin(), list.rend())
: list;
}
}
constexpr LayoutAlignment FlexLayout::kDefaultMainAxisAlignment;
constexpr LayoutAlignment FlexLayout::kDefaultCrossAxisAlignment;
class FlexLayout::ChildViewSpacing {
public:
using GetViewSpacingCallback =
base::RepeatingCallback<int(std::optional<size_t>,
std::optional<size_t>)>;
explicit ChildViewSpacing(GetViewSpacingCallback get_view_spacing);
ChildViewSpacing(const ChildViewSpacing& other) = default;
ChildViewSpacing& operator=(const ChildViewSpacing& other) = default;
bool HasViewIndex(size_t view_index) const;
int GetLeadingInset() const;
int GetTrailingInset() const;
int GetLeadingSpace(size_t view_index) const;
int GetTotalSpace() const;
SizeBound GetMaxSize(size_t view_index,
int current_size,
const SizeBound& available_space) const;
int GetTotalSizeChangeForNewSize(size_t view_index,
int current_size,
int new_size) const;
void AddViewIndex(size_t view_index,
int* new_leading = nullptr,
int* new_trailing = nullptr);
private:
std::optional<size_t> GetPreviousViewIndex(size_t view_index) const;
std::optional<size_t> GetNextViewIndex(size_t view_index) const;
int GetAddDelta(size_t view_index) const;
GetViewSpacingCallback get_view_spacing_;
std::map<size_t, int> leading_spacings_;
int trailing_space_;
};
FlexLayout::ChildViewSpacing::ChildViewSpacing(
GetViewSpacingCallback get_view_spacing)
: get_view_spacing_(std::move(get_view_spacing)),
trailing_space_(get_view_spacing_.Run(std::nullopt, std::nullopt)) {}
bool FlexLayout::ChildViewSpacing::HasViewIndex(size_t view_index) const {
return leading_spacings_.find(view_index) != leading_spacings_.end();
}
int FlexLayout::ChildViewSpacing::GetLeadingInset() const {
if (leading_spacings_.empty()) {
return 0;
}
return leading_spacings_.begin()->second;
}
int FlexLayout::ChildViewSpacing::GetTrailingInset() const {
return trailing_space_;
}
int FlexLayout::ChildViewSpacing::GetLeadingSpace(size_t view_index) const {
auto it = leading_spacings_.find(view_index);
CHECK(it != leading_spacings_.end());
return it->second;
}
int FlexLayout::ChildViewSpacing::GetTotalSpace() const {
return std::accumulate(
leading_spacings_.cbegin(), leading_spacings_.cend(), trailing_space_,
[](int total, const auto& value) { return total + value.second; });
}
SizeBound FlexLayout::ChildViewSpacing::GetMaxSize(
size_t view_index,
int current_size,
const SizeBound& available_space) const {
DCHECK_GE(available_space, 0);
if (HasViewIndex(view_index)) {
return current_size + available_space;
}
DCHECK_EQ(0, current_size);
return std::max<SizeBound>(available_space - GetAddDelta(view_index), 0);
}
int FlexLayout::ChildViewSpacing::GetTotalSizeChangeForNewSize(
size_t view_index,
int current_size,
int new_size) const {
return HasViewIndex(view_index) ? new_size - current_size
: new_size + GetAddDelta(view_index);
}
void FlexLayout::ChildViewSpacing::AddViewIndex(size_t view_index,
int* new_leading,
int* new_trailing) {
DCHECK(!HasViewIndex(view_index));
std::optional<size_t> prev = GetPreviousViewIndex(view_index);
std::optional<size_t> next = GetNextViewIndex(view_index);
const int leading_space = get_view_spacing_.Run(prev, view_index);
const int trailing_space = get_view_spacing_.Run(view_index, next);
leading_spacings_[view_index] = leading_space;
if (next) {
leading_spacings_[*next] = trailing_space;
} else {
trailing_space_ = trailing_space;
}
if (new_leading) {
*new_leading = leading_space;
}
if (new_trailing) {
*new_trailing = trailing_space;
}
}
std::optional<size_t> FlexLayout::ChildViewSpacing::GetPreviousViewIndex(
size_t view_index) const {
const auto it = leading_spacings_.lower_bound(view_index);
if (it == leading_spacings_.begin()) {
return std::nullopt;
}
return std::prev(it)->first;
}
std::optional<size_t> FlexLayout::ChildViewSpacing::GetNextViewIndex(
size_t view_index) const {
const auto it = leading_spacings_.upper_bound(view_index);
if (it == leading_spacings_.end()) {
return std::nullopt;
}
return it->first;
}
int FlexLayout::ChildViewSpacing::GetAddDelta(size_t view_index) const {
DCHECK(!HasViewIndex(view_index));
std::optional<size_t> prev = GetPreviousViewIndex(view_index);
std::optional<size_t> next = GetNextViewIndex(view_index);
const int old_spacing = next ? GetLeadingSpace(*next) : GetTrailingInset();
const int new_spacing = get_view_spacing_.Run(prev, view_index) +
get_view_spacing_.Run(view_index, next);
return new_spacing - old_spacing;
}
struct FlexLayout::FlexLayoutData {
FlexLayoutData() = default;
FlexLayoutData(const FlexLayoutData&) = delete;
FlexLayoutData& operator=(const FlexLayoutData&) = delete;
~FlexLayoutData() = default;
size_t num_children() const { return child_data.size(); }
std::string ToString() const {
std::ostringstream oss;
oss << "{ " << total_size.ToString() << "\n" << layout.ToString() << " {\n";
bool first = true;
for (const FlexChildData& flex_child : child_data) {
if (first) {
first = false;
} else {
oss << ",\n";
}
oss << flex_child.ToString();
}
oss << "}\nmargin " << interior_margin.ToString() << " insets "
<< host_insets.ToString() << "\n}";
return oss.str();
}
void SetCurrentSize(size_t view_index, NormalizedSize size) {
child_data[view_index].current_size = size;
layout.child_layouts[view_index].visible = size.main() > 0;
}
ProposedLayout layout;
std::vector<FlexChildData> child_data;
NormalizedSize total_size;
NormalizedInsets interior_margin;
NormalizedInsets host_insets;
};
FlexLayout::PropertyHandler::PropertyHandler(FlexLayout* layout)
: layout_(layout) {}
void FlexLayout::PropertyHandler::AfterPropertyChange(const void* key,
int64_t old_value) {
layout_->InvalidateHost(true);
}
FlexLayout::FlexLayout() {
SetDefault(kCrossAxisAlignmentKey, kDefaultCrossAxisAlignment);
}
FlexLayout::~FlexLayout() = default;
FlexLayout& FlexLayout::SetOrientation(LayoutOrientation orientation) {
if (orientation != orientation_) {
orientation_ = orientation;
InvalidateHost(true);
}
return *this;
}
FlexLayout& FlexLayout::SetIncludeHostInsetsInLayout(
bool include_host_insets_in_layout) {
if (include_host_insets_in_layout != include_host_insets_in_layout_) {
include_host_insets_in_layout_ = include_host_insets_in_layout;
InvalidateHost(true);
}
return *this;
}
FlexLayout& FlexLayout::SetCollapseMargins(bool collapse_margins) {
if (collapse_margins != collapse_margins_) {
collapse_margins_ = collapse_margins;
InvalidateHost(true);
}
return *this;
}
FlexLayout& FlexLayout::SetMainAxisAlignment(
LayoutAlignment main_axis_alignment) {
DCHECK_NE(main_axis_alignment, LayoutAlignment::kStretch)
<< "Main axis stretch/justify is not yet supported.";
if (main_axis_alignment_ != main_axis_alignment) {
main_axis_alignment_ = main_axis_alignment;
InvalidateHost(true);
}
return *this;
}
FlexLayout& FlexLayout::SetCrossAxisAlignment(
LayoutAlignment cross_axis_alignment) {
return SetDefault(kCrossAxisAlignmentKey, cross_axis_alignment);
}
FlexLayout& FlexLayout::SetInteriorMargin(const gfx::Insets& interior_margin) {
if (interior_margin_ != interior_margin) {
interior_margin_ = interior_margin;
InvalidateHost(true);
}
return *this;
}
FlexLayout& FlexLayout::SetIgnoreDefaultMainAxisMargins(
bool ignore_default_main_axis_margins) {
if (ignore_default_main_axis_margins_ != ignore_default_main_axis_margins) {
ignore_default_main_axis_margins_ = ignore_default_main_axis_margins;
InvalidateHost(true);
}
return *this;
}
FlexLayout& FlexLayout::SetMinimumCrossAxisSize(int size) {
if (minimum_cross_axis_size_ != size) {
minimum_cross_axis_size_ = size;
InvalidateHost(true);
}
return *this;
}
FlexLayout& FlexLayout::SetFlexAllocationOrder(
FlexAllocationOrder flex_allocation_order) {
if (flex_allocation_order_ != flex_allocation_order) {
flex_allocation_order_ = flex_allocation_order;
InvalidateHost(true);
}
return *this;
}
FlexRule FlexLayout::GetDefaultFlexRule() const {
return base::BindRepeating(&FlexLayout::DefaultFlexRuleImpl,
base::Unretained(this));
}
ProposedLayout FlexLayout::CalculateProposedLayout(
const SizeBounds& size_bounds) const {
FlexLayoutData data;
if (include_host_insets_in_layout()) {
data.interior_margin =
Normalize(orientation(), interior_margin() + host_view()->GetInsets());
} else {
data.host_insets = Normalize(orientation(), host_view()->GetInsets());
data.interior_margin = Normalize(orientation(), interior_margin());
}
NormalizedSizeBounds bounds = Normalize(orientation(), size_bounds);
bounds.Inset(data.host_insets);
bounds.set_cross(
std::max<SizeBound>(bounds.cross(), minimum_cross_axis_size()));
FlexOrderToViewIndexMap order_to_view_index;
InitializeChildData(bounds, data, order_to_view_index);
ChildViewSpacing child_spacing(
base::BindRepeating(&FlexLayout::CalculateChildSpacing,
base::Unretained(this), std::cref(data)));
UpdateLayoutFromChildren(bounds, data, child_spacing);
CalculateNonFlexAvailableSpace(
std::max<SizeBound>(0, bounds.main() - data.total_size.main()),
order_to_view_index, child_spacing, data);
if (order_to_view_index.size() > 1) {
std::vector<NormalizedSize> backup_size(data.num_children());
for (size_t i = 0; i < data.num_children(); ++i) {
FlexChildData& flex_child = data.child_data[i];
backup_size[i] = flex_child.maximum_size;
flex_child.maximum_size = flex_child.preferred_size;
}
AllocateFlexItem(bounds, order_to_view_index, data, child_spacing, true);
for (size_t i = 0; i < data.num_children(); ++i) {
FlexChildData& flex_child = data.child_data[i];
flex_child.maximum_size = backup_size[i];
}
}
AllocateFlexItem(bounds, order_to_view_index, data, child_spacing, true);
AllocateRemainingSpaceIfNeeded(bounds, order_to_view_index, data,
child_spacing);
NormalizedSize host_size = data.total_size;
host_size.Enlarge(data.host_insets.main_size(),
data.host_insets.cross_size());
data.layout.host_size = Denormalize(orientation(), host_size);
CalculateChildBounds(size_bounds, data);
return data.layout;
}
NormalizedSize FlexLayout::GetPreferredSizeForRule(
const FlexRule& rule,
const View* child,
const SizeBound& available_cross) const {
const NormalizedSize default_size =
Normalize(orientation(), rule.Run(child, SizeBounds()));
if (!available_cross.is_bounded()) {
return default_size;
}
const NormalizedSize stretch_size = Normalize(
orientation(),
rule.Run(child,
Denormalize(orientation(), NormalizedSizeBounds(
SizeBound(), available_cross))));
NormalizedSize size = default_size;
if (orientation() == LayoutOrientation::kVertical) {
const LayoutAlignment cross_align =
GetViewProperty(child, layout_defaults_, kCrossAxisAlignmentKey);
if (cross_align == LayoutAlignment::kStretch) {
return stretch_size;
}
size.set_main(std::max(size.main(), stretch_size.main()));
}
size.set_cross(std::min(size.cross(), stretch_size.cross()));
return size;
}
NormalizedSize FlexLayout::GetCurrentSizeForRule(
const FlexRule& rule,
const View* child,
const NormalizedSizeBounds& available) const {
return Normalize(orientation(),
rule.Run(child, Denormalize(orientation(), available)));
}
void FlexLayout::InitializeChildData(
const NormalizedSizeBounds& bounds,
FlexLayoutData& data,
FlexOrderToViewIndexMap& flex_order_to_index) const {
const bool main_axis_bounded = bounds.main().is_bounded();
for (View* child : host_view()->children()) {
if (!IsChildIncludedInLayout(child)) {
continue;
}
const size_t view_index = data.num_children();
data.layout.child_layouts.emplace_back(ChildLayout{child});
ChildLayout& child_layout = data.layout.child_layouts.back();
data.child_data.emplace_back(
GetViewProperty(child, layout_defaults_, views::kFlexBehaviorKey));
FlexChildData& flex_child = data.child_data.back();
flex_child.margins =
Normalize(orientation(),
GetViewProperty(child, layout_defaults_, views::kMarginsKey,
&flex_child.using_default_margins));
flex_child.internal_padding = Normalize(
orientation(),
GetViewProperty(child, layout_defaults_, views::kInternalPaddingKey));
const SizeBound available_cross =
GetAvailableCrossAxisSize(data, view_index, bounds);
SetCrossAxis(&child_layout.available_size, orientation(), available_cross);
flex_child.preferred_size =
GetPreferredSizeForRule(flex_child.flex.rule(), child, available_cross);
flex_child.minimum_size =
GetCurrentSizeForRule(flex_child.flex.rule(), child,
NormalizedSizeBounds(0, available_cross));
flex_child.maximum_size = GetCurrentSizeForRule(
flex_child.flex.rule(), child,
NormalizedSizeBounds(bounds.main(), available_cross));
data.SetCurrentSize(view_index, main_axis_bounded
? flex_child.minimum_size
: flex_child.preferred_size);
const int weight = flex_child.flex.weight();
bool can_flex =
weight > 0 ||
flex_child.current_size.main() < flex_child.preferred_size.main() ||
(weight == 0 &&
flex_child.maximum_size.main() > flex_child.preferred_size.main());
if (can_flex) {
flex_order_to_index[flex_child.flex.order()].push_back(view_index);
}
if (main_axis_bounded) {
flex_child.flex_base_content_size = std::min<NormalizedSize>(
std::max<NormalizedSize>(flex_child.minimum_size,
flex_child.preferred_size),
flex_child.maximum_size);
} else {
flex_child.flex_base_content_size = flex_child.maximum_size;
}
}
}
void FlexLayout::CalculateChildBounds(const SizeBounds& size_bounds,
FlexLayoutData& data) const {
const NormalizedSizeBounds normalized_bounds =
Normalize(orientation(), size_bounds);
const NormalizedSize normalized_host_size =
Normalize(orientation(), data.layout.host_size);
int available_main = normalized_bounds.main().is_bounded()
? normalized_bounds.main().value()
: normalized_host_size.main();
available_main = std::max(0, available_main - data.host_insets.main_size());
const int excess_main = available_main - data.total_size.main();
NormalizedPoint start(data.host_insets.main_leading(),
data.host_insets.cross_leading());
switch (main_axis_alignment()) {
case LayoutAlignment::kStart:
break;
case LayoutAlignment::kCenter:
start.set_main(start.main() + excess_main / 2);
break;
case LayoutAlignment::kEnd:
start.set_main(start.main() + excess_main);
break;
case LayoutAlignment::kStretch:
case LayoutAlignment::kBaseline:
NOTIMPLEMENTED();
break;
}
for (size_t i = 0; i < data.num_children(); ++i) {
ChildLayout& child_layout = data.layout.child_layouts[i];
if (child_layout.visible) {
FlexChildData& flex_child = data.child_data[i];
NormalizedRect actual = flex_child.actual_bounds;
actual.Offset(start.main(), start.cross());
if (actual.size_main() > flex_child.preferred_size.main() &&
flex_child.flex.alignment() != LayoutAlignment::kStretch) {
Span container(actual.origin_main(), actual.size_main());
Span new_main(0, flex_child.preferred_size.main());
new_main.Align(container, flex_child.flex.alignment());
actual.set_origin_main(new_main.start());
actual.set_size_main(new_main.length());
}
child_layout.bounds = Denormalize(orientation(), actual);
}
}
}
void FlexLayout::CalculateNonFlexAvailableSpace(
const SizeBound& available_space,
const FlexOrderToViewIndexMap& flex_views,
const ChildViewSpacing& child_spacing,
FlexLayoutData& data) const {
std::set<size_t> all_flex_indices;
for (const auto& order_to_indices : flex_views) {
all_flex_indices.insert(order_to_indices.second.begin(),
order_to_indices.second.end());
}
for (size_t index = 0; index < data.child_data.size(); ++index) {
if (base::Contains(all_flex_indices, index)) {
continue;
}
const SizeBound max_size = child_spacing.GetMaxSize(
index, data.child_data[index].current_size.main(), available_space);
SetMainAxis(&data.layout.child_layouts[index].available_size, orientation(),
max_size);
}
}
Inset1D FlexLayout::GetCrossAxisMargins(const FlexLayoutData& layout,
size_t child_index) const {
const FlexChildData& child_data = layout.child_data[child_index];
const int leading_margin =
CalculateMargin(layout.interior_margin.cross_leading(),
child_data.margins.cross_leading(),
child_data.internal_padding.cross_leading());
const int trailing_margin =
CalculateMargin(layout.interior_margin.cross_trailing(),
child_data.margins.cross_trailing(),
child_data.internal_padding.cross_trailing());
return Inset1D(leading_margin, trailing_margin);
}
int FlexLayout::CalculateMargin(int margin1,
int margin2,
int internal_padding) const {
const int result =
collapse_margins() ? std::max(margin1, margin2) : margin1 + margin2;
return std::max(0, result - internal_padding);
}
SizeBound FlexLayout::GetAvailableCrossAxisSize(
const FlexLayoutData& layout,
size_t child_index,
const NormalizedSizeBounds& bounds) const {
const Inset1D cross_margins = GetCrossAxisMargins(layout, child_index);
return std::max<SizeBound>(0, bounds.cross() - cross_margins.size());
}
int FlexLayout::CalculateChildSpacing(
const FlexLayoutData& layout,
std::optional<size_t> child1_index,
std::optional<size_t> child2_index) const {
const FlexChildData* const child1 =
child1_index ? &layout.child_data[*child1_index] : nullptr;
const FlexChildData* const child2 =
child2_index ? &layout.child_data[*child2_index] : nullptr;
const int child1_trailing =
child1 && (child2 || !ignore_default_main_axis_margins() ||
!child1->using_default_margins)
? child1->margins.main_trailing()
: 0;
const int child2_leading =
child2 && (child1 || !ignore_default_main_axis_margins() ||
!child2->using_default_margins)
? child2->margins.main_leading()
: 0;
const int left_margin =
child1 ? child1_trailing : layout.interior_margin.main_leading();
const int right_margin =
child2 ? child2_leading : layout.interior_margin.main_trailing();
const int left_padding =
child1 ? child1->internal_padding.main_trailing() : 0;
const int right_padding =
child2 ? child2->internal_padding.main_leading() : 0;
return CalculateMargin(left_margin, right_margin,
left_padding + right_padding);
}
void FlexLayout::UpdateLayoutFromChildren(
const NormalizedSizeBounds& bounds,
FlexLayoutData& data,
ChildViewSpacing& child_spacing) const {
int min_cross_size =
std::max(minimum_cross_axis_size(),
CalculateMargin(data.interior_margin.cross_leading(),
data.interior_margin.cross_trailing(), 0));
data.total_size = NormalizedSize(0, min_cross_size);
std::vector<Inset1D> cross_spacings(data.num_children());
for (size_t i = 0; i < data.num_children(); ++i) {
FlexChildData& flex_child = data.child_data[i];
const bool is_visible = data.layout.child_layouts[i].visible;
cross_spacings[i] = GetCrossAxisMargins(data, i);
if (is_visible || flex_child.preferred_size.main() == 0) {
data.total_size.SetToMax(
0, cross_spacings[i].size() + flex_child.current_size.cross());
}
if (!is_visible) {
continue;
}
int leading_space;
if (child_spacing.HasViewIndex(i)) {
leading_space = child_spacing.GetLeadingSpace(i);
} else {
child_spacing.AddViewIndex(i, &leading_space);
}
data.total_size.Enlarge(leading_space, 0);
const int size_main = flex_child.current_size.main();
flex_child.actual_bounds.set_origin_main(data.total_size.main());
flex_child.actual_bounds.set_size_main(size_main);
data.total_size.Enlarge(size_main, 0);
}
data.total_size.Enlarge(child_spacing.GetTrailingInset(), 0);
SizeBound cross_axis_size =
bounds.cross().is_bounded() && bounds.cross().value() > 0
? bounds.cross()
: data.total_size.cross();
const Span cross_span(0, cross_axis_size.value());
for (size_t i = 0; i < data.num_children(); ++i) {
FlexChildData& flex_child = data.child_data[i];
flex_child.actual_bounds.set_size_cross(flex_child.current_size.cross());
const LayoutAlignment cross_align =
GetViewProperty(data.layout.child_layouts[i].child_view,
layout_defaults_, kCrossAxisAlignmentKey);
flex_child.actual_bounds.AlignCross(cross_span, cross_align,
cross_spacings[i]);
}
}
NormalizedSize FlexLayout::ClampSizeToMinAndMax(FlexLayoutData& data,
const size_t view_index,
SizeBound size) const {
FlexChildData& flex_child = data.child_data[view_index];
if (size.value() <= flex_child.minimum_size.main()) {
return flex_child.minimum_size;
}
ChildLayout& child_layout = data.layout.child_layouts[view_index];
const NormalizedSizeBounds available(
size, GetCrossAxis(orientation(), child_layout.available_size));
const NormalizedSize new_size = GetCurrentSizeForRule(
flex_child.flex.rule(), child_layout.child_view, available);
return std::min<NormalizedSize>(
std::max<NormalizedSize>(flex_child.minimum_size, new_size),
flex_child.maximum_size);
}
void FlexLayout::AllocateFlexItem(const NormalizedSizeBounds& bounds,
const FlexOrderToViewIndexMap& order_to_index,
FlexLayoutData& data,
ChildViewSpacing& child_spacing,
bool skip_zero_preferred_size_view) const {
for (const auto& flex_elem : order_to_index) {
CalculateFlexAvailableSpace(bounds, flex_elem.second, child_spacing, data);
ChildIndices view_indices;
std::ranges::copy_if(
MaybeReverse(flex_elem.second, flex_allocation_order()),
std::back_inserter(view_indices),
[skip_zero_preferred_size_view, &data](size_t child_index) {
return !skip_zero_preferred_size_view ||
data.child_data[child_index].preferred_size.main() > 0;
});
SizeBound remaining_free_space =
AllocateZeroWeightFlex(bounds, view_indices, data, child_spacing);
if (!skip_zero_preferred_size_view) {
FilterZeroSizeChildreIfNeeded(bounds, remaining_free_space, view_indices,
data, child_spacing);
}
while (ResolveFlexibleLengths(bounds, remaining_free_space, view_indices,
data, child_spacing)) {
continue;
}
UpdateLayoutFromChildren(bounds, data, child_spacing);
}
}
SizeBound FlexLayout::AllocateZeroWeightFlex(
const NormalizedSizeBounds& bounds,
ChildIndices& child_list,
FlexLayoutData& data,
ChildViewSpacing& child_spacing) const {
if (!bounds.main().is_bounded()) {
return SizeBound();
}
SizeBound remaining =
std::max<SizeBound>(0, bounds.main() - data.total_size.main());
auto it = child_list.begin();
while (it != child_list.end()) {
const size_t child_index = *it;
FlexChildData& flex_child = data.child_data[child_index];
if (flex_child.flex.weight() > 0) {
++it;
continue;
}
ChildLayout& child_layout = data.layout.child_layouts[child_index];
const int old_size =
child_layout.visible ? flex_child.current_size.main() : 0;
const SizeBound available_main =
child_spacing.GetMaxSize(child_index, old_size, remaining);
NormalizedSize new_size =
ClampSizeToMinAndMax(data, child_index, available_main);
if (new_size.main() > old_size) {
const int delta = child_spacing.GetTotalSizeChangeForNewSize(
child_index, old_size, new_size.main());
remaining -= delta;
data.SetCurrentSize(child_index, new_size);
if (!child_spacing.HasViewIndex(child_index)) {
child_spacing.AddViewIndex(child_index);
}
}
it = child_list.erase(it);
}
return remaining;
}
void FlexLayout::FilterZeroSizeChildreIfNeeded(
const NormalizedSizeBounds& bounds,
SizeBound& to_allocate,
ChildIndices& child_list,
FlexLayoutData& data,
ChildViewSpacing& child_spacing) const {
int flex_total = CalculateFlexTotal(data, child_list);
ChildIndices zero_size_children;
ChildViewSpacing temp_spacing(child_spacing);
const int old_spacing = temp_spacing.GetTotalSpace();
std::ranges::copy_if(child_list, std::back_inserter(zero_size_children),
[&child_spacing, &data](auto index) {
return !child_spacing.HasViewIndex(index) &&
data.child_data[index].preferred_size.main() ==
0;
});
if (zero_size_children.empty()) {
return;
}
for (auto index : zero_size_children) {
temp_spacing.AddViewIndex(index);
}
const int new_spacing = temp_spacing.GetTotalSpace();
const int delta = new_spacing - old_spacing;
if (delta + flex_total > to_allocate) {
child_list.remove_if([&child_spacing, &data](size_t index) {
return !child_spacing.HasViewIndex(index) &&
data.child_data[index].preferred_size.main() == 0;
});
return;
}
to_allocate -= delta;
child_spacing = temp_spacing;
for (size_t view_index : zero_size_children) {
data.layout.child_layouts[view_index].visible = true;
}
}
bool FlexLayout::ResolveFlexibleLengths(const NormalizedSizeBounds& bounds,
SizeBound& remaining_free_space,
ChildIndices& child_list,
FlexLayoutData& data,
ChildViewSpacing& child_spacing) const {
if (!remaining_free_space.is_bounded()) {
return false;
}
ChildViewSpacing proposed_spacing(child_spacing);
int delta = 0;
for (size_t child_index : child_list) {
const FlexChildData& flex_child = data.child_data[child_index];
delta += proposed_spacing.GetTotalSizeChangeForNewSize(
child_index, flex_child.current_size.main(),
flex_child.flex_base_content_size.main());
if (!proposed_spacing.HasViewIndex(child_index)) {
proposed_spacing.AddViewIndex(child_index);
}
}
SizeBound temp_remaining_free_space = remaining_free_space - delta;
int flex_total = CalculateFlexTotal(data, child_list);
ChildIndices min_violations;
ChildIndices max_violations;
int total_violation = 0;
for (auto view_index : child_list) {
FlexChildData& flex_child = data.child_data[view_index];
SizeBound child_size = flex_child.flex_base_content_size.main();
const int weight = flex_child.flex.weight();
DCHECK_GT(weight, 0);
const SizeBound extra_space =
base::ClampFloor(temp_remaining_free_space.value() * weight /
static_cast<float>(flex_total) +
0.5f);
child_size += extra_space;
const NormalizedSize new_size =
ClampSizeToMinAndMax(data, view_index, child_size);
flex_child.pending_size = new_size;
int violation = new_size.main() - child_size.value();
if (violation > 0) {
min_violations.push_back(view_index);
} else if (violation < 0) {
max_violations.push_back(view_index);
}
total_violation += violation;
temp_remaining_free_space -= extra_space;
flex_total -= weight;
}
if (total_violation) {
FreezeViolations(child_list, remaining_free_space,
total_violation > 0 ? min_violations : max_violations,
child_spacing, data);
return true;
} else {
ChildIndices temp_list(child_list);
return FreezeViolations(child_list, remaining_free_space, temp_list,
child_spacing, data);
}
}
bool FlexLayout::FreezeViolations(ChildIndices& child_list,
SizeBound& remaining_free_space,
ChildIndices& freeze_child_list,
ChildViewSpacing& child_spacing,
FlexLayoutData& data) const {
ChildViewSpacing new_spacing(child_spacing);
auto it = child_list.rbegin();
bool force_relayout = false;
while (!freeze_child_list.empty() && it != child_list.rend()) {
const size_t view_index = *it;
if (view_index != freeze_child_list.back()) {
++it;
continue;
}
child_list.erase(--it.base());
ChildLayout& child_layout = data.layout.child_layouts[view_index];
FlexChildData& flex_child = data.child_data[view_index];
NormalizedSize old_size = flex_child.current_size;
flex_child.current_size = flex_child.pending_size;
child_layout.visible = flex_child.current_size.main() > 0;
freeze_child_list.pop_back();
if (!child_layout.visible) {
remaining_free_space -= flex_child.current_size.main() - old_size.main();
force_relayout = true;
break;
}
remaining_free_space -= new_spacing.GetTotalSizeChangeForNewSize(
view_index, old_size.main(), flex_child.current_size.main());
if (!new_spacing.HasViewIndex(view_index)) {
new_spacing.AddViewIndex(view_index);
}
}
child_spacing = new_spacing;
return force_relayout;
}
void FlexLayout::AllocateRemainingSpaceIfNeeded(
const NormalizedSizeBounds& bounds,
const FlexOrderToViewIndexMap& order_to_index,
FlexLayoutData& data,
ChildViewSpacing& child_spacing) const {
if (!bounds.main().is_bounded() || bounds.main() <= data.total_size.main()) {
return;
}
for (size_t i = 0; i < data.num_children(); ++i) {
FlexChildData& flex_child = data.child_data[i];
flex_child.flex_base_content_size = flex_child.current_size;
}
AllocateFlexItem(bounds, order_to_index, data, child_spacing, false);
}
void FlexLayout::CalculateFlexAvailableSpace(
const NormalizedSizeBounds& bounds,
const ChildIndices& child_indices,
const ChildViewSpacing& child_spacing,
FlexLayoutData& data) const {
const SizeBound remaining_at_priority =
std::max<SizeBound>(0, bounds.main() - data.total_size.main());
for (size_t index : child_indices) {
ChildLayout& child_layout = data.layout.child_layouts[index];
if (!GetMainAxis(orientation(), child_layout.available_size).is_bounded()) {
const FlexChildData& flex_child = data.child_data[index];
const int old_size =
child_layout.visible ? flex_child.current_size.main() : 0;
const SizeBound available_size = std::max<SizeBound>(
flex_child.current_size.main(),
child_spacing.GetMaxSize(index, old_size, remaining_at_priority));
SetMainAxis(&child_layout.available_size, orientation(), available_size);
}
}
}
int FlexLayout::CalculateFlexTotal(const FlexLayoutData& data,
const ChildIndices& child_indices) {
return std::accumulate(child_indices.begin(), child_indices.end(), 0,
[&data](int total, size_t index) {
return total + data.child_data[index].flex.weight();
});
}
gfx::Size FlexLayout::DefaultFlexRuleImpl(const FlexLayout* flex_layout,
const View* view,
const SizeBounds& size_bounds) {
if (size_bounds == SizeBounds()) {
return flex_layout->GetPreferredSize(view);
}
if (size_bounds == SizeBounds(0, 0)) {
return flex_layout->GetMinimumSize(view);
}
return flex_layout->CalculateProposedLayout(size_bounds).host_size;
}
}