diff --git a/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.cpp index de64b3d631..9187f35ffd 100644 --- a/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.cpp @@ -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 item’s 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 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); + } + + 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 container’s 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 it’s 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(); +} + } diff --git a/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.h b/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.h index 71b28718be..4ce825659c 100644 --- a/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.h +++ b/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.h @@ -121,6 +121,15 @@ private: bool is_direction_reverse() const { return m_flex_direction == CSS::FlexDirection::ColumnReverse || m_flex_direction == CSS::FlexDirection::RowReverse; } void populate_specified_margins(FlexItem&, CSS::FlexDirection) const; + void determine_intrinsic_size_of_flex_container(LayoutMode); + [[nodiscard]] float calculate_intrinsic_main_size_of_flex_container(LayoutMode); + [[nodiscard]] float calculate_intrinsic_cross_size_of_flex_container(LayoutMode); + + [[nodiscard]] float calculate_cross_min_content_contribution(FlexItem const&) const; + [[nodiscard]] float calculate_cross_max_content_contribution(FlexItem const&) const; + [[nodiscard]] float calculate_main_min_content_contribution(FlexItem const&) const; + [[nodiscard]] float calculate_main_max_content_contribution(FlexItem const&) const; + FormattingState::NodeState& m_flex_container_state; Vector m_flex_lines;