using System;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Animation;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Interactivity;
namespace Avalonia.Controls;
///
/// Displays according to an ,
/// using a to move between the old and new content.
///
public class TransitioningContentControl : ContentControl
{
private CancellationTokenSource? _currentTransition;
private ContentPresenter? _lastPresenter;
private ContentPresenter? _presenter2;
private bool _isFirstFull;
private bool _shouldAnimate;
///
/// Defines the property.
///
public static readonly StyledProperty PageTransitionProperty =
AvaloniaProperty.Register(
nameof(PageTransition),
defaultValue: new ImmutableCrossFade(TimeSpan.FromMilliseconds(125)));
///
/// Defines the property.
///
public static readonly StyledProperty IsTransitionReversedProperty =
AvaloniaProperty.Register(
nameof(IsTransitionReversed),
defaultValue: false);
///
/// Defines the routed event.
///
public static readonly RoutedEvent TransitionCompletedEvent =
RoutedEvent.Register(
nameof(TransitionCompleted),
RoutingStrategies.Direct);
///
/// Gets or sets the animation played when content appears and disappears.
///
public IPageTransition? PageTransition
{
get => GetValue(PageTransitionProperty);
set => SetValue(PageTransitionProperty, value);
}
///
/// Gets or sets a value indicating whether the control will be animated in the reverse direction.
///
/// May not apply to all transitions.
public bool IsTransitionReversed
{
get => GetValue(IsTransitionReversedProperty);
set => SetValue(IsTransitionReversedProperty, value);
}
///
/// Raised when the old content isn't needed anymore by the control, because the transition has completed.
///
public event EventHandler TransitionCompleted
{
add => AddHandler(TransitionCompletedEvent, value);
remove => RemoveHandler(TransitionCompletedEvent, value);
}
protected override Size ArrangeOverride(Size finalSize)
{
var result = base.ArrangeOverride(finalSize);
if (_shouldAnimate)
{
_currentTransition?.Cancel();
if (_presenter2 is not null &&
Presenter is { } presenter &&
PageTransition is { } transition)
{
_shouldAnimate = false;
var cancel = new CancellationTokenSource();
_currentTransition = cancel;
var from = _isFirstFull ? _presenter2 : presenter;
var to = _isFirstFull ? presenter : _presenter2;
var fromContent = from.Content;
var toContent = to.Content;
transition.Start(from, to, !IsTransitionReversed, cancel.Token).ContinueWith(task =>
{
OnTransitionCompleted(new TransitionCompletedEventArgs(
fromContent, toContent, task.Status == TaskStatus.RanToCompletion && !cancel.IsCancellationRequested));
if (!cancel.IsCancellationRequested)
{
HideOldPresenter();
}
}, TaskScheduler.FromCurrentSynchronizationContext());
}
_shouldAnimate = false;
}
return result;
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
UpdateContent(false);
}
protected override bool RegisterContentPresenter(ContentPresenter presenter)
{
if (base.RegisterContentPresenter(presenter))
{
return true;
}
if (presenter is ContentPresenter p &&
p.Name == "PART_ContentPresenter2")
{
_presenter2 = p;
_presenter2.IsVisible = false;
UpdateContent(false);
return true;
}
return false;
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
if (change.Property == ContentProperty)
{
UpdateContent(true);
return;
}
base.OnPropertyChanged(change);
}
private void UpdateContent(bool withTransition)
{
if (VisualRoot is null || _presenter2 is null || Presenter is null)
{
return;
}
var currentPresenter = _isFirstFull ? _presenter2 : Presenter;
var fromContent = _lastPresenter?.Content;
var toContent = Content;
if (_lastPresenter != null &&
_lastPresenter != currentPresenter &&
_lastPresenter.Content == toContent)
{
_lastPresenter.Content = null;
}
currentPresenter.Content = toContent;
currentPresenter.IsVisible = true;
_lastPresenter = currentPresenter;
_isFirstFull = !_isFirstFull;
if (PageTransition is not null && withTransition)
{
_shouldAnimate = true;
InvalidateArrange();
}
else
{
HideOldPresenter();
OnTransitionCompleted(new TransitionCompletedEventArgs(fromContent, toContent, false));
}
}
private void HideOldPresenter()
{
var oldPresenter = _isFirstFull ? _presenter2 : Presenter;
if (oldPresenter is not null)
{
oldPresenter.Content = null;
oldPresenter.IsVisible = false;
}
}
private void OnTransitionCompleted(TransitionCompletedEventArgs e)
=> RaiseEvent(e);
private class ImmutableCrossFade : IPageTransition
{
private readonly CrossFade _inner;
public ImmutableCrossFade(TimeSpan duration) => _inner = new CrossFade(duration);
public Task Start(Visual? from, Visual? to, bool forward, CancellationToken cancellationToken)
{
return _inner.Start(from, to, cancellationToken);
}
}
}