Browse Source

Improve scroll snapping with mouse wheel (#14562)

* improve scroll snapping

* remove debug code, ensure snappoints are in range
pull/16303/head
Emmanuel Hansen 3 years ago
committed by GitHub
parent
commit
e1203becc6
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 83
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs

83
src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs

@ -634,8 +634,6 @@ namespace Avalonia.Controls.Presenters
offset = new Vector(offset.X + xDistance, offset.Y + yDistance); offset = new Vector(offset.X + xDistance, offset.Y + yDistance);
System.Diagnostics.Debug.WriteLine($"{offset}");
_scrollGestureSnapPoints.Add(e.Id, SnapOffset(offset)); _scrollGestureSnapPoints.Add(e.Id, SnapOffset(offset));
double GetDistance(double speed) double GetDistance(double speed)
@ -692,7 +690,7 @@ namespace Avalonia.Controls.Presenters
x = Math.Min(x, Extent.Width - Viewport.Width); x = Math.Min(x, Extent.Width - Viewport.Width);
} }
Vector newOffset = SnapOffset(new Vector(x, y)); Vector newOffset = SnapOffset(new Vector(x, y), delta, true);
bool offsetChanged = newOffset != Offset; bool offsetChanged = newOffset != Offset;
SetCurrentValue(OffsetProperty, newOffset); SetCurrentValue(OffsetProperty, newOffset);
@ -930,67 +928,64 @@ namespace Avalonia.Controls.Presenters
} }
} }
private Vector SnapOffset(Vector offset) private Vector SnapOffset(Vector offset, Vector direction = default, bool snapToNext = false)
{ {
var scrollable = GetScrollSnapPointsInfo(Content); var scrollable = GetScrollSnapPointsInfo(Content);
if(scrollable is null) if (scrollable is null || (VerticalSnapPointsType == SnapPointsType.None && HorizontalSnapPointsType == SnapPointsType.None))
return offset; return offset;
var diff = GetAlignedDiff(); var diff = GetAlignmentDiff();
if (VerticalSnapPointsType != SnapPointsType.None) if (VerticalSnapPointsType != SnapPointsType.None && (_areVerticalSnapPointsRegular || _verticalSnapPoints?.Count > 0) && (!snapToNext || snapToNext && direction.Y != 0))
{ {
offset = new Vector(offset.X, offset.Y + diff.Y); var estimatedOffset = new Vector(offset.X, offset.Y + diff.Y);
double nearestSnapPoint = offset.Y; double previousSnapPoint = 0, nextSnapPoint = 0, midPoint = 0;
if (_areVerticalSnapPointsRegular) if (_areVerticalSnapPointsRegular)
{ {
var minSnapPoint = (int)(offset.Y / _verticalSnapPoint) * _verticalSnapPoint + _verticalSnapPointOffset; previousSnapPoint = (int)(estimatedOffset.Y / _verticalSnapPoint) * _verticalSnapPoint + _verticalSnapPointOffset;
var maxSnapPoint = minSnapPoint + _verticalSnapPoint; nextSnapPoint = previousSnapPoint + _verticalSnapPoint;
var midPoint = (minSnapPoint + maxSnapPoint) / 2; midPoint = (previousSnapPoint + nextSnapPoint) / 2;
nearestSnapPoint = offset.Y < midPoint ? minSnapPoint : maxSnapPoint;
} }
else if (_verticalSnapPoints != null && _verticalSnapPoints.Count > 0) else if (_verticalSnapPoints?.Count > 0)
{ {
var higherSnapPoint = FindNearestSnapPoint(_verticalSnapPoints, offset.Y, out var lowerSnapPoint); (previousSnapPoint, nextSnapPoint) = FindNearestSnapPoint(_verticalSnapPoints, estimatedOffset.Y);
var midPoint = (lowerSnapPoint + higherSnapPoint) / 2; midPoint = (previousSnapPoint + nextSnapPoint) / 2;
nearestSnapPoint = offset.Y < midPoint ? lowerSnapPoint : higherSnapPoint;
} }
var nearestSnapPoint = snapToNext ? (direction.Y > 0 ? previousSnapPoint : nextSnapPoint ) :
estimatedOffset.Y < midPoint ? previousSnapPoint : nextSnapPoint;
offset = new Vector(offset.X, nearestSnapPoint - diff.Y); offset = new Vector(offset.X, nearestSnapPoint - diff.Y);
} }
if (HorizontalSnapPointsType != SnapPointsType.None) if (HorizontalSnapPointsType != SnapPointsType.None && (_areHorizontalSnapPointsRegular || _horizontalSnapPoints?.Count > 0) && (!snapToNext || snapToNext && direction.X != 0))
{ {
offset = new Vector(offset.X + diff.X, offset.Y); var estimatedOffset = new Vector(offset.X + diff.X, offset.Y);
double nearestSnapPoint = offset.X; double previousSnapPoint = 0, nextSnapPoint = 0, midPoint = 0;
if (_areHorizontalSnapPointsRegular) if (_areHorizontalSnapPointsRegular)
{ {
var minSnapPoint = (int)(offset.X / _horizontalSnapPoint) * _horizontalSnapPoint + _horizontalSnapPointOffset; previousSnapPoint = (int)(estimatedOffset.X / _horizontalSnapPoint) * _horizontalSnapPoint + _horizontalSnapPointOffset;
var maxSnapPoint = minSnapPoint + _horizontalSnapPoint; nextSnapPoint = previousSnapPoint + _horizontalSnapPoint;
var midPoint = (minSnapPoint + maxSnapPoint) / 2; midPoint = (previousSnapPoint + nextSnapPoint) / 2;
nearestSnapPoint = offset.X < midPoint ? minSnapPoint : maxSnapPoint;
} }
else if (_horizontalSnapPoints != null && _horizontalSnapPoints.Count > 0) else if (_horizontalSnapPoints?.Count > 0)
{ {
var higherSnapPoint = FindNearestSnapPoint(_horizontalSnapPoints, offset.X, out var lowerSnapPoint); (previousSnapPoint, nextSnapPoint) = FindNearestSnapPoint(_horizontalSnapPoints, estimatedOffset.X);
var midPoint = (lowerSnapPoint + higherSnapPoint) / 2; midPoint = (previousSnapPoint + nextSnapPoint) / 2;
nearestSnapPoint = offset.X < midPoint ? lowerSnapPoint : higherSnapPoint;
} }
offset = new Vector(nearestSnapPoint - diff.X, offset.Y); var nearestSnapPoint = snapToNext ? (direction.X > 0 ? previousSnapPoint : nextSnapPoint) :
estimatedOffset.X < midPoint ? previousSnapPoint : nextSnapPoint;
offset = new Vector(nearestSnapPoint - diff.X, offset.Y);
} }
Vector GetAlignedDiff() Vector GetAlignmentDiff()
{ {
var vector = offset; var vector = default(Vector);
switch (VerticalSnapPointsAlignment) switch (VerticalSnapPointsAlignment)
{ {
@ -1010,31 +1005,33 @@ namespace Avalonia.Controls.Presenters
case SnapPointsAlignment.Far: case SnapPointsAlignment.Far:
vector += new Vector(Viewport.Width, 0); vector += new Vector(Viewport.Width, 0);
break; break;
} }
return vector - offset; return vector;
} }
return offset; return offset;
} }
private static double FindNearestSnapPoint(IReadOnlyList<double> snapPoints, double value, out double lowerSnapPoint) private static (double previous, double next) FindNearestSnapPoint(IReadOnlyList<double> snapPoints, double value)
{ {
var point = snapPoints.BinarySearch(value, Comparer<double>.Default); var point = snapPoints.BinarySearch(value, Comparer<double>.Default);
double previousSnapPoint, nextSnapPoint;
if (point < 0) if (point < 0)
{ {
point = ~point; point = ~point;
lowerSnapPoint = snapPoints[Math.Max(0, point - 1)]; previousSnapPoint = snapPoints[Math.Max(0, point - 1)];
nextSnapPoint = point >= snapPoints.Count ? snapPoints.Last() : snapPoints[Math.Max(0, point)];
} }
else else
{ {
lowerSnapPoint = snapPoints[point]; previousSnapPoint = nextSnapPoint = snapPoints[Math.Max(0, point)];
point += 1;
} }
return snapPoints[Math.Min(point, snapPoints.Count - 1)];
return (previousSnapPoint, nextSnapPoint);
} }
private IScrollSnapPointsInfo? GetScrollSnapPointsInfo(object? content) private IScrollSnapPointsInfo? GetScrollSnapPointsInfo(object? content)

Loading…
Cancel
Save