|
Lines 41-108
a/Source/WebCore/page/scrolling/ScrollSnapOffsetsInfo.cpp_sec1
|
| 41 |
|
41 |
|
| 42 |
namespace WebCore { |
42 |
namespace WebCore { |
| 43 |
|
43 |
|
| 44 |
template <typename LayoutType> |
44 |
template <typename UnitType> |
| 45 |
static void indicesOfNearestSnapOffsetRanges(LayoutType offset, const Vector<ScrollOffsetRange<LayoutType>>& snapOffsetRanges, unsigned& lowerIndex, unsigned& upperIndex) |
45 |
static Optional<std::pair<UnitType, unsigned>> findFirstSnapStopOffsetBetweenOriginAndDestination(const Vector<SnapOffset<UnitType>>& snapOffsets, UnitType scrollOriginOffset, UnitType scrollDestinationOffset) |
| 46 |
{ |
46 |
{ |
| 47 |
if (snapOffsetRanges.isEmpty()) { |
47 |
UnitType difference = scrollDestinationOffset - scrollOriginOffset; |
| 48 |
lowerIndex = invalidSnapOffsetIndex; |
|
|
| 49 |
upperIndex = invalidSnapOffsetIndex; |
| 50 |
return; |
| 51 |
} |
| 52 |
|
| 53 |
int lowerIndexAsInt = -1; |
| 54 |
int upperIndexAsInt = snapOffsetRanges.size(); |
| 55 |
do { |
| 56 |
int middleIndex = (lowerIndexAsInt + upperIndexAsInt) / 2; |
| 57 |
auto& range = snapOffsetRanges[middleIndex]; |
| 58 |
if (range.start < offset && offset < range.end) { |
| 59 |
lowerIndexAsInt = middleIndex; |
| 60 |
upperIndexAsInt = middleIndex; |
| 61 |
break; |
| 62 |
} |
| 63 |
|
| 64 |
if (offset > range.end) |
| 65 |
lowerIndexAsInt = middleIndex; |
| 66 |
else |
| 67 |
upperIndexAsInt = middleIndex; |
| 68 |
} while (lowerIndexAsInt < upperIndexAsInt - 1); |
| 69 |
|
| 70 |
if (offset <= snapOffsetRanges.first().start) |
| 71 |
lowerIndex = invalidSnapOffsetIndex; |
| 72 |
else |
| 73 |
lowerIndex = lowerIndexAsInt; |
| 74 |
|
| 75 |
if (offset >= snapOffsetRanges.last().end) |
| 76 |
upperIndex = invalidSnapOffsetIndex; |
| 77 |
else |
| 78 |
upperIndex = upperIndexAsInt; |
| 79 |
} |
| 80 |
|
| 81 |
template <typename LayoutType> |
| 82 |
static void indicesOfNearestSnapOffsets(LayoutType offset, const Vector<SnapOffset<LayoutType>>& snapOffsets, unsigned& lowerIndex, unsigned& upperIndex) |
| 83 |
{ |
| 84 |
lowerIndex = 0; |
| 85 |
upperIndex = snapOffsets.size() - 1; |
| 86 |
while (lowerIndex < upperIndex - 1) { |
| 87 |
int middleIndex = (lowerIndex + upperIndex) / 2; |
| 88 |
auto middleOffset = snapOffsets[middleIndex].offset; |
| 89 |
if (offset == middleOffset) { |
| 90 |
upperIndex = middleIndex; |
| 91 |
lowerIndex = middleIndex; |
| 92 |
break; |
| 93 |
} |
| 94 |
|
| 95 |
if (offset > middleOffset) |
| 96 |
lowerIndex = middleIndex; |
| 97 |
else |
| 98 |
upperIndex = middleIndex; |
| 99 |
} |
| 100 |
} |
| 101 |
|
| 102 |
template <typename LayoutType> |
| 103 |
static Optional<unsigned> findFirstSnapStopOffsetBetweenOriginAndDestination(const Vector<SnapOffset<LayoutType>>& snapOffsets, LayoutType scrollOriginOffset, LayoutType scrollDestinationOffset) |
| 104 |
{ |
| 105 |
LayoutType difference = scrollDestinationOffset - scrollOriginOffset; |
| 106 |
if (!difference) |
48 |
if (!difference) |
| 107 |
return WTF::nullopt; |
49 |
return WTF::nullopt; |
| 108 |
|
50 |
|
|
Lines 113-119
static Optional<unsigned> findFirstSnapStopOffsetBetweenOriginAndDestination(con
a/Source/WebCore/page/scrolling/ScrollSnapOffsetsInfo.cpp_sec2
|
| 113 |
iteration = -1; |
55 |
iteration = -1; |
| 114 |
} |
56 |
} |
| 115 |
|
57 |
|
| 116 |
auto isPast = [difference](LayoutType mark, LayoutType candidate) { |
58 |
auto isPast = [difference](UnitType mark, UnitType candidate) { |
| 117 |
return (difference > 0 && candidate > mark) || (difference < 0 && candidate < mark); |
59 |
return (difference > 0 && candidate > mark) || (difference < 0 && candidate < mark); |
| 118 |
}; |
60 |
}; |
| 119 |
|
61 |
|
|
Lines 124-188
static Optional<unsigned> findFirstSnapStopOffsetBetweenOriginAndDestination(con
a/Source/WebCore/page/scrolling/ScrollSnapOffsetsInfo.cpp_sec3
|
| 124 |
if (snapOffsets[i].stop != ScrollSnapStop::Always) |
66 |
if (snapOffsets[i].stop != ScrollSnapStop::Always) |
| 125 |
continue; |
67 |
continue; |
| 126 |
if (isPast(scrollOriginOffset, offset)) |
68 |
if (isPast(scrollOriginOffset, offset)) |
| 127 |
return i; |
69 |
return std::make_pair(offset, static_cast<unsigned>(i)); |
| 128 |
} |
70 |
} |
| 129 |
|
71 |
|
| 130 |
return WTF::nullopt; |
72 |
return WTF::nullopt; |
| 131 |
} |
73 |
} |
| 132 |
|
74 |
|
| 133 |
template <typename LayoutType> |
|
|
| 134 |
static std::pair<LayoutType, unsigned> closestSnapOffsetWithOffsetsAndRanges(const Vector<SnapOffset<LayoutType>>& snapOffsets, const Vector<ScrollOffsetRange<LayoutType>>& snapOffsetRanges, LayoutType scrollDestinationOffset, float velocity, Optional<LayoutType> originalOffsetForDirectionalSnapping) |
| 135 |
{ |
| 136 |
if (snapOffsets.isEmpty()) |
| 137 |
return std::make_pair(scrollDestinationOffset, invalidSnapOffsetIndex); |
| 138 |
|
| 139 |
if (originalOffsetForDirectionalSnapping.hasValue()) { |
| 140 |
auto firstSnapStopOffsetIndex = findFirstSnapStopOffsetBetweenOriginAndDestination(snapOffsets, *originalOffsetForDirectionalSnapping, scrollDestinationOffset); |
| 141 |
if (firstSnapStopOffsetIndex.hasValue()) |
| 142 |
return std::make_pair(snapOffsets[*firstSnapStopOffsetIndex].offset, *firstSnapStopOffsetIndex); |
| 143 |
} |
| 144 |
|
| 145 |
unsigned lowerSnapOffsetRangeIndex; |
| 146 |
unsigned upperSnapOffsetRangeIndex; |
| 147 |
indicesOfNearestSnapOffsetRanges<LayoutType>(scrollDestinationOffset, snapOffsetRanges, lowerSnapOffsetRangeIndex, upperSnapOffsetRangeIndex); |
| 148 |
if (lowerSnapOffsetRangeIndex == upperSnapOffsetRangeIndex && upperSnapOffsetRangeIndex != invalidSnapOffsetIndex) |
| 149 |
return std::make_pair(scrollDestinationOffset, invalidSnapOffsetIndex); |
| 150 |
|
| 151 |
if (scrollDestinationOffset <= snapOffsets.first().offset) |
| 152 |
return std::make_pair(snapOffsets.first().offset, 0u); |
| 153 |
|
| 154 |
if (scrollDestinationOffset >= snapOffsets.last().offset) |
| 155 |
return std::make_pair(snapOffsets.last().offset, snapOffsets.size() - 1); |
| 156 |
|
| 157 |
unsigned lowerIndex; |
| 158 |
unsigned upperIndex; |
| 159 |
indicesOfNearestSnapOffsets<LayoutType>(scrollDestinationOffset, snapOffsets, lowerIndex, upperIndex); |
| 160 |
LayoutType lowerSnapPosition = snapOffsets[lowerIndex].offset; |
| 161 |
LayoutType upperSnapPosition = snapOffsets[upperIndex].offset; |
| 162 |
if (!std::abs(velocity)) { |
| 163 |
bool isCloserToLowerSnapPosition = scrollDestinationOffset - lowerSnapPosition <= upperSnapPosition - scrollDestinationOffset; |
| 164 |
return isCloserToLowerSnapPosition ? std::make_pair(lowerSnapPosition, lowerIndex) : std::make_pair(upperSnapPosition, upperIndex); |
| 165 |
} |
| 166 |
|
| 167 |
// Non-zero velocity indicates a flick gesture. Even if another snap point is closer, we should choose the one in the direction of the flick gesture |
| 168 |
// as long as a scroll snap offset range does not lie between the scroll destination and the targeted snap offset. If we are doing directional |
| 169 |
// snapping, we should never snap to a point that was on the other side of the original position in the opposite direction of this scroll. |
| 170 |
// This allows directional scrolling to escape snap points. |
| 171 |
if (velocity < 0) { |
| 172 |
if (lowerSnapOffsetRangeIndex == invalidSnapOffsetIndex || lowerSnapPosition >= snapOffsetRanges[lowerSnapOffsetRangeIndex].end) |
| 173 |
return std::make_pair(lowerSnapPosition, lowerIndex); |
| 174 |
if (!originalOffsetForDirectionalSnapping.hasValue() || *originalOffsetForDirectionalSnapping > upperSnapPosition) |
| 175 |
return std::make_pair(upperSnapPosition, upperIndex); |
| 176 |
} else { |
| 177 |
if (upperSnapOffsetRangeIndex == invalidSnapOffsetIndex || snapOffsetRanges[upperSnapOffsetRangeIndex].start >= upperSnapPosition) |
| 178 |
return std::make_pair(upperSnapPosition, upperIndex); |
| 179 |
if (!originalOffsetForDirectionalSnapping.hasValue() || *originalOffsetForDirectionalSnapping < lowerSnapPosition) |
| 180 |
return std::make_pair(lowerSnapPosition, lowerIndex); |
| 181 |
} |
| 182 |
|
| 183 |
return std::make_pair(scrollDestinationOffset, invalidSnapOffsetIndex); |
| 184 |
} |
| 185 |
|
| 186 |
enum class InsetOrOutset { |
75 |
enum class InsetOrOutset { |
| 187 |
Inset, |
76 |
Inset, |
| 188 |
Outset |
77 |
Outset |
|
Lines 219-248
static LayoutUnit computeScrollSnapAlignOffset(LayoutUnit minLocation, LayoutUni
a/Source/WebCore/page/scrolling/ScrollSnapOffsetsInfo.cpp_sec4
|
| 219 |
} |
108 |
} |
| 220 |
} |
109 |
} |
| 221 |
|
110 |
|
| 222 |
static void computeAxisProximitySnapOffsetRanges(const Vector<SnapOffset<LayoutUnit>>& snapOffsets, Vector<ScrollOffsetRange<LayoutUnit>>& offsetRanges, LayoutUnit scrollPortAxisLength) |
|
|
| 223 |
{ |
| 224 |
// This is an arbitrary choice for what it means to be "in proximity" of a snap offset. We should play around with |
| 225 |
// this and see what feels best. |
| 226 |
static const float ratioOfScrollPortAxisLengthToBeConsideredForProximity = 0.3; |
| 227 |
if (snapOffsets.size() < 2) |
| 228 |
return; |
| 229 |
|
| 230 |
// The extra rule accounting for scroll offset ranges in between the scroll destination and a potential snap offset |
| 231 |
// handles the corner case where the user scrolls with momentum very lightly away from a snap offset, such that the |
| 232 |
// predicted scroll destination is still within proximity of the snap offset. In this case, the regular (mandatory |
| 233 |
// scroll snapping) behavior would be to snap to the next offset in the direction of momentum scrolling, but |
| 234 |
// instead, it is more intuitive to either return to the original snap position (which we arbitrarily choose here) |
| 235 |
// or scroll just outside of the snap offset range. This is another minor behavior tweak that we should play around |
| 236 |
// with to see what feels best. |
| 237 |
LayoutUnit proximityDistance { ratioOfScrollPortAxisLengthToBeConsideredForProximity * scrollPortAxisLength }; |
| 238 |
for (size_t index = 1; index < snapOffsets.size(); ++index) { |
| 239 |
auto startOffset = snapOffsets[index - 1].offset + proximityDistance; |
| 240 |
auto endOffset = snapOffsets[index].offset - proximityDistance; |
| 241 |
if (startOffset < endOffset) |
| 242 |
offsetRanges.append({ startOffset, endOffset }); |
| 243 |
} |
| 244 |
} |
| 245 |
|
| 246 |
void updateSnapOffsetsForScrollableArea(ScrollableArea& scrollableArea, const RenderBox& scrollingElementBox, const RenderStyle& scrollingElementStyle, LayoutRect viewportRectInBorderBoxCoordinates) |
111 |
void updateSnapOffsetsForScrollableArea(ScrollableArea& scrollableArea, const RenderBox& scrollingElementBox, const RenderStyle& scrollingElementStyle, LayoutRect viewportRectInBorderBoxCoordinates) |
| 247 |
{ |
112 |
{ |
| 248 |
auto scrollSnapType = scrollingElementStyle.scrollSnapType(); |
113 |
auto scrollSnapType = scrollingElementStyle.scrollSnapType(); |
|
Lines 262-267
void updateSnapOffsetsForScrollableArea(ScrollableArea& scrollableArea, const Re
a/Source/WebCore/page/scrolling/ScrollSnapOffsetsInfo.cpp_sec5
|
| 262 |
|
127 |
|
| 263 |
HashMap<float, SnapOffset<LayoutUnit>> verticalSnapOffsetsMap; |
128 |
HashMap<float, SnapOffset<LayoutUnit>> verticalSnapOffsetsMap; |
| 264 |
HashMap<float, SnapOffset<LayoutUnit>> horizontalSnapOffsetsMap; |
129 |
HashMap<float, SnapOffset<LayoutUnit>> horizontalSnapOffsetsMap; |
|
|
130 |
Vector<LayoutRect> snapAreas; |
| 265 |
bool hasHorizontalSnapOffsets = scrollSnapType.axis == ScrollSnapAxis::Both || scrollSnapType.axis == ScrollSnapAxis::XAxis || scrollSnapType.axis == ScrollSnapAxis::Inline; |
131 |
bool hasHorizontalSnapOffsets = scrollSnapType.axis == ScrollSnapAxis::Both || scrollSnapType.axis == ScrollSnapAxis::XAxis || scrollSnapType.axis == ScrollSnapAxis::Inline; |
| 266 |
bool hasVerticalSnapOffsets = scrollSnapType.axis == ScrollSnapAxis::Both || scrollSnapType.axis == ScrollSnapAxis::YAxis || scrollSnapType.axis == ScrollSnapAxis::Block; |
132 |
bool hasVerticalSnapOffsets = scrollSnapType.axis == ScrollSnapAxis::Both || scrollSnapType.axis == ScrollSnapAxis::YAxis || scrollSnapType.axis == ScrollSnapAxis::Block; |
| 267 |
|
133 |
|
|
Lines 288-304
void updateSnapOffsetsForScrollableArea(ScrollableArea& scrollableArea, const Re
a/Source/WebCore/page/scrolling/ScrollSnapOffsetsInfo.cpp_sec6
|
| 288 |
|
154 |
|
| 289 |
scrollSnapArea = computeScrollSnapPortOrAreaRect(scrollSnapArea, child->style().scrollMargin(), InsetOrOutset::Outset); |
155 |
scrollSnapArea = computeScrollSnapPortOrAreaRect(scrollSnapArea, child->style().scrollMargin(), InsetOrOutset::Outset); |
| 290 |
LOG_WITH_STREAM(ScrollSnap, stream << " Considering scroll snap target area " << scrollSnapArea); |
156 |
LOG_WITH_STREAM(ScrollSnap, stream << " Considering scroll snap target area " << scrollSnapArea); |
|
|
157 |
|
| 291 |
auto alignment = child->style().scrollSnapAlign(); |
158 |
auto alignment = child->style().scrollSnapAlign(); |
|
|
159 |
bool snapsHorizontally = hasHorizontalSnapOffsets && alignment.x != ScrollSnapAxisAlignType::None; |
| 160 |
bool snapsVertically = hasVerticalSnapOffsets && alignment.y != ScrollSnapAxisAlignType::None; |
| 161 |
if (!snapsHorizontally && !snapsVertically) |
| 162 |
continue; |
| 163 |
|
| 164 |
// The scroll snap area is defined via its scroll position, so convert the snap area rectangle to be relative to scroll offsets. |
| 165 |
auto snapAreaOriginRelativeToBorderEdge = scrollSnapArea.location() - scrollSnapPort.location(); |
| 166 |
LayoutRect scrollSnapAreaAsOffsets(scrollableArea.scrollOffsetFromPosition(roundedIntPoint(snapAreaOriginRelativeToBorderEdge)), scrollSnapArea.size()); |
| 167 |
snapAreas.append(scrollSnapAreaAsOffsets); |
| 168 |
|
| 292 |
auto stop = child->style().scrollSnapStop(); |
169 |
auto stop = child->style().scrollSnapStop(); |
| 293 |
if (hasHorizontalSnapOffsets && alignment.x != ScrollSnapAxisAlignType::None) { |
170 |
if (snapsHorizontally) { |
| 294 |
auto absoluteScrollXPosition = computeScrollSnapAlignOffset(scrollSnapArea.x(), scrollSnapArea.maxX(), alignment.x, scrollerIsRTL) - computeScrollSnapAlignOffset(scrollSnapPort.x(), scrollSnapPort.maxX(), alignment.x, scrollerIsRTL); |
171 |
auto absoluteScrollXPosition = computeScrollSnapAlignOffset(scrollSnapArea.x(), scrollSnapArea.maxX(), alignment.x, scrollerIsRTL) - computeScrollSnapAlignOffset(scrollSnapPort.x(), scrollSnapPort.maxX(), alignment.x, scrollerIsRTL); |
| 295 |
auto absoluteScrollOffset = clampTo<int>(scrollableArea.scrollOffsetFromPosition({ roundToInt(absoluteScrollXPosition), 0 }).x(), 0, maxScrollOffset.x()); |
172 |
auto absoluteScrollOffset = clampTo<int>(scrollableArea.scrollOffsetFromPosition({ roundToInt(absoluteScrollXPosition), 0 }).x(), 0, maxScrollOffset.x()); |
| 296 |
addOrUpdateStopForSnapOffset(horizontalSnapOffsetsMap, { absoluteScrollOffset, stop }); |
173 |
addOrUpdateStopForSnapOffset(horizontalSnapOffsetsMap, { absoluteScrollOffset, stop, snapAreas.size() - 1 }); |
| 297 |
} |
174 |
} |
| 298 |
if (hasVerticalSnapOffsets && alignment.y != ScrollSnapAxisAlignType::None) { |
175 |
if (snapsVertically) { |
| 299 |
auto absoluteScrollYPosition = computeScrollSnapAlignOffset(scrollSnapArea.y(), scrollSnapArea.maxY(), alignment.y, false) - computeScrollSnapAlignOffset(scrollSnapPort.y(), scrollSnapPort.maxY(), alignment.y, false); |
176 |
auto absoluteScrollYPosition = computeScrollSnapAlignOffset(scrollSnapArea.y(), scrollSnapArea.maxY(), alignment.y, false) - computeScrollSnapAlignOffset(scrollSnapPort.y(), scrollSnapPort.maxY(), alignment.y, false); |
| 300 |
auto absoluteScrollOffset = clampTo<int>(scrollableArea.scrollOffsetFromPosition({ 0, roundToInt(absoluteScrollYPosition) }).y(), 0, maxScrollOffset.y()); |
177 |
auto absoluteScrollOffset = clampTo<int>(scrollableArea.scrollOffsetFromPosition({ 0, roundToInt(absoluteScrollYPosition) }).y(), 0, maxScrollOffset.y()); |
| 301 |
addOrUpdateStopForSnapOffset(verticalSnapOffsetsMap, { absoluteScrollOffset, stop }); |
178 |
addOrUpdateStopForSnapOffset(verticalSnapOffsetsMap, { absoluteScrollOffset, stop, snapAreas.size() - 1 }); |
| 302 |
} |
179 |
} |
| 303 |
} |
180 |
} |
| 304 |
|
181 |
|
|
Lines 308-339
void updateSnapOffsetsForScrollableArea(ScrollableArea& scrollableArea, const Re
a/Source/WebCore/page/scrolling/ScrollSnapOffsetsInfo.cpp_sec7
|
| 308 |
}; |
185 |
}; |
| 309 |
|
186 |
|
| 310 |
Vector<SnapOffset<LayoutUnit>> horizontalSnapOffsets = copyToVector(horizontalSnapOffsetsMap.values()); |
187 |
Vector<SnapOffset<LayoutUnit>> horizontalSnapOffsets = copyToVector(horizontalSnapOffsetsMap.values()); |
| 311 |
Vector<ScrollOffsetRange<LayoutUnit>> horizontalSnapOffsetRanges; |
|
|
| 312 |
if (!horizontalSnapOffsets.isEmpty()) { |
188 |
if (!horizontalSnapOffsets.isEmpty()) { |
| 313 |
std::sort(horizontalSnapOffsets.begin(), horizontalSnapOffsets.end(), compareSnapOffsets); |
189 |
std::sort(horizontalSnapOffsets.begin(), horizontalSnapOffsets.end(), compareSnapOffsets); |
| 314 |
if (scrollSnapType.strictness == ScrollSnapStrictness::Proximity) |
|
|
| 315 |
computeAxisProximitySnapOffsetRanges(horizontalSnapOffsets, horizontalSnapOffsetRanges, scrollSnapPort.width()); |
| 316 |
|
| 317 |
LOG_WITH_STREAM(ScrollSnap, stream << " => Computed horizontal scroll snap offsets: " << horizontalSnapOffsets); |
190 |
LOG_WITH_STREAM(ScrollSnap, stream << " => Computed horizontal scroll snap offsets: " << horizontalSnapOffsets); |
| 318 |
LOG_WITH_STREAM(ScrollSnap, stream << " => Computed horizontal scroll snap offset ranges: " << horizontalSnapOffsetRanges); |
|
|
| 319 |
} |
191 |
} |
| 320 |
|
192 |
|
| 321 |
Vector<SnapOffset<LayoutUnit>> verticalSnapOffsets = copyToVector(verticalSnapOffsetsMap.values()); |
193 |
Vector<SnapOffset<LayoutUnit>> verticalSnapOffsets = copyToVector(verticalSnapOffsetsMap.values()); |
| 322 |
Vector<ScrollOffsetRange<LayoutUnit>> verticalSnapOffsetRanges; |
|
|
| 323 |
if (!verticalSnapOffsets.isEmpty()) { |
194 |
if (!verticalSnapOffsets.isEmpty()) { |
| 324 |
std::sort(verticalSnapOffsets.begin(), verticalSnapOffsets.end(), compareSnapOffsets); |
195 |
std::sort(verticalSnapOffsets.begin(), verticalSnapOffsets.end(), compareSnapOffsets); |
| 325 |
if (scrollSnapType.strictness == ScrollSnapStrictness::Proximity) |
|
|
| 326 |
computeAxisProximitySnapOffsetRanges(verticalSnapOffsets, verticalSnapOffsetRanges, scrollSnapPort.height()); |
| 327 |
|
| 328 |
LOG_WITH_STREAM(ScrollSnap, stream << " => Computed vertical scroll snap offsets: " << verticalSnapOffsets); |
196 |
LOG_WITH_STREAM(ScrollSnap, stream << " => Computed vertical scroll snap offsets: " << verticalSnapOffsets); |
| 329 |
LOG_WITH_STREAM(ScrollSnap, stream << " => Computed vertical scroll snap offset ranges: " << verticalSnapOffsetRanges); |
|
|
| 330 |
} |
197 |
} |
| 331 |
|
198 |
|
|
|
199 |
if (!snapAreas.isEmpty()) |
| 200 |
LOG_WITH_STREAM(ScrollSnap, stream << " => Computed snap areas: " << snapAreas); |
| 201 |
|
| 332 |
scrollableArea.setScrollSnapOffsetInfo({ |
202 |
scrollableArea.setScrollSnapOffsetInfo({ |
|
|
203 |
scrollSnapType.strictness, |
| 333 |
horizontalSnapOffsets, |
204 |
horizontalSnapOffsets, |
| 334 |
verticalSnapOffsets, |
205 |
verticalSnapOffsets, |
| 335 |
horizontalSnapOffsetRanges, |
206 |
snapAreas, |
| 336 |
verticalSnapOffsetRanges |
|
|
| 337 |
}); |
207 |
}); |
| 338 |
} |
208 |
} |
| 339 |
|
209 |
|
|
Lines 347-403
static LayoutUnit convertOffsetUnit(float input, float /* scaleFactor */)
a/Source/WebCore/page/scrolling/ScrollSnapOffsetsInfo.cpp_sec8
|
| 347 |
return LayoutUnit(input); |
217 |
return LayoutUnit(input); |
| 348 |
} |
218 |
} |
| 349 |
|
219 |
|
| 350 |
template <typename InputType, typename OutputType> |
220 |
template <typename InputType, typename InputRectType, typename OutputType, typename OutputRectType> |
| 351 |
static ScrollSnapOffsetsInfo<OutputType> convertOffsetInfo(const ScrollSnapOffsetsInfo<InputType>& input, float scaleFactor = 0.0) |
221 |
static ScrollSnapOffsetsInfo<OutputType, OutputRectType> convertOffsetInfo(const ScrollSnapOffsetsInfo<InputType, InputRectType>& input, float scaleFactor = 0.0) |
| 352 |
{ |
222 |
{ |
| 353 |
auto convertOffsets = [scaleFactor](const Vector<SnapOffset<InputType>>& input) |
223 |
auto convertOffsets = [scaleFactor](const Vector<SnapOffset<InputType>>& input) |
| 354 |
{ |
224 |
{ |
| 355 |
Vector<SnapOffset<OutputType>> output; |
225 |
Vector<SnapOffset<OutputType>> output; |
| 356 |
output.reserveInitialCapacity(input.size()); |
226 |
output.reserveInitialCapacity(input.size()); |
| 357 |
for (auto& offset : input) |
227 |
for (auto& offset : input) |
| 358 |
output.uncheckedAppend({ convertOffsetUnit(offset.offset, scaleFactor), offset.stop }); |
228 |
output.uncheckedAppend({ convertOffsetUnit(offset.offset, scaleFactor), offset.stop, offset.snapAreaIndex }); |
| 359 |
return output; |
229 |
return output; |
| 360 |
}; |
230 |
}; |
| 361 |
|
231 |
|
| 362 |
auto convertOffsetRanges = [scaleFactor](const Vector<ScrollOffsetRange<InputType>>& input) |
232 |
auto convertRects = [scaleFactor](const Vector<InputRectType>& input) |
| 363 |
{ |
233 |
{ |
| 364 |
Vector<ScrollOffsetRange<OutputType>> output; |
234 |
Vector<OutputRectType> output; |
| 365 |
output.reserveInitialCapacity(input.size()); |
235 |
output.reserveInitialCapacity(input.size()); |
| 366 |
for (auto& range : input) |
236 |
for (auto& rect : input) { |
| 367 |
output.uncheckedAppend({ convertOffsetUnit(range.start, scaleFactor), convertOffsetUnit(range.end, scaleFactor) }); |
237 |
OutputRectType outputRect( |
|
|
238 |
convertOffsetUnit(rect.x(), scaleFactor), convertOffsetUnit(rect.y(), scaleFactor), |
| 239 |
convertOffsetUnit(rect.width(), scaleFactor), convertOffsetUnit(rect.height(), scaleFactor)); |
| 240 |
output.uncheckedAppend(outputRect); |
| 241 |
} |
| 368 |
return output; |
242 |
return output; |
| 369 |
}; |
243 |
}; |
| 370 |
|
244 |
|
| 371 |
return { |
245 |
return { |
|
|
246 |
input.strictness, |
| 372 |
convertOffsets(input.horizontalSnapOffsets), |
247 |
convertOffsets(input.horizontalSnapOffsets), |
| 373 |
convertOffsets(input.verticalSnapOffsets), |
248 |
convertOffsets(input.verticalSnapOffsets), |
| 374 |
convertOffsetRanges(input.horizontalSnapOffsetRanges), |
249 |
convertRects(input.snapAreas), |
| 375 |
convertOffsetRanges(input.verticalSnapOffsetRanges) |
|
|
| 376 |
}; |
250 |
}; |
| 377 |
} |
251 |
} |
| 378 |
|
252 |
|
| 379 |
template <> template <> |
253 |
template <> template <> |
| 380 |
ScrollSnapOffsetsInfo<LayoutUnit> ScrollSnapOffsetsInfo<float>::convertUnits(float /* unusedScaleFactor */) const |
254 |
LayoutScrollSnapOffsetsInfo FloatScrollSnapOffsetsInfo::convertUnits(float /* unusedScaleFactor */) const |
| 381 |
{ |
255 |
{ |
| 382 |
return convertOffsetInfo<float, LayoutUnit>(*this); |
256 |
return convertOffsetInfo<float, FloatRect, LayoutUnit, LayoutRect>(*this); |
| 383 |
} |
257 |
} |
| 384 |
|
258 |
|
| 385 |
template <> template <> |
259 |
template <> template <> |
| 386 |
ScrollSnapOffsetsInfo<float> ScrollSnapOffsetsInfo<LayoutUnit>::convertUnits(float deviceScaleFactor) const |
260 |
FloatScrollSnapOffsetsInfo LayoutScrollSnapOffsetsInfo::convertUnits(float deviceScaleFactor) const |
| 387 |
{ |
261 |
{ |
| 388 |
return convertOffsetInfo<LayoutUnit, float>(*this, deviceScaleFactor); |
262 |
return convertOffsetInfo<LayoutUnit, LayoutRect, float, FloatRect>(*this, deviceScaleFactor); |
| 389 |
} |
263 |
} |
| 390 |
|
264 |
|
| 391 |
template <> |
265 |
template <typename UnitType, typename RectType> |
| 392 |
std::pair<LayoutUnit, unsigned> ScrollSnapOffsetsInfo<LayoutUnit>::closestSnapOffset(ScrollEventAxis axis, LayoutUnit scrollDestinationOffset, float velocity, Optional<LayoutUnit> originalPositionForDirectionalSnapping) const |
266 |
static UnitType maxForAxis(RectType rect, ScrollEventAxis axis) |
| 393 |
{ |
267 |
{ |
| 394 |
return closestSnapOffsetWithOffsetsAndRanges(offsetsForAxis(axis), offsetRangesForAxis(axis), scrollDestinationOffset, velocity, originalPositionForDirectionalSnapping); |
268 |
return axis == ScrollEventAxis::Horizontal ? rect.maxX() : rect.maxY(); |
| 395 |
} |
269 |
} |
| 396 |
|
270 |
|
| 397 |
template <> |
271 |
template <typename UnitType, typename RectType> |
| 398 |
std::pair<float, unsigned> ScrollSnapOffsetsInfo<float>::closestSnapOffset(ScrollEventAxis axis, float scrollDestinationOffset, float velocity, Optional<float> originalPositionForDirectionalSnapping) const |
272 |
static UnitType minForAxis(RectType rect, ScrollEventAxis axis) |
|
|
273 |
{ |
| 274 |
return axis == ScrollEventAxis::Horizontal ? rect.x() : rect.y(); |
| 275 |
} |
| 276 |
|
| 277 |
template <typename UnitType> |
| 278 |
static bool isNearEnoughToOffsetForProximity(ScrollSnapStrictness strictness, UnitType scrollDestination, UnitType candidateSnapOffset, UnitType viewportLength) |
| 279 |
{ |
| 280 |
if (strictness != ScrollSnapStrictness::Proximity) |
| 281 |
return true; |
| 282 |
|
| 283 |
// This is an arbitrary choice for what it means to be "in proximity" of a snap offset. We should play around with |
| 284 |
// this and see what feels best. |
| 285 |
static const float ratioOfScrollPortAxisLengthToBeConsideredForProximity = 0.3; |
| 286 |
return std::abs(float {candidateSnapOffset - scrollDestination}) <= (viewportLength * ratioOfScrollPortAxisLengthToBeConsideredForProximity); |
| 287 |
} |
| 288 |
|
| 289 |
template <typename UnitType, typename RectType, typename SizeType> |
| 290 |
static std::pair<UnitType, unsigned> calculateClosestSnapOffset(const ScrollSnapOffsetsInfo<UnitType, RectType>& info, ScrollEventAxis axis, const SizeType& viewportSize, UnitType scrollDestinationOffset, float velocity, Optional<UnitType> originalOffset) |
| 291 |
{ |
| 292 |
const auto& snapOffsets = info.offsetsForAxis(axis); |
| 293 |
auto pairForNoSnapping = std::make_pair(scrollDestinationOffset, invalidSnapOffsetIndex); |
| 294 |
if (snapOffsets.isEmpty()) |
| 295 |
return pairForNoSnapping; |
| 296 |
|
| 297 |
// The first snap point with `scroll-snap-stop: always` between the original position and destination takes precedence. |
| 298 |
if (originalOffset) { |
| 299 |
if (auto firstSnapStopOffset = findFirstSnapStopOffsetBetweenOriginAndDestination(snapOffsets, *originalOffset, scrollDestinationOffset)) |
| 300 |
return *firstSnapStopOffset; |
| 301 |
} |
| 302 |
|
| 303 |
auto viewportLength = axis == ScrollEventAxis::Horizontal ? viewportSize.width() : viewportSize.height(); |
| 304 |
bool landedInsideSnapAreaThatConsumesViewport = false; |
| 305 |
Optional<std::pair<UnitType, unsigned>> previous, next; |
| 306 |
Optional<RectType> previousSnapArea, nextSnapArea; |
| 307 |
for (unsigned i = 0; i < snapOffsets.size(); i++) { |
| 308 |
const auto& snapArea = info.snapAreas[snapOffsets[i].snapAreaIndex]; |
| 309 |
landedInsideSnapAreaThatConsumesViewport |= (minForAxis<UnitType>(snapArea, axis) <= scrollDestinationOffset && maxForAxis<UnitType>(snapArea, axis) >= (scrollDestinationOffset + viewportLength)); |
| 310 |
// If the scroll landed exactly on a snap offset, choose that one. |
| 311 |
UnitType potentialSnapOffset = snapOffsets[i].offset; |
| 312 |
if (potentialSnapOffset == scrollDestinationOffset) |
| 313 |
return std::make_pair(potentialSnapOffset, i); |
| 314 |
|
| 315 |
if (potentialSnapOffset < scrollDestinationOffset) { |
| 316 |
previous = std::make_pair(potentialSnapOffset, i); |
| 317 |
previousSnapArea = snapArea; |
| 318 |
} else if (!next && potentialSnapOffset > scrollDestinationOffset) { |
| 319 |
next = std::make_pair(potentialSnapOffset, i); |
| 320 |
nextSnapArea = snapArea; |
| 321 |
} |
| 322 |
} |
| 323 |
|
| 324 |
// From https://www.w3.org/TR/css-scroll-snap-1/#snap-overflow |
| 325 |
// "If the snap area is larger than the snapport in a particular axis, then any scroll position |
| 326 |
// in which the snap area covers the snapport, and the distance between the geometrically |
| 327 |
// previous and subsequent snap positions in that axis is larger than size of the snapport in |
| 328 |
// that axis, is a valid snap position in that axis. The UA may use the specified alignment as a |
| 329 |
// more precise target for certain scroll operations (e.g. explicit paging)." |
| 330 |
if (landedInsideSnapAreaThatConsumesViewport && (!previous || !next || ((*next).first - (*previous).first) >= viewportLength)) |
| 331 |
return pairForNoSnapping; |
| 332 |
|
| 333 |
if (originalOffset) { |
| 334 |
// If this is a directional scroll and there are no scroll stops in a particular direction, |
| 335 |
// don't scroll past the edge of the scroll area. |
| 336 |
if (!next && previous) |
| 337 |
return std::make_pair(std::max((*previous).first, maxForAxis<UnitType>(*previousSnapArea, axis) - viewportLength), invalidSnapOffsetIndex); |
| 338 |
if (!previous && next) |
| 339 |
return std::make_pair(minForAxis<UnitType>(*nextSnapArea, axis), invalidSnapOffsetIndex); |
| 340 |
|
| 341 |
// From https://www.w3.org/TR/css-scroll-snap-1/#choosing |
| 342 |
// "User agents must ensure that a user can “escape” a snap position, regardless of the scroll |
| 343 |
// method. For example, if the snap type is mandatory and the next snap position is more than |
| 344 |
// two screen-widths away, a naïve “always snap to nearest” selection algorithm might “trap” the |
| 345 |
// user if their end position was only one screen-width away. Instead, a smarter algorithm that |
| 346 |
// only returned to the starting snap position if the end-point was a fairly small distance from |
| 347 |
// it, and otherwise ignored the starting snap position, would give better behavior." |
| 348 |
// We only do this if we are in a situation where we are choosing between two compatible |
| 349 |
// snap offsets. |
| 350 |
if (*originalOffset < scrollDestinationOffset && (*previous).first <= *originalOffset) |
| 351 |
previous.reset(); |
| 352 |
if (*originalOffset > scrollDestinationOffset && (*next).first >= *originalOffset) |
| 353 |
next.reset(); |
| 354 |
} |
| 355 |
|
| 356 |
if (next && !isNearEnoughToOffsetForProximity(info.strictness, scrollDestinationOffset, (*next).first, viewportLength)) |
| 357 |
next.reset(); |
| 358 |
if (previous && !isNearEnoughToOffsetForProximity(info.strictness, scrollDestinationOffset, (*previous).first, viewportLength)) |
| 359 |
previous.reset(); |
| 360 |
|
| 361 |
// If we don't have compatible offsets to choose between, just do the obvious thing |
| 362 |
// and do no snapping or only select the single compatible offset. |
| 363 |
if (!previous && !next) |
| 364 |
return pairForNoSnapping; |
| 365 |
if (!previous) |
| 366 |
return *next; |
| 367 |
if (!next) |
| 368 |
return *previous; |
| 369 |
|
| 370 |
// If this scroll isn't directional, then choose whatever snap point is closer. |
| 371 |
UnitType previousSnapOffset = (*previous).first; |
| 372 |
UnitType nextSnapOffset = (*next).first; |
| 373 |
if (!std::abs(velocity)) { |
| 374 |
bool isCloserToPreviousSnapIndex = scrollDestinationOffset - previousSnapOffset <= nextSnapOffset - scrollDestinationOffset; |
| 375 |
return isCloserToPreviousSnapIndex ? *previous : *next; |
| 376 |
} |
| 377 |
|
| 378 |
// Finally, if we have a directional scroll prefer the snap point that is in the scroll direction. |
| 379 |
return velocity < 0 ? *previous : *next; |
| 380 |
} |
| 381 |
|
| 382 |
|
| 383 |
template <> template <> |
| 384 |
std::pair<LayoutUnit, unsigned> LayoutScrollSnapOffsetsInfo::closestSnapOffset(ScrollEventAxis axis, const LayoutSize& viewportSize, LayoutUnit scrollDestinationOffset, float velocity, Optional<LayoutUnit> originalOffsetForDirectionalSnapping) const |
| 385 |
{ |
| 386 |
return calculateClosestSnapOffset<LayoutUnit, LayoutRect, LayoutSize>(*this, axis, viewportSize, scrollDestinationOffset, velocity, originalOffsetForDirectionalSnapping); |
| 387 |
} |
| 388 |
|
| 389 |
template <> template <> |
| 390 |
std::pair<float, unsigned> FloatScrollSnapOffsetsInfo::closestSnapOffset(ScrollEventAxis axis, const FloatSize& viewportSize, float scrollDestinationOffset, float velocity, Optional<float> originalOffsetForDirectionalSnapping) const |
| 399 |
{ |
391 |
{ |
| 400 |
return closestSnapOffsetWithOffsetsAndRanges(offsetsForAxis(axis), offsetRangesForAxis(axis), scrollDestinationOffset, velocity, originalPositionForDirectionalSnapping); |
392 |
return calculateClosestSnapOffset<float, FloatRect, FloatSize>(*this, axis, viewportSize, scrollDestinationOffset, velocity, originalOffsetForDirectionalSnapping); |
| 401 |
} |
393 |
} |
| 402 |
|
394 |
|
| 403 |
} |
395 |
} |