A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

284 lines
12 KiB

using System;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.PullToRefresh;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Reactive;
namespace Avalonia.Controls
{
/// <summary>
/// Represents a container control that provides a <see cref="RefreshVisualizer"/> and pull-to-refresh functionality for scrollable content.
/// </summary>
public class RefreshContainer : ContentControl
{
internal const int DefaultPullDimensionSize = 100;
private bool _hasDefaultRefreshInfoProviderAdapter;
private ScrollViewerIRefreshInfoProviderAdapter? _refreshInfoProviderAdapter;
private RefreshInfoProvider? _refreshInfoProvider;
private IDisposable? _visualizerSizeSubscription;
private Grid? _visualizerPresenter;
private RefreshVisualizer? _refreshVisualizer;
private bool _hasDefaultRefreshVisualizer;
/// <summary>
/// Defines the <see cref="RefreshRequested"/> event.
/// </summary>
public static readonly RoutedEvent<RefreshRequestedEventArgs> RefreshRequestedEvent =
RoutedEvent.Register<RefreshContainer, RefreshRequestedEventArgs>(nameof(RefreshRequested), RoutingStrategies.Bubble);
internal static readonly DirectProperty<RefreshContainer, ScrollViewerIRefreshInfoProviderAdapter?> RefreshInfoProviderAdapterProperty =
AvaloniaProperty.RegisterDirect<RefreshContainer, ScrollViewerIRefreshInfoProviderAdapter?>(nameof(RefreshInfoProviderAdapter),
(s) => s.RefreshInfoProviderAdapter, (s, o) => s.RefreshInfoProviderAdapter = o);
/// <summary>
/// Defines the <see cref="Visualizer"/> event.
/// </summary>
public static readonly DirectProperty<RefreshContainer, RefreshVisualizer?> VisualizerProperty =
AvaloniaProperty.RegisterDirect<RefreshContainer, RefreshVisualizer?>(nameof(Visualizer),
s => s.Visualizer, (s, o) => s.Visualizer = o);
/// <summary>
/// Defines the <see cref="PullDirection"/> property.
/// </summary>
public static readonly StyledProperty<PullDirection> PullDirectionProperty =
AvaloniaProperty.Register<RefreshContainer, PullDirection>(nameof(PullDirection), PullDirection.TopToBottom);
internal ScrollViewerIRefreshInfoProviderAdapter? RefreshInfoProviderAdapter
{
get => _refreshInfoProviderAdapter; set
{
_hasDefaultRefreshInfoProviderAdapter = false;
SetAndRaise(RefreshInfoProviderAdapterProperty, ref _refreshInfoProviderAdapter, value);
}
}
/// <summary>
/// Defines the <see cref="IsMouseEnabled"/> property.
/// </summary>
/// <remarks>
/// Allows to enable the pull 2 refresh gesture for devices using a mouse. Disabled by default
/// </remarks>
public static readonly StyledProperty<bool> IsMouseEnabledProperty =
AvaloniaProperty.Register<RefreshContainer, bool>(nameof(IsMouseEnabled), false);
/// <summary>
/// Gets or sets a value that indicates whether the pull-to-refresh gesture is enabled for desktop devices.
/// Allows to enable the pull 2 refresh gesture for devices using a mouse. Disabled by default
/// </summary>
public bool IsMouseEnabled
{
get => GetValue(IsMouseEnabledProperty);
set => SetValue(IsMouseEnabledProperty, value);
}
/// <summary>
/// Gets or sets the <see cref="RefreshVisualizer"/> for this container.
/// </summary>
public RefreshVisualizer? Visualizer
{
get => _refreshVisualizer; set
{
if (_refreshVisualizer != null)
{
_visualizerSizeSubscription?.Dispose();
_refreshVisualizer.RefreshRequested -= Visualizer_RefreshRequested;
}
SetAndRaise(VisualizerProperty, ref _refreshVisualizer, value);
}
}
/// <summary>
/// Gets or sets a value that specifies the direction to pull to initiate a refresh.
/// </summary>
public PullDirection PullDirection
{
get => GetValue(PullDirectionProperty);
set => SetValue(PullDirectionProperty, value);
}
/// <summary>
/// Occurs when an update of the content has been initiated.
/// </summary>
public event EventHandler<RefreshRequestedEventArgs>? RefreshRequested
{
add => AddHandler(RefreshRequestedEvent, value);
remove => RemoveHandler(RefreshRequestedEvent, value);
}
public RefreshContainer()
{
_hasDefaultRefreshInfoProviderAdapter = true;
_refreshInfoProviderAdapter = new ScrollViewerIRefreshInfoProviderAdapter(PullDirection, IsMouseEnabled);
RaisePropertyChanged(RefreshInfoProviderAdapterProperty, null, _refreshInfoProviderAdapter);
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
_visualizerPresenter = e.NameScope.Find<Grid>("PART_RefreshVisualizerPresenter");
if (_refreshVisualizer == null)
{
_hasDefaultRefreshVisualizer = true;
Visualizer = new RefreshVisualizer();
}
else
{
_hasDefaultRefreshVisualizer = false;
RaisePropertyChanged(VisualizerProperty, default, _refreshVisualizer);
}
OnPullDirectionChanged();
}
private void OnVisualizerSizeChanged(Rect obj)
{
if (_hasDefaultRefreshInfoProviderAdapter && _refreshInfoProviderAdapter != null)
{
_refreshInfoProviderAdapter.UpdateVisualizerSize(obj.Size);
RaisePropertyChanged(RefreshInfoProviderAdapterProperty, null, _refreshInfoProviderAdapter);
}
}
private void Visualizer_RefreshRequested(object? sender, RefreshRequestedEventArgs e)
{
var ev = new RefreshRequestedEventArgs(e.GetDeferral(), RefreshRequestedEvent);
RaiseEvent(ev);
ev.DecrementCount();
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == RefreshInfoProviderAdapterProperty)
{
if (_refreshVisualizer != null)
{
if (_refreshInfoProvider != null)
{
_refreshVisualizer.RefreshInfoProvider = _refreshInfoProvider;
}
else
{
if (RefreshInfoProviderAdapter != null && _refreshVisualizer != null)
{
_refreshInfoProvider = RefreshInfoProviderAdapter?.AdaptFromTree(this, _refreshVisualizer.Bounds.Size);
if (_refreshInfoProvider != null)
{
_refreshVisualizer.RefreshInfoProvider = _refreshInfoProvider;
RefreshInfoProviderAdapter?.SetAnimations(_refreshVisualizer);
}
}
}
}
}
else if (change.Property == VisualizerProperty)
{
if (_visualizerPresenter != null)
{
_visualizerPresenter.Children.Clear();
if (_refreshVisualizer != null)
{
_visualizerPresenter.Children.Add(_refreshVisualizer);
}
}
if (_refreshVisualizer != null)
{
_refreshVisualizer.RefreshRequested += Visualizer_RefreshRequested;
_visualizerSizeSubscription = _refreshVisualizer.GetObservable(Control.BoundsProperty).Subscribe(OnVisualizerSizeChanged);
}
}
else if (change.Property == PullDirectionProperty)
{
OnPullDirectionChanged();
}
else if (change.Property == IsMouseEnabledProperty)
{
OnIsMouseEnabledChanged();
}
}
private void OnPullDirectionChanged()
{
if (_visualizerPresenter != null && _refreshVisualizer != null)
{
switch (PullDirection)
{
case PullDirection.TopToBottom:
_visualizerPresenter.VerticalAlignment = Layout.VerticalAlignment.Top;
_visualizerPresenter.HorizontalAlignment = Layout.HorizontalAlignment.Stretch;
if (_hasDefaultRefreshVisualizer)
{
_refreshVisualizer.PullDirection = PullDirection.TopToBottom;
_refreshVisualizer.Height = DefaultPullDimensionSize;
_refreshVisualizer.Width = double.NaN;
}
break;
case PullDirection.BottomToTop:
_visualizerPresenter.VerticalAlignment = Layout.VerticalAlignment.Bottom;
_visualizerPresenter.HorizontalAlignment = Layout.HorizontalAlignment.Stretch;
if (_hasDefaultRefreshVisualizer)
{
_refreshVisualizer.PullDirection = PullDirection.BottomToTop;
_refreshVisualizer.Height = DefaultPullDimensionSize;
_refreshVisualizer.Width = double.NaN;
}
break;
case PullDirection.LeftToRight:
_visualizerPresenter.VerticalAlignment = Layout.VerticalAlignment.Stretch;
_visualizerPresenter.HorizontalAlignment = Layout.HorizontalAlignment.Left;
if (_hasDefaultRefreshVisualizer)
{
_refreshVisualizer.PullDirection = PullDirection.LeftToRight;
_refreshVisualizer.Width = DefaultPullDimensionSize;
_refreshVisualizer.Height = double.NaN;
}
break;
case PullDirection.RightToLeft:
_visualizerPresenter.VerticalAlignment = Layout.VerticalAlignment.Stretch;
_visualizerPresenter.HorizontalAlignment = Layout.HorizontalAlignment.Right;
if (_hasDefaultRefreshVisualizer)
{
_refreshVisualizer.PullDirection = PullDirection.RightToLeft;
_refreshVisualizer.Width = DefaultPullDimensionSize;
_refreshVisualizer.Height = double.NaN;
}
break;
}
if (_hasDefaultRefreshInfoProviderAdapter)
{
if (_hasDefaultRefreshVisualizer)
{
var size = new Size(_refreshVisualizer.Width, _refreshVisualizer.Height);
_refreshInfoProviderAdapter?.UpdateVisualizerSize(size);
}
_refreshInfoProviderAdapter?.UpdatePullDirection(PullDirection);
RaisePropertyChanged(RefreshInfoProviderAdapterProperty, null, _refreshInfoProviderAdapter);
}
}
}
private void OnIsMouseEnabledChanged()
{
_refreshInfoProviderAdapter?.UpdateIsMouseEnabled(IsMouseEnabled);
}
/// <summary>
/// Initiates an update of the content.
/// </summary>
public void RequestRefresh()
{
_refreshVisualizer?.RequestRefresh();
}
}
}