From 4bda65c8b361f74c4576a19a64b3f456d1224e7b Mon Sep 17 00:00:00 2001 From: Neil Viloria Date: Thu, 3 Oct 2024 20:35:57 -0600 Subject: [PATCH] LibWeb/Layout: Implement justify-content for column spacing in grid --- .../expected/grid/justify-content-cols.txt | 140 +++++++++++++++++ .../input/grid/justify-content-cols.html | 62 ++++++++ .../LibWeb/Layout/GridFormattingContext.cpp | 146 +++++++++++++----- .../LibWeb/Layout/GridFormattingContext.h | 8 +- 4 files changed, 317 insertions(+), 39 deletions(-) create mode 100644 Tests/LibWeb/Layout/expected/grid/justify-content-cols.txt create mode 100644 Tests/LibWeb/Layout/input/grid/justify-content-cols.html diff --git a/Tests/LibWeb/Layout/expected/grid/justify-content-cols.txt b/Tests/LibWeb/Layout/expected/grid/justify-content-cols.txt new file mode 100644 index 00000000000..280bf51a476 --- /dev/null +++ b/Tests/LibWeb/Layout/expected/grid/justify-content-cols.txt @@ -0,0 +1,140 @@ +Viewport <#document> at (0,0) content-size 800x600 children: not-inline + BlockContainer at (0,0) content-size 800x288 [BFC] children: not-inline + BlockContainer at (8,8) content-size 784x260 children: not-inline + Box at (8,8) content-size 784x20 [GFC] children: not-inline + BlockContainer <(anonymous)> (not painted) [BFC] children: inline + TextNode <#text> + BlockContainer
at (8,8) content-size 60x20 [BFC] children: not-inline + BlockContainer <(anonymous)> (not painted) [BFC] children: inline + TextNode <#text> + BlockContainer
at (84,8) content-size 60x20 [BFC] children: not-inline + BlockContainer <(anonymous)> (not painted) [BFC] children: inline + TextNode <#text> + BlockContainer
at (160,8) content-size 60x20 [BFC] children: not-inline + BlockContainer <(anonymous)> (not painted) [BFC] children: inline + TextNode <#text> + BlockContainer <(anonymous)> at (8,48) content-size 784x0 children: inline + TextNode <#text> + Box at (8,48) content-size 784x20 [GFC] children: not-inline + BlockContainer <(anonymous)> (not painted) [BFC] children: inline + TextNode <#text> + BlockContainer
at (580,48) content-size 60x20 [BFC] children: not-inline + BlockContainer <(anonymous)> (not painted) [BFC] children: inline + TextNode <#text> + BlockContainer
at (656,48) content-size 60x20 [BFC] children: not-inline + BlockContainer <(anonymous)> (not painted) [BFC] children: inline + TextNode <#text> + BlockContainer
at (732,48) content-size 60x20 [BFC] children: not-inline + BlockContainer <(anonymous)> (not painted) [BFC] children: inline + TextNode <#text> + BlockContainer <(anonymous)> at (8,88) content-size 784x0 children: inline + TextNode <#text> + Box at (8,88) content-size 784x20 [GFC] children: not-inline + BlockContainer <(anonymous)> (not painted) [BFC] children: inline + TextNode <#text> + BlockContainer
at (294,88) content-size 60x20 [BFC] children: not-inline + BlockContainer <(anonymous)> (not painted) [BFC] children: inline + TextNode <#text> + BlockContainer
at (370,88) content-size 60x20 [BFC] children: not-inline + BlockContainer <(anonymous)> (not painted) [BFC] children: inline + TextNode <#text> + BlockContainer
at (446,88) content-size 60x20 [BFC] children: not-inline + BlockContainer <(anonymous)> (not painted) [BFC] children: inline + TextNode <#text> + BlockContainer <(anonymous)> at (8,128) content-size 784x0 children: inline + TextNode <#text> + Box at (8,128) content-size 784x20 [GFC] children: not-inline + BlockContainer <(anonymous)> (not painted) [BFC] children: inline + TextNode <#text> + BlockContainer
at (8,128) content-size 60x20 [BFC] children: not-inline + BlockContainer <(anonymous)> (not painted) [BFC] children: inline + TextNode <#text> + BlockContainer
at (84,128) content-size 60x20 [BFC] children: not-inline + BlockContainer <(anonymous)> (not painted) [BFC] children: inline + TextNode <#text> + BlockContainer
at (160,128) content-size 60x20 [BFC] children: not-inline + BlockContainer <(anonymous)> (not painted) [BFC] children: inline + TextNode <#text> + BlockContainer <(anonymous)> at (8,168) content-size 784x0 children: inline + TextNode <#text> + Box at (8,168) content-size 784x20 [GFC] children: not-inline + BlockContainer <(anonymous)> (not painted) [BFC] children: inline + TextNode <#text> + BlockContainer
at (108.65625,168) content-size 60x20 [BFC] children: not-inline + BlockContainer <(anonymous)> (not painted) [BFC] children: inline + TextNode <#text> + BlockContainer
at (369.984375,168) content-size 60x20 [BFC] children: not-inline + BlockContainer <(anonymous)> (not painted) [BFC] children: inline + TextNode <#text> + BlockContainer
at (631.3125,168) content-size 60x20 [BFC] children: not-inline + BlockContainer <(anonymous)> (not painted) [BFC] children: inline + TextNode <#text> + BlockContainer <(anonymous)> at (8,208) content-size 784x0 children: inline + TextNode <#text> + Box at (8,208) content-size 784x20 [GFC] children: not-inline + BlockContainer <(anonymous)> (not painted) [BFC] children: inline + TextNode <#text> + BlockContainer
at (8,208) content-size 60x20 [BFC] children: not-inline + BlockContainer <(anonymous)> (not painted) [BFC] children: inline + TextNode <#text> + BlockContainer
at (370,208) content-size 60x20 [BFC] children: not-inline + BlockContainer <(anonymous)> (not painted) [BFC] children: inline + TextNode <#text> + BlockContainer
at (732,208) content-size 60x20 [BFC] children: not-inline + BlockContainer <(anonymous)> (not painted) [BFC] children: inline + TextNode <#text> + BlockContainer <(anonymous)> at (8,248) content-size 784x0 children: inline + TextNode <#text> + Box at (8,248) content-size 784x20 [GFC] children: not-inline + BlockContainer <(anonymous)> (not painted) [BFC] children: inline + TextNode <#text> + BlockContainer
at (159,248) content-size 60x20 [BFC] children: not-inline + BlockContainer <(anonymous)> (not painted) [BFC] children: inline + TextNode <#text> + BlockContainer
at (370,248) content-size 60x20 [BFC] children: not-inline + BlockContainer <(anonymous)> (not painted) [BFC] children: inline + TextNode <#text> + BlockContainer
at (581,248) content-size 60x20 [BFC] children: not-inline + BlockContainer <(anonymous)> (not painted) [BFC] children: inline + TextNode <#text> + BlockContainer <(anonymous)> at (8,288) content-size 784x0 children: inline + TextNode <#text> + +ViewportPaintable (Viewport<#document>) [0,0 800x600] + PaintableWithLines (BlockContainer) [0,0 800x288] + PaintableWithLines (BlockContainer) [8,8 784x260] overflow: [8,8 784x280] + PaintableBox (Box
.grid.justify-start) [8,8 784x20] + PaintableWithLines (BlockContainer
) [8,8 60x20] + PaintableWithLines (BlockContainer
) [84,8 60x20] + PaintableWithLines (BlockContainer
) [160,8 60x20] + PaintableWithLines (BlockContainer(anonymous)) [8,48 784x0] + PaintableBox (Box
.grid.justify-end) [8,48 784x20] + PaintableWithLines (BlockContainer
) [580,48 60x20] + PaintableWithLines (BlockContainer
) [656,48 60x20] + PaintableWithLines (BlockContainer
) [732,48 60x20] + PaintableWithLines (BlockContainer(anonymous)) [8,88 784x0] + PaintableBox (Box
.grid.justify-center) [8,88 784x20] + PaintableWithLines (BlockContainer
) [294,88 60x20] + PaintableWithLines (BlockContainer
) [370,88 60x20] + PaintableWithLines (BlockContainer
) [446,88 60x20] + PaintableWithLines (BlockContainer(anonymous)) [8,128 784x0] + PaintableBox (Box
.grid.justify-stretch) [8,128 784x20] + PaintableWithLines (BlockContainer
) [8,128 60x20] + PaintableWithLines (BlockContainer
) [84,128 60x20] + PaintableWithLines (BlockContainer
) [160,128 60x20] + PaintableWithLines (BlockContainer(anonymous)) [8,168 784x0] + PaintableBox (Box
.grid.justify-space-around) [8,168 784x20] + PaintableWithLines (BlockContainer
) [108.65625,168 60x20] + PaintableWithLines (BlockContainer
) [369.984375,168 60x20] + PaintableWithLines (BlockContainer
) [631.3125,168 60x20] + PaintableWithLines (BlockContainer(anonymous)) [8,208 784x0] + PaintableBox (Box
.grid.justify-space-between) [8,208 784x20] + PaintableWithLines (BlockContainer
) [8,208 60x20] + PaintableWithLines (BlockContainer
) [370,208 60x20] + PaintableWithLines (BlockContainer
) [732,208 60x20] + PaintableWithLines (BlockContainer(anonymous)) [8,248 784x0] + PaintableBox (Box
.grid.justify-space-evenly) [8,248 784x20] + PaintableWithLines (BlockContainer
) [159,248 60x20] + PaintableWithLines (BlockContainer
) [370,248 60x20] + PaintableWithLines (BlockContainer
) [581,248 60x20] + PaintableWithLines (BlockContainer(anonymous)) [8,288 784x0] diff --git a/Tests/LibWeb/Layout/input/grid/justify-content-cols.html b/Tests/LibWeb/Layout/input/grid/justify-content-cols.html new file mode 100644 index 00000000000..c85520cc341 --- /dev/null +++ b/Tests/LibWeb/Layout/input/grid/justify-content-cols.html @@ -0,0 +1,62 @@ + +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
diff --git a/Userland/Libraries/LibWeb/Layout/GridFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/GridFormattingContext.cpp index 94a97b2243f..2fe197d9a92 100644 --- a/Userland/Libraries/LibWeb/Layout/GridFormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/GridFormattingContext.cpp @@ -504,35 +504,40 @@ void GridFormattingContext::initialize_gap_tracks(AvailableSpace const& availabl // For the purpose of track sizing, each gutter is treated as an extra, empty, fixed-size track of // the specified size, which is spanned by any grid items that span across its corresponding grid // line. - if (!grid_container().computed_values().column_gap().is_auto() && m_grid_columns.size() > 0) { - auto column_gap_width = grid_container().computed_values().column_gap().to_px(grid_container(), available_space.width.to_px_or_zero()); + if (m_grid_columns.size() > 0) { + CSSPixels column_gap_width = 0; + if (!grid_container().computed_values().column_gap().is_auto()) { + column_gap_width = grid_container().computed_values().column_gap().to_px(grid_container(), available_space.width.to_px_or_zero()); + } + m_column_gap_tracks.ensure_capacity(m_grid_columns.size() - 1); + for (size_t column_index = 0; column_index < m_grid_columns.size(); column_index++) { m_grid_columns_and_gaps.append(m_grid_columns[column_index]); + if (column_index != m_grid_columns.size() - 1) { m_column_gap_tracks.append(GridTrack::create_gap(column_gap_width)); m_grid_columns_and_gaps.append(m_column_gap_tracks.last()); } } - } else { - for (auto& track : m_grid_columns) { - m_grid_columns_and_gaps.append(track); - } } - if (!grid_container().computed_values().row_gap().is_auto() && m_grid_rows.size() > 0) { - auto row_gap_height = grid_container().computed_values().row_gap().to_px(grid_container(), available_space.height.to_px_or_zero()); + + if (m_grid_rows.size() > 0) { + CSSPixels row_gap_height = 0; + if (!grid_container().computed_values().row_gap().is_auto()) { + row_gap_height = grid_container().computed_values().row_gap().to_px(grid_container(), available_space.height.to_px_or_zero()); + } + m_row_gap_tracks.ensure_capacity(m_grid_rows.size() - 1); + for (size_t row_index = 0; row_index < m_grid_rows.size(); row_index++) { m_grid_rows_and_gaps.append(m_grid_rows[row_index]); + if (row_index != m_grid_rows.size() - 1) { m_row_gap_tracks.append(GridTrack::create_gap(row_gap_height)); m_grid_rows_and_gaps.append(m_row_gap_tracks.last()); } } - } else { - for (auto& track : m_grid_rows) { - m_grid_rows_and_gaps.append(track); - } } } @@ -1667,6 +1672,58 @@ void GridFormattingContext::resolve_grid_item_heights() } } +void GridFormattingContext::resolve_track_spacing(GridDimension const dimension) +{ + if (dimension == GridDimension::Column) { + auto total_gap_space = m_available_space->width.to_px_or_zero(); + + for (auto& track : m_grid_columns_and_gaps) { + if (track.is_gap) + continue; + + total_gap_space -= track.base_size; + } + total_gap_space = max(total_gap_space, 0); + + auto gap_track_count = m_column_gap_tracks.size(); + if (gap_track_count == 0) + return; + + auto const& gap_space = grid_container().computed_values().column_gap(); + auto const& available_size = m_available_space->width; + + CSSPixels space_between_tracks = 0; + switch (grid_container().computed_values().justify_content()) { + case CSS::JustifyContent::SpaceBetween: + space_between_tracks = CSSPixels(total_gap_space / gap_track_count); + break; + case CSS::JustifyContent::SpaceAround: + space_between_tracks = CSSPixels(total_gap_space / (m_column_gap_tracks.size() + 1)); + break; + case CSS::JustifyContent::SpaceEvenly: + space_between_tracks = CSSPixels(total_gap_space / (m_grid_columns.size() + 1)); + break; + case CSS::JustifyContent::Start: + case CSS::JustifyContent::End: + case CSS::JustifyContent::Center: + case CSS::JustifyContent::Stretch: + default: + break; + } + + space_between_tracks = max(space_between_tracks, gap_space.to_px(grid_container(), available_size.to_px_or_zero())); + + for (auto& track : m_column_gap_tracks) { + if (!track.is_gap) + continue; + + track.base_size = space_between_tracks; + } + } else { + // TODO: align-content spacing + } +} + void GridFormattingContext::resolve_items_box_metrics(GridDimension const dimension) { for (auto& item : m_grid_items) { @@ -1719,41 +1776,56 @@ void GridFormattingContext::collapse_auto_fit_tracks_if_needed(GridDimension con CSSPixelRect GridFormattingContext::get_grid_area_rect(GridItem const& grid_item) const { - auto const& row_gap = grid_container().computed_values().row_gap(); - auto resolved_row_span = row_gap.is_auto() ? grid_item.row_span : grid_item.row_span * 2; - if (!row_gap.is_auto() && grid_item.gap_adjusted_row(grid_container()) == 0) - resolved_row_span -= 1; - if (grid_item.gap_adjusted_row(grid_container()) + resolved_row_span > m_grid_rows.size()) - resolved_row_span = m_grid_rows_and_gaps.size() - grid_item.gap_adjusted_row(grid_container()); + auto resolved_row_span = grid_item.row_span * 2; + if (grid_item.gap_adjusted_row() + resolved_row_span > m_grid_rows_and_gaps.size()) + resolved_row_span = m_grid_rows_and_gaps.size() - grid_item.gap_adjusted_row(); - auto const& column_gap = grid_container().computed_values().column_gap(); - auto resolved_column_span = column_gap.is_auto() ? grid_item.column_span : grid_item.column_span * 2; - if (!column_gap.is_auto() && grid_item.gap_adjusted_column(grid_container()) == 0) - resolved_column_span -= 1; - if (grid_item.gap_adjusted_column(grid_container()) + resolved_column_span > m_grid_columns_and_gaps.size()) - resolved_column_span = m_grid_columns_and_gaps.size() - grid_item.gap_adjusted_column(grid_container()); + auto resolved_column_span = grid_item.column_span * 2; + if (grid_item.gap_adjusted_column() + resolved_column_span > m_grid_columns_and_gaps.size()) + resolved_column_span = m_grid_columns_and_gaps.size() - grid_item.gap_adjusted_column(); - int row_start = grid_item.gap_adjusted_row(grid_container()); - int row_end = grid_item.gap_adjusted_row(grid_container()) + resolved_row_span; - int column_start = grid_item.gap_adjusted_column(grid_container()); - int column_end = grid_item.gap_adjusted_column(grid_container()) + resolved_column_span; + int row_start = grid_item.gap_adjusted_row(); + int row_end = row_start + resolved_row_span; + int column_start = grid_item.gap_adjusted_column(); + int column_end = column_start + resolved_column_span; auto grid_container_width = m_available_space->width.to_px_or_zero(); + CSSPixels sum_base_size_of_columns_and_gaps = 0; CSSPixels sum_base_size_of_columns = 0; - for (size_t i = 0; i < m_grid_columns_and_gaps.size(); i++) - sum_base_size_of_columns += m_grid_columns_and_gaps[i].base_size; + for (auto const& col_track : m_grid_columns_and_gaps) { + if (!col_track.is_gap) + sum_base_size_of_columns += col_track.base_size; + sum_base_size_of_columns_and_gaps += col_track.base_size; + } auto const& justify_content = grid_container().computed_values().justify_content(); CSSPixels x_start = 0; CSSPixels x_end = 0; if (justify_content == CSS::JustifyContent::Center) { - auto free_space = grid_container_width - sum_base_size_of_columns; + auto free_space = grid_container_width - sum_base_size_of_columns_and_gaps; x_start = free_space / 2; x_end = free_space / 2; } else if (justify_content == CSS::JustifyContent::End || justify_content == CSS::JustifyContent::Right) { - auto free_space = grid_container_width - sum_base_size_of_columns; + auto free_space = grid_container_width - sum_base_size_of_columns_and_gaps; x_start = free_space; x_end = free_space; + } else if (justify_content == CSS::JustifyContent::SpaceAround) { + auto free_space = grid_container_width - sum_base_size_of_columns; + free_space = max(free_space, 0); + + auto gap_space = free_space / (m_column_gap_tracks.size() + 1); + auto gap_half_space = gap_space / 2; + + x_start = CSSPixels(gap_half_space); + x_end = CSSPixels(gap_half_space); + } else if (justify_content == CSS::JustifyContent::SpaceEvenly) { + auto free_space = grid_container_width - sum_base_size_of_columns; + free_space = max(free_space, 0); + + auto gap_space = free_space / (m_grid_columns.size() + 1); + + x_start = gap_space; + x_end = gap_space; } CSSPixels y_start = 0; @@ -1840,6 +1912,8 @@ void GridFormattingContext::run(AvailableSpace const& available_space) determine_grid_container_height(); + resolve_track_spacing(GridDimension::Column); + auto const& containing_block_state = m_state.get(*grid_container().containing_block()); auto height_of_containing_block = containing_block_state.content_height(); auto height_of_container_block_as_available_size = AvailableSize::make_definite(height_of_containing_block); @@ -2154,14 +2228,14 @@ bool OccupationGrid::is_occupied(int column_index, int row_index) const return m_occupation_grid.contains(GridPosition { row_index, column_index }); } -int GridItem::gap_adjusted_row(Box const& grid_box) const +int GridItem::gap_adjusted_row() const { - return grid_box.computed_values().row_gap().is_auto() ? row : row * 2; + return row * 2; } -int GridItem::gap_adjusted_column(Box const& grid_box) const +int GridItem::gap_adjusted_column() const { - return grid_box.computed_values().column_gap().is_auto() ? column : column * 2; + return column * 2; } CSSPixels GridFormattingContext::calculate_grid_container_maximum_size(GridDimension const dimension) const diff --git a/Userland/Libraries/LibWeb/Layout/GridFormattingContext.h b/Userland/Libraries/LibWeb/Layout/GridFormattingContext.h index d110b241f29..e2ba6b5f067 100644 --- a/Userland/Libraries/LibWeb/Layout/GridFormattingContext.h +++ b/Userland/Libraries/LibWeb/Layout/GridFormattingContext.h @@ -49,8 +49,8 @@ struct GridItem { return box_state.margin_box_top() + content_size + box_state.margin_box_bottom(); } - [[nodiscard]] int gap_adjusted_row(Box const& grid_box) const; - [[nodiscard]] int gap_adjusted_column(Box const& grid_box) const; + [[nodiscard]] int gap_adjusted_row() const; + [[nodiscard]] int gap_adjusted_column() const; }; enum class FoundUnoccupiedPlace { @@ -65,7 +65,7 @@ public: m_max_column_index = max(0, columns_count - 1); m_max_row_index = max(0, rows_count - 1); } - OccupationGrid() {}; + OccupationGrid() { } void set_occupied(int column_start, int column_end, int row_start, int row_end); @@ -244,6 +244,8 @@ private: void resolve_grid_item_widths(); void resolve_grid_item_heights(); + void resolve_track_spacing(GridDimension const dimension); + AvailableSize get_free_space(AvailableSpace const&, GridDimension const) const; Optional get_line_index_by_line_name(GridDimension dimension, String const&);