AxisScrollSnapOffsets.cpp [plain text]
#include "config.h"
#include "AxisScrollSnapOffsets.h"
#include "ElementChildIterator.h"
#include "HTMLCollection.h"
#include "HTMLElement.h"
#include "Length.h"
#include "RenderBox.h"
#include "RenderView.h"
#include "ScrollableArea.h"
#include "StyleScrollSnapPoints.h"
#if ENABLE(CSS_SCROLL_SNAP)
namespace WebCore {
static void appendChildSnapOffsets(HTMLElement& parent, bool shouldAddHorizontalChildOffsets, Vector<LayoutUnit>& horizontalSnapOffsetSubsequence, bool shouldAddVerticalChildOffsets, Vector<LayoutUnit>& verticalSnapOffsetSubsequence)
{
RenderElement* scrollContainer = parent.renderer();
ASSERT(scrollContainer);
RenderView& renderView = scrollContainer->view();
Vector<const RenderBox*> elements;
for (auto& element : renderView.boxesWithScrollSnapCoordinates()) {
if (element->findEnclosingScrollableContainer() != scrollContainer)
continue;
elements.append(element);
}
for (auto& box : elements) {
auto& scrollSnapCoordinates = box->style().scrollSnapCoordinates();
if (scrollSnapCoordinates.isEmpty())
continue;
LayoutRect viewSize = box->contentBoxRect();
FloatPoint position = box->localToContainerPoint(FloatPoint(parent.renderBox()->scrollLeft(), parent.renderBox()->scrollTop()), parent.renderBox());
for (auto& coordinate : scrollSnapCoordinates) {
LayoutUnit lastPotentialSnapPositionX = position.x() + valueForLength(coordinate.width(), viewSize.width());
if (shouldAddHorizontalChildOffsets && lastPotentialSnapPositionX > 0)
horizontalSnapOffsetSubsequence.append(lastPotentialSnapPositionX);
LayoutUnit lastPotentialSnapPositionY = position.y() + valueForLength(coordinate.height(), viewSize.height());
if (shouldAddVerticalChildOffsets && lastPotentialSnapPositionY > 0)
verticalSnapOffsetSubsequence.append(lastPotentialSnapPositionY);
}
}
}
static LayoutUnit destinationOffsetForViewSize(ScrollEventAxis axis, const LengthSize& destination, LayoutUnit viewSize)
{
const Length& dimension = (axis == ScrollEventAxis::Horizontal) ? destination.width() : destination.height();
return valueForLength(dimension, viewSize);
}
static void updateFromStyle(Vector<LayoutUnit>& snapOffsets, const RenderStyle& style, ScrollEventAxis axis, LayoutUnit viewSize, LayoutUnit scrollSize, Vector<LayoutUnit>& snapOffsetSubsequence)
{
std::sort(snapOffsetSubsequence.begin(), snapOffsetSubsequence.end());
if (snapOffsetSubsequence.isEmpty())
snapOffsetSubsequence.append(0);
auto* points = (axis == ScrollEventAxis::Horizontal) ? style.scrollSnapPointsX() : style.scrollSnapPointsY();
bool hasRepeat = points ? points->hasRepeat : false;
LayoutUnit repeatOffset = points ? valueForLength(points->repeatOffset, viewSize) : LayoutUnit::fromPixel(1);
repeatOffset = std::max<LayoutUnit>(repeatOffset, LayoutUnit::fromPixel(1));
LayoutUnit destinationOffset = destinationOffsetForViewSize(axis, style.scrollSnapDestination(), viewSize);
LayoutUnit curSnapPositionShift = 0;
LayoutUnit maxScrollOffset = scrollSize - viewSize;
LayoutUnit lastSnapPosition = curSnapPositionShift;
do {
for (auto& snapPosition : snapOffsetSubsequence) {
LayoutUnit potentialSnapPosition = curSnapPositionShift + snapPosition - destinationOffset;
if (potentialSnapPosition < 0)
continue;
if (potentialSnapPosition >= maxScrollOffset)
break;
if (potentialSnapPosition)
snapOffsets.append(potentialSnapPosition);
lastSnapPosition = potentialSnapPosition + destinationOffset;
}
curSnapPositionShift = lastSnapPosition + repeatOffset;
} while (hasRepeat && curSnapPositionShift < maxScrollOffset);
if (snapOffsets.isEmpty())
return;
if (snapOffsets.first())
snapOffsets.insert(0, 0);
if (snapOffsets.last() != maxScrollOffset)
snapOffsets.append(maxScrollOffset);
}
static bool styleUsesElements(ScrollEventAxis axis, const RenderStyle& style)
{
const ScrollSnapPoints* scrollSnapPoints = (axis == ScrollEventAxis::Horizontal) ? style.scrollSnapPointsX() : style.scrollSnapPointsY();
if (scrollSnapPoints)
return scrollSnapPoints->usesElements;
const Length& destination = (axis == ScrollEventAxis::Horizontal) ? style.scrollSnapDestination().width() : style.scrollSnapDestination().height();
return !destination.isUndefined();
}
void updateSnapOffsetsForScrollableArea(ScrollableArea& scrollableArea, HTMLElement& scrollingElement, const RenderBox& scrollingElementBox, const RenderStyle& scrollingElementStyle)
{
if (scrollingElementStyle.scrollSnapType() == ScrollSnapType::None) {
scrollableArea.clearHorizontalSnapOffsets();
scrollableArea.clearVerticalSnapOffsets();
return;
}
LayoutRect viewSize = scrollingElementBox.contentBoxRect();
LayoutUnit viewWidth = viewSize.width();
LayoutUnit viewHeight = viewSize.height();
LayoutUnit scrollWidth = scrollingElementBox.scrollWidth();
LayoutUnit scrollHeight = scrollingElementBox.scrollHeight();
bool canComputeHorizontalOffsets = scrollWidth > 0 && viewWidth > 0 && viewWidth < scrollWidth;
bool canComputeVerticalOffsets = scrollHeight > 0 && viewHeight > 0 && viewHeight < scrollHeight;
if (!canComputeHorizontalOffsets)
scrollableArea.clearHorizontalSnapOffsets();
if (!canComputeVerticalOffsets)
scrollableArea.clearVerticalSnapOffsets();
if (!canComputeHorizontalOffsets && !canComputeVerticalOffsets)
return;
Vector<LayoutUnit> horizontalSnapOffsetSubsequence;
Vector<LayoutUnit> verticalSnapOffsetSubsequence;
bool scrollSnapPointsXUsesElements = styleUsesElements(ScrollEventAxis::Horizontal, scrollingElementStyle);
bool scrollSnapPointsYUsesElements = styleUsesElements(ScrollEventAxis::Vertical, scrollingElementStyle);
if (scrollSnapPointsXUsesElements || scrollSnapPointsYUsesElements) {
bool shouldAddHorizontalChildOffsets = scrollSnapPointsXUsesElements && canComputeHorizontalOffsets;
bool shouldAddVerticalChildOffsets = scrollSnapPointsYUsesElements && canComputeVerticalOffsets;
appendChildSnapOffsets(scrollingElement, shouldAddHorizontalChildOffsets, horizontalSnapOffsetSubsequence, shouldAddVerticalChildOffsets, verticalSnapOffsetSubsequence);
}
if (scrollingElementStyle.scrollSnapPointsX() && !scrollSnapPointsXUsesElements && canComputeHorizontalOffsets) {
for (auto& snapLength : scrollingElementStyle.scrollSnapPointsX()->offsets)
horizontalSnapOffsetSubsequence.append(valueForLength(snapLength, viewWidth));
}
if (scrollingElementStyle.scrollSnapPointsY() && !scrollSnapPointsYUsesElements && canComputeVerticalOffsets) {
for (auto& snapLength : scrollingElementStyle.scrollSnapPointsY()->offsets)
verticalSnapOffsetSubsequence.append(valueForLength(snapLength, viewHeight));
}
if (canComputeHorizontalOffsets) {
auto horizontalSnapOffsets = std::make_unique<Vector<LayoutUnit>>();
updateFromStyle(*horizontalSnapOffsets, scrollingElementStyle, ScrollEventAxis::Horizontal, viewWidth, scrollWidth, horizontalSnapOffsetSubsequence);
if (horizontalSnapOffsets->isEmpty())
scrollableArea.clearHorizontalSnapOffsets();
else
scrollableArea.setHorizontalSnapOffsets(WTF::move(horizontalSnapOffsets));
}
if (canComputeVerticalOffsets) {
auto verticalSnapOffsets = std::make_unique<Vector<LayoutUnit>>();
updateFromStyle(*verticalSnapOffsets, scrollingElementStyle, ScrollEventAxis::Vertical, viewHeight, scrollHeight, verticalSnapOffsetSubsequence);
if (verticalSnapOffsets->isEmpty())
scrollableArea.clearVerticalSnapOffsets();
else
scrollableArea.setVerticalSnapOffsets(WTF::move(verticalSnapOffsets));
}
}
}
#endif // CSS_SCROLL_SNAP