diff --git a/src/Avalonia.Controls/TransitioningContentControl.cs b/src/Avalonia.Controls/TransitioningContentControl.cs index 21b9c9b765..bf540698f1 100644 --- a/src/Avalonia.Controls/TransitioningContentControl.cs +++ b/src/Avalonia.Controls/TransitioningContentControl.cs @@ -15,8 +15,9 @@ namespace Avalonia.Controls; public class TransitioningContentControl : ContentControl { private CancellationTokenSource? _currentTransition; - private ContentPresenter? _transitionPresenter; - private Optional _transitionFrom; + private ContentPresenter? _presenter2; + private bool _isFirstFull; + private bool _shouldAnimate; /// /// Defines the property. @@ -39,46 +40,52 @@ public class TransitioningContentControl : ContentControl { var result = base.ArrangeOverride(finalSize); - if (_transitionFrom.HasValue) + if (_shouldAnimate) { _currentTransition?.Cancel(); - if (_transitionPresenter is not null && + if (_presenter2 is not null && Presenter is Visual presenter && - PageTransition is { } transition && - (_transitionFrom.Value is not Visual v || v.VisualParent is null)) - { - _transitionPresenter.Content = _transitionFrom.Value; - _transitionPresenter.IsVisible = true; - _transitionFrom = Optional.Empty; + PageTransition is { } transition) + { + _shouldAnimate = false; var cancel = new CancellationTokenSource(); _currentTransition = cancel; - transition.Start(_transitionPresenter, presenter, true, cancel.Token).ContinueWith(x => + var from = _isFirstFull ? _presenter2 : presenter; + var to = _isFirstFull ? presenter : _presenter2; + + transition.Start(from, to, true, cancel.Token).ContinueWith(x => { if (!cancel.IsCancellationRequested) { - _transitionPresenter.Content = null; - _transitionPresenter.IsVisible = false; + HideOldPresenter(); } }, TaskScheduler.FromCurrentSynchronizationContext()); } - _transitionFrom = Optional.Empty; + _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) && presenter is ContentPresenter p && - p.Name == "PART_TransitionContentPresenter") + p.Name == "PART_ContentPresenter2") { - _transitionPresenter = p; - _transitionPresenter.IsVisible = false; + _presenter2 = p; + _presenter2.IsVisible = false; + UpdateContent(false); return true; } @@ -89,14 +96,44 @@ public class TransitioningContentControl : ContentControl { base.OnPropertyChanged(change); - if (change.Property == ContentProperty && - _transitionPresenter is not null && - Presenter is Visual && - PageTransition is not null) + if (change.Property == ContentProperty) { - _transitionFrom = change.GetOldValue(); + UpdateContent(true); + } + } + + 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 diff --git a/src/Avalonia.Themes.Fluent/Controls/TransitioningContentControl.xaml b/src/Avalonia.Themes.Fluent/Controls/TransitioningContentControl.xaml index 2078322318..03cb09e395 100644 --- a/src/Avalonia.Themes.Fluent/Controls/TransitioningContentControl.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/TransitioningContentControl.xaml @@ -11,11 +11,10 @@ BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="{TemplateBinding CornerRadius}" ContentTemplate="{TemplateBinding ContentTemplate}" - Content="{TemplateBinding Content}" Padding="{TemplateBinding Padding}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" /> - counter++; + + target.Content = control; + Layout(target); + target.Content = new Control(); + Layout(target); + + Assert.Equal(1, counter); + } + + [Fact] + public void ContentPresenters2_Should_Be_Setup() { using var app = Start(); var (target, transition) = CreateTarget("foo"); - var transitionPresenter = GetTransitionContentPresenter(target); + var presenter1 = target.Presenter!; + var presenter2 = GetContentPresenters2(target); target.Content = "bar"; Layout(target); - Assert.True(transitionPresenter.IsVisible); - Assert.Equal("bar", target.Presenter!.Content); - Assert.Equal("foo", transitionPresenter.Content); + Assert.True(presenter2.IsVisible); + Assert.Equal("foo", presenter1.Content); + Assert.Equal("bar", presenter2.Content); } [Fact] - public void TransitionContentPresenter_Should_Be_Hidden_When_Transition_Completes() + public void Old_Presenter_Should_Be_Hidden_When_Transition_Completes() { using var app = Start(); using var sync = UnitTestSynchronizationContext.Begin(); var (target, transition) = CreateTarget("foo"); - var transitionPresenter = GetTransitionContentPresenter(target); + var presenter1 = target.Presenter!; + var presenter2 = GetContentPresenters2(target); target.Content = "bar"; Layout(target); - Assert.True(transitionPresenter.IsVisible); + Assert.True(presenter1.IsVisible); + Assert.True(presenter2.IsVisible); transition.Complete(); sync.ExecutePostedCallbacks(); + Assert.True(presenter2.IsVisible); + Assert.False(presenter1.IsVisible); - Assert.False(transitionPresenter.IsVisible); + target.Content = "foo"; + Layout(target); + Assert.True(presenter1.IsVisible); + Assert.True(presenter2.IsVisible); + + transition.Complete(); + sync.ExecutePostedCallbacks(); + Assert.True(presenter1.IsVisible); + Assert.False(presenter2.IsVisible); } [Fact] @@ -101,7 +133,6 @@ namespace Avalonia.Controls.UnitTests using var app = Start(); using var sync = UnitTestSynchronizationContext.Begin(); var (target, transition) = CreateTarget("foo"); - var transitionPresenter = GetTransitionContentPresenter(target); target.Content = "bar"; Layout(target); @@ -120,7 +151,7 @@ namespace Avalonia.Controls.UnitTests using var app = Start(); using var sync = UnitTestSynchronizationContext.Begin(); var (target, transition) = CreateTarget("foo"); - var transitionPresenter = GetTransitionContentPresenter(target); + var presenter2 = GetContentPresenters2(target); target.Content = "bar"; Layout(target); @@ -134,7 +165,7 @@ namespace Avalonia.Controls.UnitTests var fromPresenter = Assert.IsType(from); var toPresenter = Assert.IsType(to); - Assert.Same(transitionPresenter, fromPresenter); + Assert.Same(presenter2, fromPresenter); Assert.Same(target.Presenter, toPresenter); Assert.Equal("bar", fromPresenter.Content); Assert.Equal("baz", toPresenter.Content); @@ -149,7 +180,7 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(1, startedRaised); Assert.Equal("baz", target.Presenter!.Content); - Assert.Equal("bar", transitionPresenter.Content); + Assert.Equal("bar", presenter2.Content); } private static IDisposable Start() @@ -187,22 +218,21 @@ namespace Avalonia.Controls.UnitTests new ContentPresenter { Name = "PART_ContentPresenter", - [!ContentPresenter.ContentProperty] = x[!ContentControl.ContentProperty], }, new ContentPresenter { - Name = "PART_TransitionContentPresenter", + Name = "PART_ContentPresenter2", }, } }; }); } - private static ContentPresenter GetTransitionContentPresenter(TransitioningContentControl target) + private static ContentPresenter GetContentPresenters2(TransitioningContentControl target) { return Assert.IsType(target .GetTemplateChildren() - .First(x => x.Name == "PART_TransitionContentPresenter")); + .First(x => x.Name == "PART_ContentPresenter2")); } private void Layout(Control c) @@ -227,7 +257,7 @@ namespace Avalonia.Controls.UnitTests if (_tcs is not null) throw new InvalidOperationException("Transition already running"); _tcs = new TaskCompletionSource(); - cancellationToken.Register(() => _tcs.TrySetResult()); + cancellationToken.Register(() => _tcs?.TrySetResult()); await _tcs.Task; _tcs = null;