1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 18:37:35 +00:00

LibWeb: Implement basic intrinsic sizing algorithms for flex containers

This patch adds support for MinContent and MaxContent layout to FFC.
This means that an FFC can now calculate intrinsic sizes for the flex
container, to be used by the parent formatting context.

There are some FIXME's as usual, but this already works on basic things.
This commit is contained in:
Andreas Kling 2022-04-06 01:20:20 +02:00
parent d94c7fa417
commit c8240e31a1
2 changed files with 190 additions and 1 deletions

View file

@ -42,7 +42,7 @@ FlexFormattingContext::FlexFormattingContext(FormattingState& state, Box const&
FlexFormattingContext::~FlexFormattingContext() = default;
void FlexFormattingContext::run(Box const& run_box, LayoutMode)
void FlexFormattingContext::run(Box const& run_box, LayoutMode layout_mode)
{
VERIFY(&run_box == &flex_container());
@ -65,6 +65,16 @@ void FlexFormattingContext::run(Box const& run_box, LayoutMode)
determine_flex_base_size_and_hypothetical_main_size(flex_item);
}
if (layout_mode == LayoutMode::MinContent || layout_mode == LayoutMode::MaxContent) {
// We're computing intrinsic size for the flex container.
determine_intrinsic_size_of_flex_container(layout_mode);
// Our caller is only interested in the content-width and content-height results,
// which have now been set on m_flex_container_state, so there's no need to continue
// the main layout algorithm after this point.
return;
}
// 4. Determine the main size of the flex container
determine_main_size_of_flex_container(main_is_constrained, main_min_size, main_max_size);
@ -606,6 +616,9 @@ float FlexFormattingContext::determine_min_main_size_of_child(Box const& box)
// https://www.w3.org/TR/css-flexbox-1/#algo-main-container
void FlexFormattingContext::determine_main_size_of_flex_container(bool const main_is_constrained, float const main_min_size, float const main_max_size)
{
// FIXME: This function should make use of our ability to calculate the flex container's
// intrinsic max-content sizes via LayoutMode::MaxContent.
if (!main_is_constrained || !m_available_space->main.has_value()) {
// Uses https://www.w3.org/TR/css-flexbox-1/#intrinsic-main-sizes
// 9.9.1
@ -1149,4 +1162,171 @@ void FlexFormattingContext::copy_dimensions_from_flex_items_to_boxes()
set_offset(box, flex_item.main_offset, flex_item.cross_offset);
}
}
// https://drafts.csswg.org/css-flexbox-1/#intrinsic-sizes
void FlexFormattingContext::determine_intrinsic_size_of_flex_container(LayoutMode layout_mode)
{
VERIFY(layout_mode != LayoutMode::Normal);
float main_size = calculate_intrinsic_main_size_of_flex_container(layout_mode);
float cross_size = calculate_intrinsic_cross_size_of_flex_container(layout_mode);
if (is_row_layout()) {
m_flex_container_state.content_width = main_size;
m_flex_container_state.content_height = cross_size;
} else {
m_flex_container_state.content_height = main_size;
m_flex_container_state.content_width = cross_size;
}
}
// https://drafts.csswg.org/css-flexbox-1/#intrinsic-main-sizes
float FlexFormattingContext::calculate_intrinsic_main_size_of_flex_container(LayoutMode layout_mode)
{
VERIFY(layout_mode != LayoutMode::Normal);
// The min-content main size of a single-line flex container is calculated identically to the max-content main size,
// except that the flex items min-content contributions are used instead of their max-content contributions.
// However, for a multi-line container, it is simply the largest min-content contribution of all the non-collapsed flex items in the flex container.
if (!is_single_line() && layout_mode == LayoutMode::MinContent) {
float largest_contribution = 0;
for (auto const& flex_item : m_flex_items) {
// FIXME: Skip collapsed flex items.
largest_contribution = max(largest_contribution, calculate_main_min_content_contribution(flex_item));
}
return largest_contribution;
}
// The max-content main size of a flex container is, fundamentally, the smallest size the flex container
// can take such that when flex layout is run with that container size, each flex item ends up at least
// as large as its max-content contribution, to the extent allowed by the items flexibility.
// It is calculated, considering only non-collapsed flex items, by:
// 1. For each flex item, subtract its outer flex base size from its max-content contribution size.
// If that result is positive, divide by its flex grow factor floored at 1;
// if negative, divide by its scaled flex shrink factor having floored the flex shrink factor at 1.
// This is the items max-content flex fraction.
for (auto& flex_item : m_flex_items) {
float contribution;
if (layout_mode == LayoutMode::MinContent)
contribution = calculate_main_min_content_contribution(flex_item);
else
contribution = calculate_main_max_content_contribution(flex_item);
float flex_fraction = contribution - flex_item.flex_base_size;
if (flex_fraction >= 0)
flex_fraction /= max(flex_item.box.computed_values().flex_grow(), 1.0f);
else
flex_fraction /= max(flex_item.box.computed_values().flex_shrink(), 1.0f) * flex_item.flex_base_size;
// FIXME: The name max_content_flex_fraction here is misleading, since we also use this code path for min-content sizing.
flex_item.max_content_flex_fraction = flex_fraction;
}
// 2. Place all flex items into lines of infinite length.
m_flex_lines.clear();
if (!m_flex_items.is_empty())
m_flex_lines.append(FlexLine {});
for (auto& flex_item : m_flex_items) {
// FIXME: Honor breaking requests.
m_flex_lines.last().items.append(&flex_item);
}
// 3. Within each line, find the largest max-content flex fraction among all the flex items.
// Add each items flex base size to the product of its flex grow factor
// (or scaled flex shrink factor, if the chosen max-content flex fraction was negative)
// and the chosen max-content flex fraction, then clamp that result by the max main size floored by the min main size.
float largest_sum = 0;
for (auto& flex_line : m_flex_lines) {
float largest_flex_fraction = 0;
for (auto& flex_item : flex_line.items) {
// FIXME: The name max_content_flex_fraction here is misleading, since we also use this code path for min-content sizing.
largest_flex_fraction = max(largest_flex_fraction, flex_item->max_content_flex_fraction);
}
float sum = 0;
for (auto& flex_item : flex_line.items) {
auto product = 0;
if (flex_item->max_content_flex_fraction >= 0) {
product = largest_flex_fraction * flex_item->box.computed_values().flex_grow();
} else {
product = largest_flex_fraction * max(flex_item->box.computed_values().flex_shrink(), 1.0f) * flex_item->flex_base_size;
}
sum += flex_item->flex_base_size + flex_item->margins.main_before + flex_item->margins.main_after + flex_item->borders.main_before + flex_item->borders.main_after + flex_item->padding.main_before + flex_item->padding.main_after + product;
}
largest_sum = max(largest_sum, sum);
}
// 4. The flex containers max-content size is the largest sum of the afore-calculated sizes of all items within a single line.
return largest_sum;
}
// https://drafts.csswg.org/css-flexbox-1/#intrinsic-cross-sizes
float FlexFormattingContext::calculate_intrinsic_cross_size_of_flex_container(LayoutMode layout_mode)
{
VERIFY(layout_mode != LayoutMode::Normal);
// The min-content/max-content cross size of a single-line flex container
// is the largest min-content contribution/max-content contribution (respectively) of its flex items.
if (is_single_line()) {
float largest_contribution = 0;
for (auto& flex_item : m_flex_items) {
float contribution;
if (layout_mode == LayoutMode::MinContent)
contribution = calculate_cross_min_content_contribution(flex_item);
else if (layout_mode == LayoutMode::MaxContent)
contribution = calculate_cross_max_content_contribution(flex_item);
largest_contribution = max(largest_contribution, contribution);
}
return largest_contribution;
}
// For a multi-line flex container, the min-content/max-content cross size is the sum of the flex line cross sizes
// resulting from sizing the flex container under a cross-axis min-content constraint/max-content constraint (respectively).
// FIXME: However, if the flex container is flex-flow: column wrap;, then its sized by first finding the largest
// min-content/max-content cross-size contribution among the flex items (respectively), then using that size
// as the available space in the cross axis for each of the flex items during layout.
float sum_of_flex_line_cross_sizes = 0;
for (auto& flex_line : m_flex_lines) {
sum_of_flex_line_cross_sizes += flex_line.cross_size;
}
return sum_of_flex_line_cross_sizes;
}
float FlexFormattingContext::calculate_main_min_content_contribution(FlexItem const& flex_item) const
{
auto intrinsic_sizes = FormattingContext::calculate_intrinsic_sizes(flex_item.box);
auto const& box_state = m_state.get(flex_item.box);
if (is_row_layout())
return box_state.margin_box_left() + intrinsic_sizes.min_content_size.width() + box_state.margin_box_right();
return box_state.margin_box_top() + intrinsic_sizes.min_content_size.height() + box_state.margin_box_bottom();
}
float FlexFormattingContext::calculate_main_max_content_contribution(FlexItem const& flex_item) const
{
auto intrinsic_sizes = FormattingContext::calculate_intrinsic_sizes(flex_item.box);
auto const& box_state = m_state.get(flex_item.box);
if (is_row_layout())
return box_state.margin_box_left() + intrinsic_sizes.max_content_size.width() + box_state.margin_box_right();
return box_state.margin_box_top() + intrinsic_sizes.max_content_size.height() + box_state.margin_box_bottom();
}
float FlexFormattingContext::calculate_cross_min_content_contribution(FlexItem const& flex_item) const
{
auto intrinsic_sizes = FormattingContext::calculate_intrinsic_sizes(flex_item.box);
auto const& box_state = m_state.get(flex_item.box);
if (is_row_layout())
return box_state.margin_box_top() + intrinsic_sizes.min_content_size.height() + box_state.margin_box_bottom();
return box_state.margin_box_left() + intrinsic_sizes.min_content_size.width() + box_state.margin_box_right();
}
float FlexFormattingContext::calculate_cross_max_content_contribution(FlexItem const& flex_item) const
{
auto intrinsic_sizes = FormattingContext::calculate_intrinsic_sizes(flex_item.box);
auto const& box_state = m_state.get(flex_item.box);
if (is_row_layout())
return box_state.margin_box_top() + intrinsic_sizes.max_content_size.height() + box_state.margin_box_bottom();
return box_state.margin_box_left() + intrinsic_sizes.max_content_size.width() + box_state.margin_box_right();
}
}