mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-12-27 21:21:50 +03:00
LibWeb: Add support for grid items with negative column-start in GFC
This changes grid items position storage type from unsigned to signed integer so it can represent negative offsets and also updates placement for grid items with specified column to correctly handle negative offsets.
This commit is contained in:
parent
2c16e8371f
commit
0f1f95da46
Notes:
sideshowbarker
2024-07-17 08:43:11 +09:00
Author: https://github.com/kalenikaliaksandr Commit: https://github.com/SerenityOS/serenity/commit/0f1f95da46 Pull-request: https://github.com/SerenityOS/serenity/pull/19299
@ -0,0 +1,71 @@
|
||||
Viewport <#document> at (0,0) content-size 800x600 children: not-inline
|
||||
BlockContainer <html> at (0,0) content-size 800x600 [BFC] children: not-inline
|
||||
BlockContainer <body> at (8,8) content-size 784x69.875 children: not-inline
|
||||
Box <div.grid> at (8,8) content-size 784x69.875 [GFC] children: not-inline
|
||||
BlockContainer <(anonymous)> (not painted) [BFC] children: inline
|
||||
TextNode <#text>
|
||||
BlockContainer <div.a> at (8,8) content-size 196x17.46875 [BFC] children: inline
|
||||
line 0 width: 9.34375, height: 17.46875, bottom: 17.46875, baseline: 13.53125
|
||||
frag 0 from TextNode start: 0, length: 1, rect: [8,8 9.34375x17.46875]
|
||||
"a"
|
||||
TextNode <#text>
|
||||
BlockContainer <(anonymous)> (not painted) [BFC] children: inline
|
||||
TextNode <#text>
|
||||
BlockContainer <div.c> at (204,8) content-size 196x17.46875 [BFC] children: inline
|
||||
line 0 width: 8.890625, height: 17.46875, bottom: 17.46875, baseline: 13.53125
|
||||
frag 0 from TextNode start: 0, length: 1, rect: [204,8 8.890625x17.46875]
|
||||
"c"
|
||||
TextNode <#text>
|
||||
BlockContainer <(anonymous)> (not painted) [BFC] children: inline
|
||||
TextNode <#text>
|
||||
BlockContainer <div.b> at (400,8) content-size 196x17.46875 [BFC] children: inline
|
||||
line 0 width: 9.46875, height: 17.46875, bottom: 17.46875, baseline: 13.53125
|
||||
frag 0 from TextNode start: 0, length: 1, rect: [400,8 9.46875x17.46875]
|
||||
"b"
|
||||
TextNode <#text>
|
||||
BlockContainer <(anonymous)> (not painted) [BFC] children: inline
|
||||
TextNode <#text>
|
||||
BlockContainer <div.b> at (400,25.46875) content-size 196x17.46875 [BFC] children: inline
|
||||
line 0 width: 9.46875, height: 17.46875, bottom: 17.46875, baseline: 13.53125
|
||||
frag 0 from TextNode start: 0, length: 1, rect: [400,25.46875 9.46875x17.46875]
|
||||
"b"
|
||||
TextNode <#text>
|
||||
BlockContainer <(anonymous)> (not painted) [BFC] children: inline
|
||||
TextNode <#text>
|
||||
BlockContainer <div.d> at (596,25.46875) content-size 196x17.46875 [BFC] children: inline
|
||||
line 0 width: 7.859375, height: 17.46875, bottom: 17.46875, baseline: 13.53125
|
||||
frag 0 from TextNode start: 0, length: 1, rect: [596,25.46875 7.859375x17.46875]
|
||||
"d"
|
||||
TextNode <#text>
|
||||
BlockContainer <(anonymous)> (not painted) [BFC] children: inline
|
||||
TextNode <#text>
|
||||
BlockContainer <div.c> at (204,42.9375) content-size 196x17.46875 [BFC] children: inline
|
||||
line 0 width: 8.890625, height: 17.46875, bottom: 17.46875, baseline: 13.53125
|
||||
frag 0 from TextNode start: 0, length: 1, rect: [204,42.9375 8.890625x17.46875]
|
||||
"c"
|
||||
TextNode <#text>
|
||||
BlockContainer <(anonymous)> (not painted) [BFC] children: inline
|
||||
TextNode <#text>
|
||||
BlockContainer <div.d> at (596,42.9375) content-size 196x17.46875 [BFC] children: inline
|
||||
line 0 width: 7.859375, height: 17.46875, bottom: 17.46875, baseline: 13.53125
|
||||
frag 0 from TextNode start: 0, length: 1, rect: [596,42.9375 7.859375x17.46875]
|
||||
"d"
|
||||
TextNode <#text>
|
||||
BlockContainer <(anonymous)> (not painted) [BFC] children: inline
|
||||
TextNode <#text>
|
||||
BlockContainer <div.e> at (8,60.40625) content-size 196x17.46875 [BFC] children: inline
|
||||
line 0 width: 8.71875, height: 17.46875, bottom: 17.46875, baseline: 13.53125
|
||||
frag 0 from TextNode start: 0, length: 1, rect: [8,60.40625 8.71875x17.46875]
|
||||
"e"
|
||||
TextNode <#text>
|
||||
BlockContainer <(anonymous)> (not painted) [BFC] children: inline
|
||||
TextNode <#text>
|
||||
BlockContainer <div.f> at (204,60.40625) content-size 196x17.46875 [BFC] children: inline
|
||||
line 0 width: 6.4375, height: 17.46875, bottom: 17.46875, baseline: 13.53125
|
||||
frag 0 from TextNode start: 0, length: 1, rect: [204,60.40625 6.4375x17.46875]
|
||||
"f"
|
||||
TextNode <#text>
|
||||
BlockContainer <(anonymous)> (not painted) [BFC] children: inline
|
||||
TextNode <#text>
|
||||
BlockContainer <(anonymous)> at (8,77.875) content-size 784x0 children: inline
|
||||
TextNode <#text>
|
@ -0,0 +1,38 @@
|
||||
<style>
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: auto;
|
||||
}
|
||||
|
||||
.a {
|
||||
grid-column: -3;
|
||||
background-color: lightblue;
|
||||
}
|
||||
|
||||
.b {
|
||||
grid-column-start: -1;
|
||||
background-color: lightcoral;
|
||||
}
|
||||
|
||||
.c {
|
||||
grid-column: 1;
|
||||
background-color: lightsalmon;
|
||||
}
|
||||
|
||||
.d {
|
||||
grid-column: 3;
|
||||
background-color: lightgreen;
|
||||
}
|
||||
</style>
|
||||
<div class="grid">
|
||||
<div class="a">a</div>
|
||||
<div class="c">c</div>
|
||||
<div class="b">b</div>
|
||||
<div class="b">b</div>
|
||||
<div class="d">d</div>
|
||||
<div class="c">c</div>
|
||||
<div class="d">d</div>
|
||||
<div class="e">e</div>
|
||||
<div class="f">f</div>
|
||||
</div>
|
||||
|
@ -344,7 +344,14 @@ void GridFormattingContext::place_item_with_row_position(Box const& child_box)
|
||||
|
||||
void GridFormattingContext::place_item_with_column_position(Box const& child_box, int& auto_placement_cursor_x, int& auto_placement_cursor_y)
|
||||
{
|
||||
int column_start = child_box.computed_values().grid_column_start().raw_value() - 1;
|
||||
int column_start;
|
||||
if (child_box.computed_values().grid_column_start().raw_value() > 0) {
|
||||
column_start = child_box.computed_values().grid_column_start().raw_value() - 1;
|
||||
} else {
|
||||
// NOTE: Negative indexes count from the end side of the explicit grid
|
||||
column_start = m_explicit_columns_line_count + child_box.computed_values().grid_column_start().raw_value();
|
||||
}
|
||||
|
||||
int column_end = child_box.computed_values().grid_column_end().raw_value() - 1;
|
||||
|
||||
// https://www.w3.org/TR/css-grid-2/#line-placement
|
||||
@ -485,33 +492,39 @@ void GridFormattingContext::place_item_with_no_declared_position(Box const& chil
|
||||
else if (child_box.computed_values().grid_row_end().is_span())
|
||||
row_span = child_box.computed_values().grid_row_end().raw_value();
|
||||
auto found_unoccupied_area = false;
|
||||
for (size_t row_index = auto_placement_cursor_y; row_index < m_occupation_grid.row_count(); row_index++) {
|
||||
for (size_t column_index = auto_placement_cursor_x; column_index < m_occupation_grid.column_count(); column_index++) {
|
||||
if (column_span + column_index <= m_occupation_grid.column_count()) {
|
||||
|
||||
while (true) {
|
||||
while (auto_placement_cursor_x <= m_occupation_grid.max_column_index()) {
|
||||
if (auto_placement_cursor_x + column_span <= m_occupation_grid.max_column_index() + 1) {
|
||||
auto found_all_available = true;
|
||||
for (int span_index = 0; span_index < column_span; span_index++) {
|
||||
if (m_occupation_grid.is_occupied(column_index + span_index, row_index))
|
||||
if (m_occupation_grid.is_occupied(auto_placement_cursor_x + span_index, auto_placement_cursor_y))
|
||||
found_all_available = false;
|
||||
}
|
||||
if (found_all_available) {
|
||||
found_unoccupied_area = true;
|
||||
column_start = column_index;
|
||||
row_start = row_index;
|
||||
goto finish;
|
||||
column_start = auto_placement_cursor_x;
|
||||
row_start = auto_placement_cursor_y;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
auto_placement_cursor_x = 0;
|
||||
auto_placement_cursor_y++;
|
||||
}
|
||||
finish:
|
||||
|
||||
// 4.1.2.2. If a non-overlapping position was found in the previous step, set the item's row-start
|
||||
// and column-start lines to the cursor's position. Otherwise, increment the auto-placement cursor's
|
||||
// row position (creating new rows in the implicit grid as necessary), set its column position to the
|
||||
// start-most column line in the implicit grid, and return to the previous step.
|
||||
if (!found_unoccupied_area) {
|
||||
row_start = m_occupation_grid.row_count();
|
||||
auto_placement_cursor_x++;
|
||||
}
|
||||
|
||||
if (found_unoccupied_area) {
|
||||
break;
|
||||
}
|
||||
|
||||
// 4.1.2.2. If a non-overlapping position was found in the previous step, set the item's row-start
|
||||
// and column-start lines to the cursor's position. Otherwise, increment the auto-placement cursor's
|
||||
// row position (creating new rows in the implicit grid as necessary), set its column position to the
|
||||
// start-most column line in the implicit grid, and return to the previous step.
|
||||
if (!found_unoccupied_area) {
|
||||
auto_placement_cursor_x = m_occupation_grid.min_column_index();
|
||||
auto_placement_cursor_y++;
|
||||
row_start = auto_placement_cursor_y;
|
||||
}
|
||||
}
|
||||
|
||||
m_occupation_grid.set_occupied(column_start, column_start + column_span, row_start, row_start + row_span);
|
||||
@ -554,11 +567,21 @@ void GridFormattingContext::initialize_grid_tracks_from_definition(AvailableSpac
|
||||
void GridFormattingContext::initialize_grid_tracks_for_columns_and_rows(AvailableSpace const& available_space)
|
||||
{
|
||||
auto const& grid_computed_values = grid_container().computed_values();
|
||||
initialize_grid_tracks_from_definition(available_space, grid_computed_values.grid_template_columns().track_list(), m_grid_columns);
|
||||
initialize_grid_tracks_from_definition(available_space, grid_computed_values.grid_template_rows().track_list(), m_grid_rows);
|
||||
|
||||
auto const& grid_auto_columns = grid_computed_values.grid_auto_columns().track_list();
|
||||
size_t implicit_column_index = 0;
|
||||
// NOTE: If there are implicit tracks created by items with negative indexes they should prepend explicitly defined tracks
|
||||
auto negative_index_implied_column_tracks_count = abs(m_occupation_grid.min_column_index());
|
||||
for (int column_index = 0; column_index < negative_index_implied_column_tracks_count; column_index++) {
|
||||
if (grid_auto_columns.size() > 0) {
|
||||
auto size = grid_auto_columns[implicit_column_index % grid_auto_columns.size()];
|
||||
m_grid_columns.append(TemporaryTrack(size.grid_size()));
|
||||
} else {
|
||||
m_grid_columns.append(TemporaryTrack());
|
||||
}
|
||||
implicit_column_index++;
|
||||
}
|
||||
initialize_grid_tracks_from_definition(available_space, grid_computed_values.grid_template_columns().track_list(), m_grid_columns);
|
||||
for (size_t column_index = m_grid_columns.size(); column_index < m_occupation_grid.column_count(); column_index++) {
|
||||
if (grid_auto_columns.size() > 0) {
|
||||
auto size = grid_auto_columns[implicit_column_index % grid_auto_columns.size()];
|
||||
@ -571,6 +594,18 @@ void GridFormattingContext::initialize_grid_tracks_for_columns_and_rows(Availabl
|
||||
|
||||
auto const& grid_auto_rows = grid_computed_values.grid_auto_rows().track_list();
|
||||
size_t implicit_row_index = 0;
|
||||
// NOTE: If there are implicit tracks created by items with negative indexes they should prepend explicitly defined tracks
|
||||
auto negative_index_implied_row_tracks_count = abs(m_occupation_grid.min_row_index());
|
||||
for (int row_index = 0; row_index < negative_index_implied_row_tracks_count; row_index++) {
|
||||
if (grid_auto_rows.size() > 0) {
|
||||
auto size = grid_auto_rows[implicit_row_index % grid_auto_rows.size()];
|
||||
m_grid_rows.append(TemporaryTrack(size.grid_size()));
|
||||
} else {
|
||||
m_grid_rows.append(TemporaryTrack());
|
||||
}
|
||||
implicit_row_index++;
|
||||
}
|
||||
initialize_grid_tracks_from_definition(available_space, grid_computed_values.grid_template_rows().track_list(), m_grid_rows);
|
||||
for (size_t row_index = m_grid_rows.size(); row_index < m_occupation_grid.row_count(); row_index++) {
|
||||
if (grid_auto_rows.size() > 0) {
|
||||
auto size = grid_auto_rows[implicit_row_index % grid_auto_rows.size()];
|
||||
@ -1313,6 +1348,12 @@ void GridFormattingContext::place_grid_items(AvailableSpace const& available_spa
|
||||
|
||||
// FIXME: 4.2. For dense packing:
|
||||
}
|
||||
|
||||
// NOTE: When final implicit grid sizes are known, we can offset their positions so leftmost grid track has 0 index.
|
||||
for (auto& item : m_grid_items) {
|
||||
item.set_raw_row(item.raw_row() - m_occupation_grid.min_row_index());
|
||||
item.set_raw_column(item.raw_column() - m_occupation_grid.min_column_index());
|
||||
}
|
||||
}
|
||||
|
||||
void GridFormattingContext::determine_grid_container_height()
|
||||
@ -1369,6 +1410,12 @@ void GridFormattingContext::run(Box const& box, LayoutMode, AvailableSpace const
|
||||
{
|
||||
m_available_space = available_space;
|
||||
|
||||
auto const& grid_computed_values = grid_container().computed_values();
|
||||
|
||||
// NOTE: We store explicit grid sizes to later use in determining the position of items with negative index.
|
||||
m_explicit_columns_line_count = get_count_of_tracks(grid_computed_values.grid_template_columns().track_list(), available_space) + 1;
|
||||
m_explicit_rows_line_count = get_count_of_tracks(grid_computed_values.grid_template_rows().track_list(), available_space) + 1;
|
||||
|
||||
place_grid_items(available_space);
|
||||
|
||||
initialize_grid_tracks_for_columns_and_rows(available_space);
|
||||
@ -1566,33 +1613,31 @@ int GridFormattingContext::get_line_index_by_line_name(String const& needle, CSS
|
||||
return -1;
|
||||
}
|
||||
|
||||
void OccupationGrid::set_occupied(size_t column_start, size_t column_end, size_t row_start, size_t row_end)
|
||||
void OccupationGrid::set_occupied(int column_start, int column_end, int row_start, int row_end)
|
||||
{
|
||||
for (size_t row = row_start; row < row_end; row++) {
|
||||
for (size_t column = column_start; column < column_end; column++) {
|
||||
set_occupied(column, row);
|
||||
for (int row_index = row_start; row_index < row_end; row_index++) {
|
||||
for (int column_index = column_start; column_index < column_end; column_index++) {
|
||||
m_min_column_index = min(m_min_column_index, column_index);
|
||||
m_max_column_index = max(m_max_column_index, column_index);
|
||||
m_min_row_index = min(m_min_row_index, row_index);
|
||||
m_max_row_index = max(m_max_row_index, row_index);
|
||||
|
||||
m_occupation_grid.set(GridPosition { .row = row_index, .column = column_index });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OccupationGrid::set_occupied(size_t column_index, size_t row_index)
|
||||
{
|
||||
m_columns_count = max(m_columns_count, column_index + 1);
|
||||
m_rows_count = max(m_rows_count, row_index + 1);
|
||||
m_occupation_grid.try_set(GridPosition { .row = row_index, .column = column_index }).release_value_but_fixme_should_propagate_errors();
|
||||
}
|
||||
|
||||
bool OccupationGrid::is_occupied(size_t column_index, size_t row_index) const
|
||||
bool OccupationGrid::is_occupied(int column_index, int row_index) const
|
||||
{
|
||||
return m_occupation_grid.contains(GridPosition { row_index, column_index });
|
||||
}
|
||||
|
||||
size_t GridItem::gap_adjusted_row(Box const& grid_box) const
|
||||
int GridItem::gap_adjusted_row(Box const& grid_box) const
|
||||
{
|
||||
return grid_box.computed_values().row_gap().is_auto() ? m_row : m_row * 2;
|
||||
}
|
||||
|
||||
size_t GridItem::gap_adjusted_column(Box const& grid_box) const
|
||||
int GridItem::gap_adjusted_column(Box const& grid_box) const
|
||||
{
|
||||
return grid_box.computed_values().column_gap().is_auto() ? m_column : m_column * 2;
|
||||
}
|
||||
|
@ -17,31 +17,11 @@ enum class GridDimension {
|
||||
};
|
||||
|
||||
struct GridPosition {
|
||||
size_t row;
|
||||
size_t column;
|
||||
int row;
|
||||
int column;
|
||||
inline bool operator==(GridPosition const&) const = default;
|
||||
};
|
||||
|
||||
class OccupationGrid {
|
||||
public:
|
||||
OccupationGrid(size_t columns_count, size_t rows_count)
|
||||
: m_columns_count(columns_count)
|
||||
, m_rows_count(rows_count) {};
|
||||
OccupationGrid() {};
|
||||
|
||||
void set_occupied(size_t column_start, size_t column_end, size_t row_start, size_t row_end);
|
||||
void set_occupied(size_t column_index, size_t row_index);
|
||||
|
||||
size_t column_count() const { return m_columns_count; }
|
||||
size_t row_count() const { return m_rows_count; }
|
||||
bool is_occupied(size_t column_index, size_t row_index) const;
|
||||
|
||||
private:
|
||||
HashTable<GridPosition> m_occupation_grid;
|
||||
size_t m_columns_count { 0 };
|
||||
size_t m_rows_count { 0 };
|
||||
};
|
||||
|
||||
class GridItem {
|
||||
public:
|
||||
GridItem(Box const& box, int row, int row_span, int column, int column_span)
|
||||
@ -60,7 +40,7 @@ public:
|
||||
return dimension == GridDimension::Column ? m_column_span : m_row_span;
|
||||
}
|
||||
|
||||
size_t raw_position(GridDimension const dimension) const
|
||||
int raw_position(GridDimension const dimension) const
|
||||
{
|
||||
return dimension == GridDimension::Column ? m_column : m_row;
|
||||
}
|
||||
@ -75,23 +55,63 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
size_t raw_row() const { return m_row; }
|
||||
size_t raw_column() const { return m_column; }
|
||||
int raw_row() const { return m_row; }
|
||||
void set_raw_row(int row) { m_row = row; }
|
||||
|
||||
int raw_column() const { return m_column; }
|
||||
void set_raw_column(int column) { m_column = column; }
|
||||
|
||||
size_t raw_row_span() const { return m_row_span; }
|
||||
size_t raw_column_span() const { return m_column_span; }
|
||||
|
||||
size_t gap_adjusted_row(Box const& grid_box) const;
|
||||
size_t gap_adjusted_column(Box const& grid_box) const;
|
||||
int gap_adjusted_row(Box const& grid_box) const;
|
||||
int gap_adjusted_column(Box const& grid_box) const;
|
||||
|
||||
private:
|
||||
JS::NonnullGCPtr<Box const> m_box;
|
||||
size_t m_row { 0 };
|
||||
int m_row { 0 };
|
||||
size_t m_row_span { 1 };
|
||||
size_t m_column { 0 };
|
||||
int m_column { 0 };
|
||||
size_t m_column_span { 1 };
|
||||
};
|
||||
|
||||
class OccupationGrid {
|
||||
public:
|
||||
OccupationGrid(size_t columns_count, size_t rows_count)
|
||||
{
|
||||
m_max_column_index = max(0, columns_count - 1);
|
||||
m_max_row_index = max(0, rows_count - 1);
|
||||
};
|
||||
OccupationGrid() {};
|
||||
|
||||
void set_occupied(int column_start, int column_end, int row_start, int row_end);
|
||||
|
||||
size_t column_count() const
|
||||
{
|
||||
return abs(m_min_column_index) + m_max_column_index + 1;
|
||||
}
|
||||
|
||||
size_t row_count() const
|
||||
{
|
||||
return abs(m_min_row_index) + m_max_row_index + 1;
|
||||
}
|
||||
|
||||
int min_column_index() const { return m_min_column_index; }
|
||||
int max_column_index() const { return m_max_column_index; };
|
||||
int min_row_index() const { return m_min_row_index; };
|
||||
int max_row_index() const { return m_max_row_index; };
|
||||
|
||||
bool is_occupied(int column_index, int row_index) const;
|
||||
|
||||
private:
|
||||
HashTable<GridPosition> m_occupation_grid;
|
||||
|
||||
int m_min_column_index { 0 };
|
||||
int m_max_column_index { 0 };
|
||||
int m_min_row_index { 0 };
|
||||
int m_max_row_index { 0 };
|
||||
};
|
||||
|
||||
class GridFormattingContext final : public FormattingContext {
|
||||
public:
|
||||
explicit GridFormattingContext(LayoutState&, Box const& grid_container, FormattingContext* parent);
|
||||
@ -198,6 +218,9 @@ private:
|
||||
Vector<TemporaryTrack&> m_grid_rows_and_gaps;
|
||||
Vector<TemporaryTrack&> m_grid_columns_and_gaps;
|
||||
|
||||
size_t m_explicit_rows_line_count { 0 };
|
||||
size_t m_explicit_columns_line_count { 0 };
|
||||
|
||||
OccupationGrid m_occupation_grid;
|
||||
Vector<GridItem> m_grid_items;
|
||||
Vector<JS::NonnullGCPtr<Box const>> m_boxes_to_place;
|
||||
|
Loading…
Reference in New Issue
Block a user