Browse Source

Merge pull request #11721 from danielmayost/TCCUseTwoPresenters

Rework TransitioningContentControl
pull/11798/head
Max Katz 3 years ago
committed by GitHub
parent
commit
1f2fe2b72e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 81
      src/Avalonia.Controls/TransitioningContentControl.cs
  2. 3
      src/Avalonia.Themes.Fluent/Controls/TransitioningContentControl.xaml
  3. 72
      tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs

81
src/Avalonia.Controls/TransitioningContentControl.cs

@ -15,8 +15,9 @@ namespace Avalonia.Controls;
public class TransitioningContentControl : ContentControl
{
private CancellationTokenSource? _currentTransition;
private ContentPresenter? _transitionPresenter;
private Optional<object?> _transitionFrom;
private ContentPresenter? _presenter2;
private bool _isFirstFull;
private bool _shouldAnimate;
/// <summary>
/// Defines the <see cref="PageTransition"/> 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<object?>.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<object?>.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<object?>();
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

3
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}" />
<ContentPresenter Name="PART_TransitionContentPresenter"
<ContentPresenter Name="PART_ContentPresenter2"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"

72
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]
@ -63,36 +63,68 @@ namespace Avalonia.Controls.UnitTests
}
[Fact]
public void ContentPresenters_Should_Be_Setup_For_Transition()
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 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<ContentPresenter>(from);
var toPresenter = Assert.IsType<ContentPresenter>(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<ContentPresenter>(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;

Loading…
Cancel
Save