diff options
author | Andreas Kling <kling@serenityos.org> | 2022-10-01 13:48:28 +0200 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2022-10-02 21:14:02 +0200 |
commit | de6d01236741760665d6ef022e6029b1e6dbde56 (patch) | |
tree | 9671baa972118dbbae3b09594201f230092a0b3e | |
parent | 74840c5537e583d99e152d0ac4598f160f3dfb43 (diff) | |
download | serenity-de6d01236741760665d6ef022e6029b1e6dbde56.zip |
LibWeb: Improve placement of abspos boxes with dual-auto insets
When an absolutely positioned box has auto insets on both sides of an
axis, it's placed according to the "static position rectangle". This is,
roughly, the rectangle a box would occupy if it were position:static
instead of position:absolute or position:fixed.
This patch implements a rough, but still significantly better,
estimation of such static positions. It gets pretty hairy in the case
where an abspos box has a parent whose children are inline.
3 files changed, 71 insertions, 17 deletions
diff --git a/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.cpp index 0416ffeac3..4f5145d434 100644 --- a/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.cpp @@ -447,7 +447,7 @@ void BlockFormattingContext::place_block_level_element_in_normal_flow_vertically resolve_vertical_box_model_metrics(child_box, available_space, m_state); - auto y = FormattingContext::compute_box_y_position_with_respect_to_siblings(child_box, box_state); + auto y = FormattingContext::compute_box_y_position_with_respect_to_siblings(child_box); auto clear_floating_boxes = [&](FloatSideData& float_side) { if (!float_side.current_boxes.is_empty()) { diff --git a/Userland/Libraries/LibWeb/Layout/FormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/FormattingContext.cpp index af477a3d98..e01bd1406c 100644 --- a/Userland/Libraries/LibWeb/Layout/FormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/FormattingContext.cpp @@ -10,6 +10,7 @@ #include <LibWeb/Layout/FlexFormattingContext.h> #include <LibWeb/Layout/FormattingContext.h> #include <LibWeb/Layout/GridFormattingContext.h> +#include <LibWeb/Layout/InitialContainingBlock.h> #include <LibWeb/Layout/ReplacedBox.h> #include <LibWeb/Layout/SVGFormattingContext.h> #include <LibWeb/Layout/SVGSVGBox.h> @@ -732,6 +733,65 @@ void FormattingContext::compute_height_for_absolutely_positioned_non_replaced_el box_state.set_content_height(used_height); } +// NOTE: This is different from content_box_rect_in_ancestor_coordinate_space() as this does *not* follow the containing block chain up, but rather the parent() chain. +static Gfx::FloatRect content_box_rect_in_static_position_ancestor_coordinate_space(Box const& box, Box const& ancestor_box, LayoutState const& state) +{ + auto rect = content_box_rect(box, state); + if (&box == &ancestor_box) + return rect; + for (auto const* current = box.parent(); current; current = current->parent()) { + if (current == &ancestor_box) + return rect; + auto const& current_state = state.get(static_cast<Box const&>(*current)); + rect.translate_by(current_state.offset); + } + // If we get here, ancestor_box was not an ancestor of `box`! + VERIFY_NOT_REACHED(); +} + +// https://www.w3.org/TR/css-position-3/#staticpos-rect +Gfx::FloatPoint FormattingContext::calculate_static_position(Box const& box) const +{ + // NOTE: This is very ad-hoc. + // The purpose of this function is to calculate the approximate position that `box` + // would have had if it were position:static. + + float x = 0.0f; + float y = 0.0f; + + VERIFY(box.parent()); + if (box.parent()->children_are_inline()) { + // We're an abspos box with inline siblings. This is gonna get messy! + if (auto* sibling = box.previous_sibling()) { + // Hard case: there's a previous sibling. This means there's already inline content + // preceding the hypothetical static position of `box` within its containing block. + // If we had been position:static, that inline content would have been wrapped in + // anonymous block box, so now we get to imagine what the world might have looked like + // in that scenario.. + // Basically, we find its last associated line box fragment and place `box` under it. + // FIXME: I'm 100% sure this can be smarter, better and faster. + LineBoxFragment const* last_fragment = nullptr; + auto& cb_state = m_state.get(*sibling->containing_block()); + for (auto& line_box : cb_state.line_boxes) { + for (auto& fragment : line_box.fragments()) { + if (&fragment.layout_node() == sibling) + last_fragment = &fragment; + } + } + if (last_fragment) { + y = last_fragment->offset().y() + last_fragment->height(); + } + } else { + // Easy case: no previous sibling, we're at the top of the containing block. + } + } else { + // We're among block siblings, Y can be calculated easily. + y = compute_box_y_position_with_respect_to_siblings(box); + } + auto offset_to_static_parent = content_box_rect_in_static_position_ancestor_coordinate_space(box, *box.containing_block(), m_state); + return offset_to_static_parent.location().translated(x, y); +} + void FormattingContext::layout_absolutely_positioned_element(Box const& box, AvailableSpace const& available_space) { auto& containing_block_state = m_state.get_mutable(*box.containing_block()); @@ -775,17 +835,9 @@ void FormattingContext::layout_absolutely_positioned_element(Box const& box, Ava box_state.margin_right = 0; } - Gfx::FloatPoint used_offset; + auto static_position = calculate_static_position(box); - auto* relevant_parent = box.first_ancestor_of_type<Layout::BlockContainer>(); - while (relevant_parent != nullptr) { - if (!relevant_parent->is_absolutely_positioned() && !relevant_parent->is_floating()) { - break; - } else { - relevant_parent = relevant_parent->first_ancestor_of_type<Layout::BlockContainer>(); - } - } - auto parent_location = absolute_content_rect(static_cast<Box const&>(*relevant_parent), m_state); + Gfx::FloatPoint used_offset; if (!computed_left.is_auto()) { float x_offset = box_state.inset_left @@ -798,7 +850,7 @@ void FormattingContext::layout_absolutely_positioned_element(Box const& box, Ava used_offset.set_x(width_of_containing_block + x_offset - box_state.content_width() - box_state.margin_right); } else { float x_offset = box_state.margin_box_left() - + (relevant_parent->computed_values().position() == CSS::Position::Relative ? 0 : parent_location.x()); + + static_position.x(); used_offset.set_x(x_offset); } @@ -813,8 +865,7 @@ void FormattingContext::layout_absolutely_positioned_element(Box const& box, Ava used_offset.set_y(height_of_containing_block + y_offset - box_state.content_height() - box_state.margin_bottom); } else { float y_offset = box_state.margin_box_top() - + compute_box_y_position_with_respect_to_siblings(box, box_state) - + (relevant_parent->computed_values().position() == CSS::Position::Relative ? 0 : parent_location.y()); + + static_position.y(); used_offset.set_y(y_offset); } @@ -1088,13 +1139,14 @@ static Box const* previous_block_level_sibling(Box const& box) return nullptr; } -float FormattingContext::compute_box_y_position_with_respect_to_siblings(Box const& child_box, LayoutState::UsedValues const& box_state) +float FormattingContext::compute_box_y_position_with_respect_to_siblings(Box const& box) const { + auto const& box_state = m_state.get(box); float y = box_state.border_box_top(); Vector<float> collapsible_margins; - auto* relevant_sibling = previous_block_level_sibling(child_box); + auto* relevant_sibling = previous_block_level_sibling(box); while (relevant_sibling != nullptr) { if (!relevant_sibling->is_absolutely_positioned() && !relevant_sibling->is_floating()) { auto const& relevant_sibling_state = m_state.get(*relevant_sibling); diff --git a/Userland/Libraries/LibWeb/Layout/FormattingContext.h b/Userland/Libraries/LibWeb/Layout/FormattingContext.h index bb0d8e519a..78312ff8c7 100644 --- a/Userland/Libraries/LibWeb/Layout/FormattingContext.h +++ b/Userland/Libraries/LibWeb/Layout/FormattingContext.h @@ -70,7 +70,7 @@ public: void run_intrinsic_sizing(Box const&); - float compute_box_y_position_with_respect_to_siblings(Box const&, LayoutState::UsedValues const&); + float compute_box_y_position_with_respect_to_siblings(Box const&) const; float calculate_stretch_fit_width(Box const&, AvailableSize const&) const; @@ -78,6 +78,8 @@ public: virtual void determine_width_of_child(Box const&, AvailableSpace const&) { } virtual void determine_height_of_child(Box const&, AvailableSpace const&) { } + virtual Gfx::FloatPoint calculate_static_position(Box const&) const; + protected: FormattingContext(Type, LayoutState&, Box const&, FormattingContext* parent = nullptr); |