diff --git a/tests/grid/AutoPlacementTest.cpp b/tests/grid/AutoPlacementTest.cpp new file mode 100644 index 0000000000..96c7ed8a92 --- /dev/null +++ b/tests/grid/AutoPlacementTest.cpp @@ -0,0 +1,591 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include +#include +#include +#include +#include +#include + +using namespace facebook::yoga; + +class GridAutoplacementTest : public ::testing::Test { + protected: + void SetUp() override { + gridContainer = new Node(); + gridContainer->style().setDisplay(Display::Grid); + } + + void TearDown() override { + delete gridContainer; + } + + Node* gridContainer{nullptr}; + + Node* createGridItem( + GridLine columnStart = GridLine::auto_(), + GridLine columnEnd = GridLine::auto_(), + GridLine rowStart = GridLine::auto_(), + GridLine rowEnd = GridLine::auto_()) { + auto item = new Node(); + item->style().setGridColumnStart(columnStart); + item->style().setGridColumnEnd(columnEnd); + item->style().setGridRowStart(rowStart); + item->style().setGridRowEnd(rowEnd); + gridContainer->insertChild(item, gridContainer->getChildren().size()); + return item; + } +}; + +TEST_F(GridAutoplacementTest, places_items_with_definite_positions) { + createGridItem( + GridLine::fromInteger(1), + GridLine::fromInteger(3), + GridLine::fromInteger(1), + GridLine::fromInteger(2)); + createGridItem( + GridLine::fromInteger(2), + GridLine::fromInteger(4), + GridLine::fromInteger(2), + GridLine::fromInteger(3)); + + auto autoPlacementResult = AutoPlacement::performAutoPlacement(gridContainer); + auto& placements = autoPlacementResult.gridItems; + ASSERT_EQ(placements.size(), 2); + + EXPECT_EQ(placements[0].columnStart, 0); + EXPECT_EQ(placements[0].columnEnd, 2); + EXPECT_EQ(placements[0].rowStart, 0); + EXPECT_EQ(placements[0].rowEnd, 1); + + EXPECT_EQ(placements[1].columnStart, 1); + EXPECT_EQ(placements[1].columnEnd, 3); + EXPECT_EQ(placements[1].rowStart, 1); + EXPECT_EQ(placements[1].rowEnd, 2); +} + +TEST_F(GridAutoplacementTest, places_items_with_definite_row_auto_column) { + createGridItem( + GridLine::fromInteger(1), + GridLine::fromInteger(3), + GridLine::fromInteger(1), + GridLine::fromInteger(2)); + + createGridItem( + GridLine::auto_(), + GridLine::auto_(), + GridLine::fromInteger(1), + GridLine::fromInteger(2)); + + auto autoPlacementResult = AutoPlacement::performAutoPlacement(gridContainer); + auto& placements = autoPlacementResult.gridItems; + ASSERT_EQ(placements.size(), 2); + + EXPECT_EQ(placements[0].columnStart, 0); + EXPECT_EQ(placements[0].columnEnd, 2); + EXPECT_EQ(placements[0].rowStart, 0); + EXPECT_EQ(placements[0].rowEnd, 1); + EXPECT_EQ(placements[1].columnStart, 2); + EXPECT_EQ(placements[1].columnEnd, 3); + EXPECT_EQ(placements[1].rowStart, 0); + EXPECT_EQ(placements[1].rowEnd, 1); +} + +TEST_F(GridAutoplacementTest, handles_overlapping_definite_row_items) { + createGridItem( + GridLine::auto_(), + GridLine::span(2), + GridLine::fromInteger(1), + GridLine::fromInteger(2)); + createGridItem( + GridLine::auto_(), + GridLine::span(2), + GridLine::fromInteger(1), + GridLine::fromInteger(2)); + + auto autoPlacementResult = AutoPlacement::performAutoPlacement(gridContainer); + auto& placements = autoPlacementResult.gridItems; + + ASSERT_EQ(placements.size(), 2); + + EXPECT_EQ(placements[0].columnStart, 0); + EXPECT_EQ(placements[0].columnEnd, 2); + EXPECT_EQ(placements[0].rowStart, 0); + EXPECT_EQ(placements[0].rowEnd, 1); + + EXPECT_EQ(placements[1].columnStart, 2); + EXPECT_EQ(placements[1].columnEnd, 4); + EXPECT_EQ(placements[1].rowStart, 0); + EXPECT_EQ(placements[1].rowEnd, 1); +} + +TEST_F(GridAutoplacementTest, places_auto_positioned_items) { + createGridItem(); + createGridItem(); + + auto autoPlacementResult = AutoPlacement::performAutoPlacement(gridContainer); + auto& placements = autoPlacementResult.gridItems; + + ASSERT_EQ(placements.size(), 2); + + EXPECT_EQ(placements[0].columnStart, 0); + EXPECT_EQ(placements[0].columnEnd, 1); + EXPECT_EQ(placements[0].rowStart, 0); + EXPECT_EQ(placements[0].rowEnd, 1); + + EXPECT_EQ(placements[1].columnStart, 0); + EXPECT_EQ(placements[1].columnEnd, 1); + EXPECT_EQ(placements[1].rowStart, 1); + EXPECT_EQ(placements[1].rowEnd, 2); +} + +TEST_F(GridAutoplacementTest, handles_large_spans) { + createGridItem(GridLine::auto_(), GridLine::span(5)); + + auto autoPlacementResult = AutoPlacement::performAutoPlacement(gridContainer); + auto& placements = autoPlacementResult.gridItems; + + ASSERT_EQ(placements.size(), 1); + + EXPECT_EQ(placements[0].columnStart, 0); + EXPECT_EQ(placements[0].columnEnd, 5); + EXPECT_EQ(placements[0].rowStart, 0); + EXPECT_EQ(placements[0].rowEnd, 1); +} + +TEST_F(GridAutoplacementTest, places_items_with_definite_column_auto_row) { + createGridItem( + GridLine::fromInteger(2), + GridLine::fromInteger(4), + GridLine::auto_(), + GridLine::auto_()); + + createGridItem( + GridLine::fromInteger(1), + GridLine::fromInteger(2), + GridLine::auto_(), + GridLine::auto_()); + + auto autoPlacementResult = AutoPlacement::performAutoPlacement(gridContainer); + auto& placements = autoPlacementResult.gridItems; + + ASSERT_EQ(placements.size(), 2); + + EXPECT_EQ(placements[0].columnStart, 1); + EXPECT_EQ(placements[0].columnEnd, 3); + EXPECT_EQ(placements[0].rowStart, 0); + EXPECT_EQ(placements[0].rowEnd, 1); + + EXPECT_EQ(placements[1].columnStart, 0); + EXPECT_EQ(placements[1].columnEnd, 1); + EXPECT_EQ(placements[1].rowStart, 1); + EXPECT_EQ(placements[1].rowEnd, 2); +} + +TEST_F(GridAutoplacementTest, avoids_overlaps_with_definite_column_items) { + createGridItem( + GridLine::fromInteger(1), + GridLine::fromInteger(3), + GridLine::fromInteger(1), + GridLine::fromInteger(3)); + + createGridItem( + GridLine::fromInteger(2), + GridLine::fromInteger(3), + GridLine::auto_(), + GridLine::auto_()); + + auto autoPlacementResult = AutoPlacement::performAutoPlacement(gridContainer); + auto& placements = autoPlacementResult.gridItems; + + ASSERT_EQ(placements.size(), 2); + + EXPECT_EQ(placements[0].columnStart, 0); + EXPECT_EQ(placements[0].columnEnd, 2); + EXPECT_EQ(placements[0].rowStart, 0); + EXPECT_EQ(placements[0].rowEnd, 2); + + EXPECT_EQ(placements[1].columnStart, 1); + EXPECT_EQ(placements[1].columnEnd, 2); + EXPECT_EQ(placements[1].rowStart, 2); + EXPECT_EQ(placements[1].rowEnd, 3); +} + +TEST_F(GridAutoplacementTest, handles_mixed_positioning_strategies) { + createGridItem( + GridLine::fromInteger(1), + GridLine::fromInteger(2), + GridLine::fromInteger(1), + GridLine::fromInteger(2)); + + createGridItem( + GridLine::auto_(), + GridLine::span(2), + GridLine::fromInteger(1), + GridLine::fromInteger(2)); + + createGridItem(); + + auto autoPlacementResult = AutoPlacement::performAutoPlacement(gridContainer); + auto& placements = autoPlacementResult.gridItems; + + ASSERT_EQ(placements.size(), 3); + + EXPECT_EQ(placements[0].columnStart, 0); + EXPECT_EQ(placements[0].columnEnd, 1); + EXPECT_EQ(placements[0].rowStart, 0); + EXPECT_EQ(placements[0].rowEnd, 1); + + EXPECT_EQ(placements[1].columnStart, 1); + EXPECT_EQ(placements[1].columnEnd, 3); + EXPECT_EQ(placements[1].rowStart, 0); + EXPECT_EQ(placements[1].rowEnd, 1); + + EXPECT_EQ(placements[2].columnStart, 0); + EXPECT_EQ(placements[2].columnEnd, 1); + EXPECT_EQ(placements[2].rowStart, 1); + EXPECT_EQ(placements[2].rowEnd, 2); +} + +TEST_F(GridAutoplacementTest, handles_negative_grid_line_references_simple) { + std::vector columns = { + GridTrackSize::length(20.0f), + GridTrackSize::length(20.0f), + GridTrackSize::length(20.0f)}; + gridContainer->style().setGridTemplateColumns(std::move(columns)); + createGridItem(GridLine::fromInteger(-1), GridLine::fromInteger(-2)); + + auto autoPlacementResult = AutoPlacement::performAutoPlacement(gridContainer); + auto& placements = autoPlacementResult.gridItems; + + ASSERT_EQ(placements.size(), 1); + EXPECT_EQ(placements[0].columnStart, 2); + EXPECT_EQ(placements[0].columnEnd, 3); + EXPECT_EQ(placements[0].rowStart, 0); + EXPECT_EQ(placements[0].rowEnd, 1); +} + +TEST_F(GridAutoplacementTest, handles_negative_grid_line_references) { + std::vector columns = { + GridTrackSize::length(20.0f), + GridTrackSize::length(20.0f), + GridTrackSize::length(20.0f)}; + gridContainer->style().setGridTemplateColumns(std::move(columns)); + + // .one { grid-column-start: -5; grid-column-end: -4; } + auto node1 = + createGridItem(GridLine::fromInteger(-5), GridLine::fromInteger(-4)); + + // .two { grid-column-start: 1; grid-column-end: 2; grid-row-start: 1; + // grid-row-end: 2; } + auto node2 = createGridItem( + GridLine::fromInteger(1), + GridLine::fromInteger(2), + GridLine::fromInteger(1), + GridLine::fromInteger(2)); + + auto node3 = createGridItem(); + auto node4 = createGridItem(); + auto node5 = createGridItem(); + + auto autoPlacementResult = AutoPlacement::performAutoPlacement(gridContainer); + auto& placements = autoPlacementResult.gridItems; + + ASSERT_EQ(placements.size(), 5); + + for (auto placement : placements) { + if (placement.node == node1) { + EXPECT_EQ(placement.columnStart, -1); + EXPECT_EQ(placement.columnEnd, 0); + EXPECT_EQ(placement.rowStart, 0); + EXPECT_EQ(placement.rowEnd, 1); + } else if (placement.node == node2) { + EXPECT_EQ(placement.columnStart, 0); + EXPECT_EQ(placement.columnEnd, 1); + EXPECT_EQ(placement.rowStart, 0); + EXPECT_EQ(placement.rowEnd, 1); + } else if (placement.node == node3) { + EXPECT_EQ(placement.columnStart, 1); + EXPECT_EQ(placement.columnEnd, 2); + EXPECT_EQ(placement.rowStart, 0); + EXPECT_EQ(placement.rowEnd, 1); + } else if (placement.node == node4) { + EXPECT_EQ(placement.columnStart, 2); + EXPECT_EQ(placement.columnEnd, 3); + EXPECT_EQ(placement.rowStart, 0); + EXPECT_EQ(placement.rowEnd, 1); + } else if (placement.node == node5) { + EXPECT_EQ(placement.columnStart, -1); + EXPECT_EQ(placement.columnEnd, 0); + EXPECT_EQ(placement.rowStart, 1); + EXPECT_EQ(placement.rowEnd, 2); + } + } +} + +TEST_F(GridAutoplacementTest, same_start_and_end_line_spans_one_track) { + createGridItem( + GridLine::fromInteger(1), + GridLine::fromInteger(1), + GridLine::fromInteger(1), + GridLine::fromInteger(2)); + + auto autoPlacementResult = AutoPlacement::performAutoPlacement(gridContainer); + auto& placements = autoPlacementResult.gridItems; + + ASSERT_EQ(placements.size(), 1); + EXPECT_EQ(placements[0].columnStart, 0); + EXPECT_EQ(placements[0].columnEnd, 1); + EXPECT_EQ(placements[0].rowStart, 0); + EXPECT_EQ(placements[0].rowEnd, 1); +} + +TEST_F( + GridAutoplacementTest, + handles_negative_grid_lines_with_row_positioning) { + std::vector columns = { + GridTrackSize::length(20.0f), + GridTrackSize::length(20.0f), + GridTrackSize::length(20.0f)}; + gridContainer->style().setGridTemplateColumns(std::move(columns)); + + // .one { grid-column-start: -5; grid-column-end: -4; grid-row-start: 2; + // grid-row-end: 3; } + auto node1 = createGridItem( + GridLine::fromInteger(-5), + GridLine::fromInteger(-4), + GridLine::fromInteger(2), + GridLine::fromInteger(3)); + + // .two { grid-row-start: 1; grid-row-end: 2; } + auto node2 = createGridItem( + GridLine::auto_(), + GridLine::auto_(), + GridLine::fromInteger(1), + GridLine::fromInteger(2)); + + auto node3 = createGridItem(); + auto node4 = createGridItem(); + auto node5 = createGridItem(); + + auto autoPlacementResult = AutoPlacement::performAutoPlacement(gridContainer); + auto& placements = autoPlacementResult.gridItems; + + ASSERT_EQ(placements.size(), 5); + + for (auto placement : placements) { + if (placement.node == node1) { + EXPECT_EQ(placement.columnStart, -1); + EXPECT_EQ(placement.columnEnd, 0); + EXPECT_EQ(placement.rowStart, 1); + EXPECT_EQ(placement.rowEnd, 2); + } else if (placement.node == node2) { + EXPECT_EQ(placement.columnStart, -1); + EXPECT_EQ(placement.columnEnd, 0); + EXPECT_EQ(placement.rowStart, 0); + EXPECT_EQ(placement.rowEnd, 1); + } else if (placement.node == node3) { + EXPECT_EQ(placement.columnStart, 0); + EXPECT_EQ(placement.columnEnd, 1); + EXPECT_EQ(placement.rowStart, 0); + EXPECT_EQ(placement.rowEnd, 1); + } else if (placement.node == node4) { + EXPECT_EQ(placement.columnStart, 1); + EXPECT_EQ(placement.columnEnd, 2); + EXPECT_EQ(placement.rowStart, 0); + EXPECT_EQ(placement.rowEnd, 1); + } else if (placement.node == node5) { + EXPECT_EQ(placement.columnStart, 2); + EXPECT_EQ(placement.columnEnd, 3); + EXPECT_EQ(placement.rowStart, 0); + EXPECT_EQ(placement.rowEnd, 1); + } + } +} + +TEST_F(GridAutoplacementTest, skips_past_large_blocking_items) { + auto largeItem = createGridItem( + GridLine::fromInteger(1), + GridLine::fromInteger(6), + GridLine::fromInteger(1), + GridLine::fromInteger(2)); + + auto autoItem1 = createGridItem( + GridLine::auto_(), + GridLine::auto_(), + GridLine::fromInteger(1), + GridLine::fromInteger(2)); + auto autoItem2 = createGridItem( + GridLine::auto_(), + GridLine::auto_(), + GridLine::fromInteger(1), + GridLine::fromInteger(2)); + + auto autoPlacementResult = AutoPlacement::performAutoPlacement(gridContainer); + auto& placements = autoPlacementResult.gridItems; + + ASSERT_EQ(placements.size(), 3); + + for (auto placement : placements) { + if (placement.node == largeItem) { + EXPECT_EQ(placement.columnStart, 0); + EXPECT_EQ(placement.columnEnd, 5); + EXPECT_EQ(placement.rowStart, 0); + EXPECT_EQ(placement.rowEnd, 1); + } else if (placement.node == autoItem1) { + EXPECT_EQ(placement.columnStart, 5); + EXPECT_EQ(placement.columnEnd, 6); + EXPECT_EQ(placement.rowStart, 0); + EXPECT_EQ(placement.rowEnd, 1); + } else if (placement.node == autoItem2) { + EXPECT_EQ(placement.columnStart, 6); + EXPECT_EQ(placement.columnEnd, 7); + EXPECT_EQ(placement.rowStart, 0); + EXPECT_EQ(placement.rowEnd, 1); + } + } +} + +TEST_F(GridAutoplacementTest, skips_past_large_blocking_rows) { + createGridItem( + GridLine::fromInteger(1), + GridLine::fromInteger(2), + GridLine::fromInteger(1), + GridLine::fromInteger(6)); + + auto autoItem = createGridItem( + GridLine::fromInteger(1), + GridLine::fromInteger(2), + GridLine::auto_(), + GridLine::auto_()); + + auto autoPlacementResult = AutoPlacement::performAutoPlacement(gridContainer); + auto& placements = autoPlacementResult.gridItems; + + ASSERT_EQ(placements.size(), 2); + + for (auto placement : placements) { + if (placement.node == autoItem) { + EXPECT_EQ(placement.columnStart, 0); + EXPECT_EQ(placement.columnEnd, 1); + EXPECT_EQ(placement.rowStart, 5); + EXPECT_EQ(placement.rowEnd, 6); + } + } +} + +TEST_F(GridAutoplacementTest, handles_nested_overlapping_items) { + createGridItem( + GridLine::fromInteger(1), + GridLine::fromInteger(8), + GridLine::fromInteger(1), + GridLine::fromInteger(2)); + + createGridItem( + GridLine::fromInteger(3), + GridLine::fromInteger(5), + GridLine::fromInteger(2), + GridLine::fromInteger(3)); + + auto autoItem = createGridItem( + GridLine::auto_(), + GridLine::auto_(), + GridLine::fromInteger(1), + GridLine::fromInteger(2)); + + auto autoPlacementResult = AutoPlacement::performAutoPlacement(gridContainer); + auto& placements = autoPlacementResult.gridItems; + + ASSERT_EQ(placements.size(), 3); + + for (auto placement : placements) { + if (placement.node == autoItem) { + EXPECT_EQ(placement.columnStart, 7); + EXPECT_EQ(placement.columnEnd, 8); + EXPECT_EQ(placement.rowStart, 0); + EXPECT_EQ(placement.rowEnd, 1); + } + } +} + +TEST_F( + GridAutoplacementTest, + handles_negative_and_positive_grid_lines_auto_row) { + std::vector columns = { + GridTrackSize::length(20.0f), + GridTrackSize::length(20.0f), + GridTrackSize::length(20.0f)}; + gridContainer->style().setGridTemplateColumns(std::move(columns)); + + // .one { grid-column-start: -5; grid-column-end: -4; grid-row-start: 1; + // grid-row-end: 2; } + auto node1 = createGridItem( + GridLine::fromInteger(-5), + GridLine::fromInteger(-4), + GridLine::fromInteger(1), + GridLine::fromInteger(2)); + + // .two { grid-column-start: 1; grid-column-end: 2; } (auto rows) + auto node2 = createGridItem( + GridLine::fromInteger(1), + GridLine::fromInteger(2), + GridLine::auto_(), + GridLine::auto_()); + + auto node3 = createGridItem(); + auto node4 = createGridItem(); + auto node5 = createGridItem(); + + auto autoPlacementResult = AutoPlacement::performAutoPlacement(gridContainer); + auto& placements = autoPlacementResult.gridItems; + ASSERT_EQ(placements.size(), 5); + + for (auto placement : placements) { + if (placement.node == node1) { + EXPECT_EQ(placement.columnStart, -1); + EXPECT_EQ(placement.columnEnd, 0); + EXPECT_EQ(placement.rowStart, 0); + EXPECT_EQ(placement.rowEnd, 1); + } else if (placement.node == node2) { + EXPECT_EQ(placement.columnStart, 0); + EXPECT_EQ(placement.columnEnd, 1); + EXPECT_EQ(placement.rowStart, 0); + EXPECT_EQ(placement.rowEnd, 1); + } else if (placement.node == node3) { + EXPECT_EQ(placement.columnStart, 1); + EXPECT_EQ(placement.columnEnd, 2); + EXPECT_EQ(placement.rowStart, 0); + EXPECT_EQ(placement.rowEnd, 1); + } else if (placement.node == node4) { + EXPECT_EQ(placement.columnStart, 2); + EXPECT_EQ(placement.columnEnd, 3); + EXPECT_EQ(placement.rowStart, 0); + EXPECT_EQ(placement.rowEnd, 1); + } else if (placement.node == node5) { + EXPECT_EQ(placement.columnStart, -1); + EXPECT_EQ(placement.columnEnd, 0); + EXPECT_EQ(placement.rowStart, 1); + EXPECT_EQ(placement.rowEnd, 2); + } + } +} + +// grid-column-start: 0 is an invalid line and is treated as auto, so the item +// auto-places at the first cell rather than at an implicit negative column. +TEST_F(GridAutoplacementTest, grid_line_zero_is_treated_as_auto) { + createGridItem(GridLine::fromInteger(0), GridLine::auto_()); + + auto autoPlacementResult = AutoPlacement::performAutoPlacement(gridContainer); + auto& placements = autoPlacementResult.gridItems; + + ASSERT_EQ(placements.size(), 1); + EXPECT_EQ(placements[0].columnStart, 0); + EXPECT_EQ(placements[0].columnEnd, 1); + EXPECT_EQ(placements[0].rowStart, 0); + EXPECT_EQ(placements[0].rowEnd, 1); +} diff --git a/tests/grid/LinePlacementTest.cpp b/tests/grid/LinePlacementTest.cpp new file mode 100644 index 0000000000..2f3df7d678 --- /dev/null +++ b/tests/grid/LinePlacementTest.cpp @@ -0,0 +1,167 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include +#include + +namespace facebook::yoga { + +namespace { +// explicitLineCount is the number of grid lines in the explicit grid, i.e. the +// track count + 1. The examples below assume a 3-column grid (4 lines). +constexpr int32_t kThreeColumnLineCount = 4; + +void expectPlacement( + const LinePlacement& placement, + int32_t start, + int32_t end, + int32_t span) { + EXPECT_EQ(placement.start, start); + EXPECT_EQ(placement.end, end); + EXPECT_EQ(placement.span, span); +} +} // namespace + +// grid-column: 1 / 3 -> tracks [0, 2) +TEST(LinePlacement, two_definite_lines) { + expectPlacement( + resolveLinePlacement( + GridLine::fromInteger(1), + GridLine::fromInteger(3), + kThreeColumnLineCount), + 0, + 2, + 2); +} + +// grid-column: 3 / 1 -> reversed lines are swapped +TEST(LinePlacement, reversed_lines_are_swapped) { + expectPlacement( + resolveLinePlacement( + GridLine::fromInteger(3), + GridLine::fromInteger(1), + kThreeColumnLineCount), + 0, + 2, + 2); +} + +// grid-column: 2 / 2 -> equal lines, end line is dropped, spans one track +TEST(LinePlacement, equal_lines_span_one_track) { + expectPlacement( + resolveLinePlacement( + GridLine::fromInteger(2), + GridLine::fromInteger(2), + kThreeColumnLineCount), + 1, + 2, + 1); +} + +// grid-column: 2 / span 2 +TEST(LinePlacement, integer_start_with_span_end) { + expectPlacement( + resolveLinePlacement( + GridLine::fromInteger(2), GridLine::span(2), kThreeColumnLineCount), + 1, + 3, + 2); +} + +// grid-column: span 2 / 3 +TEST(LinePlacement, span_start_with_integer_end) { + expectPlacement( + resolveLinePlacement( + GridLine::span(2), GridLine::fromInteger(3), kThreeColumnLineCount), + 0, + 2, + 2); +} + +// grid-column: -1 -> the last line of a 3-column grid (line 4 -> index 3) +TEST(LinePlacement, negative_line_counts_from_end) { + expectPlacement( + resolveLinePlacement( + GridLine::fromInteger(-1), + GridLine::auto_(), + kThreeColumnLineCount), + 3, + 4, + 1); +} + +// grid-column: -5 / -4 -> resolves to the implicit track [-1, 0) +TEST(LinePlacement, negative_range_before_explicit_grid) { + expectPlacement( + resolveLinePlacement( + GridLine::fromInteger(-5), + GridLine::fromInteger(-4), + kThreeColumnLineCount), + -1, + 0, + 1); +} + +// grid-column: auto -> placeholder, resolved later by auto-placement +TEST(LinePlacement, auto_is_a_placeholder) { + expectPlacement( + resolveLinePlacement( + GridLine::auto_(), GridLine::auto_(), kThreeColumnLineCount), + -1, + -1, + 1); +} + +// grid-column: span 0 is invalid; a span must be at least 1. The -1/-1 result +// is the auto placeholder, so the item is auto-placed downstream (span 1). +TEST(LinePlacement, zero_span_clamps_to_one) { + expectPlacement( + resolveLinePlacement( + GridLine::span(0), GridLine::auto_(), kThreeColumnLineCount), + -1, + -1, + 1); +} + +// grid-column: span -2 is invalid; a span must be at least 1. The -1/-1 result +// is the auto placeholder, so the item is auto-placed downstream (span 1). +TEST(LinePlacement, negative_span_clamps_to_one) { + expectPlacement( + resolveLinePlacement( + GridLine::span(-2), GridLine::auto_(), kThreeColumnLineCount), + -1, + -1, + 1); +} + +// grid-column: 0 is an invalid line; it resolves to the auto placeholder +// (-1/-1), so the item is auto-placed downstream. +TEST(LinePlacement, line_zero_is_treated_as_auto) { + expectPlacement( + resolveLinePlacement( + GridLine::fromInteger(0), + GridLine::auto_(), + kThreeColumnLineCount), + -1, + -1, + 1); +} + +// grid-column: 0 / 2 -> the invalid 0 start becomes auto, leaving a definite end +// at line 2, so the item resolves to track [0, 1). +TEST(LinePlacement, line_zero_start_with_definite_end) { + expectPlacement( + resolveLinePlacement( + GridLine::fromInteger(0), + GridLine::fromInteger(2), + kThreeColumnLineCount), + 0, + 1, + 1); +} + +} // namespace facebook::yoga diff --git a/yoga/CMakeLists.txt b/yoga/CMakeLists.txt index b6eca1ac72..594600f702 100644 --- a/yoga/CMakeLists.txt +++ b/yoga/CMakeLists.txt @@ -20,7 +20,8 @@ include(${YOGA_ROOT}/cmake/project-defaults.cmake) file(GLOB SOURCES CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/**/*.cpp) + ${CMAKE_CURRENT_SOURCE_DIR}/**/*.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/algorithm/grid/*.cpp) add_library(yogacore STATIC ${SOURCES}) diff --git a/yoga/YGNodeStyle.cpp b/yoga/YGNodeStyle.cpp index e43a38e64e..82f8dd9619 100644 --- a/yoga/YGNodeStyle.cpp +++ b/yoga/YGNodeStyle.cpp @@ -8,7 +8,7 @@ #include #include #include -#include +#include using namespace facebook; using namespace facebook::yoga; diff --git a/yoga/algorithm/grid/AutoPlacement.cpp b/yoga/algorithm/grid/AutoPlacement.cpp new file mode 100644 index 0000000000..260a5c2614 --- /dev/null +++ b/yoga/algorithm/grid/AutoPlacement.cpp @@ -0,0 +1,328 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace facebook::yoga { + +namespace { + +bool isPlaceable(const Node* child) { + return child->style().positionType() != PositionType::Absolute && + child->style().display() != Display::None; +} + +} // namespace + +// 8.5. Grid Item Placement Algorithm +// https://www.w3.org/TR/css-grid-1/#auto-placement-algo +AutoPlacement AutoPlacement::performAutoPlacement(Node* node) { + std::vector items; + items.reserve(node->getChildCount()); + std::unordered_set placedItems; + placedItems.reserve(node->getChildCount()); + + int32_t minColumnStart = 0; + int32_t minRowStart = 0; + int32_t maxColumnEnd = + static_cast(node->style().gridTemplateColumns().size()); + int32_t maxRowEnd = + static_cast(node->style().gridTemplateRows().size()); + OccupancyGrid occupancy; + occupancy.occupied.reserve(node->getChildCount()); + + // Records a placed grid area and updates the running grid bounds. + const auto recordGridArea = [&](const AutoPlacementItem& area) { + yoga::assertFatal( + area.columnEnd > area.columnStart, + "Grid item column end must be greater than column start"); + yoga::assertFatal( + area.rowEnd > area.rowStart, + "Grid item row end must be greater than row start"); + items.push_back(area); + placedItems.insert(area.node); + occupancy.markOccupied( + area.rowStart, area.rowEnd, area.columnStart, area.columnEnd); + minColumnStart = std::min(minColumnStart, area.columnStart); + minRowStart = std::min(minRowStart, area.rowStart); + maxColumnEnd = std::max(maxColumnEnd, area.columnEnd); + maxRowEnd = std::max(maxRowEnd, area.rowEnd); + }; + + const auto explicitColumnLineCount = + static_cast(node->style().gridTemplateColumns().size() + 1); + const auto explicitRowLineCount = + static_cast(node->style().gridTemplateRows().size() + 1); + + // Step 1: Position anything that is not auto-positioned. In level 1 a span is + // always definite (default 1), so an item is definitely positioned when both + // its row and column have at least one integer line. + for (Node* child : node->getLayoutChildren()) { + if (!isPlaceable(child)) { + continue; + } + + const auto& gridItemColumnStart = child->style().gridColumnStart(); + const auto& gridItemColumnEnd = child->style().gridColumnEnd(); + const auto& gridItemRowStart = child->style().gridRowStart(); + const auto& gridItemRowEnd = child->style().gridRowEnd(); + const bool hasDefiniteColumn = isDefiniteLine(gridItemColumnStart) || + isDefiniteLine(gridItemColumnEnd); + const bool hasDefiniteRow = + isDefiniteLine(gridItemRowStart) || isDefiniteLine(gridItemRowEnd); + + if (hasDefiniteColumn && hasDefiniteRow) { + const auto columnPlacement = resolveLinePlacement( + gridItemColumnStart, gridItemColumnEnd, explicitColumnLineCount); + const auto rowPlacement = resolveLinePlacement( + gridItemRowStart, gridItemRowEnd, explicitRowLineCount); + + recordGridArea(AutoPlacementItem{ + .columnStart = columnPlacement.start, + .columnEnd = columnPlacement.end, + .rowStart = rowPlacement.start, + .rowEnd = rowPlacement.end, + .node = child}); + } + } + + // Step 2: Process the items locked to a given row (definite row, no definite + // column), placing each into the first non-overlapping slot to its right. + std::unordered_map rowStartToColumnStartCache; + for (Node* child : node->getLayoutChildren()) { + if (!isPlaceable(child)) { + continue; + } + + const auto& gridItemColumnStart = child->style().gridColumnStart(); + const auto& gridItemColumnEnd = child->style().gridColumnEnd(); + const auto& gridItemRowStart = child->style().gridRowStart(); + const auto& gridItemRowEnd = child->style().gridRowEnd(); + const bool hasDefiniteColumn = isDefiniteLine(gridItemColumnStart) || + isDefiniteLine(gridItemColumnEnd); + const bool hasDefiniteRow = + isDefiniteLine(gridItemRowStart) || isDefiniteLine(gridItemRowEnd); + + if (hasDefiniteRow && !hasDefiniteColumn) { + const auto rowPlacement = resolveLinePlacement( + gridItemRowStart, gridItemRowEnd, explicitRowLineCount); + const auto rowStart = rowPlacement.start; + const auto rowEnd = rowPlacement.end; + + const auto columnSpan = + resolveLinePlacement( + gridItemColumnStart, gridItemColumnEnd, explicitColumnLineCount) + .span; + + auto columnStart = rowStartToColumnStartCache.contains(rowStart) + ? rowStartToColumnStartCache[rowStart] + : minColumnStart; + auto columnEnd = columnStart + columnSpan; + + while (const auto* overlap = + occupancy.hasOverlap( + rowStart, rowEnd, columnStart, columnEnd)) { + columnStart = overlap->columnEnd; + columnEnd = columnStart + columnSpan; + } + + recordGridArea(AutoPlacementItem{ + .columnStart = columnStart, + .columnEnd = columnEnd, + .rowStart = rowStart, + .rowEnd = rowEnd, + .node = child}); + rowStartToColumnStartCache[rowStart] = columnEnd; + } + } + + // Step 3: Determine the columns of the implicit grid, accounting for definite + // columns that extend the grid and for the largest auto-placed column span. + auto largestColumnSpan = 1; + for (Node* child : node->getLayoutChildren()) { + if (!isPlaceable(child)) { + continue; + } + + const auto& gridItemColumnStart = child->style().gridColumnStart(); + const auto& gridItemColumnEnd = child->style().gridColumnEnd(); + const bool hasDefiniteColumn = isDefiniteLine(gridItemColumnStart) || + isDefiniteLine(gridItemColumnEnd); + + const auto columnPlacement = resolveLinePlacement( + gridItemColumnStart, gridItemColumnEnd, explicitColumnLineCount); + if (hasDefiniteColumn) { + minColumnStart = std::min(minColumnStart, columnPlacement.start); + maxColumnEnd = std::max(maxColumnEnd, columnPlacement.end); + } else { + largestColumnSpan = std::max(largestColumnSpan, columnPlacement.span); + } + } + + // If the largest span is wider than the current grid, extend the end. + const auto currentGridWidth = maxColumnEnd - minColumnStart; + if (largestColumnSpan > currentGridWidth) { + maxColumnEnd = minColumnStart + largestColumnSpan; + } + + // Step 4: Position the remaining grid items using the auto-placement cursor. + std::array autoPlacementCursor = {minColumnStart, minRowStart}; + for (Node* child : node->getLayoutChildren()) { + if (!isPlaceable(child) || placedItems.contains(child)) { + continue; + } + + const auto& gridItemColumnStart = child->style().gridColumnStart(); + const auto& gridItemColumnEnd = child->style().gridColumnEnd(); + const auto& gridItemRowStart = child->style().gridRowStart(); + const auto& gridItemRowEnd = child->style().gridRowEnd(); + const bool hasDefiniteColumn = isDefiniteLine(gridItemColumnStart) || + isDefiniteLine(gridItemColumnEnd); + const bool hasDefiniteRow = + isDefiniteLine(gridItemRowStart) || isDefiniteLine(gridItemRowEnd); + + const auto columnPlacement = resolveLinePlacement( + gridItemColumnStart, gridItemColumnEnd, explicitColumnLineCount); + const auto rowPlacement = resolveLinePlacement( + gridItemRowStart, gridItemRowEnd, explicitRowLineCount); + + // If the item has a definite column position, keep it and search downward + // for a row where it does not overlap an occupied cell. + if (hasDefiniteColumn) { + const auto columnStart = columnPlacement.start; + const auto columnEnd = columnPlacement.end; + + // Set the cursor to the item's column-start line; if that moves the cursor + // backwards, advance to the next row. + const auto previousColumnPosition = autoPlacementCursor[0]; + autoPlacementCursor[0] = columnStart; + if (autoPlacementCursor[0] < previousColumnPosition) { + autoPlacementCursor[1]++; + } + + const auto rowSpan = rowPlacement.span; + while (const auto* overlap = occupancy.hasOverlap( + autoPlacementCursor[1], + autoPlacementCursor[1] + rowSpan, + columnStart, + columnEnd)) { + autoPlacementCursor[1] = overlap->rowEnd; + } + + recordGridArea(AutoPlacementItem{ + .columnStart = columnStart, + .columnEnd = columnEnd, + .rowStart = autoPlacementCursor[1], + .rowEnd = autoPlacementCursor[1] + rowSpan, + .node = child}); + } + // If the item has an automatic position in both axes, sweep the cursor + // left-to-right then top-to-bottom until it fits. + else if (!hasDefiniteRow) { + const auto itemColumnSpan = columnPlacement.span; + const auto itemRowSpan = rowPlacement.span; + + bool foundPosition = false; + while (!foundPosition) { + while (autoPlacementCursor[0] + itemColumnSpan <= maxColumnEnd) { + const auto columnStart = autoPlacementCursor[0]; + const auto columnEnd = columnStart + itemColumnSpan; + const auto rowStart = autoPlacementCursor[1]; + const auto rowEnd = rowStart + itemRowSpan; + + if (const auto* overlap = + occupancy.hasOverlap( + rowStart, rowEnd, columnStart, columnEnd)) { + autoPlacementCursor[0] = overlap->columnEnd; + } else { + recordGridArea(AutoPlacementItem{ + .columnStart = columnStart, + .columnEnd = columnEnd, + .rowStart = rowStart, + .rowEnd = rowEnd, + .node = child}); + foundPosition = true; + break; + } + } + + if (!foundPosition) { + // Cursor overflowed the grid width; move to the next row. + autoPlacementCursor[1]++; + autoPlacementCursor[0] = minColumnStart; + } + } + } + } + + return AutoPlacement{ + .gridItems = std::move(items), + .minColumnStart = minColumnStart, + .minRowStart = minRowStart, + .maxColumnEnd = maxColumnEnd, + .maxRowEnd = maxRowEnd}; +} + +// 1. Runs the grid placement algorithm and normalizes the output into the 0-based coordinate space. +// 2. Builds the baseline sharing groups for each row. +ResolvedAutoPlacement ResolvedAutoPlacement::resolveGridItemPlacements( + Node* node) { + const AutoPlacement autoPlacement = AutoPlacement::performAutoPlacement(node); + + const int32_t minColumnStart = autoPlacement.minColumnStart; + const int32_t minRowStart = autoPlacement.minRowStart; + + ResolvedAutoPlacement resolved; + resolved.minColumnStart = minColumnStart; + resolved.minRowStart = minRowStart; + resolved.maxColumnEnd = autoPlacement.maxColumnEnd; + resolved.maxRowEnd = autoPlacement.maxRowEnd; + + resolved.gridItems.reserve(autoPlacement.gridItems.size()); + + const Align alignItems = node->style().alignItems(); + + for (const auto& placement : autoPlacement.gridItems) { + resolved.gridItems.emplace_back( + static_cast(placement.columnStart - minColumnStart), + static_cast(placement.columnEnd - minColumnStart), + static_cast(placement.rowStart - minRowStart), + static_cast(placement.rowEnd - minRowStart), + placement.node); + + GridItem& item = resolved.gridItems.back(); + Align alignSelf = item.node->style().alignSelf(); + if (alignSelf == Align::Auto) { + alignSelf = alignItems; + } + // https://www.w3.org/TR/css-grid-1/#algo-baseline-shims - only items that + // span a single row participate in a row's baseline sharing group. + const bool spansOneRow = (item.rowEnd - item.rowStart) == 1; + if (alignSelf == Align::Baseline && spansOneRow) { + resolved.baselineItemGroups[item.rowStart].push_back(&item); + } + } + + return resolved; +} + +} // namespace facebook::yoga diff --git a/yoga/algorithm/grid/AutoPlacement.h b/yoga/algorithm/grid/AutoPlacement.h new file mode 100644 index 0000000000..19630f6cff --- /dev/null +++ b/yoga/algorithm/grid/AutoPlacement.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include + +namespace facebook::yoga { + +class Node; + +struct AutoPlacement { + // A grid item's placement in the algorithm's signed coordinate space. + struct AutoPlacementItem { + int32_t columnStart; + int32_t columnEnd; + int32_t rowStart; + int32_t rowEnd; + Node* node; + }; + + std::vector gridItems; + int32_t minColumnStart; + int32_t minRowStart; + int32_t maxColumnEnd; + int32_t maxRowEnd; + + // Runs the four-step auto-placement algorithm + // (https://www.w3.org/TR/css-grid-1/#auto-placement-algo) over node's in-flow + // children. Children that are display: none or absolutely positioned are + // ignored. + static AutoPlacement performAutoPlacement(Node* node); +}; + +// 1. Runs the grid placement algorithm and normalizes the output into the 0-based coordinate space. +// 2. Builds the baseline sharing groups for each row. +struct ResolvedAutoPlacement { + // Items with resolved 0-based positions, in child order. + std::vector gridItems; + + // Baseline-aligned items grouped by starting row. + BaselineItemGroups baselineItemGroups; + + int32_t minColumnStart = 0; + int32_t minRowStart = 0; + int32_t maxColumnEnd = 0; + int32_t maxRowEnd = 0; + + ResolvedAutoPlacement() = default; + ResolvedAutoPlacement(ResolvedAutoPlacement&&) = default; + ResolvedAutoPlacement& operator=(ResolvedAutoPlacement&&) = default; + ResolvedAutoPlacement(const ResolvedAutoPlacement&) = delete; + ResolvedAutoPlacement& operator=(const ResolvedAutoPlacement&) = delete; + + // Normalizes performAutoPlacement's output to 0-based size_t coordinates and + // builds the baseline sharing groups. + static ResolvedAutoPlacement resolveGridItemPlacements(Node* node); +}; + +} // namespace facebook::yoga diff --git a/yoga/algorithm/grid/GridItem.h b/yoga/algorithm/grid/GridItem.h new file mode 100644 index 0000000000..72f92e4964 --- /dev/null +++ b/yoga/algorithm/grid/GridItem.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace facebook::yoga { + +// A grid item with its resolved position in the grid. Positions are set by the +// auto-placement algorithm and consumed by the track sizing algorithm. +struct GridItem { + size_t columnStart; + size_t columnEnd; + size_t rowStart; + size_t rowEnd; + yoga::Node* node; + + // Additional offset added to item to align baselines + // https://www.w3.org/TR/css-grid-1/#algo-baseline-shims + float baselineShim = 0.0f; + + // Flags used for optimizations in track sizing algorithm. + bool crossesIntrinsicRow = false; + bool crossesIntrinsicColumn = false; + bool crossesFlexibleRow = false; + bool crossesFlexibleColumn = false; + + GridItem( + size_t columnStart, + size_t columnEnd, + size_t rowStart, + size_t rowEnd, + yoga::Node* node, + float baselineShim = 0.0f) + : columnStart(columnStart), + columnEnd(columnEnd), + rowStart(rowStart), + rowEnd(rowEnd), + node(node), + baselineShim(baselineShim) {} + + // Helper functions used by the track sizing algorithm + bool crossesIntrinsicTrack(Dimension dimension) const { + return dimension == Dimension::Width ? crossesIntrinsicColumn + : crossesIntrinsicRow; + } + bool crossesFlexibleTrack(Dimension dimension) const { + return dimension == Dimension::Width ? crossesFlexibleColumn + : crossesFlexibleRow; + } +}; + +// Baseline sharing groups - items grouped by their starting row for the +// resolve intrinsic size step in track sizing algorithm. +// https://www.w3.org/TR/css-grid-1/#algo-baseline-shims +using BaselineItemGroups = std::map>; + +} // namespace facebook::yoga diff --git a/yoga/algorithm/grid/GridTrack.h b/yoga/algorithm/grid/GridTrack.h new file mode 100644 index 0000000000..056721013e --- /dev/null +++ b/yoga/algorithm/grid/GridTrack.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include + +namespace facebook::yoga { + +// https://www.w3.org/TR/css-grid-1/#algo-track-sizing +struct GridTrack { + // Sizing functions for this track, immutable after construction + const GridTrackSize trackSize; + + // Mutable state used by the track sizing algorithm + // https://www.w3.org/TR/css-grid-1/#base-size + float baseSize = 0.0f; + // https://www.w3.org/TR/css-grid-1/#growth-limit + float growthLimit = 0.0f; + // https://www.w3.org/TR/css-grid-1/#infinitely-growable + bool infinitelyGrowable = false; + + explicit GridTrack(const GridTrackSize& ts) : trackSize(ts) {} +}; + +} // namespace facebook::yoga diff --git a/yoga/algorithm/grid/LinePlacement.cpp b/yoga/algorithm/grid/LinePlacement.cpp new file mode 100644 index 0000000000..4c33c592a3 --- /dev/null +++ b/yoga/algorithm/grid/LinePlacement.cpp @@ -0,0 +1,117 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include + +namespace facebook::yoga { + +LinePlacement resolveLinePlacement( + const GridLine& startLine, + const GridLine& endLine, + int32_t explicitLineCount) { + LinePlacement placement; + + // Normalize invalid inputs per CSS Grid: line 0 is not a valid line (treated + // as auto), and a span must be at least 1 (an invalid 0 or negative span + // becomes 1). + const auto normalize = [](const GridLine& line) -> GridLine { + if (line.type == GridLineType::Integer && line.integer == 0) { + return GridLine::auto_(); + } + if (line.type == GridLineType::Span && line.integer < 1) { + return GridLine::span(1); + } + return line; + }; + const GridLine start = normalize(startLine); + const GridLine end = normalize(endLine); + + const auto resolveNegativeLineValue = + [](int32_t lineValue, int32_t lineCount) -> int32_t { + // Negative lines count back from the last line, e.g. -1 is the last line. + return lineValue < 0 ? lineCount + lineValue + 1 : lineValue; + }; + + // If the placement for a grid item contains two lines. + if (start.type == GridLineType::Integer && + end.type == GridLineType::Integer) { + const auto normalizedStartLine = + resolveNegativeLineValue(start.integer, explicitLineCount); + const auto normalizedEndLine = + resolveNegativeLineValue(end.integer, explicitLineCount); + // If the start line is further end-ward than the end line, swap the two. + if (normalizedStartLine > normalizedEndLine) { + placement.start = normalizedEndLine; + placement.end = normalizedStartLine; + placement.span = placement.end - placement.start; + } + // If the start line is equal to the end line, remove the end line. + else if (normalizedStartLine == normalizedEndLine) { + placement.start = normalizedStartLine; + placement.end = normalizedStartLine + 1; + placement.span = 1; + } else { + placement.start = normalizedStartLine; + placement.end = normalizedEndLine; + placement.span = placement.end - placement.start; + } + } + // If the placement contains two spans, remove the one contributed by the end + // grid-placement property. + else if (start.type == GridLineType::Span && end.type == GridLineType::Span) { + placement.start = 0; + placement.end = 0; + placement.span = start.integer; + } else if ( + start.type == GridLineType::Integer && end.type == GridLineType::Span) { + const auto normalizedStartLine = + resolveNegativeLineValue(start.integer, explicitLineCount); + placement.start = normalizedStartLine; + placement.span = end.integer; + placement.end = placement.start + placement.span; + } else if ( + start.type == GridLineType::Span && end.type == GridLineType::Integer) { + const auto normalizedEndLine = + resolveNegativeLineValue(end.integer, explicitLineCount); + placement.end = normalizedEndLine; + placement.span = start.integer; + placement.start = placement.end - placement.span; + } else if (start.type == GridLineType::Integer) { + const auto normalizedStartLine = + resolveNegativeLineValue(start.integer, explicitLineCount); + placement.start = normalizedStartLine; + placement.span = 1; + placement.end = placement.start + placement.span; + } else if (start.type == GridLineType::Span) { + placement.span = start.integer; + placement.start = 0; + placement.end = 0; + } else if (end.type == GridLineType::Integer) { + const auto normalizedEndLine = + resolveNegativeLineValue(end.integer, explicitLineCount); + placement.end = normalizedEndLine; + placement.span = 1; + placement.start = placement.end - placement.span; + } else if (end.type == GridLineType::Span) { + placement.span = end.integer; + placement.start = 0; + placement.end = 0; + } else { + placement.start = 0; + placement.end = 0; + placement.span = 1; + } + + // Grid lines above are 1-based; convert to 0-based track indices. Negative + // values imply implicit grid lines before the explicit grid. + placement.start = placement.start - 1; + placement.end = placement.end - 1; + + return placement; +} + +} // namespace facebook::yoga diff --git a/yoga/algorithm/grid/LinePlacement.h b/yoga/algorithm/grid/LinePlacement.h new file mode 100644 index 0000000000..a0ca2b29a6 --- /dev/null +++ b/yoga/algorithm/grid/LinePlacement.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include + +namespace facebook::yoga { + +// A resolved grid-line placement: a half-open [start, end) track interval plus +// its span. Line number can be negative to denote an implicit line before the explicit grid. +struct LinePlacement { + int32_t start = 0; + int32_t end = 0; + int32_t span = 1; +}; + +inline bool isDefiniteLine(const GridLine& line) { + return line.type == GridLineType::Integer && line.integer != 0; +} + +// Handles placement errors as defined in https://www.w3.org/TR/css-grid-1/#grid-placement-errors +// and returns 0 index based line positions from user added grid-columns and grid-rows. +LinePlacement resolveLinePlacement( + const GridLine& startLine, + const GridLine& endLine, + int32_t explicitLineCount); + +} // namespace facebook::yoga diff --git a/yoga/algorithm/grid/OccupancyGrid.h b/yoga/algorithm/grid/OccupancyGrid.h new file mode 100644 index 0000000000..d20644ff94 --- /dev/null +++ b/yoga/algorithm/grid/OccupancyGrid.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include + +namespace facebook::yoga { + +// Occupancy of the grid used by CSS Grid auto-placement. +// TODO(optimisation): Better data structure can be used than a flat vector +struct OccupancyGrid { + struct Rect { + int32_t rowStart; + int32_t rowEnd; + int32_t columnStart; + int32_t columnEnd; + }; + + std::vector occupied; + + void markOccupied( + int32_t rowStart, + int32_t rowEnd, + int32_t columnStart, + int32_t columnEnd) { + occupied.push_back({rowStart, rowEnd, columnStart, columnEnd}); + } + + const Rect* hasOverlap( + int32_t rowStart, + int32_t rowEnd, + int32_t columnStart, + int32_t columnEnd) const { + for (const auto& rect : occupied) { + if (rect.rowStart < rowEnd && rect.rowEnd > rowStart && + rect.columnStart < columnEnd && rect.columnEnd > columnStart) { + return ▭ + } + } + return nullptr; + } +}; + +} // namespace facebook::yoga diff --git a/yoga/style/GridTrack.h b/yoga/style/GridTrackSize.h similarity index 87% rename from yoga/style/GridTrack.h rename to yoga/style/GridTrackSize.h index 2a2bafb50c..a889054ac5 100644 --- a/yoga/style/GridTrack.h +++ b/yoga/style/GridTrackSize.h @@ -11,18 +11,13 @@ #include namespace facebook::yoga { +// Represents a track size as defined in // https://www.w3.org/TR/css-grid-1/#typedef-track-size +// and helper functions for creating common track sizes. struct GridTrackSize { StyleSizeLength minSizingFunction; StyleSizeLength maxSizingFunction; - // These are used in the grid layout algorithm when distributing spaces among - // tracks - // TODO: maybe move them to TrackSizing since these are track states - float baseSize = 0.0f; - float growthLimit = 0.0f; - bool infinitelyGrowable = false; - // Static factory methods for common cases constexpr static GridTrackSize auto_() { return GridTrackSize{ diff --git a/yoga/style/Style.h b/yoga/style/Style.h index a06bd246b4..45de20b693 100644 --- a/yoga/style/Style.h +++ b/yoga/style/Style.h @@ -30,7 +30,7 @@ #include #include #include -#include +#include #include #include #include