|
@@ -1,66 +1,243 @@
|
|
|
using System;
|
|
using System;
|
|
|
-using Avalonia.LogicalTree;
|
|
|
|
|
-using Avalonia.UnitTests;
|
|
|
|
|
-using Xunit;
|
|
|
|
|
|
|
+using System.Linq;
|
|
|
using System.Threading;
|
|
using System.Threading;
|
|
|
using System.Threading.Tasks;
|
|
using System.Threading.Tasks;
|
|
|
using Avalonia.Animation;
|
|
using Avalonia.Animation;
|
|
|
|
|
+using Avalonia.Controls.Presenters;
|
|
|
|
|
+using Avalonia.Controls.Templates;
|
|
|
|
|
+using Avalonia.Headless;
|
|
|
|
|
+using Avalonia.Layout;
|
|
|
|
|
+using Avalonia.UnitTests;
|
|
|
|
|
+using Avalonia.VisualTree;
|
|
|
|
|
+using Xunit;
|
|
|
|
|
+
|
|
|
|
|
+#nullable enable
|
|
|
|
|
|
|
|
namespace Avalonia.Controls.UnitTests
|
|
namespace Avalonia.Controls.UnitTests
|
|
|
{
|
|
{
|
|
|
public class TransitioningContentControlTests
|
|
public class TransitioningContentControlTests
|
|
|
{
|
|
{
|
|
|
[Fact]
|
|
[Fact]
|
|
|
- public void Old_Content_Shuold_Be_Removed__From_Logical_Tree_After_Out_Animation()
|
|
|
|
|
|
|
+ public void Transition_Should_Not_Be_Run_When_First_Shown()
|
|
|
|
|
+ {
|
|
|
|
|
+ using var app = Start();
|
|
|
|
|
+ var (target, transition) = CreateTarget("foo");
|
|
|
|
|
+
|
|
|
|
|
+ Assert.Equal(0, transition.StartCount);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ [Fact]
|
|
|
|
|
+ public void TransitionContentPresenter_Should_Initially_Be_Hidden()
|
|
|
|
|
+ {
|
|
|
|
|
+ using var app = Start();
|
|
|
|
|
+ var (target, transition) = CreateTarget("foo");
|
|
|
|
|
+ var transitionPresenter = GetTransitionContentPresenter(target);
|
|
|
|
|
+
|
|
|
|
|
+ Assert.False(transitionPresenter.IsVisible);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ [Fact]
|
|
|
|
|
+ public void Transition_Should_Be_Run_On_Layout()
|
|
|
|
|
+ {
|
|
|
|
|
+ using var app = Start();
|
|
|
|
|
+ var (target, transition) = CreateTarget("foo");
|
|
|
|
|
+
|
|
|
|
|
+ target.Content = "bar";
|
|
|
|
|
+ Assert.Equal(0, transition.StartCount);
|
|
|
|
|
+
|
|
|
|
|
+ Layout(target);
|
|
|
|
|
+ Assert.Equal(1, transition.StartCount);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ [Fact]
|
|
|
|
|
+ public void Control_Transition_Should_Be_Run_On_Layout()
|
|
|
|
|
+ {
|
|
|
|
|
+ using var app = Start();
|
|
|
|
|
+ var (target, transition) = CreateTarget(new Button());
|
|
|
|
|
+
|
|
|
|
|
+ target.Content = new Canvas();
|
|
|
|
|
+ Assert.Equal(0, transition.StartCount);
|
|
|
|
|
+
|
|
|
|
|
+ Layout(target);
|
|
|
|
|
+ Assert.Equal(1, transition.StartCount);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ [Fact]
|
|
|
|
|
+ public void ContentPresenters_Should_Be_Setup_For_Transition()
|
|
|
|
|
+ {
|
|
|
|
|
+ using var app = Start();
|
|
|
|
|
+ var (target, transition) = CreateTarget("foo");
|
|
|
|
|
+ var transitionPresenter = GetTransitionContentPresenter(target);
|
|
|
|
|
+
|
|
|
|
|
+ target.Content = "bar";
|
|
|
|
|
+ Layout(target);
|
|
|
|
|
+
|
|
|
|
|
+ Assert.True(transitionPresenter.IsVisible);
|
|
|
|
|
+ Assert.Equal("bar", target.Presenter!.Content);
|
|
|
|
|
+ Assert.Equal("foo", transitionPresenter.Content);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ [Fact]
|
|
|
|
|
+ public void TransitionContentPresenter_Should_Be_Hidden_When_Transition_Completes()
|
|
|
|
|
+ {
|
|
|
|
|
+ using var app = Start();
|
|
|
|
|
+ using var sync = UnitTestSynchronizationContext.Begin();
|
|
|
|
|
+ var (target, transition) = CreateTarget("foo");
|
|
|
|
|
+ var transitionPresenter = GetTransitionContentPresenter(target);
|
|
|
|
|
+
|
|
|
|
|
+ target.Content = "bar";
|
|
|
|
|
+ Layout(target);
|
|
|
|
|
+ Assert.True(transitionPresenter.IsVisible);
|
|
|
|
|
+
|
|
|
|
|
+ transition.Complete();
|
|
|
|
|
+ sync.ExecutePostedCallbacks();
|
|
|
|
|
+
|
|
|
|
|
+ Assert.False(transitionPresenter.IsVisible);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ [Fact]
|
|
|
|
|
+ public void Transition_Should_Be_Canceled_If_Content_Changes_While_Running()
|
|
|
{
|
|
{
|
|
|
- using (UnitTestApplication.Start(TestServices.StyledWindow))
|
|
|
|
|
|
|
+ using var app = Start();
|
|
|
|
|
+ using var sync = UnitTestSynchronizationContext.Begin();
|
|
|
|
|
+ var (target, transition) = CreateTarget("foo");
|
|
|
|
|
+ var transitionPresenter = GetTransitionContentPresenter(target);
|
|
|
|
|
+
|
|
|
|
|
+ target.Content = "bar";
|
|
|
|
|
+ Layout(target);
|
|
|
|
|
+ target.Content = "baz";
|
|
|
|
|
+
|
|
|
|
|
+ Assert.Equal(0, transition.CancelCount);
|
|
|
|
|
+
|
|
|
|
|
+ Layout(target);
|
|
|
|
|
+
|
|
|
|
|
+ Assert.Equal(1, transition.CancelCount);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ [Fact]
|
|
|
|
|
+ public void New_Transition_Should_Be_Started_If_Content_Changes_While_Running()
|
|
|
|
|
+ {
|
|
|
|
|
+ using var app = Start();
|
|
|
|
|
+ using var sync = UnitTestSynchronizationContext.Begin();
|
|
|
|
|
+ var (target, transition) = CreateTarget("foo");
|
|
|
|
|
+ var transitionPresenter = GetTransitionContentPresenter(target);
|
|
|
|
|
+
|
|
|
|
|
+ target.Content = "bar";
|
|
|
|
|
+ Layout(target);
|
|
|
|
|
+
|
|
|
|
|
+ target.Content = "baz";
|
|
|
|
|
+
|
|
|
|
|
+ var startedRaised = 0;
|
|
|
|
|
+
|
|
|
|
|
+ transition.Started += (from, to, forward) =>
|
|
|
{
|
|
{
|
|
|
- var testTransition = new TestTransition();
|
|
|
|
|
|
|
+ var fromPresenter = Assert.IsType<ContentPresenter>(from);
|
|
|
|
|
+ var toPresenter = Assert.IsType<ContentPresenter>(to);
|
|
|
|
|
+
|
|
|
|
|
+ Assert.Same(transitionPresenter, fromPresenter);
|
|
|
|
|
+ Assert.Same(target.Presenter, toPresenter);
|
|
|
|
|
+ Assert.Equal("bar", fromPresenter.Content);
|
|
|
|
|
+ Assert.Equal("baz", toPresenter.Content);
|
|
|
|
|
+ Assert.True(forward);
|
|
|
|
|
+ Assert.Equal(1, transition.CancelCount);
|
|
|
|
|
|
|
|
- var target = new TransitioningContentControl();
|
|
|
|
|
- target.PageTransition = testTransition;
|
|
|
|
|
|
|
+ ++startedRaised;
|
|
|
|
|
+ };
|
|
|
|
|
|
|
|
- var root = new TestRoot() { Child = target };
|
|
|
|
|
|
|
+ Layout(target);
|
|
|
|
|
+ sync.ExecutePostedCallbacks();
|
|
|
|
|
+
|
|
|
|
|
+ Assert.Equal(1, startedRaised);
|
|
|
|
|
+ Assert.Equal("baz", target.Presenter!.Content);
|
|
|
|
|
+ Assert.Equal("bar", transitionPresenter.Content);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- var oldControl = new Control();
|
|
|
|
|
- var newControl = new Control();
|
|
|
|
|
|
|
+ private static IDisposable Start()
|
|
|
|
|
+ {
|
|
|
|
|
+ return UnitTestApplication.Start(
|
|
|
|
|
+ TestServices.MockThreadingInterface.With(
|
|
|
|
|
+ fontManagerImpl: new HeadlessFontManagerStub(),
|
|
|
|
|
+ renderInterface: new HeadlessPlatformRenderInterface(),
|
|
|
|
|
+ textShaperImpl: new HeadlessTextShaperStub()));
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- target.Content = oldControl;
|
|
|
|
|
- Threading.Dispatcher.UIThread.RunJobs();
|
|
|
|
|
|
|
+ private static (TransitioningContentControl, TestTransition) CreateTarget(object content)
|
|
|
|
|
+ {
|
|
|
|
|
+ var transition = new TestTransition();
|
|
|
|
|
+ var target = new TransitioningContentControl
|
|
|
|
|
+ {
|
|
|
|
|
+ Content = content,
|
|
|
|
|
+ PageTransition = transition,
|
|
|
|
|
+ Template = CreateTemplate(),
|
|
|
|
|
+ };
|
|
|
|
|
|
|
|
- Assert.Equal(target, oldControl.GetLogicalParent());
|
|
|
|
|
- Assert.Equal(null, newControl.GetLogicalParent());
|
|
|
|
|
|
|
+ var root = new TestRoot(target);
|
|
|
|
|
+ root.LayoutManager.ExecuteInitialLayoutPass();
|
|
|
|
|
+ return (target, transition);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- testTransition.BeginTransition += isFrom =>
|
|
|
|
|
|
|
+ private static IControlTemplate CreateTemplate()
|
|
|
|
|
+ {
|
|
|
|
|
+ return new FuncControlTemplate((x, ns) =>
|
|
|
|
|
+ {
|
|
|
|
|
+ return new Panel
|
|
|
{
|
|
{
|
|
|
- // Old out
|
|
|
|
|
- if (isFrom)
|
|
|
|
|
|
|
+ Children =
|
|
|
{
|
|
{
|
|
|
- Assert.Equal(target, oldControl.GetLogicalParent());
|
|
|
|
|
- Assert.Equal(null, newControl.GetLogicalParent());
|
|
|
|
|
- }
|
|
|
|
|
- // New in
|
|
|
|
|
- else
|
|
|
|
|
- {
|
|
|
|
|
- Assert.Equal(null, oldControl.GetLogicalParent());
|
|
|
|
|
- Assert.Equal(target, newControl.GetLogicalParent());
|
|
|
|
|
|
|
+ new ContentPresenter
|
|
|
|
|
+ {
|
|
|
|
|
+ Name = "PART_ContentPresenter",
|
|
|
|
|
+ [!ContentPresenter.ContentProperty] = x[!ContentControl.ContentProperty],
|
|
|
|
|
+ },
|
|
|
|
|
+ new ContentPresenter
|
|
|
|
|
+ {
|
|
|
|
|
+ Name = "PART_TransitionContentPresenter",
|
|
|
|
|
+ },
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- target.Content = newControl;
|
|
|
|
|
- Threading.Dispatcher.UIThread.RunJobs();
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ private static ContentPresenter GetTransitionContentPresenter(TransitioningContentControl target)
|
|
|
|
|
+ {
|
|
|
|
|
+ return Assert.IsType<ContentPresenter>(target
|
|
|
|
|
+ .GetTemplateChildren()
|
|
|
|
|
+ .First(x => x.Name == "PART_TransitionContentPresenter"));
|
|
|
}
|
|
}
|
|
|
- }
|
|
|
|
|
- public class TestTransition : IPageTransition
|
|
|
|
|
- {
|
|
|
|
|
- public event Action<bool> BeginTransition;
|
|
|
|
|
|
|
|
|
|
- public Task Start(Visual from, Visual to, bool forward, CancellationToken cancellationToken)
|
|
|
|
|
|
|
+ private void Layout(Control c)
|
|
|
{
|
|
{
|
|
|
- bool isFrom = from != null && to == null;
|
|
|
|
|
- BeginTransition?.Invoke(isFrom);
|
|
|
|
|
- return Task.CompletedTask;
|
|
|
|
|
|
|
+ (c.GetVisualRoot() as ILayoutRoot)?.LayoutManager.ExecuteLayoutPass();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private class TestTransition : IPageTransition
|
|
|
|
|
+ {
|
|
|
|
|
+ private TaskCompletionSource? _tcs;
|
|
|
|
|
+
|
|
|
|
|
+ public int StartCount { get; private set; }
|
|
|
|
|
+ public int FinishCount { get; private set; }
|
|
|
|
|
+ public int CancelCount { get; private set; }
|
|
|
|
|
+
|
|
|
|
|
+ public event Action<Visual?, Visual?, bool>? Started;
|
|
|
|
|
+
|
|
|
|
|
+ public async Task Start(Visual? from, Visual? to, bool forward, CancellationToken cancellationToken)
|
|
|
|
|
+ {
|
|
|
|
|
+ ++StartCount;
|
|
|
|
|
+ Started?.Invoke(from, to, forward);
|
|
|
|
|
+ if (_tcs is not null)
|
|
|
|
|
+ throw new InvalidOperationException("Transition already running");
|
|
|
|
|
+ _tcs = new TaskCompletionSource();
|
|
|
|
|
+ cancellationToken.Register(() => _tcs.TrySetResult());
|
|
|
|
|
+ await _tcs.Task;
|
|
|
|
|
+ _tcs = null;
|
|
|
|
|
+
|
|
|
|
|
+ if (!cancellationToken.IsCancellationRequested)
|
|
|
|
|
+ ++FinishCount;
|
|
|
|
|
+ else
|
|
|
|
|
+ ++CancelCount;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void Complete() => _tcs!.TrySetResult();
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|