From 9fc43d5766eb9fd04a5e8226a52381dd72af1667 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Wed, 13 Jul 2022 13:26:02 +0200 Subject: [PATCH] LibWeb: Update flex container intrinsic size algorithm to draft spec The CSSWG draft Flexbox spec is a bit clearer on some details, so let's update our implementation to match the latest version. --- .../LibWeb/Layout/FlexFormattingContext.cpp | 99 +++++++++++++------ .../LibWeb/Layout/FlexFormattingContext.h | 5 +- 2 files changed, 72 insertions(+), 32 deletions(-) diff --git a/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.cpp index 9b85dab7da..d4fc529cfe 100644 --- a/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.cpp @@ -725,7 +725,7 @@ void FlexFormattingContext::determine_main_size_of_flex_container(bool const mai } else { max_content_flex_fraction /= flex_item.scaled_flex_shrink_factor; } - flex_item.max_content_flex_fraction = max_content_flex_fraction; + flex_item.desired_flex_fraction = max_content_flex_fraction; if (max_content_flex_fraction > largest_max_content_flex_fraction) largest_max_content_flex_fraction = max_content_flex_fraction; @@ -736,7 +736,7 @@ void FlexFormattingContext::determine_main_size_of_flex_container(bool const mai float result = 0; for (auto& flex_item : m_flex_items) { auto product = 0; - if (flex_item.max_content_flex_fraction > 0) { + if (flex_item.desired_flex_fraction > 0) { product = largest_max_content_flex_fraction * flex_item.box.computed_values().flex_grow(); } else { product = largest_max_content_flex_fraction * flex_item.scaled_flex_shrink_factor; @@ -1329,7 +1329,7 @@ float FlexFormattingContext::calculate_intrinsic_main_size_of_flex_container(Lay // 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() && (m_flex_container_state.width_constraint == SizeConstraint::MinContent || m_flex_container_state.height_constraint == SizeConstraint::MinContent)) { + if (!is_single_line() && flex_container_main_constraint() == SizeConstraint::MinContent) { float largest_contribution = 0; for (auto const& flex_item : m_flex_items) { // FIXME: Skip collapsed flex items. @@ -1344,9 +1344,11 @@ float FlexFormattingContext::calculate_intrinsic_main_size_of_flex_container(Lay // 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 item’s max-content flex fraction. + // If that result is positive, divide it by the item’s flex grow factor if the flex grow factor is ≥ 1, + // or multiply it by the flex grow factor if the flex grow factor is < 1; if the result is negative, + // divide it by the item’s scaled flex shrink factor (if dividing by zero, treat the result as negative infinity). + // This is the item’s desired flex fraction. + for (auto& flex_item : m_flex_items) { float contribution; if (m_flex_container_state.width_constraint == SizeConstraint::MinContent || m_flex_container_state.height_constraint == SizeConstraint::MinContent) @@ -1354,14 +1356,23 @@ float FlexFormattingContext::calculate_intrinsic_main_size_of_flex_container(Lay else contribution = calculate_main_max_content_contribution(flex_item); - float flex_fraction = contribution - (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); - if (flex_fraction >= 0) - flex_fraction /= max(flex_item.box.computed_values().flex_grow(), 1.0f); - else - flex_fraction /= max(flex_item.scaled_flex_shrink_factor, 1.0f); + float outer_flex_base_size = 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; - // 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; + float result = contribution - outer_flex_base_size; + if (result > 0) { + if (flex_item.box.computed_values().flex_grow() >= 1) { + result /= flex_item.box.computed_values().flex_grow(); + } else { + result *= flex_item.box.computed_values().flex_grow(); + } + } else if (result < 0) { + if (flex_item.scaled_flex_shrink_factor == 0) + result = -INFINITY; + else + result /= flex_item.scaled_flex_shrink_factor; + } + + flex_item.desired_flex_fraction = result; } // 2. Place all flex items into lines of infinite length. @@ -1373,33 +1384,49 @@ float FlexFormattingContext::calculate_intrinsic_main_size_of_flex_container(Lay 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 item’s 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); - } + // Within each line, find the greatest (most positive) desired flex fraction among all the flex items. + // This is the line’s chosen flex fraction. + for (auto& flex_line : m_flex_lines) { + float greatest_desired_flex_fraction = 0; + float sum_of_flex_grow_factors = 0; + float sum_of_flex_shrink_factors = 0; + for (auto& flex_item : flex_line.items) { + greatest_desired_flex_fraction = max(greatest_desired_flex_fraction, flex_item->desired_flex_fraction); + sum_of_flex_grow_factors += flex_item->box.computed_values().flex_grow(); + sum_of_flex_shrink_factors += flex_item->box.computed_values().flex_shrink(); + } + float chosen_flex_fraction = greatest_desired_flex_fraction; + + // 3. If the chosen flex fraction is positive, and the sum of the line’s flex grow factors is less than 1, + // divide the chosen flex fraction by that sum. + if (chosen_flex_fraction > 0 && sum_of_flex_grow_factors < 1) + chosen_flex_fraction /= sum_of_flex_grow_factors; + + // If the chosen flex fraction is negative, and the sum of the line’s flex shrink factors is less than 1, + // multiply the chosen flex fraction by that sum. + if (chosen_flex_fraction < 0 && sum_of_flex_shrink_factors < 1) + chosen_flex_fraction *= sum_of_flex_shrink_factors; + + // 4. Add each item’s flex base size to the product of its flex grow factor (scaled flex shrink factor, if shrinking) + // and the chosen flex fraction, then clamp that result by the max main size floored by the min main size. 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 * flex_item->scaled_flex_shrink_factor; - } - 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; + float product = 0; + if (flex_item->desired_flex_fraction > 0) + product = chosen_flex_fraction * flex_item->box.computed_values().flex_grow(); + else if (flex_item->desired_flex_fraction < 0) + product = chosen_flex_fraction * flex_item->scaled_flex_shrink_factor; + auto result = flex_item->flex_base_size + product; + // FIXME: Clamp result to min/max main size + + sum += result; } largest_sum = max(largest_sum, sum); } - // 4. The flex container’s max-content size is the largest sum of the afore-calculated sizes of all items within a single line. return largest_sum; } @@ -1559,4 +1586,14 @@ float FlexFormattingContext::calculate_max_content_cross_size(FlexItem const& it return is_row_layout() ? calculate_max_content_height(item.box) : calculate_max_content_width(item.box); } +SizeConstraint FlexFormattingContext::flex_container_main_constraint() const +{ + return is_row_layout() ? m_flex_container_state.width_constraint : m_flex_container_state.height_constraint; +} + +SizeConstraint FlexFormattingContext::flex_container_cross_constraint() const +{ + return is_row_layout() ? m_flex_container_state.height_constraint : m_flex_container_state.width_constraint; +} + } diff --git a/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.h b/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.h index eccceaee85..46021e522e 100644 --- a/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.h +++ b/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.h @@ -23,6 +23,9 @@ public: Box const& flex_container() const { return context_box(); } private: + SizeConstraint flex_container_main_constraint() const; + SizeConstraint flex_container_cross_constraint() const; + void dump_items() const; struct DirectionAgnosticMargins { @@ -47,7 +50,7 @@ private: bool frozen { false }; Optional flex_factor {}; float scaled_flex_shrink_factor { 0 }; - float max_content_flex_fraction { 0 }; + float desired_flex_fraction { 0 }; float main_size { 0 }; float cross_size { 0 }; float main_offset { 0 };