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.
 
 
 

155 lines
4.4 KiB

using System;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Animation;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Data;
namespace Avalonia.Controls;
/// <summary>
/// Displays <see cref="ContentControl.Content"/> according to an <see cref="IDataTemplate"/>,
/// using a <see cref="PageTransition"/> to move between the old and new content.
/// </summary>
public class TransitioningContentControl : ContentControl
{
private CancellationTokenSource? _currentTransition;
private ContentPresenter? _presenter2;
private bool _isFirstFull;
private bool _shouldAnimate;
/// <summary>
/// Defines the <see cref="PageTransition"/> property.
/// </summary>
public static readonly StyledProperty<IPageTransition?> PageTransitionProperty =
AvaloniaProperty.Register<TransitioningContentControl, IPageTransition?>(
nameof(PageTransition),
defaultValue: new ImmutableCrossFade(TimeSpan.FromMilliseconds(125)));
/// <summary>
/// Gets or sets the animation played when content appears and disappears.
/// </summary>
public IPageTransition? PageTransition
{
get => GetValue(PageTransitionProperty);
set => SetValue(PageTransitionProperty, value);
}
protected override Size ArrangeOverride(Size finalSize)
{
var result = base.ArrangeOverride(finalSize);
if (_shouldAnimate)
{
_currentTransition?.Cancel();
if (_presenter2 is not null &&
Presenter is Visual presenter &&
PageTransition is { } transition)
{
_shouldAnimate = false;
var cancel = new CancellationTokenSource();
_currentTransition = cancel;
var from = _isFirstFull ? _presenter2 : presenter;
var to = _isFirstFull ? presenter : _presenter2;
transition.Start(from, to, true, cancel.Token).ContinueWith(x =>
{
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;
currentPresenter.Content = Content;
currentPresenter.IsVisible = true;
_isFirstFull = !_isFirstFull;
if (PageTransition is not null && withTransition)
{
_shouldAnimate = true;
InvalidateArrange();
}
else
{
HideOldPresenter();
}
}
private void HideOldPresenter()
{
var oldPresenter = _isFirstFull ? _presenter2 : Presenter;
if (oldPresenter is not null)
{
oldPresenter.Content = null;
oldPresenter.IsVisible = false;
}
}
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);
}
}
}