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; /// /// Displays according to an , /// using a to move between the old and new content. /// public class TransitioningContentControl : ContentControl { private CancellationTokenSource? _currentTransition; 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))); /// /// Gets or sets the animation played when content appears and disappears. /// 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); } } }