mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 14:28:12 +00:00
LibWeb: Let parent formatting context determine size of flex containers
Until now, we had implemented flex container sizing by awkwardly doing exactly what the spec said (basically having FFC size the container) despite that not really making sense in the big picture. (Parent formatting contexts should be responsible for sizing and placing their children) This patch moves us away from the Flexbox spec text a little bit, by removing the logic for sizing the flex container in FFC, and instead making sure that all formatting contexts can set both width and height of flex container children. This required changes in BFC and IFC, but it's actually quite simple! Width was already not a problem, and it turns out height isn't either, since the automatic height of a flex container is max-content. With this in mind, we can simply determine the height of flex containers before transferring control to FFC, and everything flows nicely. With this change, we can remove all the virtuals and FFC logic for negotiating container size with the parent formatting context. We also don't need the "available space for flex container" stuff anymore either, so that's gone as well. There are some minor diffs in layout test results from this, but the new results actually match other browsers more closely, so that's fine. This should make flex layout, and indeed layout in general, easier to understand, since this was the main weird special case outside of BFC/IFC where a formatting context delegates work to its parent instead of the other way around. :^)
This commit is contained in:
parent
5f0230a57e
commit
5af02a914c
9 changed files with 40 additions and 196 deletions
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2021-2023, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2021-2024, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
|
@ -56,35 +56,17 @@ CSSPixels FlexFormattingContext::automatic_content_height() const
|
|||
return m_flex_container_state.content_height();
|
||||
}
|
||||
|
||||
void FlexFormattingContext::run(Box const& run_box, LayoutMode, AvailableSpace const& available_content_space)
|
||||
void FlexFormattingContext::run(Box const& run_box, LayoutMode, AvailableSpace const& available_space)
|
||||
{
|
||||
VERIFY(&run_box == &flex_container());
|
||||
|
||||
// NOTE: The available space provided by the parent context is basically our *content box*.
|
||||
// FFC is currently written in a way that expects that to include padding and border as well,
|
||||
// so we pad out the available space here to accommodate that.
|
||||
// FIXME: Refactor the necessary parts of FFC so we don't need this hack!
|
||||
|
||||
auto available_width = available_content_space.width;
|
||||
if (available_width.is_definite())
|
||||
available_width = AvailableSize::make_definite(available_width.to_px_or_zero() + m_flex_container_state.border_box_left() + m_flex_container_state.border_box_right());
|
||||
auto available_height = available_content_space.height;
|
||||
if (available_height.is_definite())
|
||||
available_height = AvailableSize::make_definite(available_height.to_px_or_zero() + m_flex_container_state.border_box_top() + m_flex_container_state.border_box_bottom());
|
||||
|
||||
m_available_space_for_flex_container = AxisAgnosticAvailableSpace {
|
||||
.main = is_row_layout() ? available_width : available_height,
|
||||
.cross = !is_row_layout() ? available_width : available_height,
|
||||
.space = { available_width, available_height },
|
||||
};
|
||||
|
||||
// This implements https://www.w3.org/TR/css-flexbox-1/#layout-algorithm
|
||||
|
||||
// 1. Generate anonymous flex items
|
||||
generate_anonymous_flex_items();
|
||||
|
||||
// 2. Determine the available main and cross space for the flex items
|
||||
determine_available_space_for_items(AvailableSpace(available_width, available_height));
|
||||
determine_available_space_for_items(available_space);
|
||||
|
||||
{
|
||||
// https://drafts.csswg.org/css-flexbox-1/#definite-sizes
|
||||
|
@ -114,12 +96,16 @@ void FlexFormattingContext::run(Box const& run_box, LayoutMode, AvailableSpace c
|
|||
determine_flex_base_size_and_hypothetical_main_size(item);
|
||||
}
|
||||
|
||||
if (available_width.is_intrinsic_sizing_constraint() || available_height.is_intrinsic_sizing_constraint()) {
|
||||
if (available_space.width.is_intrinsic_sizing_constraint() || available_space.height.is_intrinsic_sizing_constraint()) {
|
||||
// We're computing intrinsic size for the flex container. This happens at the end of run().
|
||||
} else {
|
||||
|
||||
// 4. Determine the main size of the flex container
|
||||
determine_main_size_of_flex_container();
|
||||
// Determine the main size of the flex container using the rules of the formatting context in which it participates.
|
||||
// NOTE: The automatic block size of a block-level flex container is its max-content size.
|
||||
|
||||
// NOTE: We've already handled this in the parent formatting context.
|
||||
// Specifically, all formatting contexts will have assigned width & height to the flex container
|
||||
// before this formatting context runs.
|
||||
}
|
||||
|
||||
// 5. Collect flex items into flex lines:
|
||||
|
@ -186,7 +172,7 @@ void FlexFormattingContext::run(Box const& run_box, LayoutMode, AvailableSpace c
|
|||
// 16. Align all flex lines (per align-content)
|
||||
align_all_flex_lines();
|
||||
|
||||
if (available_width.is_intrinsic_sizing_constraint() || available_height.is_intrinsic_sizing_constraint()) {
|
||||
if (available_space.width.is_intrinsic_sizing_constraint() || available_space.height.is_intrinsic_sizing_constraint()) {
|
||||
// We're computing intrinsic size for the flex container.
|
||||
determine_intrinsic_size_of_flex_container();
|
||||
} else {
|
||||
|
@ -195,7 +181,7 @@ void FlexFormattingContext::run(Box const& run_box, LayoutMode, AvailableSpace c
|
|||
copy_dimensions_from_flex_items_to_boxes();
|
||||
for (auto& item : m_flex_items) {
|
||||
auto& box_state = m_state.get(item.box);
|
||||
if (auto independent_formatting_context = layout_inside(item.box, LayoutMode::Normal, box_state.available_inner_space_or_constraints_from(m_available_space_for_flex_container->space)))
|
||||
if (auto independent_formatting_context = layout_inside(item.box, LayoutMode::Normal, box_state.available_inner_space_or_constraints_from(m_available_space_for_items->space)))
|
||||
independent_formatting_context->parent_context_did_dimension_child_root_box();
|
||||
|
||||
compute_inset(item.box);
|
||||
|
@ -443,66 +429,17 @@ void FlexFormattingContext::set_main_axis_second_margin(FlexItem& item, CSSPixel
|
|||
// https://drafts.csswg.org/css-flexbox-1/#algo-available
|
||||
void FlexFormattingContext::determine_available_space_for_items(AvailableSpace const& available_space)
|
||||
{
|
||||
// For each dimension, if that dimension of the flex container’s content box is a definite size, use that;
|
||||
// if that dimension of the flex container is being sized under a min or max-content constraint, the available space in that dimension is that constraint;
|
||||
// otherwise, subtract the flex container’s margin, border, and padding from the space available to the flex container in that dimension and use that value.
|
||||
// This might result in an infinite value.
|
||||
|
||||
Optional<AvailableSize> available_width_for_items;
|
||||
if (m_flex_container_state.has_definite_width()) {
|
||||
available_width_for_items = AvailableSize::make_definite(m_flex_container_state.content_width());
|
||||
} else {
|
||||
if (available_space.width.is_intrinsic_sizing_constraint()) {
|
||||
available_width_for_items = available_space.width;
|
||||
} else {
|
||||
if (available_space.width.is_definite()) {
|
||||
auto remaining = available_space.width.to_px_or_zero()
|
||||
- m_flex_container_state.margin_left
|
||||
- m_flex_container_state.margin_right
|
||||
- m_flex_container_state.border_left
|
||||
- m_flex_container_state.padding_right
|
||||
- m_flex_container_state.padding_left
|
||||
- m_flex_container_state.padding_right;
|
||||
available_width_for_items = AvailableSize::make_definite(remaining);
|
||||
} else {
|
||||
available_width_for_items = AvailableSize::make_indefinite();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Optional<AvailableSize> available_height_for_items;
|
||||
if (m_flex_container_state.has_definite_height()) {
|
||||
available_height_for_items = AvailableSize::make_definite(m_flex_container_state.content_height());
|
||||
} else {
|
||||
if (available_space.height.is_intrinsic_sizing_constraint()) {
|
||||
available_height_for_items = available_space.height;
|
||||
} else {
|
||||
if (available_space.height.is_definite()) {
|
||||
auto remaining = available_space.height.to_px_or_zero()
|
||||
- m_flex_container_state.margin_top
|
||||
- m_flex_container_state.margin_bottom
|
||||
- m_flex_container_state.border_top
|
||||
- m_flex_container_state.padding_bottom
|
||||
- m_flex_container_state.padding_top
|
||||
- m_flex_container_state.padding_bottom;
|
||||
available_height_for_items = AvailableSize::make_definite(remaining);
|
||||
} else {
|
||||
available_height_for_items = AvailableSize::make_indefinite();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (is_row_layout()) {
|
||||
m_available_space_for_items = AxisAgnosticAvailableSpace {
|
||||
.main = *available_width_for_items,
|
||||
.cross = *available_height_for_items,
|
||||
.space = { *available_width_for_items, *available_height_for_items },
|
||||
.main = available_space.width,
|
||||
.cross = available_space.height,
|
||||
.space = { available_space.width, available_space.height },
|
||||
};
|
||||
} else {
|
||||
m_available_space_for_items = AxisAgnosticAvailableSpace {
|
||||
.main = *available_height_for_items,
|
||||
.cross = *available_width_for_items,
|
||||
.space = { *available_width_for_items, *available_height_for_items },
|
||||
.main = available_space.height,
|
||||
.cross = available_space.width,
|
||||
.space = { available_space.width, available_space.height },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -785,59 +722,6 @@ CSSPixels FlexFormattingContext::content_based_minimum_size(FlexItem const& item
|
|||
return unclamped_size;
|
||||
}
|
||||
|
||||
bool FlexFormattingContext::can_determine_size_of_child() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void FlexFormattingContext::determine_width_of_child(Box const&, AvailableSpace const&)
|
||||
{
|
||||
// NOTE: For now, we simply do nothing here. If a child context is calling up to us
|
||||
// and asking us to determine its width, we've already done so as part of the
|
||||
// flex layout algorithm.
|
||||
}
|
||||
|
||||
void FlexFormattingContext::determine_height_of_child(Box const&, AvailableSpace const&)
|
||||
{
|
||||
// NOTE: For now, we simply do nothing here. If a child context is calling up to us
|
||||
// and asking us to determine its height, we've already done so as part of the
|
||||
// flex layout algorithm.
|
||||
}
|
||||
|
||||
// https://drafts.csswg.org/css-flexbox-1/#algo-main-container
|
||||
void FlexFormattingContext::determine_main_size_of_flex_container()
|
||||
{
|
||||
// Determine the main size of the flex container using the rules of the formatting context in which it participates.
|
||||
// NOTE: The automatic block size of a block-level flex container is its max-content size.
|
||||
|
||||
// FIXME: The code below doesn't know how to size absolutely positioned flex containers at all.
|
||||
// We just leave it alone for now and let the parent context deal with it.
|
||||
if (flex_container().is_absolutely_positioned())
|
||||
return;
|
||||
|
||||
// FIXME: Once all parent contexts now how to size a given child, we can remove
|
||||
// `can_determine_size_of_child()`.
|
||||
if (parent()->can_determine_size_of_child()) {
|
||||
if (is_row_layout()) {
|
||||
parent()->determine_width_of_child(flex_container(), m_available_space_for_flex_container->space);
|
||||
} else {
|
||||
parent()->determine_height_of_child(flex_container(), m_available_space_for_flex_container->space);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_row_layout()) {
|
||||
if (!flex_container().is_out_of_flow(*parent()) && m_state.get(*flex_container().containing_block()).has_definite_width()) {
|
||||
set_main_size(flex_container(), calculate_stretch_fit_width(flex_container(), m_available_space_for_flex_container->space.width));
|
||||
} else {
|
||||
set_main_size(flex_container(), calculate_max_content_width(flex_container()));
|
||||
}
|
||||
} else {
|
||||
if (!has_definite_main_size(flex_container()))
|
||||
set_main_size(flex_container(), calculate_max_content_height(flex_container(), m_available_space_for_flex_container->space.width.to_px_or_zero()));
|
||||
}
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/css-flexbox-1/#algo-line-break
|
||||
void FlexFormattingContext::collect_flex_items_into_flex_lines()
|
||||
{
|
||||
|
@ -1229,7 +1113,7 @@ void FlexFormattingContext::calculate_cross_size_of_each_flex_line()
|
|||
// If the flex container is single-line, then clamp the line’s cross-size to be within the container’s computed min and max cross sizes.
|
||||
// Note that if CSS 2.1’s definition of min/max-width/height applied more generally, this behavior would fall out automatically.
|
||||
// AD-HOC: We don't do this when the flex container is being sized under a min-content or max-content constraint.
|
||||
if (is_single_line() && !m_available_space_for_flex_container->cross.is_intrinsic_sizing_constraint()) {
|
||||
if (is_single_line() && !m_available_space_for_items->cross.is_intrinsic_sizing_constraint()) {
|
||||
auto const& computed_min_size = this->computed_cross_min_size(flex_container());
|
||||
auto const& computed_max_size = this->computed_cross_max_size(flex_container());
|
||||
auto cross_min_size = (!computed_min_size.is_auto() && !computed_min_size.contains_percentage()) ? specified_cross_min_size(flex_container()) : 0;
|
||||
|
@ -1553,7 +1437,7 @@ void FlexFormattingContext::determine_flex_container_used_cross_size()
|
|||
}
|
||||
|
||||
// AD-HOC: We don't apply min/max cross size constraints when sizing the flex container under an intrinsic sizing constraint.
|
||||
if (!m_available_space_for_flex_container->cross.is_intrinsic_sizing_constraint()) {
|
||||
if (!m_available_space_for_items->cross.is_intrinsic_sizing_constraint()) {
|
||||
auto const& computed_min_size = this->computed_cross_min_size(flex_container());
|
||||
auto const& computed_max_size = this->computed_cross_max_size(flex_container());
|
||||
auto cross_min_size = (!computed_min_size.is_auto() && !computed_min_size.contains_percentage()) ? specified_cross_min_size(flex_container()) : 0;
|
||||
|
@ -1689,7 +1573,7 @@ void FlexFormattingContext::copy_dimensions_from_flex_items_to_boxes()
|
|||
// https://drafts.csswg.org/css-flexbox-1/#intrinsic-sizes
|
||||
void FlexFormattingContext::determine_intrinsic_size_of_flex_container()
|
||||
{
|
||||
if (m_available_space_for_flex_container->main.is_intrinsic_sizing_constraint()) {
|
||||
if (m_available_space_for_items->main.is_intrinsic_sizing_constraint()) {
|
||||
CSSPixels main_size = calculate_intrinsic_main_size_of_flex_container();
|
||||
set_main_size(flex_container(), main_size);
|
||||
}
|
||||
|
@ -2051,7 +1935,7 @@ CSSPixels FlexFormattingContext::calculate_fit_content_cross_size(FlexItem const
|
|||
CSSPixels FlexFormattingContext::calculate_min_content_cross_size(FlexItem const& item) const
|
||||
{
|
||||
if (is_row_layout()) {
|
||||
auto available_space = m_state.get(item.box).available_inner_space_or_constraints_from(m_available_space_for_flex_container->space);
|
||||
auto available_space = m_state.get(item.box).available_inner_space_or_constraints_from(m_available_space_for_items->space);
|
||||
if (available_space.width.is_indefinite()) {
|
||||
available_space.width = AvailableSize::make_definite(calculate_width_to_use_when_determining_intrinsic_height_of_item(item));
|
||||
}
|
||||
|
@ -2063,7 +1947,7 @@ CSSPixels FlexFormattingContext::calculate_min_content_cross_size(FlexItem const
|
|||
CSSPixels FlexFormattingContext::calculate_max_content_cross_size(FlexItem const& item) const
|
||||
{
|
||||
if (is_row_layout()) {
|
||||
auto available_space = m_state.get(item.box).available_inner_space_or_constraints_from(m_available_space_for_flex_container->space);
|
||||
auto available_space = m_state.get(item.box).available_inner_space_or_constraints_from(m_available_space_for_items->space);
|
||||
if (available_space.width.is_indefinite()) {
|
||||
available_space.width = AvailableSize::make_definite(calculate_width_to_use_when_determining_intrinsic_height_of_item(item));
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue