Browse Source

wip

wip_snappoint_anchor
Emmanuel Hansen 1 year ago
parent
commit
859c1be57e
  1. 89
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  2. 14
      src/Avalonia.Controls/Primitives/IScrollSnapPointAnchorProvider.cs
  3. 13
      src/Avalonia.Controls/ScrollViewer.cs
  4. 8
      src/Avalonia.Controls/VirtualizingStackPanel.cs

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

@ -8,13 +8,14 @@ using Avalonia.Utilities;
using Avalonia.VisualTree;
using System.Linq;
using Avalonia.Layout;
using System.Xml.Linq;
namespace Avalonia.Controls.Presenters
{
/// <summary>
/// Presents a scrolling view of content inside a <see cref="ScrollViewer"/>.
/// </summary>
public class ScrollContentPresenter : ContentPresenter, IScrollable, IScrollAnchorProvider
public class ScrollContentPresenter : ContentPresenter, IScrollable, IScrollAnchorProvider, IScrollSnapPointAnchorProvider
{
private const double EdgeDetectionTolerance = 0.1;
@ -87,6 +88,7 @@ namespace Avalonia.Controls.Presenters
private Dictionary<int, Vector>? _activeLogicalGestureScrolls;
private Dictionary<int, Vector>? _scrollGestureSnapPoints;
private HashSet<Control>? _anchorCandidates;
private HashSet<IScrollSnapPointsInfo>? _scrollSnapPointsCandidates;
private Control? _anchorElement;
private Rect _anchorElementBounds;
private bool _isAnchorElementDirty;
@ -606,7 +608,7 @@ namespace Avalonia.Controls.Presenters
if (Content is ItemsControl itemsControl)
scrollable = itemsControl.Presenter?.Panel;
if (scrollable is not IScrollSnapPointsInfo)
if (scrollable is not IScrollSnapPointsInfo && _scrollSnapPointsCandidates?.Any() == false)
return;
if (_scrollGestureSnapPoints == null)
@ -921,6 +923,58 @@ namespace Avalonia.Controls.Presenters
_horizontalSnapPoint = scrollSnapPointsInfo.GetRegularSnapPoints(Layout.Orientation.Horizontal, HorizontalSnapPointsAlignment, out _horizontalSnapPointOffset);
}
}
else if (_scrollSnapPointsCandidates is { } candidates && candidates.Count > 0 && this.GetVisualChildren().FirstOrDefault() is { } contentVisual)
{
var sources = candidates.Select(x => x as Visual).ToList();
var horizontalSnapPoints = new List<double>();
var verticalSnapPoints = new List<double>();
_horizontalSnapPoints = horizontalSnapPoints;
_verticalSnapPoints = verticalSnapPoints;
foreach (var source in sources)
{
if (source is not IScrollSnapPointsInfo info)
continue;
var transform = source.TransformToVisual(contentVisual) ?? Matrix.Identity;
var visualEdge = transform.Transform(source.Bounds.BottomRight);
if (!info.AreVerticalSnapPointsRegular)
{
verticalSnapPoints.AddRange(info.GetIrregularSnapPoints(Layout.Orientation.Vertical, VerticalSnapPointsAlignment).Select(y => transform.Transform(new Point(0, y)).Y).ToList());
}
else
{
var interval = info.GetRegularSnapPoints(Layout.Orientation.Vertical, VerticalSnapPointsAlignment, out var offset);
if (interval == 0)
continue;
var snapPoint = transform.Transform(new Point(0, offset)).Y;
while (snapPoint < visualEdge.Y)
{
verticalSnapPoints.Add(snapPoint);
offset += interval;
snapPoint = transform.Transform(new Point(0, offset)).Y;
}
}
if (!info.AreHorizontalSnapPointsRegular)
{
horizontalSnapPoints.AddRange(info.GetIrregularSnapPoints(Layout.Orientation.Horizontal, HorizontalSnapPointsAlignment).Select(x => transform.Transform(new Point(x, 0)).X).ToList());
}
else
{
var interval = info.GetRegularSnapPoints(Layout.Orientation.Horizontal, HorizontalSnapPointsAlignment, out var offset);
if (interval == 0)
continue;
var snapPoint = transform.Transform(new Point(offset, 0)).X;
while (snapPoint < visualEdge.Y)
{
horizontalSnapPoints.Add(snapPoint);
offset += interval;
snapPoint = transform.Transform(new Point(offset, 0)).X;
}
}
}
}
else
{
_horizontalSnapPoints = new List<double>();
@ -932,7 +986,7 @@ namespace Avalonia.Controls.Presenters
{
var scrollable = GetScrollSnapPointsInfo(Content);
if (scrollable is null || (VerticalSnapPointsType == SnapPointsType.None && HorizontalSnapPointsType == SnapPointsType.None))
if ((scrollable is null && _scrollSnapPointsCandidates?.Count == 0) || (VerticalSnapPointsType == SnapPointsType.None && HorizontalSnapPointsType == SnapPointsType.None))
return offset;
var diff = GetAlignmentDiff();
@ -1065,5 +1119,34 @@ namespace Avalonia.Controls.Presenters
return snapPointsInfo;
}
public void RegisterScrollSnapPointsInfoSource(IScrollSnapPointsInfo scrollSnapPointsInfo)
{
if(scrollSnapPointsInfo is not Visual visual)
{
throw new InvalidOperationException("ScrollSnapPointsInfo must be a visual");
}
if (!this.IsVisualAncestorOf(visual))
{
throw new InvalidOperationException(
"A ScrollSnapPointsInfo source must be a visual descendent of the ScrollContentPresenter.");
}
_scrollSnapPointsCandidates ??= new();
_scrollSnapPointsCandidates.Add(scrollSnapPointsInfo);
scrollSnapPointsInfo.HorizontalSnapPointsChanged += ScrollSnapPointsInfoSnapPointsChanged;
scrollSnapPointsInfo.VerticalSnapPointsChanged += ScrollSnapPointsInfoSnapPointsChanged;
}
public void UnregisterScrollSnapPointsInfoSource(IScrollSnapPointsInfo scrollSnapPointsInfo)
{
if (_scrollSnapPointsCandidates?.Contains(scrollSnapPointsInfo) == true)
{
scrollSnapPointsInfo.HorizontalSnapPointsChanged -= ScrollSnapPointsInfoSnapPointsChanged;
scrollSnapPointsInfo.VerticalSnapPointsChanged -= ScrollSnapPointsInfoSnapPointsChanged;
_scrollSnapPointsCandidates?.Remove(scrollSnapPointsInfo);
}
}
}
}

