Changeset 277083 in webkit
- Timestamp:
- May 6, 2021, 5:11:15 AM (5 years ago)
- Location:
- trunk/Source
- Files:
-
- 15 edited
-
WebCore/ChangeLog (modified) (1 diff)
-
WebCore/page/scrolling/AsyncScrollingCoordinator.cpp (modified) (1 diff)
-
WebCore/page/scrolling/ScrollSnapOffsetsInfo.cpp (modified) (12 diffs)
-
WebCore/page/scrolling/ScrollSnapOffsetsInfo.h (modified) (5 diffs)
-
WebCore/page/scrolling/mac/ScrollingTreeScrollingNodeDelegateMac.mm (modified) (1 diff)
-
WebCore/page/scrolling/nicosia/ScrollingTreeScrollingNodeDelegateNicosia.cpp (modified) (1 diff)
-
WebCore/platform/ScrollController.cpp (modified) (2 diffs)
-
WebCore/platform/ScrollSnapAnimatorState.cpp (modified) (4 diffs)
-
WebCore/platform/ScrollSnapAnimatorState.h (modified) (2 diffs)
-
WebKit/ChangeLog (modified) (1 diff)
-
WebKit/Shared/RemoteLayerTree/RemoteScrollingCoordinatorTransaction.cpp (modified) (4 diffs)
-
WebKit/Shared/WebCoreArgumentCoders.cpp (modified) (1 diff)
-
WebKit/Shared/WebCoreArgumentCoders.h (modified) (1 diff)
-
WebKit/UIProcess/RemoteLayerTree/ios/RemoteScrollingCoordinatorProxyIOS.mm (modified) (1 diff)
-
WebKit/UIProcess/RemoteLayerTree/ios/ScrollingTreeScrollingNodeDelegateIOS.mm (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
trunk/Source/WebCore/ChangeLog
r277080 r277083 1 2021-05-06 Martin Robinson <mrobinson@webkit.org> 2 3 [css-scroll-snap] Compute proximity information while snapping 4 https://bugs.webkit.org/show_bug.cgi?id=224326 5 6 Reviewed by Simon Fraser. 7 8 Have ScrollSnapOffsetsInfo carry snap area rectangles instead of scroll offset 9 ranges. This allows proximity information to be handled during snap point selection. 10 The geometry will be used in a future patch to follow spec behavior for snap 11 areas that overflow the snapport and when handling masonry layouts. 12 13 No new tests. This is just a refactor. It should not change behavior at all. 14 15 * page/scrolling/AsyncScrollingCoordinator.cpp: 16 (WebCore::setStateScrollingNodeSnapOffsetsAsFloat): Pass in the new template argument. 17 * page/scrolling/ScrollSnapOffsetsInfo.cpp: 18 (WebCore::isNearEnoughToOffsetForProximity): Added. 19 (WebCore::closestSnapOffsetWithInfoAndAxis): Modified to take in the ScrollSnapOffsetsInfo data 20 structure and axis along with the viewport size. This function now explicitly determines if a 21 snap point is too far away to affect scrolling due to scroll-snap proximity. 22 (WebCore::updateSnapOffsetsForScrollableArea): Add snap areas and snap area indices to SnapOffset. 23 (WebCore::convertOffsetInfo): Instead of converting scroll offset ranges, convert the snap 24 area rectangles. 25 (WebCore::FloatScrollSnapOffsetsInfo::convertUnits const): Ditto. 26 (WebCore::LayoutScrollSnapOffsetsInfo::convertUnits const): Ditto. 27 (WebCore::LayoutScrollSnapOffsetsInfo::closestSnapOffset const): Updated to reflect new 28 internal function. 29 (WebCore::FloatScrollSnapOffsetsInfo::closestSnapOffset const): Ditto. 30 * page/scrolling/ScrollSnapOffsetsInfo.h: 31 (WebCore::ScrollSnapOffsetsInfo::isEqual const): Update to reflect new mem ers. 32 (WebCore::ScrollSnapOffsetsInfo::offsetsForAxis const): Changed template parameter name. 33 * page/scrolling/mac/ScrollingTreeScrollingNodeDelegateMac.mm: 34 (WebCore::ScrollingTreeScrollingNodeDelegateMac::updateFromStateNode): ::convertUnits 35 takes a new template parameter. 36 * page/scrolling/nicosia/ScrollingTreeScrollingNodeDelegateNicosia.cpp: 37 (WebCore::ScrollingTreeScrollingNodeDelegateNicosia::handleWheelEvent): Pass in viewport size. 38 * platform/ScrollController.cpp: 39 (WebCore::ScrollController::setNearestScrollSnapIndexForAxisAndOffset): Ditto. 40 (WebCore::ScrollController::adjustScrollDestination): Ditto. 41 * platform/ScrollSnapAnimatorState.cpp: 42 (WebCore::ScrollSnapAnimatorState::setupAnimationForState): Ditto. 43 (WebCore::ScrollSnapAnimatorState::targetOffsetForStartOffset const): Ditto. 44 (WebCore::operator<<): No longer print snap offset ranges. 45 * platform/ScrollSnapAnimatorState.h: Remove references to snap offset ranges. 46 1 47 2021-05-06 Philippe Normand <pnormand@igalia.com> 2 48 -
trunk/Source/WebCore/page/scrolling/AsyncScrollingCoordinator.cpp
r275790 r277083 106 106 107 107 // FIXME: Incorporate current page scale factor in snapping to device pixel. Perhaps we should just convert to float here and let UI process do the pixel snapping? 108 node.setSnapOffsetsInfo(offsetInfo->convertUnits< float>(deviceScaleFactor));108 node.setSnapOffsetsInfo(offsetInfo->convertUnits<FloatScrollSnapOffsetsInfo>(deviceScaleFactor)); 109 109 } 110 110 #endif -
trunk/Source/WebCore/page/scrolling/ScrollSnapOffsetsInfo.cpp
r275422 r277083 43 43 namespace WebCore { 44 44 45 template <typename LayoutType> 46 static void indicesOfNearestSnapOffsetRanges(LayoutType offset, const Vector<ScrollOffsetRange<LayoutType>>& snapOffsetRanges, unsigned& lowerIndex, unsigned& upperIndex) 47 { 48 if (snapOffsetRanges.isEmpty()) { 49 lowerIndex = invalidSnapOffsetIndex; 50 upperIndex = invalidSnapOffsetIndex; 51 return; 52 } 53 54 int lowerIndexAsInt = -1; 55 int upperIndexAsInt = snapOffsetRanges.size(); 56 do { 57 int middleIndex = (lowerIndexAsInt + upperIndexAsInt) / 2; 58 auto& range = snapOffsetRanges[middleIndex]; 59 if (range.start < offset && offset < range.end) { 60 lowerIndexAsInt = middleIndex; 61 upperIndexAsInt = middleIndex; 62 break; 63 } 64 65 if (offset > range.end) 66 lowerIndexAsInt = middleIndex; 67 else 68 upperIndexAsInt = middleIndex; 69 } while (lowerIndexAsInt < upperIndexAsInt - 1); 70 71 if (offset <= snapOffsetRanges.first().start) 72 lowerIndex = invalidSnapOffsetIndex; 73 else 74 lowerIndex = lowerIndexAsInt; 75 76 if (offset >= snapOffsetRanges.last().end) 77 upperIndex = invalidSnapOffsetIndex; 78 else 79 upperIndex = upperIndexAsInt; 45 template <typename UnitType> 46 static bool isNearEnoughToOffsetForProximity(ScrollSnapStrictness strictness, UnitType scrollDestination, UnitType candidateSnapOffset, UnitType viewportLength) 47 { 48 if (strictness != ScrollSnapStrictness::Proximity) 49 return true; 50 51 // This is an arbitrary choice for what it means to be "in proximity" of a snap offset. We should play around with 52 // this and see what feels best. 53 static const float ratioOfScrollPortAxisLengthToBeConsideredForProximity = 0.3; 54 return std::abs(float {candidateSnapOffset - scrollDestination}) <= (viewportLength * ratioOfScrollPortAxisLengthToBeConsideredForProximity); 80 55 } 81 56 … … 132 107 } 133 108 134 template <typename LayoutType> 135 static std::pair<LayoutType, unsigned> closestSnapOffsetWithOffsetsAndRanges(const Vector<SnapOffset<LayoutType>>& snapOffsets, const Vector<ScrollOffsetRange<LayoutType>>& snapOffsetRanges, LayoutType scrollDestinationOffset, float velocity, Optional<LayoutType> originalOffsetForDirectionalSnapping) 136 { 109 template <typename InfoType, typename SizeType, typename LayoutType> 110 static std::pair<LayoutType, unsigned> closestSnapOffsetWithInfoAndAxis(const InfoType& info, ScrollEventAxis axis, const SizeType& viewportSize, LayoutType scrollDestinationOffset, float velocity, Optional<LayoutType> originalOffsetForDirectionalSnapping) 111 { 112 const auto& snapOffsets = info.offsetsForAxis(axis); 137 113 if (snapOffsets.isEmpty()) 138 114 return std::make_pair(scrollDestinationOffset, invalidSnapOffsetIndex); … … 143 119 return std::make_pair(snapOffsets[*firstSnapStopOffsetIndex].offset, *firstSnapStopOffsetIndex); 144 120 } 145 146 unsigned lowerSnapOffsetRangeIndex;147 unsigned upperSnapOffsetRangeIndex;148 indicesOfNearestSnapOffsetRanges<LayoutType>(scrollDestinationOffset, snapOffsetRanges, lowerSnapOffsetRangeIndex, upperSnapOffsetRangeIndex);149 if (lowerSnapOffsetRangeIndex == upperSnapOffsetRangeIndex && upperSnapOffsetRangeIndex != invalidSnapOffsetIndex)150 return std::make_pair(scrollDestinationOffset, invalidSnapOffsetIndex);151 121 152 122 if (scrollDestinationOffset <= snapOffsets.first().offset) … … 161 131 LayoutType lowerSnapPosition = snapOffsets[lowerIndex].offset; 162 132 LayoutType upperSnapPosition = snapOffsets[upperIndex].offset; 133 134 auto viewportLength = axis == ScrollEventAxis::Horizontal ? viewportSize.width() : viewportSize.height(); 135 if (!isNearEnoughToOffsetForProximity<LayoutType>(info.strictness, scrollDestinationOffset, lowerSnapPosition, viewportLength)) { 136 lowerSnapPosition = scrollDestinationOffset; 137 lowerIndex = invalidSnapOffsetIndex; 138 } 139 140 if (!isNearEnoughToOffsetForProximity<LayoutType>(info.strictness, scrollDestinationOffset, upperSnapPosition, viewportLength)) { 141 upperSnapPosition = scrollDestinationOffset; 142 upperIndex = invalidSnapOffsetIndex; 143 } 163 144 if (!std::abs(velocity)) { 164 bool isCloserToLowerSnapPosition = scrollDestinationOffset - lowerSnapPosition <= upperSnapPosition - scrollDestinationOffset; 145 bool isCloserToLowerSnapPosition = (upperIndex == invalidSnapOffsetIndex) 146 || (lowerIndex != invalidSnapOffsetIndex && scrollDestinationOffset - lowerSnapPosition <= upperSnapPosition - scrollDestinationOffset); 165 147 return isCloserToLowerSnapPosition ? std::make_pair(lowerSnapPosition, lowerIndex) : std::make_pair(upperSnapPosition, upperIndex); 166 148 } 167 149 168 150 // 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 169 // as long as a scroll snap offset range does not lie between the scroll destination and the targeted snap offset. If we are doing directional151 // as long as a scroll snap offset is close enough for proximity (or we aren't using proximity). If we are doing directional 170 152 // 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. 171 153 // This allows directional scrolling to escape snap points. 172 if (velocity < 0) { 173 if (lowerSnapOffsetRangeIndex == invalidSnapOffsetIndex || lowerSnapPosition >= snapOffsetRanges[lowerSnapOffsetRangeIndex].end) 174 return std::make_pair(lowerSnapPosition, lowerIndex); 175 if (!originalOffsetForDirectionalSnapping.hasValue() || *originalOffsetForDirectionalSnapping > upperSnapPosition) 154 if (velocity < 0) { 155 if (upperIndex != invalidSnapOffsetIndex && (!originalOffsetForDirectionalSnapping || *originalOffsetForDirectionalSnapping > upperSnapPosition)) 176 156 return std::make_pair(upperSnapPosition, upperIndex); 177 } else { 178 if (upperSnapOffsetRangeIndex == invalidSnapOffsetIndex || snapOffsetRanges[upperSnapOffsetRangeIndex].start >= upperSnapPosition) 179 return std::make_pair(upperSnapPosition, upperIndex); 180 if (!originalOffsetForDirectionalSnapping.hasValue() || *originalOffsetForDirectionalSnapping < lowerSnapPosition) 181 return std::make_pair(lowerSnapPosition, lowerIndex); 182 } 183 184 return std::make_pair(scrollDestinationOffset, invalidSnapOffsetIndex); 157 return std::make_pair(lowerSnapPosition, lowerIndex); 158 } 159 160 if (lowerIndex != invalidSnapOffsetIndex && (!originalOffsetForDirectionalSnapping || *originalOffsetForDirectionalSnapping < lowerSnapPosition)) 161 return std::make_pair(lowerSnapPosition, lowerIndex); 162 return std::make_pair(upperSnapPosition, upperIndex); 185 163 } 186 164 … … 221 199 } 222 200 223 static void computeAxisProximitySnapOffsetRanges(const Vector<SnapOffset<LayoutUnit>>& snapOffsets, Vector<ScrollOffsetRange<LayoutUnit>>& offsetRanges, LayoutUnit scrollPortAxisLength)224 {225 // This is an arbitrary choice for what it means to be "in proximity" of a snap offset. We should play around with226 // this and see what feels best.227 static const float ratioOfScrollPortAxisLengthToBeConsideredForProximity = 0.3;228 if (snapOffsets.size() < 2)229 return;230 231 // The extra rule accounting for scroll offset ranges in between the scroll destination and a potential snap offset232 // handles the corner case where the user scrolls with momentum very lightly away from a snap offset, such that the233 // predicted scroll destination is still within proximity of the snap offset. In this case, the regular (mandatory234 // scroll snapping) behavior would be to snap to the next offset in the direction of momentum scrolling, but235 // instead, it is more intuitive to either return to the original snap position (which we arbitrarily choose here)236 // or scroll just outside of the snap offset range. This is another minor behavior tweak that we should play around237 // with to see what feels best.238 LayoutUnit proximityDistance { ratioOfScrollPortAxisLengthToBeConsideredForProximity * scrollPortAxisLength };239 for (size_t index = 1; index < snapOffsets.size(); ++index) {240 auto startOffset = snapOffsets[index - 1].offset + proximityDistance;241 auto endOffset = snapOffsets[index].offset - proximityDistance;242 if (startOffset < endOffset)243 offsetRanges.append({ startOffset, endOffset });244 }245 }246 247 201 void updateSnapOffsetsForScrollableArea(ScrollableArea& scrollableArea, const RenderBox& scrollingElementBox, const RenderStyle& scrollingElementStyle, LayoutRect viewportRectInBorderBoxCoordinates) 248 202 { … … 264 218 HashMap<float, SnapOffset<LayoutUnit>> verticalSnapOffsetsMap; 265 219 HashMap<float, SnapOffset<LayoutUnit>> horizontalSnapOffsetsMap; 220 Vector<LayoutRect> snapAreas; 266 221 bool hasHorizontalSnapOffsets = scrollSnapType.axis == ScrollSnapAxis::Both || scrollSnapType.axis == ScrollSnapAxis::XAxis || scrollSnapType.axis == ScrollSnapAxis::Inline; 267 222 bool hasVerticalSnapOffsets = scrollSnapType.axis == ScrollSnapAxis::Both || scrollSnapType.axis == ScrollSnapAxis::YAxis || scrollSnapType.axis == ScrollSnapAxis::Block; … … 292 247 auto alignment = child->style().scrollSnapAlign(); 293 248 auto stop = child->style().scrollSnapStop(); 294 if (hasHorizontalSnapOffsets && alignment.x != ScrollSnapAxisAlignType::None) { 249 250 bool snapsHorizontally = hasHorizontalSnapOffsets && alignment.x != ScrollSnapAxisAlignType::None; 251 bool snapsVertically = hasVerticalSnapOffsets && alignment.y != ScrollSnapAxisAlignType::None; 252 if (!snapsHorizontally && !snapsVertically) 253 continue; 254 255 // The scroll snap area is defined via its scroll position, so convert the snap area rectangle to be relative to scroll offsets. 256 auto snapAreaOriginRelativeToBorderEdge = scrollSnapArea.location() - scrollSnapPort.location(); 257 LayoutRect scrollSnapAreaAsOffsets(scrollableArea.scrollOffsetFromPosition(roundedIntPoint(snapAreaOriginRelativeToBorderEdge)), scrollSnapArea.size()); 258 snapAreas.append(scrollSnapAreaAsOffsets); 259 260 if (snapsHorizontally) { 295 261 auto absoluteScrollXPosition = computeScrollSnapAlignOffset(scrollSnapArea.x(), scrollSnapArea.maxX(), alignment.x, scrollerIsRTL) - computeScrollSnapAlignOffset(scrollSnapPort.x(), scrollSnapPort.maxX(), alignment.x, scrollerIsRTL); 296 262 auto absoluteScrollOffset = clampTo<int>(scrollableArea.scrollOffsetFromPosition({ roundToInt(absoluteScrollXPosition), 0 }).x(), 0, maxScrollOffset.x()); 297 addOrUpdateStopForSnapOffset(horizontalSnapOffsetsMap, { absoluteScrollOffset, stop });263 addOrUpdateStopForSnapOffset(horizontalSnapOffsetsMap, { absoluteScrollOffset, stop, snapAreas.size() - 1 }); 298 264 } 299 if ( hasVerticalSnapOffsets && alignment.y != ScrollSnapAxisAlignType::None) {265 if (snapsVertically) { 300 266 auto absoluteScrollYPosition = computeScrollSnapAlignOffset(scrollSnapArea.y(), scrollSnapArea.maxY(), alignment.y, false) - computeScrollSnapAlignOffset(scrollSnapPort.y(), scrollSnapPort.maxY(), alignment.y, false); 301 267 auto absoluteScrollOffset = clampTo<int>(scrollableArea.scrollOffsetFromPosition({ 0, roundToInt(absoluteScrollYPosition) }).y(), 0, maxScrollOffset.y()); 302 addOrUpdateStopForSnapOffset(verticalSnapOffsetsMap, { absoluteScrollOffset, stop });268 addOrUpdateStopForSnapOffset(verticalSnapOffsetsMap, { absoluteScrollOffset, stop, snapAreas.size() - 1 }); 303 269 } 270 271 if (!snapAreas.isEmpty()) 272 LOG_WITH_STREAM(ScrollSnap, stream << " => Computed snap areas: " << snapAreas); 304 273 } 305 274 … … 310 279 311 280 Vector<SnapOffset<LayoutUnit>> horizontalSnapOffsets = copyToVector(horizontalSnapOffsetsMap.values()); 312 Vector<ScrollOffsetRange<LayoutUnit>> horizontalSnapOffsetRanges;313 281 if (!horizontalSnapOffsets.isEmpty()) { 314 282 std::sort(horizontalSnapOffsets.begin(), horizontalSnapOffsets.end(), compareSnapOffsets); 315 if (scrollSnapType.strictness == ScrollSnapStrictness::Proximity)316 computeAxisProximitySnapOffsetRanges(horizontalSnapOffsets, horizontalSnapOffsetRanges, scrollSnapPort.width());317 318 283 LOG_WITH_STREAM(ScrollSnap, stream << " => Computed horizontal scroll snap offsets: " << horizontalSnapOffsets); 319 LOG_WITH_STREAM(ScrollSnap, stream << " => Computed horizontal scroll snap offset ranges: " << horizontalSnapOffsetRanges);320 284 } 321 285 322 286 Vector<SnapOffset<LayoutUnit>> verticalSnapOffsets = copyToVector(verticalSnapOffsetsMap.values()); 323 Vector<ScrollOffsetRange<LayoutUnit>> verticalSnapOffsetRanges;324 287 if (!verticalSnapOffsets.isEmpty()) { 325 288 std::sort(verticalSnapOffsets.begin(), verticalSnapOffsets.end(), compareSnapOffsets); 326 if (scrollSnapType.strictness == ScrollSnapStrictness::Proximity)327 computeAxisProximitySnapOffsetRanges(verticalSnapOffsets, verticalSnapOffsetRanges, scrollSnapPort.height());328 329 289 LOG_WITH_STREAM(ScrollSnap, stream << " => Computed vertical scroll snap offsets: " << verticalSnapOffsets); 330 LOG_WITH_STREAM(ScrollSnap, stream << " => Computed vertical scroll snap offset ranges: " << verticalSnapOffsetRanges);331 290 } 332 291 333 292 scrollableArea.setScrollSnapOffsetInfo({ 293 scrollSnapType.strictness, 334 294 horizontalSnapOffsets, 335 295 verticalSnapOffsets, 336 horizontalSnapOffsetRanges, 337 verticalSnapOffsetRanges 296 snapAreas 338 297 }); 339 298 } … … 349 308 } 350 309 351 template <typename InputType, typename OutputType>352 static ScrollSnapOffsetsInfo<OutputType > convertOffsetInfo(const ScrollSnapOffsetsInfo<InputType>& input, float scaleFactor = 0.0)310 template <typename InputType, typename InputRectType, typename OutputType, typename OutputRectType> 311 static ScrollSnapOffsetsInfo<OutputType, OutputRectType> convertOffsetInfo(const ScrollSnapOffsetsInfo<InputType, InputRectType>& input, float scaleFactor = 0.0) 353 312 { 354 313 auto convertOffsets = [scaleFactor](const Vector<SnapOffset<InputType>>& input) … … 357 316 output.reserveInitialCapacity(input.size()); 358 317 for (auto& offset : input) 359 output.uncheckedAppend({ convertOffsetUnit(offset.offset, scaleFactor), offset.stop });318 output.uncheckedAppend({ convertOffsetUnit(offset.offset, scaleFactor), offset.stop, offset.snapAreaIndex }); 360 319 return output; 361 320 }; 362 321 363 auto convert OffsetRanges = [scaleFactor](const Vector<ScrollOffsetRange<InputType>>& input)322 auto convertRects = [scaleFactor](const Vector<InputRectType>& input) 364 323 { 365 Vector< ScrollOffsetRange<OutputType>> output;324 Vector<OutputRectType> output; 366 325 output.reserveInitialCapacity(input.size()); 367 for (auto& range : input) 368 output.uncheckedAppend({ convertOffsetUnit(range.start, scaleFactor), convertOffsetUnit(range.end, scaleFactor) }); 326 for (auto& rect : input) { 327 OutputRectType outputRect( 328 convertOffsetUnit(rect.x(), scaleFactor), convertOffsetUnit(rect.y(), scaleFactor), 329 convertOffsetUnit(rect.width(), scaleFactor), convertOffsetUnit(rect.height(), scaleFactor)); 330 output.uncheckedAppend(outputRect); 331 } 332 369 333 return output; 370 334 }; 371 335 372 336 return { 337 input.strictness, 373 338 convertOffsets(input.horizontalSnapOffsets), 374 339 convertOffsets(input.verticalSnapOffsets), 375 convertOffsetRanges(input.horizontalSnapOffsetRanges), 376 convertOffsetRanges(input.verticalSnapOffsetRanges) 340 convertRects(input.snapAreas), 377 341 }; 378 342 } … … 381 345 LayoutScrollSnapOffsetsInfo FloatScrollSnapOffsetsInfo::convertUnits(float /* unusedScaleFactor */) const 382 346 { 383 return convertOffsetInfo<float, LayoutUnit>(*this); 347 return convertOffsetInfo<float, FloatRect, LayoutUnit, LayoutRect>(*this); 348 384 349 } 385 350 … … 387 352 FloatScrollSnapOffsetsInfo LayoutScrollSnapOffsetsInfo::convertUnits(float deviceScaleFactor) const 388 353 { 389 return convertOffsetInfo<LayoutUnit, float>(*this, deviceScaleFactor); 390 } 391 392 template <> 393 std::pair<LayoutUnit, unsigned> LayoutScrollSnapOffsetsInfo::closestSnapOffset(ScrollEventAxis axis, LayoutUnit scrollDestinationOffset, float velocity, Optional<LayoutUnit> originalPositionForDirectionalSnapping) const 394 { 395 return closestSnapOffsetWithOffsetsAndRanges(offsetsForAxis(axis), offsetRangesForAxis(axis), scrollDestinationOffset, velocity, originalPositionForDirectionalSnapping); 396 } 397 398 template <> 399 std::pair<float, unsigned> FloatScrollSnapOffsetsInfo::closestSnapOffset(ScrollEventAxis axis, float scrollDestinationOffset, float velocity, Optional<float> originalPositionForDirectionalSnapping) const 400 { 401 return closestSnapOffsetWithOffsetsAndRanges(offsetsForAxis(axis), offsetRangesForAxis(axis), scrollDestinationOffset, velocity, originalPositionForDirectionalSnapping); 354 return convertOffsetInfo<LayoutUnit, LayoutRect, float, FloatRect>(*this, deviceScaleFactor); 355 356 } 357 358 template <> template <> 359 std::pair<LayoutUnit, unsigned> LayoutScrollSnapOffsetsInfo::closestSnapOffset(ScrollEventAxis axis, const LayoutSize& viewportSize, LayoutUnit scrollDestinationOffset, float velocity, Optional<LayoutUnit> originalPositionForDirectionalSnapping) const 360 { 361 return closestSnapOffsetWithInfoAndAxis(*this, axis, viewportSize, scrollDestinationOffset, velocity, originalPositionForDirectionalSnapping); 362 } 363 364 template <> template<> 365 std::pair<float, unsigned> FloatScrollSnapOffsetsInfo::closestSnapOffset(ScrollEventAxis axis, const FloatSize& viewportSize, float scrollDestinationOffset, float velocity, Optional<float> originalPositionForDirectionalSnapping) const 366 { 367 return closestSnapOffsetWithInfoAndAxis(*this, axis, viewportSize, scrollDestinationOffset, velocity, originalPositionForDirectionalSnapping); 402 368 } 403 369 -
trunk/Source/WebCore/page/scrolling/ScrollSnapOffsetsInfo.h
r275354 r277083 28 28 #if ENABLE(CSS_SCROLL_SNAP) 29 29 30 #include "FloatRect.h" 31 #include "LayoutRect.h" 30 32 #include "LayoutUnit.h" 31 33 #include "ScrollTypes.h" … … 36 38 namespace WebCore { 37 39 38 class LayoutRect;39 40 class ScrollableArea; 40 41 class RenderBox; … … 45 46 T offset; 46 47 ScrollSnapStop stop; 48 size_t snapAreaIndex; 47 49 }; 48 50 49 template <typename T> 50 struct ScrollOffsetRange { 51 T start; 52 T end; 53 }; 54 55 template <typename T> 51 template <typename UnitType, typename RectType> 56 52 struct ScrollSnapOffsetsInfo { 57 53 WTF_MAKE_STRUCT_FAST_ALLOCATED; 58 Vector<SnapOffset<T>> horizontalSnapOffsets; 59 Vector<SnapOffset<T>> verticalSnapOffsets; 54 ScrollSnapStrictness strictness; 55 Vector<SnapOffset<UnitType>> horizontalSnapOffsets; 56 Vector<SnapOffset<UnitType>> verticalSnapOffsets; 57 Vector<RectType> snapAreas; 60 58 61 // Snap offset ranges represent non-empty ranges of scroll offsets in which scrolling may rest after scroll snapping. 62 // These are used in two cases: (1) for proximity scroll snapping, where portions of areas between adjacent snap offsets 63 // may emit snap offset ranges, and (2) in the case where the snap area is larger than the snap port, in which case areas 64 // where the snap port fits within the snap area are considered to be valid snap positions. 65 Vector<ScrollOffsetRange<T>> horizontalSnapOffsetRanges; 66 Vector<ScrollOffsetRange<T>> verticalSnapOffsetRanges; 67 68 bool isEqual(const ScrollSnapOffsetsInfo<T>& other) const 59 bool isEqual(const ScrollSnapOffsetsInfo<UnitType, RectType>& other) const 69 60 { 70 return horizontalSnapOffsets == other.horizontalSnapOffsets && verticalSnapOffsets == other.verticalSnapOffsets && horizontalSnapOffsetRanges == other.horizontalSnapOffsetRanges && verticalSnapOffsetRanges == other.verticalSnapOffsetRanges;61 return strictness == other.strictness && horizontalSnapOffsets == other.horizontalSnapOffsets && verticalSnapOffsets == other.verticalSnapOffsets && snapAreas == other.snapAreas; 71 62 } 72 63 … … 76 67 } 77 68 78 Vector<SnapOffset< T>> offsetsForAxis(ScrollEventAxis axis) const69 Vector<SnapOffset<UnitType>> offsetsForAxis(ScrollEventAxis axis) const 79 70 { 80 71 return axis == ScrollEventAxis::Vertical ? verticalSnapOffsets : horizontalSnapOffsets; 81 72 } 82 73 83 Vector<ScrollOffsetRange<T>> offsetRangesForAxis(ScrollEventAxis axis) const 84 { 85 return axis == ScrollEventAxis::Vertical ? verticalSnapOffsetRanges : horizontalSnapOffsetRanges; 86 } 87 88 template<typename OutputType> ScrollSnapOffsetsInfo<OutputType> convertUnits(float deviceScaleFactor = 0.0) const; 89 WEBCORE_EXPORT std::pair<T, unsigned> closestSnapOffset(ScrollEventAxis, T scrollDestinationOffset, float velocity, Optional<T> originalPositionForDirectionalSnapping = WTF::nullopt) const; 74 template<typename OutputType> OutputType convertUnits(float deviceScaleFactor = 0.0) const; 75 template<typename SizeType> 76 WEBCORE_EXPORT std::pair<UnitType, unsigned> closestSnapOffset(ScrollEventAxis, const SizeType& viewportSize, UnitType scrollDestinationOffset, float velocity, Optional<UnitType> originalPositionForDirectionalSnapping = WTF::nullopt) const; 90 77 }; 91 78 92 using LayoutScrollSnapOffsetsInfo = ScrollSnapOffsetsInfo<LayoutUnit >;93 using FloatScrollSnapOffsetsInfo = ScrollSnapOffsetsInfo<float >;79 using LayoutScrollSnapOffsetsInfo = ScrollSnapOffsetsInfo<LayoutUnit, LayoutRect>; 80 using FloatScrollSnapOffsetsInfo = ScrollSnapOffsetsInfo<float, FloatRect>; 94 81 95 82 template <> template <> 96 83 LayoutScrollSnapOffsetsInfo FloatScrollSnapOffsetsInfo::convertUnits(float /* unusedScaleFactor */) const; 97 template <> 98 WEBCORE_EXPORT std::pair<float, unsigned> FloatScrollSnapOffsetsInfo::closestSnapOffset(ScrollEventAxis, float scrollDestinationOffset, float velocity, Optional<float> originalPositionForDirectionalSnapping) const; 84 template <> template <> 85 WEBCORE_EXPORT std::pair<float, unsigned> FloatScrollSnapOffsetsInfo::closestSnapOffset(ScrollEventAxis, const FloatSize& viewportSize, float scrollDestinationOffset, float velocity, Optional<float> originalPositionForDirectionalSnapping) const; 86 99 87 100 88 template <> template <> 101 89 FloatScrollSnapOffsetsInfo LayoutScrollSnapOffsetsInfo::convertUnits(float deviceScaleFactor) const; 102 template <> 103 WEBCORE_EXPORT std::pair<LayoutUnit, unsigned> LayoutScrollSnapOffsetsInfo::closestSnapOffset(ScrollEventAxis, LayoutUnit scrollDestinationOffset, float velocity, Optional<LayoutUnit> originalPositionForDirectionalSnapping) const; 90 template <> template <> 91 WEBCORE_EXPORT std::pair<LayoutUnit, unsigned> LayoutScrollSnapOffsetsInfo::closestSnapOffset(ScrollEventAxis, const LayoutSize& viewportSize, LayoutUnit scrollDestinationOffset, float velocity, Optional<LayoutUnit> originalPositionForDirectionalSnapping) const; 92 104 93 105 94 const unsigned invalidSnapOffsetIndex = UINT_MAX; … … 118 107 } 119 108 120 template<typename T>121 TextStream& operator<<(TextStream& ts, const ScrollOffsetRange<T>& range)122 {123 ts << "start: " << range.start << " end: " << range.end;124 return ts;125 }126 127 109 }; // namespace WebCore 128 110 -
trunk/Source/WebCore/page/scrolling/mac/ScrollingTreeScrollingNodeDelegateMac.mm
r271937 r277083 66 66 #if ENABLE(CSS_SCROLL_SNAP) 67 67 if (scrollingStateNode.hasChangedProperty(ScrollingStateNode::Property::SnapOffsetsInfo)) 68 m_scrollController.updateScrollSnapPoints(scrollingStateNode.snapOffsetsInfo().convertUnits<Layout Unit>());68 m_scrollController.updateScrollSnapPoints(scrollingStateNode.snapOffsetsInfo().convertUnits<LayoutScrollSnapOffsetsInfo>()); 69 69 70 70 if (scrollingStateNode.hasChangedProperty(ScrollingStateNode::Property::CurrentHorizontalSnapOffsetIndex)) -
trunk/Source/WebCore/page/scrolling/nicosia/ScrollingTreeScrollingNodeDelegateNicosia.cpp
r274519 r277083 174 174 auto newOffset = LayoutPoint(newFloatOffset.x() / scale, newFloatOffset.y() / scale); 175 175 176 auto offsetX = scrollingNode().snapOffsetsInfo().closestSnapOffset(ScrollEventAxis::Horizontal, newOffset.x(), deltaX, originalOffset.x()).first;177 auto offsetY = scrollingNode().snapOffsetsInfo().closestSnapOffset(ScrollEventAxis::Vertical, newOffset.y(), deltaY, originalOffset.y()).first;176 auto offsetX = scrollingNode().snapOffsetsInfo().closestSnapOffset(ScrollEventAxis::Horizontal, scrollableAreaSize(), newOffset.x(), deltaX, originalOffset.x()).first; 177 auto offsetY = scrollingNode().snapOffsetsInfo().closestSnapOffset(ScrollEventAxis::Vertical, scrollableAreaSize(), newOffset.y(), deltaY, originalOffset.y()).first; 178 178 179 179 deltaX = (offsetX - originalOffset.x()) * scale; -
trunk/Source/WebCore/platform/ScrollController.cpp
r275354 r277083 117 117 LayoutUnit clampedOffset = std::min(std::max(LayoutUnit(offset / scaleFactor), snapOffsets.first().offset), snapOffsets.last().offset); 118 118 119 unsigned activeIndex = snapState.snapOffsetInfo().closestSnapOffset(axis, clampedOffset, 0).second; 119 LayoutSize viewportSize(m_client.viewportSize().width(), m_client.viewportSize().height()); 120 unsigned activeIndex = snapState.snapOffsetInfo().closestSnapOffset(axis, viewportSize, clampedOffset, 0).second; 120 121 if (activeIndex == activeScrollSnapIndexForAxis(axis)) 121 122 return; … … 138 139 if (originalOffset.hasValue()) 139 140 originalOffsetInLayoutUnits = LayoutUnit(*originalOffset / m_client.pageScaleFactor()); 140 LayoutUnit offset = snapState.snapOffsetInfo().closestSnapOffset(axis, LayoutUnit(destinationOffset / m_client.pageScaleFactor()), velocity, originalOffsetInLayoutUnits).first; 141 LayoutSize viewportSize(m_client.viewportSize().width(), m_client.viewportSize().height()); 142 LayoutUnit offset = snapState.snapOffsetInfo().closestSnapOffset(axis, viewportSize, LayoutUnit(destinationOffset / m_client.pageScaleFactor()), velocity, originalOffsetInLayoutUnits).first; 141 143 return offset * m_client.pageScaleFactor(); 142 144 } -
trunk/Source/WebCore/platform/ScrollSnapAnimatorState.cpp
r274853 r277083 52 52 m_momentumCalculator = ScrollingMomentumCalculator::create(viewportSize, contentSize, initialOffset, initialDelta, initialVelocity); 53 53 auto predictedScrollTarget = m_momentumCalculator->predictedDestinationOffset(); 54 float targetOffsetX = targetOffsetForStartOffset(ScrollEventAxis::Horizontal, contentSize.width() - viewportSize.width(), initialOffset.x(), predictedScrollTarget.width(), pageScale, initialDelta.width(), m_activeSnapIndexX);55 float targetOffsetY = targetOffsetForStartOffset(ScrollEventAxis::Vertical, contentSize.height() - viewportSize.height(), initialOffset.y(), predictedScrollTarget.height(), pageScale, initialDelta.height(), m_activeSnapIndexY);54 float targetOffsetX = targetOffsetForStartOffset(ScrollEventAxis::Horizontal, viewportSize, contentSize.width() - viewportSize.width(), initialOffset.x(), predictedScrollTarget.width(), pageScale, initialDelta.width(), m_activeSnapIndexX); 55 float targetOffsetY = targetOffsetForStartOffset(ScrollEventAxis::Vertical, viewportSize, contentSize.height() - viewportSize.height(), initialOffset.y(), predictedScrollTarget.height(), pageScale, initialDelta.height(), m_activeSnapIndexY); 56 56 m_momentumCalculator->setRetargetedScrollOffset({ targetOffsetX, targetOffsetY }); 57 57 m_startTime = MonotonicTime::now(); … … 92 92 } 93 93 94 float ScrollSnapAnimatorState::targetOffsetForStartOffset(ScrollEventAxis axis, float maxScrollOffset, float startOffset, float predictedOffset, float pageScale, float initialDelta, unsigned& outActiveSnapIndex) const94 float ScrollSnapAnimatorState::targetOffsetForStartOffset(ScrollEventAxis axis, const FloatSize& viewportSize, float maxScrollOffset, float startOffset, float predictedOffset, float pageScale, float initialDelta, unsigned& outActiveSnapIndex) const 95 95 { 96 96 const auto& snapOffsets = m_snapOffsetsInfo.offsetsForAxis(axis); … … 100 100 } 101 101 102 float targetOffset = m_snapOffsetsInfo.closestSnapOffset(axis, Layout Unit(predictedOffset / pageScale), initialDelta, LayoutUnit(startOffset / pageScale)).first;102 float targetOffset = m_snapOffsetsInfo.closestSnapOffset(axis, LayoutSize { viewportSize }, LayoutUnit(predictedOffset / pageScale), initialDelta, LayoutUnit(startOffset / pageScale)).first; 103 103 float minimumTargetOffset = std::max<float>(0, snapOffsets.first().offset); 104 104 float maximumTargetOffset = std::min<float>(maxScrollOffset, snapOffsets.last().offset); … … 112 112 ts.dumpProperty("snap offsets x", state.snapOffsetsForAxis(ScrollEventAxis::Horizontal)); 113 113 ts.dumpProperty("snap offsets y", state.snapOffsetsForAxis(ScrollEventAxis::Vertical)); 114 if (!state.snapOffsetRangesForAxis(ScrollEventAxis::Horizontal).isEmpty())115 ts.dumpProperty("snap offsets ranges x", state.snapOffsetRangesForAxis(ScrollEventAxis::Horizontal));116 if (!state.snapOffsetRangesForAxis(ScrollEventAxis::Vertical).isEmpty())117 ts.dumpProperty("snap offsets ranges y", state.snapOffsetRangesForAxis(ScrollEventAxis::Vertical));118 114 119 115 ts.dumpProperty("active snap index x", state.activeSnapIndexForAxis(ScrollEventAxis::Horizontal)); -
trunk/Source/WebCore/platform/ScrollSnapAnimatorState.h
r275354 r277083 58 58 } 59 59 60 const Vector<ScrollOffsetRange<LayoutUnit>>& snapOffsetRangesForAxis(ScrollEventAxis axis) const61 {62 return axis == ScrollEventAxis::Horizontal ? m_snapOffsetsInfo.horizontalSnapOffsetRanges : m_snapOffsetsInfo.verticalSnapOffsetRanges;63 }64 65 60 const LayoutScrollSnapOffsetsInfo& snapOffsetInfo() const { return m_snapOffsetsInfo; } 66 61 void setSnapOffsetInfo(const LayoutScrollSnapOffsetsInfo& newInfo) { m_snapOffsetsInfo = newInfo; } 67 68 void setSnapOffsetsAndPositionRangesForAxis(ScrollEventAxis axis, const Vector<SnapOffset<LayoutUnit>>& snapOffsets, const Vector<ScrollOffsetRange<LayoutUnit>>& snapOffsetRanges)69 {70 if (axis == ScrollEventAxis::Horizontal) {71 m_snapOffsetsInfo.horizontalSnapOffsets = snapOffsets;72 m_snapOffsetsInfo.horizontalSnapOffsetRanges = snapOffsetRanges;73 } else {74 m_snapOffsetsInfo.verticalSnapOffsets = snapOffsets;75 m_snapOffsetsInfo.verticalSnapOffsetRanges = snapOffsetRanges;76 }77 }78 62 79 63 ScrollSnapState currentState() const { return m_currentState; } … … 101 85 102 86 private: 103 float targetOffsetForStartOffset(ScrollEventAxis, float maxScrollOffset, float startOffset, float predictedOffset, float pageScale, float initialDelta, unsigned& outActiveSnapIndex) const;87 float targetOffsetForStartOffset(ScrollEventAxis, const FloatSize& viewportSize, float maxScrollOffset, float startOffset, float predictedOffset, float pageScale, float initialDelta, unsigned& outActiveSnapIndex) const; 104 88 void teardownAnimationForState(ScrollSnapState); 105 89 void setupAnimationForState(ScrollSnapState, const FloatSize& contentSize, const FloatSize& viewportSize, float pageScale, const FloatPoint& initialOffset, const FloatSize& initialVelocity, const FloatSize& initialDelta); -
trunk/Source/WebKit/ChangeLog
r277055 r277083 1 2021-05-06 Martin Robinson <mrobinson@webkit.org> 2 3 [css-scroll-snap] Compute proximity information while snapping 4 https://bugs.webkit.org/show_bug.cgi?id=224326 5 6 Reviewed by Simon Fraser. 7 8 * Shared/RemoteLayerTree/RemoteScrollingCoordinatorTransaction.cpp: 9 (ArgumentCoder<SnapOffset<float>>::encode): Add the snap area index to the encoded arguments. 10 (ArgumentCoder<SnapOffset<float>>::decode): Add the snap area index to the decoded arguments. 11 (ArgumentCoder<FloatScrollSnapOffsetsInfo>::encode): No longer encode ranges, but encode snap areas. 12 (ArgumentCoder<FloatScrollSnapOffsetsInfo>::decode): Ditto for decode. 13 * Shared/WebCoreArgumentCoders.cpp: Remove code dealing with scroll offset ranges. 14 * Shared/WebCoreArgumentCoders.h: Ditto. 15 * UIProcess/RemoteLayerTree/ios/RemoteScrollingCoordinatorProxyIOS.mm: 16 (WebKit::RemoteScrollingCoordinatorProxy::closestSnapOffsetForMainFrameScrolling const): Pass 17 in viewport size to closestSnapOffset which is necessary for calculating proximity. 18 * UIProcess/RemoteLayerTree/ios/ScrollingTreeScrollingNodeDelegateIOS.mm: 19 (-[WKScrollingNodeScrollViewDelegate scrollViewWillEndDragging:withVelocity:targetContentOffset:]): Ditto. 20 1 21 2021-05-05 Kate Cheney <katherine_cheney@apple.com> 2 22 -
trunk/Source/WebKit/Shared/RemoteLayerTree/RemoteScrollingCoordinatorTransaction.cpp
r275354 r277083 533 533 encoder << offset.offset; 534 534 encoder << offset.stop; 535 encoder << offset.snapAreaIndex; 535 536 } 536 537 … … 540 541 return false; 541 542 if (!decoder.decode(offset.stop)) 543 return false; 544 if (!decoder.decode(offset.snapAreaIndex)) 542 545 return false; 543 546 return true; … … 549 552 encoder << info.horizontalSnapOffsets; 550 553 encoder << info.verticalSnapOffsets; 551 encoder << info.horizontalSnapOffsetRanges; 552 encoder << info.verticalSnapOffsetRanges; 554 encoder << info.snapAreas; 553 555 } 554 556 … … 559 561 if (!decoder.decode(info.verticalSnapOffsets)) 560 562 return false; 561 if (!decoder.decode(info.horizontalSnapOffsetRanges)) 562 return false; 563 if (!decoder.decode(info.verticalSnapOffsetRanges)) 563 if (!decoder.decode(info.snapAreas)) 564 564 return false; 565 565 return true; -
trunk/Source/WebKit/Shared/WebCoreArgumentCoders.cpp
r276510 r277083 2841 2841 #endif 2842 2842 2843 #if ENABLE(CSS_SCROLL_SNAP)2844 2845 void ArgumentCoder<ScrollOffsetRange<float>>::encode(Encoder& encoder, const ScrollOffsetRange<float>& range)2846 {2847 encoder << range.start;2848 encoder << range.end;2849 }2850 2851 auto ArgumentCoder<ScrollOffsetRange<float>>::decode(Decoder& decoder) -> Optional<WebCore::ScrollOffsetRange<float>>2852 {2853 WebCore::ScrollOffsetRange<float> range;2854 float start;2855 if (!decoder.decode(start))2856 return WTF::nullopt;2857 2858 float end;2859 if (!decoder.decode(end))2860 return WTF::nullopt;2861 2862 range.start = start;2863 range.end = end;2864 return range;2865 }2866 2867 #endif2868 2869 2843 void ArgumentCoder<MediaSelectionOption>::encode(Encoder& encoder, const MediaSelectionOption& option) 2870 2844 { -
trunk/Source/WebKit/Shared/WebCoreArgumentCoders.h
r276510 r277083 743 743 #endif 744 744 745 #if ENABLE(CSS_SCROLL_SNAP)746 747 template<> struct ArgumentCoder<WebCore::ScrollOffsetRange<float>> {748 static void encode(Encoder&, const WebCore::ScrollOffsetRange<float>&);749 static Optional<WebCore::ScrollOffsetRange<float>> decode(Decoder&);750 };751 752 #endif753 754 745 template<> struct ArgumentCoder<WebCore::MediaSelectionOption> { 755 746 static void encode(Encoder&, const WebCore::MediaSelectionOption&); -
trunk/Source/WebKit/UIProcess/RemoteLayerTree/ios/RemoteScrollingCoordinatorProxyIOS.mm
r276523 r277083 231 231 float scaledScrollDestination = scrollDestination / m_webPageProxy.displayedContentScale(); 232 232 float rawClosestSnapOffset; 233 std::tie(rawClosestSnapOffset, currentIndex) = snapOffsetsInfo.closestSnapOffset(axis, scaledScrollDestination, velocity);233 std::tie(rawClosestSnapOffset, currentIndex) = snapOffsetsInfo.closestSnapOffset(axis, rootScrollingNode->layoutViewport().size(), scaledScrollDestination, velocity); 234 234 return rawClosestSnapOffset * m_webPageProxy.displayedContentScale(); 235 235 } -
trunk/Source/WebKit/UIProcess/RemoteLayerTree/ios/ScrollingTreeScrollingNodeDelegateIOS.mm
r272019 r277083 107 107 unsigned originalVerticalSnapPosition = _scrollingTreeNodeDelegate->scrollingNode().currentVerticalSnapPointIndex(); 108 108 109 WebCore::FloatSize viewportSize(static_cast<float>(CGRectGetWidth([scrollView bounds])), static_cast<float>(CGRectGetHeight([scrollView bounds]))); 109 110 const auto& snapOffsetsInfo = _scrollingTreeNodeDelegate->scrollingNode().snapOffsetsInfo(); 110 111 if (!snapOffsetsInfo.horizontalSnapOffsets.isEmpty()) { 111 auto [potentialSnapPosition, index] = snapOffsetsInfo.closestSnapOffset(WebCore::ScrollEventAxis::Horizontal, horizontalTarget, velocity.x);112 auto [potentialSnapPosition, index] = snapOffsetsInfo.closestSnapOffset(WebCore::ScrollEventAxis::Horizontal, viewportSize, horizontalTarget, velocity.x); 112 113 _scrollingTreeNodeDelegate->scrollingNode().setCurrentHorizontalSnapPointIndex(index); 113 114 if (horizontalTarget >= 0 && horizontalTarget <= scrollView.contentSize.width) … … 116 117 117 118 if (!snapOffsetsInfo.verticalSnapOffsets.isEmpty()) { 118 auto [potentialSnapPosition, index] = snapOffsetsInfo.closestSnapOffset(WebCore::ScrollEventAxis::Vertical, v erticalTarget, velocity.x);119 auto [potentialSnapPosition, index] = snapOffsetsInfo.closestSnapOffset(WebCore::ScrollEventAxis::Vertical, viewportSize, verticalTarget, velocity.x); 119 120 _scrollingTreeNodeDelegate->scrollingNode().setCurrentVerticalSnapPointIndex(index); 120 121 if (verticalTarget >= 0 && verticalTarget <= scrollView.contentSize.height)
Note:
See TracChangeset
for help on using the changeset viewer.