From 5157ebccaeb91825946cced24af1594ee83317aa Mon Sep 17 00:00:00 2001 From: daniel Date: Fri, 9 Jun 2023 08:02:36 +0300 Subject: [PATCH 1/3] Added failing test for #11668 --- .../TransitioningContentControlTests.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs b/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs index 77fc207554..be3c35e8e0 100644 --- a/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs @@ -62,6 +62,25 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(1, transition.StartCount); } + [Fact] + public void Control_Should_Connect_To_VisualTree_Once() + { + using var app = Start(); + var (target, transition) = CreateTarget(new Control()); + + var control = new Control(); + int counter = 0; + + control.AttachedToVisualTree += (s,e) => counter++; + + target.Content = control; + Layout(target); + target.Content = new Control(); + Layout(target); + + Assert.Equal(1, counter); + } + [Fact] public void ContentPresenters_Should_Be_Setup_For_Transition() { From 849d4995393cb6ca5be4a501a5da7dadc216eb74 Mon Sep 17 00:00:00 2001 From: daniel Date: Fri, 9 Jun 2023 08:13:35 +0300 Subject: [PATCH 2/3] Use two presenter --- .../TransitioningContentControl.cs | 81 ++++++++++++++----- .../Controls/TransitioningContentControl.xaml | 3 +- 2 files changed, 60 insertions(+), 24 deletions(-) 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}" /> - Date: Fri, 9 Jun 2023 08:15:50 +0300 Subject: [PATCH 3/3] Fix tests --- .../TransitioningContentControlTests.cs | 53 +++++++++++-------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs b/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs index be3c35e8e0..02dd4e6c03 100644 --- a/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs @@ -27,13 +27,13 @@ namespace Avalonia.Controls.UnitTests } [Fact] - public void TransitionContentPresenter_Should_Initially_Be_Hidden() + public void ContentPresenters2_Should_Initially_Be_Hidden() { using var app = Start(); var (target, transition) = CreateTarget("foo"); - var transitionPresenter = GetTransitionContentPresenter(target); + var presenter2 = GetContentPresenters2(target); - Assert.False(transitionPresenter.IsVisible); + Assert.False(presenter2.IsVisible); } [Fact] @@ -82,36 +82,49 @@ namespace Avalonia.Controls.UnitTests } [Fact] - public void ContentPresenters_Should_Be_Setup_For_Transition() + 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] @@ -120,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); @@ -139,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); @@ -153,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); @@ -168,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() @@ -206,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) @@ -246,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;