14
src/Avalonia.Controls/Primitives/IScrollSnapPointAnchorProvider.cs

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Avalonia.Controls.Primitives
{
internal interface IScrollSnapPointAnchorProvider
{
void RegisterScrollSnapPointsInfoSource(IScrollSnapPointsInfo scrollSnapPointsInfo);
void UnregisterScrollSnapPointsInfoSource(IScrollSnapPointsInfo scrollSnapPointsInfo);
}
}

13
src/Avalonia.Controls/ScrollViewer.cs

@ -6,6 +6,7 @@ using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
using System.Xml.Linq;
namespace Avalonia.Controls
{
@ -14,7 +15,7 @@ namespace Avalonia.Controls
/// </summary>
[TemplatePart("PART_HorizontalScrollBar", typeof(ScrollBar))]
[TemplatePart("PART_VerticalScrollBar", typeof(ScrollBar))]
public class ScrollViewer : ContentControl, IScrollable, IScrollAnchorProvider, IInternalScroller
public class ScrollViewer : ContentControl, IScrollable, IScrollAnchorProvider, IInternalScroller, IScrollSnapPointAnchorProvider
{
/// <summary>
/// Defines the <see cref="BringIntoViewOnFocusChange "/> property.
@ -873,5 +874,15 @@ namespace Avalonia.Controls
_oldViewport = Viewport;
}
}
public void RegisterScrollSnapPointsInfoSource(IScrollSnapPointsInfo scrollSnapPointsInfo)
{
(Presenter as IScrollSnapPointAnchorProvider)?.RegisterScrollSnapPointsInfoSource(scrollSnapPointsInfo);
}
public void UnregisterScrollSnapPointsInfoSource(IScrollSnapPointsInfo scrollSnapPointsInfo)
{
(Presenter as IScrollSnapPointAnchorProvider)?.UnregisterScrollSnapPointsInfoSource(scrollSnapPointsInfo);
}
}
}

8
src/Avalonia.Controls/VirtualizingStackPanel.cs

@ -73,6 +73,7 @@ namespace Avalonia.Controls
private int _focusedIndex = -1;
private Control? _realizingElement;
private int _realizingIndex = -1;
private IScrollSnapPointAnchorProvider? _attachedSnapPointProvider;
public VirtualizingStackPanel()
{
@ -247,11 +248,18 @@ namespace Avalonia.Controls
{
base.OnAttachedToVisualTree(e);
_scrollAnchorProvider = this.FindAncestorOfType<IScrollAnchorProvider>();
if (this.FindAncestorOfType<IScrollSnapPointAnchorProvider>() is { } scrollSnapPointsAnchor)
{
_attachedSnapPointProvider = scrollSnapPointsAnchor;
scrollSnapPointsAnchor.RegisterScrollSnapPointsInfoSource(this);
}
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
_attachedSnapPointProvider?.UnregisterScrollSnapPointsInfoSource(this);
_attachedSnapPointProvider = null;
_scrollAnchorProvider = null;
}

Loading…
Cancel
Save