Browse Source

Added IScrollAnchorProvider.

Though nothing implements it yet. Uncomment/implement the parts of `ViewportManager` that required this interface.
pull/2603/head
Steven Kirk 7 years ago
parent
commit
f944dde6c4
  1. 9
      src/Avalonia.Controls/IScrollAnchorProvider.cs
  2. 123
      src/Avalonia.Controls/Repeaters/ViewportManager.cs

9
src/Avalonia.Controls/IScrollAnchorProvider.cs

@ -0,0 +1,9 @@
namespace Avalonia.Controls
{
public interface IScrollAnchorProvider
{
IControl CurrentAnchor { get; }
void RegisterAnchorCandidate(IControl element);
void UnregisterAnchorCandidate(IControl element);
}
}

123
src/Avalonia.Controls/Repeaters/ViewportManager.cs

@ -17,6 +17,8 @@ namespace Avalonia.Controls.Repeaters
{ {
private const double CacheBufferPerSideInflationPixelDelta = 40.0; private const double CacheBufferPerSideInflationPixelDelta = 40.0;
private readonly ItemsRepeater _owner; private readonly ItemsRepeater _owner;
private bool _ensuredScroller;
private IScrollAnchorProvider _scroller;
private IControl _makeAnchorElement; private IControl _makeAnchorElement;
private bool _isAnchorOutsideRealizedRange; private bool _isAnchorOutsideRealizedRange;
private Task _cacheBuildAction; private Task _cacheBuildAction;
@ -50,11 +52,45 @@ namespace Avalonia.Controls.Repeaters
_owner = owner; _owner = owner;
} }
// TODO: Implement public IControl SuggestedAnchor
public IControl SuggestedAnchor => null; {
get
{
// The element generated during the ItemsRepeater.MakeAnchor call has precedence over the next tick.
var suggestedAnchor = _makeAnchorElement;
var owner = _owner;
if (suggestedAnchor == null)
{
var anchorElement = _scroller?.CurrentAnchor;
if (anchorElement != null)
{
// We can't simply return anchorElement because, in case of nested Repeaters, it may not
// be a direct child of ours, or even an indirect child. We need to walk up the tree starting
// from anchorElement to figure out what child of ours (if any) to use as the suggested element.
var child = anchorElement;
var parent = child.VisualParent as IControl;
while (parent != null)
{
if (parent == owner)
{
suggestedAnchor = child;
break;
}
child = parent;
parent = parent.VisualParent as IControl;
}
}
}
// TODO: Implement return suggestedAnchor;
public bool HasScroller => false; }
}
public bool HasScroller => _scroller != null;
public IControl MadeAnchor => _makeAnchorElement; public IControl MadeAnchor => _makeAnchorElement;
@ -169,13 +205,10 @@ namespace Avalonia.Controls.Repeaters
// We just finished a measure pass and have a new extent. // We just finished a measure pass and have a new extent.
// Let's make sure the scrollers will run its arrange so that they track the anchor. // Let's make sure the scrollers will run its arrange so that they track the anchor.
////if (_scroller != null) ((IControl)_scroller)?.InvalidateArrange();
////{
//// ((IControl)_scroller).InvalidateArrange();
////}
} }
public Point GetOrigin() => throw new NotImplementedException(); public Point GetOrigin() => _layoutExtent.TopLeft;
public void OnLayoutChanged(bool isVirtualizing) public void OnLayoutChanged(bool isVirtualizing)
{ {
@ -190,18 +223,7 @@ namespace Avalonia.Controls.Repeaters
if (!_managingViewportDisabled) if (!_managingViewportDisabled)
{ {
// HACK: This is a bit of a hack. We need the effective viewport of the ItemsRepeater - _effectiveViewportChangedRevoker = SubscribeToEffectiveViewportChanged(_owner);
// we can get this from TransformedBounds, but this property is updated after layout has
// run, resulting in the UI being updated too late when scrolling quickly. We can
// partially remedey this by triggering also on Bounds changes, but this won't work so
// well for nested ItemsRepeaters.
//
// UWP uses the EffectiveBoundsChanged event (which I think was implemented specially
// for this case): we need to implement that in Avalonia.
_effectiveViewportChangedRevoker = _owner.GetObservable(Visual.TransformedBoundsProperty)
.Merge(_owner.GetObservable(Visual.BoundsProperty).Select(_ => _owner.TransformedBounds))
.Skip(1)
.Subscribe(OnEffectiveViewportChanged);
} }
} }
@ -222,7 +244,7 @@ namespace Avalonia.Controls.Repeaters
// This is because of a bug that causes effective viewport to not // This is because of a bug that causes effective viewport to not
// fire if you register during arrange. // fire if you register during arrange.
// Bug 17411076: EffectiveViewport: registering for effective viewport in arrange should invalidate viewport // Bug 17411076: EffectiveViewport: registering for effective viewport in arrange should invalidate viewport
//EnsureScroller(); EnsureScroller();
} }
public void OnOwnerArranged() public void OnOwnerArranged()
@ -342,9 +364,10 @@ namespace Avalonia.Controls.Repeaters
public void ResetScrollers() public void ResetScrollers()
{ {
////_scroller = null; _scroller = null;
////_effectiveViewportChangedRevoker.Dispose(); _effectiveViewportChangedRevoker.Dispose();
////m_ensuredScroller = false; _effectiveViewportChangedRevoker = null;
_ensuredScroller = false;
} }
private void OnEffectiveViewportChanged(TransformedBounds? bounds) private void OnEffectiveViewportChanged(TransformedBounds? bounds)
@ -377,6 +400,40 @@ namespace Avalonia.Controls.Repeaters
} }
} }
private void EnsureScroller()
{
if (!_ensuredScroller)
{
ResetScrollers();
var parent = _owner.GetVisualParent();
while (parent != null)
{
if (parent is IScrollAnchorProvider scroller)
{
_scroller = scroller;
break;
}
parent = parent.VisualParent;
}
if (_scroller == null)
{
// We usually update the viewport in the post arrange handler. But, since we don't have
// a scroller, let's do it now.
UpdateViewport(Rect.Empty);
}
else if (!_managingViewportDisabled)
{
_effectiveViewportChangedRevoker?.Dispose();
_effectiveViewportChangedRevoker = SubscribeToEffectiveViewportChanged(_owner);
}
_ensuredScroller = true;
}
}
private void UpdateViewport(Rect viewport) private void UpdateViewport(Rect viewport)
{ {
var currentVisibleWindow = viewport; var currentVisibleWindow = viewport;
@ -415,6 +472,22 @@ namespace Avalonia.Controls.Repeaters
} }
} }
private IDisposable SubscribeToEffectiveViewportChanged(IControl control)
{
// HACK: This is a bit of a hack. We need the effective viewport of the ItemsRepeater -
// we can get this from TransformedBounds, but this property is updated after layout has
// run, resulting in the UI being updated too late when scrolling quickly. We can
// partially remedey this by triggering also on Bounds changes, but this won't work so
// well for nested ItemsRepeaters.
//
// UWP uses the EffectiveBoundsChanged event (which I think was implemented specially
// for this case): we need to implement that in Avalonia.
return control.GetObservable(Visual.TransformedBoundsProperty)
.Merge(control.GetObservable(Visual.BoundsProperty).Select(_ => control.TransformedBounds))
.Skip(1)
.Subscribe(OnEffectiveViewportChanged);
}
private class ScrollerInfo private class ScrollerInfo
{ {
public ScrollerInfo(ScrollViewer scroller) public ScrollerInfo(ScrollViewer scroller)

Loading…
Cancel
Save