blob: 1faf3ba45cd6827546e7a14cc14cf85d3d1506e8 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file
#include "third_party/blink/renderer/core/layout/ng/grid/ng_grid_placement.h"
#include "third_party/blink/renderer/core/layout/ng/grid/ng_grid_child_iterator.h"
namespace blink {
NGGridPlacement::NGGridPlacement(const ComputedStyle& grid_style,
const wtf_size_t column_auto_repetitions,
const wtf_size_t row_auto_repetitions)
: grid_style_(grid_style),
packing_behavior_(grid_style.IsGridAutoFlowAlgorithmSparse()
? PackingBehavior::kSparse
: PackingBehavior::kDense),
// The major direction is the one specified in the 'grid-auto-flow'
// property (row or column), the minor direction is its opposite.
major_direction_(grid_style.IsGridAutoFlowDirectionRow() ? kForRows
: kForColumns),
minor_direction_(grid_style.IsGridAutoFlowDirectionRow() ? kForColumns
: kForRows),
column_auto_repeat_track_count_(
grid_style.GridTemplateColumns().NGTrackList().AutoRepeatSize() *
column_auto_repetitions),
row_auto_repeat_track_count_(
grid_style.GridTemplateRows().NGTrackList().AutoRepeatSize() *
row_auto_repetitions),
column_auto_repetitions_(column_auto_repetitions),
row_auto_repetitions_(row_auto_repetitions) {}
// https://drafts.csswg.org/css-grid/#auto-placement-algo
void NGGridPlacement::RunAutoPlacementAlgorithm(GridItems* grid_items) {
DCHECK(grid_items);
minor_max_end_line_ = (minor_direction_ == kForColumns)
? GridPositionsResolver::ExplicitGridColumnCount(
grid_style_, column_auto_repeat_track_count_)
: GridPositionsResolver::ExplicitGridRowCount(
grid_style_, row_auto_repeat_track_count_);
// We need these variables in order to use |GridPositionsResolver|.
column_start_offset_ = DetermineTrackStartOffset(*grid_items, kForColumns);
row_start_offset_ = DetermineTrackStartOffset(*grid_items, kForRows);
// Step 1. Position anything that’s not auto-placed; if no items need
// auto-placement, then we are done.
if (!PlaceNonAutoGridItems(grid_items))
return;
// Step 2. Process the items locked to the major axis.
PlaceGridItemsLockedToMajorAxis(grid_items);
// Step 3. Determine the number of minor tracks in the implicit grid.
// This is already accomplished within the |PlaceNonAutoGridItems| and
// |PlaceGridItemsLockedToMajorAxis| methods; nothing else to do here.
// Step 4. Position remaining grid items.
AutoPlacementCursor placement_cursor;
for (GridItemData& grid_item : grid_items->item_data) {
switch (grid_item.AutoPlacement(major_direction_)) {
case AutoPlacementType::kBoth:
PlaceAutoBothAxisGridItem(&grid_item, &placement_cursor, *grid_items);
break;
case AutoPlacementType::kMajor:
PlaceAutoMajorAxisGridItem(&grid_item, &placement_cursor, *grid_items);
break;
case AutoPlacementType::kMinor:
NOTREACHED() << "Minor axis placement should've already occurred.";
break;
case AutoPlacementType::kNotNeeded:
break;
}
}
}
wtf_size_t NGGridPlacement::DetermineTrackStartOffset(
const GridItems& grid_items,
GridTrackSizingDirection track_direction) const {
wtf_size_t track_start_offset = 0;
for (const auto& grid_item : grid_items.item_data) {
GridSpan grid_item_span =
GridPositionsResolver::ResolveGridPositionsFromStyle(
grid_style_, grid_item.node.Style(), track_direction,
AutoRepeatTrackCount(track_direction));
if (!grid_item_span.IsIndefinite()) {
DCHECK(grid_item_span.IsUntranslatedDefinite());
track_start_offset = std::max<int>(
track_start_offset, -grid_item_span.UntranslatedStartLine());
}
}
return track_start_offset;
}
bool NGGridPlacement::PlaceNonAutoGridItems(GridItems* grid_items) {
DCHECK(grid_items);
bool has_auto_placed_items = false;
for (GridItemData& grid_item : grid_items->item_data) {
bool has_definite_major_placement =
PlaceGridItem(&grid_item, major_direction_);
bool has_definite_minor_placement =
PlaceGridItem(&grid_item, minor_direction_);
if (has_definite_minor_placement) {
minor_max_end_line_ =
std::max(minor_max_end_line_, grid_item.EndLine(minor_direction_));
} else {
has_auto_placed_items = true;
minor_max_end_line_ = std::max<wtf_size_t>(
minor_max_end_line_, GridPositionsResolver::SpanSizeForAutoPlacedItem(
grid_item.node.Style(), minor_direction_));
}
has_auto_placed_items |= !has_definite_major_placement;
}
return has_auto_placed_items;
}
void NGGridPlacement::PlaceGridItemsLockedToMajorAxis(GridItems* grid_items) {
DCHECK(grid_items);
// Mapping between the major axis tracks and the last auto-placed item's end
// line inserted on that track. This is needed to implement "sparse" packing
// for grid items locked to a given major axis track.
// See https://drafts.csswg.org/css-grid/#auto-placement-algo.
HashMap<wtf_size_t, wtf_size_t> minor_cursors;
for (GridItemData& grid_item : grid_items->item_data) {
// Only consider grid items that require minor axis auto-placement.
if (grid_item.AutoPlacement(major_direction_) != AutoPlacementType::kMinor)
continue;
wtf_size_t minor_start;
if (HasSparsePacking() &&
minor_cursors.Contains(grid_item.StartLine(major_direction_) + 1)) {
minor_start = minor_cursors.at(grid_item.StartLine(major_direction_) + 1);
} else {
minor_start = 0;
}
wtf_size_t minor_span_size =
GridPositionsResolver::SpanSizeForAutoPlacedItem(grid_item.node.Style(),
minor_direction_);
while (DoesItemOverlap(grid_item.StartLine(major_direction_),
grid_item.EndLine(major_direction_), minor_start,
minor_start + minor_span_size, *grid_items)) {
++minor_start;
}
wtf_size_t minor_end = minor_start + minor_span_size;
if (HasSparsePacking())
minor_cursors.Set(grid_item.StartLine(major_direction_) + 1, minor_end);
minor_max_end_line_ = std::max(minor_max_end_line_, minor_end);
// Update grid item placement for minor axis.
GridSpan grid_item_span =
GridSpan::TranslatedDefiniteGridSpan(minor_start, minor_end);
grid_item.SetSpan(grid_item_span, minor_direction_);
}
}
void NGGridPlacement::PlaceAutoMajorAxisGridItem(
GridItemData* grid_item,
AutoPlacementCursor* placement_cursor,
const GridItems& grid_items) {
DCHECK(grid_item);
if (HasSparsePacking()) {
// For sparse packing, set the minor position of the cursor to the grid
// item’s minor starting line. If this is less than the previous column
// position of the cursor, increment the major position by 1.
if (grid_item->StartLine(minor_direction_) <
placement_cursor->minor_position) {
++placement_cursor->major_position;
}
} else {
// Otherwise, for dense packing, reset the auto-placement cursor's major
// position to the start-most major line in the implicit grid.
placement_cursor->major_position = 0;
}
placement_cursor->minor_position = grid_item->StartLine(minor_direction_);
wtf_size_t major_span_size = GridPositionsResolver::SpanSizeForAutoPlacedItem(
grid_item->node.Style(), major_direction_);
// Increment the cursor’s major position until a value is found where the
// grid item does not overlap any occupied grid cells
while (DoesItemOverlap(placement_cursor->major_position,
placement_cursor->major_position + major_span_size,
grid_item->StartLine(minor_direction_),
grid_item->EndLine(minor_direction_), grid_items)) {
++placement_cursor->major_position;
}
// Update grid item placement for major axis.
GridSpan grid_item_span = GridSpan::TranslatedDefiniteGridSpan(
placement_cursor->major_position,
placement_cursor->major_position + major_span_size);
grid_item->SetSpan(grid_item_span, major_direction_);
}
void NGGridPlacement::PlaceAutoBothAxisGridItem(
GridItemData* grid_item,
AutoPlacementCursor* placement_cursor,
const GridItems& grid_items) {
DCHECK(grid_item);
// For dense packing, set the cursor’s major and minor positions to the
// start-most row and column lines in the implicit grid.
if (!HasSparsePacking()) {
placement_cursor->major_position = 0;
placement_cursor->minor_position = 0;
}
wtf_size_t major_span_size = GridPositionsResolver::SpanSizeForAutoPlacedItem(
grid_item->node.Style(), major_direction_);
wtf_size_t minor_span_size = GridPositionsResolver::SpanSizeForAutoPlacedItem(
grid_item->node.Style(), minor_direction_);
// Check to see if there would be overlap if this item was placed at the
// cursor. If overlap exists, increment minor position until no conflict
// exists or the item would overflow the minor axis.
while (DoesItemOverlap(placement_cursor->major_position,
placement_cursor->major_position + major_span_size,
placement_cursor->minor_position,
placement_cursor->minor_position + minor_span_size,
grid_items)) {
++placement_cursor->minor_position;
if (placement_cursor->minor_position + minor_span_size >
minor_max_end_line_) {
// If the cursor overflows the minor axis, increment cursor on the major
// axis and start from the beginning.
++placement_cursor->major_position;
placement_cursor->minor_position = 0;
}
}
// Update grid item placement for both axis.
GridSpan grid_item_span = GridSpan::TranslatedDefiniteGridSpan(
placement_cursor->major_position,
placement_cursor->major_position + major_span_size);
grid_item->SetSpan(grid_item_span, major_direction_);
grid_item_span = GridSpan::TranslatedDefiniteGridSpan(
placement_cursor->minor_position,
placement_cursor->minor_position + minor_span_size);
grid_item->SetSpan(grid_item_span, minor_direction_);
}
bool NGGridPlacement::PlaceGridItem(
GridItemData* grid_item,
GridTrackSizingDirection track_direction) const {
DCHECK(grid_item);
GridSpan span = GridPositionsResolver::ResolveGridPositionsFromStyle(
grid_style_, grid_item->node.Style(), track_direction,
AutoRepeatTrackCount(track_direction));
if (span.IsIndefinite()) {
DCHECK(grid_item->Span(track_direction).IsIndefinite());
return false;
}
DCHECK(span.IsUntranslatedDefinite());
span.Translate(StartOffset(track_direction));
grid_item->SetSpan(span, track_direction);
return true;
}
bool NGGridPlacement::DoesItemOverlap(wtf_size_t major_start,
wtf_size_t major_end,
wtf_size_t minor_start,
wtf_size_t minor_end,
const GridItems& grid_items) const {
DCHECK_LE(major_start, major_end);
DCHECK_LE(minor_start, minor_end);
// TODO(janewman): Implement smarter collision detection, iterating over all
// items is not ideal and has large performance implications.
for (const auto& grid_item : grid_items.item_data) {
if (grid_item.Span(major_direction_).IsIndefinite())
continue;
// Only test against definite positions.
// No collision if both start and end are on the same side of the item.
wtf_size_t item_major_start = grid_item.StartLine(major_direction_);
wtf_size_t item_major_end = grid_item.EndLine(major_direction_);
if (major_end <= item_major_start)
continue;
if (major_start >= item_major_end)
continue;
// Do the same for the minor axis.
if (grid_item.Span(minor_direction_).IsIndefinite())
continue;
wtf_size_t item_minor_start = grid_item.StartLine(minor_direction_);
wtf_size_t item_minor_end = grid_item.EndLine(minor_direction_);
if (minor_end <= item_minor_start)
continue;
if (minor_start >= item_minor_end)
continue;
return true;
}
return false;
}
wtf_size_t NGGridPlacement::AutoRepetitions(
GridTrackSizingDirection track_direction) const {
return (track_direction == kForColumns) ? column_auto_repetitions_
: row_auto_repetitions_;
}
wtf_size_t NGGridPlacement::StartOffset(
GridTrackSizingDirection track_direction) const {
return (track_direction == kForColumns) ? column_start_offset_
: row_start_offset_;
}
wtf_size_t NGGridPlacement::AutoRepeatTrackCount(
GridTrackSizingDirection track_direction) const {
return (track_direction == kForColumns) ? column_auto_repeat_track_count_
: row_auto_repeat_track_count_;
}
bool NGGridPlacement::HasSparsePacking() const {
return packing_behavior_ == PackingBehavior::kSparse;
}
namespace {
bool IsStartLineAuto(const GridTrackSizingDirection track_direction,
const ComputedStyle& out_of_flow_item_style) {
return (track_direction == kForColumns)
? out_of_flow_item_style.GridColumnStart().IsAuto()
: out_of_flow_item_style.GridRowStart().IsAuto();
}
bool IsEndLineAuto(const GridTrackSizingDirection track_direction,
const ComputedStyle& out_of_flow_item_style) {
return (track_direction == kForColumns)
? out_of_flow_item_style.GridColumnEnd().IsAuto()
: out_of_flow_item_style.GridRowEnd().IsAuto();
}
} // namespace
void NGGridPlacement::ResolveOutOfFlowItemGridLines(
const NGGridLayoutAlgorithmTrackCollection& track_collection,
const ComputedStyle& out_of_flow_item_style,
wtf_size_t* start_line,
wtf_size_t* end_line) const {
DCHECK(start_line);
DCHECK(end_line);
const GridTrackSizingDirection track_direction = track_collection.Direction();
GridSpan span = GridPositionsResolver::ResolveGridPositionsFromStyle(
grid_style_, out_of_flow_item_style, track_direction,
AutoRepeatTrackCount(track_direction));
if (span.IsIndefinite()) {
*start_line = kNotFound;
*end_line = kNotFound;
return;
}
wtf_size_t start_offset = StartOffset(track_direction);
int span_start_line = span.UntranslatedStartLine() + start_offset;
int span_end_line = span.UntranslatedEndLine() + start_offset;
if (span_start_line < 0 ||
IsStartLineAuto(track_direction, out_of_flow_item_style) ||
!track_collection.IsGridLineWithinImplicitGrid(span_start_line)) {
*start_line = kNotFound;
} else {
*start_line = span_start_line;
}
if (span_end_line < 0 ||
IsEndLineAuto(track_direction, out_of_flow_item_style) ||
!track_collection.IsGridLineWithinImplicitGrid(span_end_line)) {
*end_line = kNotFound;
} else {
*end_line = span_end_line;
}
}
} // namespace blink