|
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|
using Avalonia.Reactive;
|
|
using Avalonia.Reactive;
|
|
using Avalonia.Controls.Primitives;
|
|
using Avalonia.Controls.Primitives;
|
|
using Avalonia.Input;
|
|
using Avalonia.Input;
|
|
|
|
+using Avalonia.Input.GestureRecognizers;
|
|
using Avalonia.Utilities;
|
|
using Avalonia.Utilities;
|
|
using Avalonia.VisualTree;
|
|
using Avalonia.VisualTree;
|
|
|
|
|
|
@@ -14,6 +15,7 @@ namespace Avalonia.Controls.Presenters
|
|
public class ScrollContentPresenter : ContentPresenter, IPresenter, IScrollable, IScrollAnchorProvider
|
|
public class ScrollContentPresenter : ContentPresenter, IPresenter, IScrollable, IScrollAnchorProvider
|
|
{
|
|
{
|
|
private const double EdgeDetectionTolerance = 0.1;
|
|
private const double EdgeDetectionTolerance = 0.1;
|
|
|
|
+ private const int ProximityPoints = 10;
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
/// Defines the <see cref="CanHorizontallyScroll"/> property.
|
|
/// Defines the <see cref="CanHorizontallyScroll"/> property.
|
|
@@ -57,6 +59,30 @@ namespace Avalonia.Controls.Presenters
|
|
o => o.Viewport,
|
|
o => o.Viewport,
|
|
(o, v) => o.Viewport = v);
|
|
(o, v) => o.Viewport = v);
|
|
|
|
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// Defines the <see cref="HorizontalSnapPointsType"/> property.
|
|
|
|
+ /// </summary>
|
|
|
|
+ public static readonly StyledProperty<SnapPointsType> HorizontalSnapPointsTypeProperty =
|
|
|
|
+ ScrollViewer.HorizontalSnapPointsTypeProperty.AddOwner<ScrollContentPresenter>();
|
|
|
|
+
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// Defines the <see cref="VerticalSnapPointsType"/> property.
|
|
|
|
+ /// </summary>
|
|
|
|
+ public static readonly StyledProperty<SnapPointsType> VerticalSnapPointsTypeProperty =
|
|
|
|
+ ScrollViewer.VerticalSnapPointsTypeProperty.AddOwner<ScrollContentPresenter>();
|
|
|
|
+
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// Defines the <see cref="HorizontalSnapPointsAlignment"/> property.
|
|
|
|
+ /// </summary>
|
|
|
|
+ public static readonly StyledProperty<SnapPointsAlignment> HorizontalSnapPointsAlignmentProperty =
|
|
|
|
+ ScrollViewer.HorizontalSnapPointsAlignmentProperty.AddOwner<ScrollContentPresenter>();
|
|
|
|
+
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// Defines the <see cref="VerticalSnapPointsAlignment"/> property.
|
|
|
|
+ /// </summary>
|
|
|
|
+ public static readonly StyledProperty<SnapPointsAlignment> VerticalSnapPointsAlignmentProperty =
|
|
|
|
+ ScrollViewer.VerticalSnapPointsAlignmentProperty.AddOwner<ScrollContentPresenter>();
|
|
|
|
+
|
|
/// <summary>
|
|
/// <summary>
|
|
/// Defines the <see cref="IsScrollChainingEnabled"/> property.
|
|
/// Defines the <see cref="IsScrollChainingEnabled"/> property.
|
|
/// </summary>
|
|
/// </summary>
|
|
@@ -71,10 +97,19 @@ namespace Avalonia.Controls.Presenters
|
|
private IDisposable? _logicalScrollSubscription;
|
|
private IDisposable? _logicalScrollSubscription;
|
|
private Size _viewport;
|
|
private Size _viewport;
|
|
private Dictionary<int, Vector>? _activeLogicalGestureScrolls;
|
|
private Dictionary<int, Vector>? _activeLogicalGestureScrolls;
|
|
|
|
+ private Dictionary<int, Vector>? _scrollGestureSnapPoints;
|
|
private List<Control>? _anchorCandidates;
|
|
private List<Control>? _anchorCandidates;
|
|
private Control? _anchorElement;
|
|
private Control? _anchorElement;
|
|
private Rect _anchorElementBounds;
|
|
private Rect _anchorElementBounds;
|
|
private bool _isAnchorElementDirty;
|
|
private bool _isAnchorElementDirty;
|
|
|
|
+ private bool _areVerticalSnapPointsRegular;
|
|
|
|
+ private bool _areHorizontalSnapPointsRegular;
|
|
|
|
+ private IReadOnlyList<double>? _horizontalSnapPoints;
|
|
|
|
+ private double _horizontalSnapPoint;
|
|
|
|
+ private IReadOnlyList<double>? _verticalSnapPoints;
|
|
|
|
+ private double _verticalSnapPoint;
|
|
|
|
+ private double _verticalSnapPointOffset;
|
|
|
|
+ private double _horizontalSnapPointOffset;
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
/// Initializes static members of the <see cref="ScrollContentPresenter"/> class.
|
|
/// Initializes static members of the <see cref="ScrollContentPresenter"/> class.
|
|
@@ -93,6 +128,7 @@ namespace Avalonia.Controls.Presenters
|
|
AddHandler(RequestBringIntoViewEvent, BringIntoViewRequested);
|
|
AddHandler(RequestBringIntoViewEvent, BringIntoViewRequested);
|
|
AddHandler(Gestures.ScrollGestureEvent, OnScrollGesture);
|
|
AddHandler(Gestures.ScrollGestureEvent, OnScrollGesture);
|
|
AddHandler(Gestures.ScrollGestureEndedEvent, OnScrollGestureEnded);
|
|
AddHandler(Gestures.ScrollGestureEndedEvent, OnScrollGestureEnded);
|
|
|
|
+ AddHandler(Gestures.ScrollGestureInertiaStartingEvent, OnScrollGestureInertiaStartingEnded);
|
|
|
|
|
|
this.GetObservable(ChildProperty).Subscribe(UpdateScrollableSubscription);
|
|
this.GetObservable(ChildProperty).Subscribe(UpdateScrollableSubscription);
|
|
}
|
|
}
|
|
@@ -142,6 +178,42 @@ namespace Avalonia.Controls.Presenters
|
|
private set { SetAndRaise(ViewportProperty, ref _viewport, value); }
|
|
private set { SetAndRaise(ViewportProperty, ref _viewport, value); }
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// Gets or sets how scroll gesture reacts to the snap points along the horizontal axis.
|
|
|
|
+ /// </summary>
|
|
|
|
+ public SnapPointsType HorizontalSnapPointsType
|
|
|
|
+ {
|
|
|
|
+ get => GetValue(HorizontalSnapPointsTypeProperty);
|
|
|
|
+ set => SetValue(HorizontalSnapPointsTypeProperty, value);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// Gets or sets how scroll gesture reacts to the snap points along the vertical axis.
|
|
|
|
+ /// </summary>
|
|
|
|
+ public SnapPointsType VerticalSnapPointsType
|
|
|
|
+ {
|
|
|
|
+ get => GetValue(VerticalSnapPointsTypeProperty);
|
|
|
|
+ set => SetValue(VerticalSnapPointsTypeProperty, value);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// Gets or sets how the existing snap points are horizontally aligned versus the initial viewport.
|
|
|
|
+ /// </summary>
|
|
|
|
+ public SnapPointsAlignment HorizontalSnapPointsAlignment
|
|
|
|
+ {
|
|
|
|
+ get => GetValue(HorizontalSnapPointsAlignmentProperty);
|
|
|
|
+ set => SetValue(HorizontalSnapPointsAlignmentProperty, value);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// Gets or sets how the existing snap points are vertically aligned versus the initial viewport.
|
|
|
|
+ /// </summary>
|
|
|
|
+ public SnapPointsAlignment VerticalSnapPointsAlignment
|
|
|
|
+ {
|
|
|
|
+ get => GetValue(VerticalSnapPointsAlignmentProperty);
|
|
|
|
+ set => SetValue(VerticalSnapPointsAlignmentProperty, value);
|
|
|
|
+ }
|
|
|
|
+
|
|
/// <summary>
|
|
/// <summary>
|
|
/// Gets or sets if scroll chaining is enabled. The default value is true.
|
|
/// Gets or sets if scroll chaining is enabled. The default value is true.
|
|
/// </summary>
|
|
/// </summary>
|
|
@@ -424,6 +496,25 @@ namespace Avalonia.Controls.Presenters
|
|
}
|
|
}
|
|
|
|
|
|
Vector newOffset = new Vector(x, y);
|
|
Vector newOffset = new Vector(x, y);
|
|
|
|
+
|
|
|
|
+ if (_scrollGestureSnapPoints?.TryGetValue(e.Id, out var snapPoint) == true)
|
|
|
|
+ {
|
|
|
|
+ double xOffset = x;
|
|
|
|
+ double yOffset = y;
|
|
|
|
+
|
|
|
|
+ if (HorizontalSnapPointsType != SnapPointsType.None)
|
|
|
|
+ {
|
|
|
|
+ xOffset = delta.X < 0 ? Math.Max(snapPoint.X, newOffset.X) : Math.Min(snapPoint.X, newOffset.X);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (VerticalSnapPointsType != SnapPointsType.None)
|
|
|
|
+ {
|
|
|
|
+ yOffset = delta.Y < 0 ? Math.Max(snapPoint.Y, newOffset.Y) : Math.Min(snapPoint.Y, newOffset.Y);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ newOffset = new Vector(xOffset, yOffset);
|
|
|
|
+ }
|
|
|
|
+
|
|
bool offsetChanged = newOffset != Offset;
|
|
bool offsetChanged = newOffset != Offset;
|
|
Offset = newOffset;
|
|
Offset = newOffset;
|
|
|
|
|
|
@@ -434,7 +525,65 @@ namespace Avalonia.Controls.Presenters
|
|
}
|
|
}
|
|
|
|
|
|
private void OnScrollGestureEnded(object? sender, ScrollGestureEndedEventArgs e)
|
|
private void OnScrollGestureEnded(object? sender, ScrollGestureEndedEventArgs e)
|
|
- => _activeLogicalGestureScrolls?.Remove(e.Id);
|
|
|
|
|
|
+ {
|
|
|
|
+ _activeLogicalGestureScrolls?.Remove(e.Id);
|
|
|
|
+ _scrollGestureSnapPoints?.Remove(e.Id);
|
|
|
|
+
|
|
|
|
+ Offset = SnapOffset(Offset);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private void OnScrollGestureInertiaStartingEnded(object? sender, ScrollGestureInertiaStartingEventArgs e)
|
|
|
|
+ {
|
|
|
|
+ if (Content is not IScrollSnapPointsInfo)
|
|
|
|
+ return;
|
|
|
|
+
|
|
|
|
+ if (_scrollGestureSnapPoints == null)
|
|
|
|
+ _scrollGestureSnapPoints = new Dictionary<int, Vector>();
|
|
|
|
+
|
|
|
|
+ var offset = Offset;
|
|
|
|
+
|
|
|
|
+ if (HorizontalSnapPointsType != SnapPointsType.None && VerticalSnapPointsType != SnapPointsType.None)
|
|
|
|
+ {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ double xDistance = 0;
|
|
|
|
+ double yDistance = 0;
|
|
|
|
+
|
|
|
|
+ if (HorizontalSnapPointsType != SnapPointsType.None)
|
|
|
|
+ {
|
|
|
|
+ xDistance = HorizontalSnapPointsType == SnapPointsType.Mandatory ? GetDistance(e.Inertia.X) : 0;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (VerticalSnapPointsType != SnapPointsType.None)
|
|
|
|
+ {
|
|
|
|
+ yDistance = VerticalSnapPointsType == SnapPointsType.Mandatory ? GetDistance(e.Inertia.Y) : 0;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ offset = new Vector(offset.X + xDistance, offset.Y + yDistance);
|
|
|
|
+
|
|
|
|
+ System.Diagnostics.Debug.WriteLine($"{offset}");
|
|
|
|
+
|
|
|
|
+ _scrollGestureSnapPoints.Add(e.Id, SnapOffset(offset));
|
|
|
|
+
|
|
|
|
+ double GetDistance(double speed)
|
|
|
|
+ {
|
|
|
|
+ var time = Math.Log(ScrollGestureRecognizer.InertialScrollSpeedEnd / Math.Abs(speed)) / Math.Log(ScrollGestureRecognizer.InertialResistance);
|
|
|
|
+
|
|
|
|
+ double timeElapsed = 0, distance = 0, step = 0;
|
|
|
|
+
|
|
|
|
+ while (timeElapsed <= time)
|
|
|
|
+ {
|
|
|
|
+ double s = speed * Math.Pow(ScrollGestureRecognizer.InertialResistance, timeElapsed);
|
|
|
|
+ distance += (s * step);
|
|
|
|
+
|
|
|
|
+ timeElapsed += 0.016f;
|
|
|
|
+ step = 0.016f;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return distance;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
/// <inheritdoc/>
|
|
/// <inheritdoc/>
|
|
protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
|
|
protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
|
|
@@ -458,6 +607,30 @@ namespace Avalonia.Controls.Presenters
|
|
if (Extent.Height > Viewport.Height)
|
|
if (Extent.Height > Viewport.Height)
|
|
{
|
|
{
|
|
double height = isLogical ? scrollable!.ScrollSize.Height : 50;
|
|
double height = isLogical ? scrollable!.ScrollSize.Height : 50;
|
|
|
|
+ if(VerticalSnapPointsType == SnapPointsType.MandatorySingle && Content is IScrollSnapPointsInfo)
|
|
|
|
+ {
|
|
|
|
+ if(_areVerticalSnapPointsRegular)
|
|
|
|
+ {
|
|
|
|
+ height = _verticalSnapPoint;
|
|
|
|
+ }
|
|
|
|
+ else if(_verticalSnapPoints != null)
|
|
|
|
+ {
|
|
|
|
+ double yOffset = Offset.Y;
|
|
|
|
+ switch (VerticalSnapPointsAlignment)
|
|
|
|
+ {
|
|
|
|
+ case SnapPointsAlignment.Center:
|
|
|
|
+ yOffset += Viewport.Height / 2;
|
|
|
|
+ break;
|
|
|
|
+ case SnapPointsAlignment.Far:
|
|
|
|
+ yOffset += Viewport.Height;
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var snapPoint = FindNearestSnapPoint(_verticalSnapPoints, yOffset, out var lowerSnapPoint);
|
|
|
|
+
|
|
|
|
+ height = snapPoint - lowerSnapPoint;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
y += -delta.Y * height;
|
|
y += -delta.Y * height;
|
|
y = Math.Max(y, 0);
|
|
y = Math.Max(y, 0);
|
|
y = Math.Min(y, Extent.Height - Viewport.Height);
|
|
y = Math.Min(y, Extent.Height - Viewport.Height);
|
|
@@ -466,12 +639,37 @@ namespace Avalonia.Controls.Presenters
|
|
if (Extent.Width > Viewport.Width)
|
|
if (Extent.Width > Viewport.Width)
|
|
{
|
|
{
|
|
double width = isLogical ? scrollable!.ScrollSize.Width : 50;
|
|
double width = isLogical ? scrollable!.ScrollSize.Width : 50;
|
|
|
|
+ if (HorizontalSnapPointsType == SnapPointsType.MandatorySingle && Content is IScrollSnapPointsInfo)
|
|
|
|
+ {
|
|
|
|
+ if (_areHorizontalSnapPointsRegular)
|
|
|
|
+ {
|
|
|
|
+ width = _horizontalSnapPoint;
|
|
|
|
+ }
|
|
|
|
+ else if(_horizontalSnapPoints != null)
|
|
|
|
+ {
|
|
|
|
+ double xOffset = Offset.X;
|
|
|
|
+ switch (VerticalSnapPointsAlignment)
|
|
|
|
+ {
|
|
|
|
+ case SnapPointsAlignment.Center:
|
|
|
|
+ xOffset += Viewport.Width / 2;
|
|
|
|
+ break;
|
|
|
|
+ case SnapPointsAlignment.Far:
|
|
|
|
+ xOffset += Viewport.Width;
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var snapPoint = FindNearestSnapPoint(_horizontalSnapPoints, xOffset, out var lowerSnapPoint);
|
|
|
|
+
|
|
|
|
+ width = snapPoint - lowerSnapPoint;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
x += -delta.X * width;
|
|
x += -delta.X * width;
|
|
x = Math.Max(x, 0);
|
|
x = Math.Max(x, 0);
|
|
x = Math.Min(x, Extent.Width - Viewport.Width);
|
|
x = Math.Min(x, Extent.Width - Viewport.Width);
|
|
}
|
|
}
|
|
|
|
|
|
- Vector newOffset = new Vector(x, y);
|
|
|
|
|
|
+ Vector newOffset = SnapOffset(new Vector(x, y));
|
|
|
|
+
|
|
bool offsetChanged = newOffset != Offset;
|
|
bool offsetChanged = newOffset != Offset;
|
|
Offset = newOffset;
|
|
Offset = newOffset;
|
|
|
|
|
|
@@ -485,10 +683,36 @@ namespace Avalonia.Controls.Presenters
|
|
{
|
|
{
|
|
InvalidateArrange();
|
|
InvalidateArrange();
|
|
}
|
|
}
|
|
|
|
+ else if (change.Property == ContentProperty)
|
|
|
|
+ {
|
|
|
|
+ if (change.OldValue is IScrollSnapPointsInfo oldSnapPointsInfo)
|
|
|
|
+ {
|
|
|
|
+ oldSnapPointsInfo.VerticalSnapPointsChanged -= ScrollSnapPointsInfoSnapPointsChanged;
|
|
|
|
+ oldSnapPointsInfo.HorizontalSnapPointsChanged += ScrollSnapPointsInfoSnapPointsChanged;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (Content is IScrollSnapPointsInfo scrollSnapPointsInfo)
|
|
|
|
+ {
|
|
|
|
+ scrollSnapPointsInfo.VerticalSnapPointsChanged += ScrollSnapPointsInfoSnapPointsChanged;
|
|
|
|
+ scrollSnapPointsInfo.HorizontalSnapPointsChanged += ScrollSnapPointsInfoSnapPointsChanged;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ UpdateSnapPoints();
|
|
|
|
+ }
|
|
|
|
+ else if (change.Property == HorizontalSnapPointsAlignmentProperty ||
|
|
|
|
+ change.Property == VerticalSnapPointsAlignmentProperty)
|
|
|
|
+ {
|
|
|
|
+ UpdateSnapPoints();
|
|
|
|
+ }
|
|
|
|
|
|
base.OnPropertyChanged(change);
|
|
base.OnPropertyChanged(change);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ private void ScrollSnapPointsInfoSnapPointsChanged(object? sender, Interactivity.RoutedEventArgs e)
|
|
|
|
+ {
|
|
|
|
+ UpdateSnapPoints();
|
|
|
|
+ }
|
|
|
|
+
|
|
private void BringIntoViewRequested(object? sender, RequestBringIntoViewEventArgs e)
|
|
private void BringIntoViewRequested(object? sender, RequestBringIntoViewEventArgs e)
|
|
{
|
|
{
|
|
if (e.TargetObject is not null)
|
|
if (e.TargetObject is not null)
|
|
@@ -635,5 +859,145 @@ namespace Avalonia.Controls.Presenters
|
|
bounds = p.HasValue ? new Rect(p.Value, control.Bounds.Size) : default;
|
|
bounds = p.HasValue ? new Rect(p.Value, control.Bounds.Size) : default;
|
|
return p.HasValue;
|
|
return p.HasValue;
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ private void UpdateSnapPoints()
|
|
|
|
+ {
|
|
|
|
+ if (Content is IScrollSnapPointsInfo scrollSnapPointsInfo)
|
|
|
|
+ {
|
|
|
|
+ _areVerticalSnapPointsRegular = scrollSnapPointsInfo.AreVerticalSnapPointsRegular;
|
|
|
|
+ _areHorizontalSnapPointsRegular = scrollSnapPointsInfo.AreHorizontalSnapPointsRegular;
|
|
|
|
+
|
|
|
|
+ if (!_areVerticalSnapPointsRegular)
|
|
|
|
+ {
|
|
|
|
+ _verticalSnapPoints = scrollSnapPointsInfo.GetIrregularSnapPoints(Layout.Orientation.Vertical, VerticalSnapPointsAlignment);
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ _verticalSnapPoints = new List<double>();
|
|
|
|
+ _verticalSnapPoint = scrollSnapPointsInfo.GetRegularSnapPoints(Layout.Orientation.Vertical, VerticalSnapPointsAlignment, out _verticalSnapPointOffset);
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!_areHorizontalSnapPointsRegular)
|
|
|
|
+ {
|
|
|
|
+ _horizontalSnapPoints = scrollSnapPointsInfo.GetIrregularSnapPoints(Layout.Orientation.Horizontal, HorizontalSnapPointsAlignment);
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ _horizontalSnapPoints = new List<double>();
|
|
|
|
+ _horizontalSnapPoint = scrollSnapPointsInfo.GetRegularSnapPoints(Layout.Orientation.Vertical, VerticalSnapPointsAlignment, out _horizontalSnapPointOffset);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ _horizontalSnapPoints = new List<double>();
|
|
|
|
+ _verticalSnapPoints = new List<double>();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private Vector SnapOffset(Vector offset)
|
|
|
|
+ {
|
|
|
|
+ if(Content is not IScrollSnapPointsInfo)
|
|
|
|
+ return offset;
|
|
|
|
+
|
|
|
|
+ var diff = GetAlignedDiff();
|
|
|
|
+
|
|
|
|
+ if (VerticalSnapPointsType != SnapPointsType.None)
|
|
|
|
+ {
|
|
|
|
+ offset = new Vector(offset.X, offset.Y + diff.Y);
|
|
|
|
+ double nearestSnapPoint = offset.Y;
|
|
|
|
+
|
|
|
|
+ if (_areVerticalSnapPointsRegular)
|
|
|
|
+ {
|
|
|
|
+ var minSnapPoint = (int)(offset.Y / _verticalSnapPoint) * _verticalSnapPoint + _verticalSnapPointOffset;
|
|
|
|
+ var maxSnapPoint = minSnapPoint + _verticalSnapPoint;
|
|
|
|
+ var midPoint = (minSnapPoint + maxSnapPoint) / 2;
|
|
|
|
+
|
|
|
|
+ nearestSnapPoint = offset.Y < midPoint ? minSnapPoint : maxSnapPoint;
|
|
|
|
+ }
|
|
|
|
+ else if (_verticalSnapPoints != null && _verticalSnapPoints.Count > 0)
|
|
|
|
+ {
|
|
|
|
+ var higherSnapPoint = FindNearestSnapPoint(_verticalSnapPoints, offset.Y, out var lowerSnapPoint);
|
|
|
|
+ var midPoint = (lowerSnapPoint + higherSnapPoint) / 2;
|
|
|
|
+
|
|
|
|
+ nearestSnapPoint = offset.Y < midPoint ? lowerSnapPoint : higherSnapPoint;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ offset = new Vector(offset.X, nearestSnapPoint - diff.Y);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (HorizontalSnapPointsType != SnapPointsType.None)
|
|
|
|
+ {
|
|
|
|
+ offset = new Vector(offset.X + diff.X, offset.Y);
|
|
|
|
+ double nearestSnapPoint = offset.X;
|
|
|
|
+
|
|
|
|
+ if (_areHorizontalSnapPointsRegular)
|
|
|
|
+ {
|
|
|
|
+ var minSnapPoint = (int)(offset.X / _horizontalSnapPoint) * _horizontalSnapPoint + _horizontalSnapPointOffset;
|
|
|
|
+ var maxSnapPoint = minSnapPoint + _horizontalSnapPoint;
|
|
|
|
+ var midPoint = (minSnapPoint + maxSnapPoint) / 2;
|
|
|
|
+
|
|
|
|
+ nearestSnapPoint = offset.X < midPoint ? minSnapPoint : maxSnapPoint;
|
|
|
|
+ }
|
|
|
|
+ else if (_horizontalSnapPoints != null && _horizontalSnapPoints.Count > 0)
|
|
|
|
+ {
|
|
|
|
+ var higherSnapPoint = FindNearestSnapPoint(_horizontalSnapPoints, offset.X, out var lowerSnapPoint);
|
|
|
|
+ var midPoint = (lowerSnapPoint + higherSnapPoint) / 2;
|
|
|
|
+
|
|
|
|
+ nearestSnapPoint = offset.X < midPoint ? lowerSnapPoint : higherSnapPoint;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ offset = new Vector(nearestSnapPoint - diff.X, offset.Y);
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ Vector GetAlignedDiff()
|
|
|
|
+ {
|
|
|
|
+ var vector = offset;
|
|
|
|
+
|
|
|
|
+ switch (VerticalSnapPointsAlignment)
|
|
|
|
+ {
|
|
|
|
+ case SnapPointsAlignment.Center:
|
|
|
|
+ vector += new Vector(0, Viewport.Height / 2);
|
|
|
|
+ break;
|
|
|
|
+ case SnapPointsAlignment.Far:
|
|
|
|
+ vector += new Vector(0, Viewport.Height);
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ switch (HorizontalSnapPointsAlignment)
|
|
|
|
+ {
|
|
|
|
+ case SnapPointsAlignment.Center:
|
|
|
|
+ vector += new Vector(Viewport.Width / 2, 0);
|
|
|
|
+ break;
|
|
|
|
+ case SnapPointsAlignment.Far:
|
|
|
|
+ vector += new Vector(Viewport.Width, 0);
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return vector - offset;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return offset;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private static double FindNearestSnapPoint(IReadOnlyList<double> snapPoints, double value, out double lowerSnapPoint)
|
|
|
|
+ {
|
|
|
|
+ var point = snapPoints.BinarySearch(value, Comparer<double>.Default);
|
|
|
|
+
|
|
|
|
+ if (point < 0)
|
|
|
|
+ {
|
|
|
|
+ point = ~point;
|
|
|
|
+
|
|
|
|
+ lowerSnapPoint = snapPoints[Math.Max(0, point - 1)];
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ lowerSnapPoint = snapPoints[point];
|
|
|
|
+
|
|
|
|
+ point += 1;
|
|
|
|
+ }
|
|
|
|
+ return snapPoints[Math.Min(point, snapPoints.Count - 1)];
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|