|
@@ -6,6 +6,7 @@ using Avalonia.Input;
|
|
|
using Avalonia.Input.GestureRecognizers;
|
|
|
using Avalonia.Utilities;
|
|
|
using Avalonia.VisualTree;
|
|
|
+using System.Linq;
|
|
|
|
|
|
namespace Avalonia.Controls.Presenters
|
|
|
{
|
|
@@ -19,44 +20,34 @@ namespace Avalonia.Controls.Presenters
|
|
|
/// <summary>
|
|
|
/// Defines the <see cref="CanHorizontallyScroll"/> property.
|
|
|
/// </summary>
|
|
|
- public static readonly DirectProperty<ScrollContentPresenter, bool> CanHorizontallyScrollProperty =
|
|
|
- AvaloniaProperty.RegisterDirect<ScrollContentPresenter, bool>(
|
|
|
- nameof(CanHorizontallyScroll),
|
|
|
- o => o.CanHorizontallyScroll,
|
|
|
- (o, v) => o.CanHorizontallyScroll = v);
|
|
|
+ public static readonly StyledProperty<bool> CanHorizontallyScrollProperty =
|
|
|
+ AvaloniaProperty.Register<ScrollContentPresenter, bool>(nameof(CanHorizontallyScroll));
|
|
|
|
|
|
/// <summary>
|
|
|
/// Defines the <see cref="CanVerticallyScroll"/> property.
|
|
|
/// </summary>
|
|
|
- public static readonly DirectProperty<ScrollContentPresenter, bool> CanVerticallyScrollProperty =
|
|
|
- AvaloniaProperty.RegisterDirect<ScrollContentPresenter, bool>(
|
|
|
- nameof(CanVerticallyScroll),
|
|
|
- o => o.CanVerticallyScroll,
|
|
|
- (o, v) => o.CanVerticallyScroll = v);
|
|
|
+ public static readonly StyledProperty<bool> CanVerticallyScrollProperty =
|
|
|
+ AvaloniaProperty.Register<ScrollContentPresenter, bool>(nameof(CanVerticallyScroll));
|
|
|
|
|
|
/// <summary>
|
|
|
/// Defines the <see cref="Extent"/> property.
|
|
|
/// </summary>
|
|
|
public static readonly DirectProperty<ScrollContentPresenter, Size> ExtentProperty =
|
|
|
ScrollViewer.ExtentProperty.AddOwner<ScrollContentPresenter>(
|
|
|
- o => o.Extent,
|
|
|
- (o, v) => o.Extent = v);
|
|
|
+ o => o.Extent);
|
|
|
|
|
|
/// <summary>
|
|
|
/// Defines the <see cref="Offset"/> property.
|
|
|
/// </summary>
|
|
|
- public static readonly DirectProperty<ScrollContentPresenter, Vector> OffsetProperty =
|
|
|
- ScrollViewer.OffsetProperty.AddOwner<ScrollContentPresenter>(
|
|
|
- o => o.Offset,
|
|
|
- (o, v) => o.Offset = v);
|
|
|
+ public static readonly StyledProperty<Vector> OffsetProperty =
|
|
|
+ ScrollViewer.OffsetProperty.AddOwner<ScrollContentPresenter>(new(coerce: ScrollViewer.CoerceOffset));
|
|
|
|
|
|
/// <summary>
|
|
|
/// Defines the <see cref="Viewport"/> property.
|
|
|
/// </summary>
|
|
|
public static readonly DirectProperty<ScrollContentPresenter, Size> ViewportProperty =
|
|
|
ScrollViewer.ViewportProperty.AddOwner<ScrollContentPresenter>(
|
|
|
- o => o.Viewport,
|
|
|
- (o, v) => o.Viewport = v);
|
|
|
+ o => o.Viewport);
|
|
|
|
|
|
/// <summary>
|
|
|
/// Defines the <see cref="HorizontalSnapPointsType"/> property.
|
|
@@ -88,16 +79,13 @@ namespace Avalonia.Controls.Presenters
|
|
|
public static readonly StyledProperty<bool> IsScrollChainingEnabledProperty =
|
|
|
ScrollViewer.IsScrollChainingEnabledProperty.AddOwner<ScrollContentPresenter>();
|
|
|
|
|
|
- private bool _canHorizontallyScroll;
|
|
|
- private bool _canVerticallyScroll;
|
|
|
private bool _arranging;
|
|
|
private Size _extent;
|
|
|
- private Vector _offset;
|
|
|
private IDisposable? _logicalScrollSubscription;
|
|
|
private Size _viewport;
|
|
|
private Dictionary<int, Vector>? _activeLogicalGestureScrolls;
|
|
|
private Dictionary<int, Vector>? _scrollGestureSnapPoints;
|
|
|
- private List<Control>? _anchorCandidates;
|
|
|
+ private HashSet<Control>? _anchorCandidates;
|
|
|
private Control? _anchorElement;
|
|
|
private Rect _anchorElementBounds;
|
|
|
private bool _isAnchorElementDirty;
|
|
@@ -109,6 +97,8 @@ namespace Avalonia.Controls.Presenters
|
|
|
private double _verticalSnapPoint;
|
|
|
private double _verticalSnapPointOffset;
|
|
|
private double _horizontalSnapPointOffset;
|
|
|
+ private CompositeDisposable? _ownerSubscriptions;
|
|
|
+ private ScrollViewer? _owner;
|
|
|
|
|
|
/// <summary>
|
|
|
/// Initializes static members of the <see cref="ScrollContentPresenter"/> class.
|
|
@@ -116,7 +106,6 @@ namespace Avalonia.Controls.Presenters
|
|
|
static ScrollContentPresenter()
|
|
|
{
|
|
|
ClipToBoundsProperty.OverrideDefaultValue(typeof(ScrollContentPresenter), true);
|
|
|
- ChildProperty.Changed.AddClassHandler<ScrollContentPresenter>((x, e) => x.ChildChanged(e));
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
@@ -137,8 +126,8 @@ namespace Avalonia.Controls.Presenters
|
|
|
/// </summary>
|
|
|
public bool CanHorizontallyScroll
|
|
|
{
|
|
|
- get { return _canHorizontallyScroll; }
|
|
|
- set { SetAndRaise(CanHorizontallyScrollProperty, ref _canHorizontallyScroll, value); }
|
|
|
+ get => GetValue(CanHorizontallyScrollProperty);
|
|
|
+ set => SetValue(CanHorizontallyScrollProperty, value);
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
@@ -146,8 +135,8 @@ namespace Avalonia.Controls.Presenters
|
|
|
/// </summary>
|
|
|
public bool CanVerticallyScroll
|
|
|
{
|
|
|
- get { return _canVerticallyScroll; }
|
|
|
- set { SetAndRaise(CanVerticallyScrollProperty, ref _canVerticallyScroll, value); }
|
|
|
+ get => GetValue(CanVerticallyScrollProperty);
|
|
|
+ set => SetValue(CanVerticallyScrollProperty, value);
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
@@ -164,8 +153,8 @@ namespace Avalonia.Controls.Presenters
|
|
|
/// </summary>
|
|
|
public Vector Offset
|
|
|
{
|
|
|
- get { return _offset; }
|
|
|
- set { SetAndRaise(OffsetProperty, ref _offset, ScrollViewer.CoerceOffset(Extent, Viewport, value)); }
|
|
|
+ get => GetValue(OffsetProperty);
|
|
|
+ set => SetValue(OffsetProperty, value);
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
@@ -295,12 +284,60 @@ namespace Avalonia.Controls.Presenters
|
|
|
|
|
|
if (result)
|
|
|
{
|
|
|
- Offset = offset;
|
|
|
+ SetCurrentValue(OffsetProperty, offset);
|
|
|
}
|
|
|
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
+ protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
|
|
+ {
|
|
|
+ base.OnAttachedToVisualTree(e);
|
|
|
+ AttachToScrollViewer();
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Locates the first <see cref="ScrollViewer"/> ancestor and binds to it. Properties which have been set through other means are not bound.
|
|
|
+ /// </summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// This method is automatically called when the control is attached to a visual tree.
|
|
|
+ /// </remarks>
|
|
|
+ protected internal virtual void AttachToScrollViewer()
|
|
|
+ {
|
|
|
+ var owner = this.FindAncestorOfType<ScrollViewer>();
|
|
|
+
|
|
|
+ if (owner == null)
|
|
|
+ {
|
|
|
+ _owner = null;
|
|
|
+ _ownerSubscriptions?.Dispose();
|
|
|
+ _ownerSubscriptions = null;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (owner == _owner)
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ _ownerSubscriptions?.Dispose();
|
|
|
+
|
|
|
+ var subscriptionDisposables = new IDisposable?[]
|
|
|
+ {
|
|
|
+ IfUnset(CanHorizontallyScrollProperty, p => Bind(p, owner.GetObservable(ScrollViewer.HorizontalScrollBarVisibilityProperty, NotDisabled), Data.BindingPriority.Template)),
|
|
|
+ IfUnset(CanVerticallyScrollProperty, p => Bind(p, owner.GetObservable(ScrollViewer.VerticalScrollBarVisibilityProperty, NotDisabled), Data.BindingPriority.Template)),
|
|
|
+ IfUnset(OffsetProperty, p => Bind(p, owner.GetBindingObservable(ScrollViewer.OffsetProperty), Data.BindingPriority.Template)),
|
|
|
+ IfUnset(IsScrollChainingEnabledProperty, p => Bind(p, owner.GetBindingObservable(ScrollViewer.IsScrollChainingEnabledProperty), Data.BindingPriority.Template)),
|
|
|
+ IfUnset(ContentProperty, p => Bind(p, owner.GetBindingObservable(ContentProperty), Data.BindingPriority.Template)),
|
|
|
+ }.Where(d => d != null).Cast<IDisposable>().ToArray();
|
|
|
+
|
|
|
+ _owner = owner;
|
|
|
+ _ownerSubscriptions = new CompositeDisposable(subscriptionDisposables);
|
|
|
+
|
|
|
+ static bool NotDisabled(ScrollBarVisibility v) => v != ScrollBarVisibility.Disabled;
|
|
|
+
|
|
|
+ IDisposable? IfUnset<T>(T property, Func<T, IDisposable> func) where T : AvaloniaProperty => IsSet(property) ? null : func(property);
|
|
|
+ }
|
|
|
+
|
|
|
/// <inheritdoc/>
|
|
|
void IScrollAnchorProvider.RegisterAnchorCandidate(Control element)
|
|
|
{
|
|
@@ -310,7 +347,7 @@ namespace Avalonia.Controls.Presenters
|
|
|
"An anchor control must be a visual descendent of the ScrollContentPresenter.");
|
|
|
}
|
|
|
|
|
|
- _anchorCandidates ??= new List<Control>();
|
|
|
+ _anchorCandidates ??= new();
|
|
|
_anchorCandidates.Add(element);
|
|
|
_isAnchorElementDirty = true;
|
|
|
}
|
|
@@ -410,7 +447,7 @@ namespace Avalonia.Controls.Presenters
|
|
|
try
|
|
|
{
|
|
|
_arranging = true;
|
|
|
- Offset = newOffset;
|
|
|
+ SetCurrentValue(OffsetProperty, newOffset);
|
|
|
}
|
|
|
finally
|
|
|
{
|
|
@@ -427,7 +464,6 @@ namespace Avalonia.Controls.Presenters
|
|
|
|
|
|
Viewport = finalSize;
|
|
|
Extent = Child!.Bounds.Size.Inflate(Child.Margin);
|
|
|
- Offset = ScrollViewer.CoerceOffset(Extent, finalSize, Offset);
|
|
|
_isAnchorElementDirty = true;
|
|
|
|
|
|
return finalSize;
|
|
@@ -516,7 +552,7 @@ namespace Avalonia.Controls.Presenters
|
|
|
}
|
|
|
|
|
|
bool offsetChanged = newOffset != Offset;
|
|
|
- Offset = newOffset;
|
|
|
+ SetCurrentValue(OffsetProperty, newOffset);
|
|
|
|
|
|
e.Handled = !IsScrollChainingEnabled || offsetChanged;
|
|
|
|
|
@@ -529,7 +565,7 @@ namespace Avalonia.Controls.Presenters
|
|
|
_activeLogicalGestureScrolls?.Remove(e.Id);
|
|
|
_scrollGestureSnapPoints?.Remove(e.Id);
|
|
|
|
|
|
- Offset = SnapOffset(Offset);
|
|
|
+ SetCurrentValue(OffsetProperty, SnapOffset(Offset));
|
|
|
}
|
|
|
|
|
|
private void OnScrollGestureInertiaStartingEnded(object? sender, ScrollGestureInertiaStartingEventArgs e)
|
|
@@ -623,7 +659,7 @@ namespace Avalonia.Controls.Presenters
|
|
|
Vector newOffset = SnapOffset(new Vector(x, y));
|
|
|
|
|
|
bool offsetChanged = newOffset != Offset;
|
|
|
- Offset = newOffset;
|
|
|
+ SetCurrentValue(OffsetProperty, newOffset);
|
|
|
|
|
|
e.Handled = !IsScrollChainingEnabled || offsetChanged;
|
|
|
}
|
|
@@ -631,9 +667,14 @@ namespace Avalonia.Controls.Presenters
|
|
|
|
|
|
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
|
|
{
|
|
|
- if (change.Property == OffsetProperty && !_arranging)
|
|
|
+ if (change.Property == OffsetProperty)
|
|
|
{
|
|
|
- InvalidateArrange();
|
|
|
+ if (!_arranging)
|
|
|
+ {
|
|
|
+ InvalidateArrange();
|
|
|
+ }
|
|
|
+
|
|
|
+ _owner?.SetCurrentValue(OffsetProperty, change.GetNewValue<Vector>());
|
|
|
}
|
|
|
else if (change.Property == ContentProperty)
|
|
|
{
|
|
@@ -651,11 +692,31 @@ namespace Avalonia.Controls.Presenters
|
|
|
|
|
|
UpdateSnapPoints();
|
|
|
}
|
|
|
+ else if (change.Property == ChildProperty)
|
|
|
+ {
|
|
|
+ ChildChanged(change);
|
|
|
+ }
|
|
|
else if (change.Property == HorizontalSnapPointsAlignmentProperty ||
|
|
|
change.Property == VerticalSnapPointsAlignmentProperty)
|
|
|
{
|
|
|
UpdateSnapPoints();
|
|
|
}
|
|
|
+ else if (change.Property == ExtentProperty)
|
|
|
+ {
|
|
|
+ if (_owner != null)
|
|
|
+ {
|
|
|
+ _owner.Extent = change.GetNewValue<Size>();
|
|
|
+ }
|
|
|
+ CoerceValue(OffsetProperty);
|
|
|
+ }
|
|
|
+ else if (change.Property == ViewportProperty)
|
|
|
+ {
|
|
|
+ if (_owner != null)
|
|
|
+ {
|
|
|
+ _owner.Viewport = change.GetNewValue<Size>();
|
|
|
+ }
|
|
|
+ CoerceValue(OffsetProperty);
|
|
|
+ }
|
|
|
|
|
|
base.OnPropertyChanged(change);
|
|
|
}
|
|
@@ -677,7 +738,7 @@ namespace Avalonia.Controls.Presenters
|
|
|
|
|
|
if (e.OldValue != null)
|
|
|
{
|
|
|
- Offset = default;
|
|
|
+ SetCurrentValue(OffsetProperty, default);
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -719,14 +780,14 @@ namespace Avalonia.Controls.Presenters
|
|
|
if (logicalScroll != scrollable.IsLogicalScrollEnabled)
|
|
|
{
|
|
|
UpdateScrollableSubscription(Child);
|
|
|
- Offset = default;
|
|
|
+ SetCurrentValue(OffsetProperty, default);
|
|
|
InvalidateMeasure();
|
|
|
}
|
|
|
else if (scrollable.IsLogicalScrollEnabled)
|
|
|
{
|
|
|
Viewport = scrollable.Viewport;
|
|
|
Extent = scrollable.Extent;
|
|
|
- Offset = scrollable.Offset;
|
|
|
+ SetCurrentValue(OffsetProperty, scrollable.Offset);
|
|
|
}
|
|
|
}
|
|
|
|