blob: 5faee5eae174984286557941bbb539f777dd9d24 [file]
/*
* Copyright (C) 2022 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "GridLanesLayout.h"
#include "GridLayoutFunctions.h"
#include "RenderBoxInlines.h"
#include "RenderGrid.h"
#include "StyleComputedStyle+GettersInlines.h"
#include "StyleGridPositionsResolver.h"
#include "WritingMode.h"
namespace WebCore {
GridLanesLayout::GridLanesLayout(RenderGrid& renderGrid, unsigned gridAxisTracksCount, Style::GridTrackSizingDirection stackingAxisDirection)
: m_runningPositions(gridAxisTracksCount)
, m_renderGrid(renderGrid)
, m_stackingAxisGridGap(renderGrid.gridGap(stackingAxisDirection))
, m_stackingAxisDirection(stackingAxisDirection)
{
m_renderGrid->currentGrid().setupForGridLanesLayout();
m_renderGrid->populateExplicitGridAndOrderIterator();
}
GridLanesResult GridLanesLayout::performGridLanesPlacement(const GridTrackSizingAlgorithm& algorithm, ResolvedFitTolerance fitTolerance, Phase layoutPhase)
{
// 4.4 Grid Lanes Layout and Placement Algorithm
// https://drafts.csswg.org/css-grid-3/#grid-lanes-layout-algorithm
return placeGridLanesItems(algorithm, fitTolerance, layoutPhase);
}
GridLanesResult GridLanesLayout::placeGridLanesItems(const GridTrackSizingAlgorithm& algorithm, ResolvedFitTolerance fitTolerance, Phase layoutPhase)
{
if (!gridAxisTracksCount())
return { };
HashMap<SingleThreadWeakRef<const RenderBox>, LayoutUnit> stackingAxisOffsets;
LayoutUnit gridContentSize;
auto& grid = m_renderGrid->currentGrid();
for (CheckedPtr gridItem = grid.orderIterator().first(); gridItem; gridItem = grid.orderIterator().next()) {
if (grid.orderIterator().shouldSkipChild(*gridItem))
continue;
bool isAutoPlacedInGridAxis = !hasDefiniteGridAxisPosition(*gridItem, gridAxisDirection());
auto gridArea = isAutoPlacedInGridAxis ? gridAreaForIndefiniteGridAxisItem(*gridItem, fitTolerance) : gridAreaForDefiniteGridAxisItem(*gridItem);
auto placement = insertIntoGridAndLayoutItem(algorithm, *gridItem, gridArea, layoutPhase);
if (isAutoPlacedInGridAxis)
m_autoFlowNextCursor = gridAxisSpanFromArea(gridArea).endLine() % gridAxisTracksCount();
stackingAxisOffsets.set(*gridItem, placement.marginBoxStart);
gridContentSize = std::max(gridContentSize, placement.marginBoxEnd);
}
return { WTF::move(stackingAxisOffsets), gridContentSize };
}
GridArea GridLanesLayout::gridAreaForDefiniteGridAxisItem(const RenderBox& gridItem) const
{
auto itemSpan = m_renderGrid->currentGrid().gridItemSpan(gridItem, gridAxisDirection());
ASSERT(!itemSpan.isIndefinite());
itemSpan.translate(m_renderGrid->currentGrid().explicitGridStart(gridAxisDirection()));
return gridAreaFromGridAxisSpan(itemSpan);
}
LayoutUnit GridLanesLayout::calculateGridLanesIntrinsicLogicalWidth(RenderBox& gridItem, Phase layoutPhase)
{
switch (layoutPhase) {
case Phase::MinContent:
return gridItem.computeSizingKeywordLogicalWidthUsing(CSS::Keyword::MinContent { }, { }, gridItem.borderAndPaddingLogicalWidth());
case Phase::MaxContent:
return gridItem.computeSizingKeywordLogicalWidthUsing(CSS::Keyword::MaxContent { }, { }, gridItem.borderAndPaddingLogicalWidth());
case Phase::Layout:
ASSERT_NOT_REACHED();
return { };
}
return { };
}
void GridLanesLayout::setItemContainingBlockToGridArea(const GridTrackSizingAlgorithm& algorithm, RenderBox& gridItem)
{
CheckedPtr<RenderGrid> containingBlock = dynamicDowncast<RenderGrid>(gridItem.containingBlock());
if (!containingBlock) {
ASSERT_NOT_REACHED();
return;
}
// FIXME: We need to set both axes here because RenderGrid sets and expects them all over the place.
// Ideally we untangle all that and only set the grid axis that we need. webkit.org/b/305136
auto direction = gridAxisDirection();
if (direction == Style::GridTrackSizingDirection::Columns) {
gridItem.setGridAreaContentLogicalWidth(algorithm.gridAreaBreadthForGridItem(gridItem, direction));
gridItem.setGridAreaContentLogicalHeight(containingBlock->availableLogicalHeightForContentBox());
} else {
gridItem.setGridAreaContentLogicalHeight(algorithm.gridAreaBreadthForGridItem(gridItem, direction));
gridItem.setGridAreaContentLogicalWidth(containingBlock->contentBoxLogicalWidth());
}
// FIXME(249230): Try to cache grid lanes layout sizes
gridItem.setChildNeedsLayout(MarkingBehavior::MarkOnlyThis);
}
GridLanesLayout::StackingAxisPlacement GridLanesLayout::insertIntoGridAndLayoutItem(const GridTrackSizingAlgorithm& algorithm, RenderBox& gridItem, const GridArea& area, Phase layoutPhase)
{
auto shouldOverrideLogicalWidth = [&](RenderBox& gridItem, Phase layoutPhase) {
if (layoutPhase == Phase::Layout)
return false;
if (!(gridItem.style().logicalWidth().isAuto() || gridItem.style().logicalWidth().isPercent()))
return false;
ASSERT(m_renderGrid->isStackingAxis(Style::GridTrackSizingDirection::Columns));
if (gridItem.style().writingMode().isOrthogonal(m_renderGrid->style().writingMode()))
return false;
if (auto* renderGrid = dynamicDowncast<RenderGrid>(gridItem); renderGrid && renderGrid->isSubgridRows())
return false;
return true;
};
if (shouldOverrideLogicalWidth(gridItem, layoutPhase))
gridItem.setOverridingBorderBoxLogicalWidth(calculateGridLanesIntrinsicLogicalWidth(gridItem, layoutPhase));
m_renderGrid->currentGrid().insert(gridItem, area);
setItemContainingBlockToGridArea(algorithm, gridItem);
gridItem.layoutIfNeeded();
return updateRunningPositions(gridItem, area);
}
LayoutUnit GridLanesLayout::stackingAxisMarginBoxForItem(const RenderBox& gridItem)
{
LayoutUnit marginBoxSize;
if (m_stackingAxisDirection == Style::GridTrackSizingDirection::Rows) {
if (GridLayoutFunctions::isOrthogonalGridItem(m_renderGrid, gridItem))
marginBoxSize = gridItem.isHorizontalWritingMode() ? gridItem.borderBoxWidth() + gridItem.horizontalMarginExtent() : gridItem.borderBoxHeight() + gridItem.verticalMarginExtent();
else
marginBoxSize = gridItem.logicalHeight() + gridItem.marginLogicalHeight();
} else {
if (GridLayoutFunctions::isOrthogonalGridItem(m_renderGrid, gridItem))
marginBoxSize = gridItem.isHorizontalWritingMode() ? gridItem.borderBoxHeight() + gridItem.verticalMarginExtent() : gridItem.borderBoxWidth() + gridItem.horizontalMarginExtent();
else
marginBoxSize = gridItem.logicalWidth() + gridItem.marginLogicalWidth();
}
return marginBoxSize;
}
GridLanesLayout::StackingAxisPlacement GridLanesLayout::updateRunningPositions(const RenderBox& gridItem, const GridArea& area)
{
auto gridAxisSpan = gridAxisSpanFromArea(area);
ASSERT(gridAxisSpan.startLine() < m_runningPositions.size() && gridAxisSpan.endLine() <= m_runningPositions.size());
gridAxisSpan.clamp(m_runningPositions.size());
LayoutUnit previousRunningPosition;
for (auto line : gridAxisSpan)
previousRunningPosition = std::max(previousRunningPosition, m_runningPositions[line]);
auto marginBoxEnd = previousRunningPosition + stackingAxisMarginBoxForItem(gridItem);
for (auto span : gridAxisSpan)
m_runningPositions[span] = marginBoxEnd + m_stackingAxisGridGap;
return { previousRunningPosition, marginBoxEnd };
}
LayoutUnit GridLanesLayout::maxRunningPositionForSpan(unsigned startLine, unsigned spanLength) const
{
LayoutUnit maxPosition;
for (unsigned lineOffset = 0; lineOffset < spanLength; lineOffset++)
maxPosition = std::max(maxPosition, m_runningPositions[startLine + lineOffset]);
return maxPosition;
}
GridArea GridLanesLayout::gridAreaForIndefiniteGridAxisItem(const RenderBox& item, ResolvedFitTolerance fitTolerance)
{
auto itemSpanLength = std::min<unsigned>(Style::GridPositionsResolver::spanSizeForAutoPlacedItem(item, gridAxisDirection()), gridAxisTracksCount());
auto gridAxisLines = gridAxisTracksCount() + 1;
if (WTF::holdsAlternative<CSS::Keyword::Infinite>(fitTolerance)) {
// Infinite tolerance: place items strictly in order without considering track lengths
// Use round-robin placement starting from the cursor position
auto startingLine = m_autoFlowNextCursor;
// If the item doesn't fit at the cursor position, wrap to the beginning
if (startingLine + itemSpanLength > gridAxisTracksCount())
startingLine = 0;
auto gridAxisPosition = GridSpan::translatedDefiniteGridSpan(startingLine, startingLine + itemSpanLength);
return gridAreaFromGridAxisSpan(gridAxisPosition);
}
// For normal and length-percentage tolerances, find positions within tolerance of the shortest track
auto toleranceValue = std::get<LayoutUnit>(fitTolerance);
// Step 1: Find the absolute shortest position across all tracks
auto maxStartingLine = gridAxisLines - itemSpanLength;
LayoutUnit absoluteShortest = LayoutUnit::max();
for (unsigned i = 0; i < maxStartingLine; i++)
absoluteShortest = std::min(absoluteShortest, maxRunningPositionForSpan(i, itemSpanLength));
// Step 2: Find first position within tolerance of shortest, starting from the cursor position.
unsigned smallestMaxPosLine = 0;
auto autoFlowNextCursorShift = (m_autoFlowNextCursor > maxStartingLine) ? 0 : m_autoFlowNextCursor;
for (unsigned i = 0; i < maxStartingLine; i++) {
auto startingLine = (autoFlowNextCursorShift + i) % maxStartingLine;
auto maxPosForCurrentStartingLine = maxRunningPositionForSpan(startingLine, itemSpanLength);
// Accept first position within tolerance of the absolute shortest
if (maxPosForCurrentStartingLine <= absoluteShortest + toleranceValue) {
smallestMaxPosLine = startingLine;
break;
}
}
auto gridAxisPosition = GridSpan::translatedDefiniteGridSpan(smallestMaxPosLine, smallestMaxPosLine + itemSpanLength);
return gridAreaFromGridAxisSpan(gridAxisPosition);
}
LayoutUnit GridLanesResult::stackingAxisOffsetForGridItem(const RenderBox& gridItem) const
{
return m_stackingAxisOffsets.getOptional(gridItem).value_or(0_lu);
}
inline Style::GridTrackSizingDirection GridLanesLayout::gridAxisDirection() const
{
// The stacking axis and grid axis can never be the same.
// They are always perpendicular to each other.
return orthogonalDirection(m_stackingAxisDirection);
}
bool GridLanesLayout::hasDefiniteGridAxisPosition(const RenderBox& gridItem, Style::GridTrackSizingDirection gridAxisDirection) const
{
return !Style::GridPositionsResolver::resolveGridPositionsFromStyle(m_renderGrid, gridItem, gridAxisDirection).isIndefinite();
}
GridSpan GridLanesLayout::gridAxisSpanFromArea(const GridArea& gridArea) const
{
return gridArea.span(gridAxisDirection());
}
GridArea GridLanesLayout::gridAreaFromGridAxisSpan(const GridSpan& gridAxisSpan) const
{
auto stackingAxisSpan = GridSpan::stackingAxisTranslatedDefiniteGridSpan();
return m_stackingAxisDirection == Style::GridTrackSizingDirection::Rows
? GridArea { stackingAxisSpan, gridAxisSpan }
: GridArea { gridAxisSpan, stackingAxisSpan };
}
} // end namespace WebCore