Browse Source

Merge pull request #6018 from AvaloniaUI/cancellable-animations-expander-fix

Cancellable animations (for async methods) + Expander fix
# Conflicts:
#	src/Avalonia.Visuals/ApiCompatBaseline.txt
release/0.10.11-rc.1
Max Katz 5 years ago
committed by Dan Walmsley
parent
commit
85e7643caa
  1. 30
      src/Avalonia.Animation/Animation.cs
  2. 6
      src/Avalonia.Animation/ApiCompatBaseline.txt
  3. 3
      src/Avalonia.Animation/IAnimation.cs
  4. 21
      src/Avalonia.Controls/Expander.cs
  5. 2
      src/Avalonia.Controls/Presenters/CarouselPresenter.cs
  6. 13
      src/Avalonia.ReactiveUI/TransitioningContentControl.cs
  7. 21
      src/Avalonia.Visuals/Animation/CompositePageTransition.cs
  8. 64
      src/Avalonia.Visuals/Animation/CrossFade.cs
  9. 6
      src/Avalonia.Visuals/Animation/IPageTransition.cs
  10. 30
      src/Avalonia.Visuals/Animation/PageSlide.cs
  11. 11
      src/Avalonia.Visuals/ApiCompatBaseline.txt
  12. 268
      tests/Avalonia.Animation.UnitTests/AnimationIterationTests.cs

30
src/Avalonia.Animation/Animation.cs

@ -3,10 +3,11 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Animation.Animators; using Avalonia.Animation.Animators;
using Avalonia.Animation.Easings; using Avalonia.Animation.Easings;
using Avalonia.Collections;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Metadata; using Avalonia.Metadata;
@ -319,7 +320,7 @@ namespace Avalonia.Animation
return (newAnimatorInstances, subscriptions); return (newAnimatorInstances, subscriptions);
} }
/// <inheritdocs/> /// <inheritdoc/>
public IDisposable Apply(Animatable control, IClock clock, IObservable<bool> match, Action onComplete) public IDisposable Apply(Animatable control, IClock clock, IObservable<bool> match, Action onComplete)
{ {
var (animators, subscriptions) = InterpretKeyframes(control); var (animators, subscriptions) = InterpretKeyframes(control);
@ -344,25 +345,40 @@ namespace Avalonia.Animation
if (onComplete != null) if (onComplete != null)
{ {
Task.WhenAll(completionTasks).ContinueWith(_ => onComplete()); Task.WhenAll(completionTasks).ContinueWith(
(_, state) => ((Action)state).Invoke(),
onComplete);
} }
} }
return new CompositeDisposable(subscriptions); return new CompositeDisposable(subscriptions);
} }
/// <inheritdocs/> /// <inheritdoc/>
public Task RunAsync(Animatable control, IClock clock = null) public Task RunAsync(Animatable control, IClock clock = null, CancellationToken cancellationToken = default)
{ {
if (cancellationToken.IsCancellationRequested)
{
return Task.CompletedTask;
}
var run = new TaskCompletionSource<object>(); var run = new TaskCompletionSource<object>();
if (this.IterationCount == IterationCount.Infinite) if (this.IterationCount == IterationCount.Infinite)
run.SetException(new InvalidOperationException("Looping animations must not use the Run method.")); run.SetException(new InvalidOperationException("Looping animations must not use the Run method."));
IDisposable subscriptions = null; IDisposable subscriptions = null, cancellation = null;
subscriptions = this.Apply(control, clock, Observable.Return(true), () => subscriptions = this.Apply(control, clock, Observable.Return(true), () =>
{ {
run.SetResult(null); run.TrySetResult(null);
subscriptions?.Dispose();
cancellation?.Dispose();
});
cancellation = cancellationToken.Register(() =>
{
run.TrySetResult(null);
subscriptions?.Dispose(); subscriptions?.Dispose();
cancellation?.Dispose();
}); });
return run.Task; return run.Task;

6
src/Avalonia.Animation/ApiCompatBaseline.txt

@ -0,0 +1,6 @@
Compat issues with assembly Avalonia.Animation:
MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.Animation.RunAsync(Avalonia.Animation.Animatable, Avalonia.Animation.IClock)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Threading.Tasks.Task Avalonia.Animation.IAnimation.RunAsync(Avalonia.Animation.Animatable, Avalonia.Animation.IClock)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.IAnimation.RunAsync(Avalonia.Animation.Animatable, Avalonia.Animation.IClock)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Threading.Tasks.Task Avalonia.Animation.IAnimation.RunAsync(Avalonia.Animation.Animatable, Avalonia.Animation.IClock, System.Threading.CancellationToken)' is present in the implementation but not in the contract.
Total Issues: 4

3
src/Avalonia.Animation/IAnimation.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Avalonia.Animation namespace Avalonia.Animation
@ -16,6 +17,6 @@ namespace Avalonia.Animation
/// <summary> /// <summary>
/// Run the animation on the specified control. /// Run the animation on the specified control.
/// </summary> /// </summary>
Task RunAsync(Animatable control, IClock clock); Task RunAsync(Animatable control, IClock clock, CancellationToken cancellationToken = default);
} }
} }

21
src/Avalonia.Controls/Expander.cs

@ -1,7 +1,11 @@
using System.Threading;
using Avalonia.Animation; using Avalonia.Animation;
using Avalonia.Controls.Metadata; using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
#nullable enable
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
/// <summary> /// <summary>
@ -36,8 +40,8 @@ namespace Avalonia.Controls
[PseudoClasses(":expanded", ":up", ":down", ":left", ":right")] [PseudoClasses(":expanded", ":up", ":down", ":left", ":right")]
public class Expander : HeaderedContentControl public class Expander : HeaderedContentControl
{ {
public static readonly StyledProperty<IPageTransition> ContentTransitionProperty = public static readonly StyledProperty<IPageTransition?> ContentTransitionProperty =
AvaloniaProperty.Register<Expander, IPageTransition>(nameof(ContentTransition)); AvaloniaProperty.Register<Expander, IPageTransition?>(nameof(ContentTransition));
public static readonly StyledProperty<ExpandDirection> ExpandDirectionProperty = public static readonly StyledProperty<ExpandDirection> ExpandDirectionProperty =
AvaloniaProperty.Register<Expander, ExpandDirection>(nameof(ExpandDirection), ExpandDirection.Down); AvaloniaProperty.Register<Expander, ExpandDirection>(nameof(ExpandDirection), ExpandDirection.Down);
@ -50,6 +54,7 @@ namespace Avalonia.Controls
defaultBindingMode: Data.BindingMode.TwoWay); defaultBindingMode: Data.BindingMode.TwoWay);
private bool _isExpanded; private bool _isExpanded;
private CancellationTokenSource? _lastTransitionCts;
static Expander() static Expander()
{ {
@ -61,7 +66,7 @@ namespace Avalonia.Controls
UpdatePseudoClasses(ExpandDirection); UpdatePseudoClasses(ExpandDirection);
} }
public IPageTransition ContentTransition public IPageTransition? ContentTransition
{ {
get => GetValue(ContentTransitionProperty); get => GetValue(ContentTransitionProperty);
set => SetValue(ContentTransitionProperty, value); set => SetValue(ContentTransitionProperty, value);
@ -83,19 +88,23 @@ namespace Avalonia.Controls
} }
} }
protected virtual void OnIsExpandedChanged(AvaloniaPropertyChangedEventArgs e) protected virtual async void OnIsExpandedChanged(AvaloniaPropertyChangedEventArgs e)
{ {
if (Content != null && ContentTransition != null && Presenter is Visual visualContent) if (Content != null && ContentTransition != null && Presenter is Visual visualContent)
{ {
bool forward = ExpandDirection == ExpandDirection.Left || bool forward = ExpandDirection == ExpandDirection.Left ||
ExpandDirection == ExpandDirection.Up; ExpandDirection == ExpandDirection.Up;
_lastTransitionCts?.Cancel();
_lastTransitionCts = new CancellationTokenSource();
if (IsExpanded) if (IsExpanded)
{ {
ContentTransition.Start(null, visualContent, forward); await ContentTransition.Start(null, visualContent, forward, _lastTransitionCts.Token);
} }
else else
{ {
ContentTransition.Start(visualContent, null, !forward); await ContentTransition.Start(visualContent, null, forward, _lastTransitionCts.Token);
} }
} }
} }

2
src/Avalonia.Controls/Presenters/CarouselPresenter.cs

@ -186,7 +186,7 @@ namespace Avalonia.Controls.Presenters
if (PageTransition != null && (from != null || to != null)) if (PageTransition != null && (from != null || to != null))
{ {
await PageTransition.Start((Visual)from, (Visual)to, fromIndex < toIndex); await PageTransition.Start((Visual)from, (Visual)to, fromIndex < toIndex, default);
} }
else if (to != null) else if (to != null)
{ {

13
src/Avalonia.ReactiveUI/TransitioningContentControl.cs

@ -1,4 +1,6 @@
using System; using System;
using System.Threading;
using Avalonia.Animation; using Avalonia.Animation;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Styling; using Avalonia.Styling;
@ -22,7 +24,9 @@ namespace Avalonia.ReactiveUI
/// </summary> /// </summary>
public static readonly StyledProperty<object?> DefaultContentProperty = public static readonly StyledProperty<object?> DefaultContentProperty =
AvaloniaProperty.Register<TransitioningContentControl, object?>(nameof(DefaultContent)); AvaloniaProperty.Register<TransitioningContentControl, object?>(nameof(DefaultContent));
private CancellationTokenSource? _lastTransitionCts;
/// <summary> /// <summary>
/// Gets or sets the animation played when content appears and disappears. /// Gets or sets the animation played when content appears and disappears.
/// </summary> /// </summary>
@ -62,11 +66,14 @@ namespace Avalonia.ReactiveUI
/// <param name="content">New content to set.</param> /// <param name="content">New content to set.</param>
private async void UpdateContentWithTransition(object? content) private async void UpdateContentWithTransition(object? content)
{ {
_lastTransitionCts?.Cancel();
_lastTransitionCts = new CancellationTokenSource();
if (PageTransition != null) if (PageTransition != null)
await PageTransition.Start(this, null, true); await PageTransition.Start(this, null, true, _lastTransitionCts.Token);
base.Content = content; base.Content = content;
if (PageTransition != null) if (PageTransition != null)
await PageTransition.Start(null, this, true); await PageTransition.Start(null, this, true, _lastTransitionCts.Token);
} }
} }
} }

21
src/Avalonia.Visuals/Animation/CompositePageTransition.cs

@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Metadata; using Avalonia.Metadata;
@ -35,25 +36,11 @@ namespace Avalonia.Animation
[Content] [Content]
public List<IPageTransition> PageTransitions { get; set; } = new List<IPageTransition>(); public List<IPageTransition> PageTransitions { get; set; } = new List<IPageTransition>();
/// <summary> /// <inheritdoc />
/// Starts the animation. public Task Start(Visual from, Visual to, bool forward, CancellationToken cancellationToken)
/// </summary>
/// <param name="from">
/// The control that is being transitioned away from. May be null.
/// </param>
/// <param name="to">
/// The control that is being transitioned to. May be null.
/// </param>
/// <param name="forward">
/// Defines the direction of the transition.
/// </param>
/// <returns>
/// A <see cref="Task"/> that tracks the progress of the animation.
/// </returns>
public Task Start(Visual from, Visual to, bool forward)
{ {
var transitionTasks = PageTransitions var transitionTasks = PageTransitions
.Select(transition => transition.Start(from, to, forward)) .Select(transition => transition.Start(from, to, forward, cancellationToken))
.ToList(); .ToList();
return Task.WhenAll(transitionTasks); return Task.WhenAll(transitionTasks);
} }

64
src/Avalonia.Visuals/Animation/CrossFade.cs

@ -1,5 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reactive.Disposables;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Animation.Easings; using Avalonia.Animation.Easings;
using Avalonia.Styling; using Avalonia.Styling;
@ -97,49 +99,39 @@ namespace Avalonia.Animation
set => _fadeOutAnimation.Easing = value; set => _fadeOutAnimation.Easing = value;
} }
/// <summary> /// <inheritdoc cref="Start(Visual, Visual, CancellationToken)" />
/// Starts the animation. public async Task Start(Visual from, Visual to, CancellationToken cancellationToken)
/// </summary>
/// <param name="from">
/// The control that is being transitioned away from. May be null.
/// </param>
/// <param name="to">
/// The control that is being transitioned to. May be null.
/// </param>
/// <returns>
/// A <see cref="Task"/> that tracks the progress of the animation.
/// </returns>
public async Task Start(Visual from, Visual to)
{ {
var tasks = new List<Task>(); if (cancellationToken.IsCancellationRequested)
if (to != null)
{
to.Opacity = 0;
}
if (from != null)
{ {
tasks.Add(_fadeOutAnimation.RunAsync(from)); return;
} }
if (to != null) var tasks = new List<Task>();
using (var disposables = new CompositeDisposable())
{ {
to.IsVisible = true; if (to != null)
tasks.Add(_fadeInAnimation.RunAsync(to)); {
disposables.Add(to.SetValue(Visual.OpacityProperty, 0, Data.BindingPriority.Animation));
}
} if (from != null)
{
tasks.Add(_fadeOutAnimation.RunAsync(from, null, cancellationToken));
}
await Task.WhenAll(tasks); if (to != null)
{
to.IsVisible = true;
tasks.Add(_fadeInAnimation.RunAsync(to, null, cancellationToken));
}
if (from != null) await Task.WhenAll(tasks);
{
from.IsVisible = false;
}
if (to != null) if (from != null && !cancellationToken.IsCancellationRequested)
{ {
to.Opacity = 1; from.IsVisible = false;
}
} }
} }
@ -159,9 +151,9 @@ namespace Avalonia.Animation
/// <returns> /// <returns>
/// A <see cref="Task"/> that tracks the progress of the animation. /// A <see cref="Task"/> that tracks the progress of the animation.
/// </returns> /// </returns>
Task IPageTransition.Start(Visual from, Visual to, bool forward) Task IPageTransition.Start(Visual from, Visual to, bool forward, CancellationToken cancellationToken)
{ {
return Start(from, to); return Start(from, to, cancellationToken);
} }
} }
} }

6
src/Avalonia.Visuals/Animation/IPageTransition.cs

@ -1,3 +1,4 @@
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Avalonia.Animation namespace Avalonia.Animation
@ -19,9 +20,12 @@ namespace Avalonia.Animation
/// <param name="forward"> /// <param name="forward">
/// If the animation is bidirectional, controls the direction of the animation. /// If the animation is bidirectional, controls the direction of the animation.
/// </param> /// </param>
/// <param name="cancellationToken">
/// Animation cancellation.
/// </param>
/// <returns> /// <returns>
/// A <see cref="Task"/> that tracks the progress of the animation. /// A <see cref="Task"/> that tracks the progress of the animation.
/// </returns> /// </returns>
Task Start(Visual from, Visual to, bool forward); Task Start(Visual from, Visual to, bool forward, CancellationToken cancellationToken);
} }
} }

30
src/Avalonia.Visuals/Animation/PageSlide.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Animation.Easings; using Avalonia.Animation.Easings;
using Avalonia.Media; using Avalonia.Media;
@ -60,23 +61,14 @@ namespace Avalonia.Animation
/// </summary> /// </summary>
public Easing SlideOutEasing { get; set; } = new LinearEasing(); public Easing SlideOutEasing { get; set; } = new LinearEasing();
/// <summary> /// <inheritdoc />
/// Starts the animation. public async Task Start(Visual from, Visual to, bool forward, CancellationToken cancellationToken)
/// </summary>
/// <param name="from">
/// The control that is being transitioned away from. May be null.
/// </param>
/// <param name="to">
/// The control that is being transitioned to. May be null.
/// </param>
/// <param name="forward">
/// If true, the new page is slid in from the right, or if false from the left.
/// </param>
/// <returns>
/// A <see cref="Task"/> that tracks the progress of the animation.
/// </returns>
public async Task Start(Visual from, Visual to, bool forward)
{ {
if (cancellationToken.IsCancellationRequested)
{
return;
}
var tasks = new List<Task>(); var tasks = new List<Task>();
var parent = GetVisualParent(from, to); var parent = GetVisualParent(from, to);
var distance = Orientation == SlideAxis.Horizontal ? parent.Bounds.Width : parent.Bounds.Height; var distance = Orientation == SlideAxis.Horizontal ? parent.Bounds.Width : parent.Bounds.Height;
@ -109,7 +101,7 @@ namespace Avalonia.Animation
}, },
Duration = Duration Duration = Duration
}; };
tasks.Add(animation.RunAsync(from)); tasks.Add(animation.RunAsync(from, null, cancellationToken));
} }
if (to != null) if (to != null)
@ -140,12 +132,12 @@ namespace Avalonia.Animation
}, },
Duration = Duration Duration = Duration
}; };
tasks.Add(animation.RunAsync(to)); tasks.Add(animation.RunAsync(to, null, cancellationToken));
} }
await Task.WhenAll(tasks); await Task.WhenAll(tasks);
if (from != null) if (from != null && !cancellationToken.IsCancellationRequested)
{ {
from.IsVisible = false; from.IsVisible = false;
} }

11
src/Avalonia.Visuals/ApiCompatBaseline.txt

@ -1,4 +1,10 @@
Compat issues with assembly Avalonia.Visuals: Compat issues with assembly Avalonia.Visuals:
MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.CompositePageTransition.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.CrossFade.Start(Avalonia.Visual, Avalonia.Visual)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Threading.Tasks.Task Avalonia.Animation.IPageTransition.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.IPageTransition.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Threading.Tasks.Task Avalonia.Animation.IPageTransition.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean, System.Threading.CancellationToken)' is present in the implementation but not in the contract.
MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.PageSlide.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.PopBitmapBlendMode()' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.PopBitmapBlendMode()' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.PushBitmapBlendMode(Avalonia.Visuals.Media.Imaging.BitmapBlendingMode)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.PushBitmapBlendMode(Avalonia.Visuals.Media.Imaging.BitmapBlendingMode)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Double Avalonia.Platform.IGeometryImpl.ContourLength' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Double Avalonia.Platform.IGeometryImpl.ContourLength' is present in the implementation but not in the contract.
@ -8,11 +14,8 @@ InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalon
InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IGeometryImpl.TryGetSegment(System.Double, System.Double, System.Boolean, Avalonia.Platform.IGeometryImpl)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IGeometryImpl.TryGetSegment(System.Double, System.Double, System.Boolean, Avalonia.Platform.IGeometryImpl)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IGeometryImpl Avalonia.Platform.IPlatformRenderInterface.CreateCombinedGeometry(Avalonia.Media.GeometryCombineMode, Avalonia.Media.Geometry, Avalonia.Media.Geometry)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IGeometryImpl Avalonia.Platform.IPlatformRenderInterface.CreateCombinedGeometry(Avalonia.Media.GeometryCombineMode, Avalonia.Media.Geometry, Avalonia.Media.Geometry)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IGeometryImpl Avalonia.Platform.IPlatformRenderInterface.CreateGeometryGroup(Avalonia.Media.FillRule, System.Collections.Generic.IReadOnlyList<Avalonia.Media.Geometry>)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IGeometryImpl Avalonia.Platform.IPlatformRenderInterface.CreateGeometryGroup(Avalonia.Media.FillRule, System.Collections.Generic.IReadOnlyList<Avalonia.Media.Geometry>)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IGlyphRunImpl Avalonia.Platform.IPlatformRenderInterface.CreateGlyphRun(Avalonia.Media.GlyphRun)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IGlyphRunImpl Avalonia.Platform.IPlatformRenderInterface.CreateGlyphRun(Avalonia.Media.GlyphRun, System.Double)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public Avalonia.Platform.IGlyphRunImpl Avalonia.Platform.IPlatformRenderInterface.CreateGlyphRun(Avalonia.Media.GlyphRun, System.Double)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmap(System.IO.Stream)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmap(System.IO.Stream)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmap(System.String)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmap(System.String)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToHeight(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToHeight(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToWidth(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToWidth(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract.
Total Issues: 17 Total Issues: 19

268
tests/Avalonia.Animation.UnitTests/AnimationIterationTests.cs

@ -9,6 +9,8 @@ using Avalonia.UnitTests;
using Avalonia.Data; using Avalonia.Data;
using Xunit; using Xunit;
using Avalonia.Animation.Easings; using Avalonia.Animation.Easings;
using System.Threading;
using System.Reactive.Linq;
namespace Avalonia.Animation.UnitTests namespace Avalonia.Animation.UnitTests
{ {
@ -176,5 +178,271 @@ namespace Avalonia.Animation.UnitTests
clock.Step(TimeSpan.FromSeconds(0.100d)); clock.Step(TimeSpan.FromSeconds(0.100d));
Assert.Equal(border.Width, 300d); Assert.Equal(border.Width, 300d);
} }
[Fact(Skip = "See #6111")]
public void Dispose_Subscription_Should_Stop_Animation()
{
var keyframe1 = new KeyFrame()
{
Setters =
{
new Setter(Border.WidthProperty, 200d),
},
Cue = new Cue(1d)
};
var keyframe2 = new KeyFrame()
{
Setters =
{
new Setter(Border.WidthProperty, 100d),
},
Cue = new Cue(0d)
};
var animation = new Animation()
{
Duration = TimeSpan.FromSeconds(10),
Delay = TimeSpan.FromSeconds(0),
DelayBetweenIterations = TimeSpan.FromSeconds(0),
IterationCount = new IterationCount(1),
Children =
{
keyframe2,
keyframe1
}
};
var border = new Border()
{
Height = 100d,
Width = 50d
};
var propertyChangedCount = 0;
var animationCompletedCount = 0;
border.PropertyChanged += (sender, e) =>
{
if (e.Property == Control.WidthProperty)
{
propertyChangedCount++;
}
};
var clock = new TestClock();
var disposable = animation.Apply(border, clock, Observable.Return(true), () => animationCompletedCount++);
Assert.Equal(0, propertyChangedCount);
clock.Step(TimeSpan.FromSeconds(0));
Assert.Equal(0, animationCompletedCount);
Assert.Equal(1, propertyChangedCount);
disposable.Dispose();
// Clock ticks should be ignored after Dispose
clock.Step(TimeSpan.FromSeconds(5));
clock.Step(TimeSpan.FromSeconds(6));
clock.Step(TimeSpan.FromSeconds(7));
// On animation disposing (cancellation) on completed is not invoked (is it expected?)
Assert.Equal(0, animationCompletedCount);
// Initial property changed before cancellation + animation value removal.
Assert.Equal(2, propertyChangedCount);
}
[Fact]
public void Do_Not_Run_Cancelled_Animation()
{
var keyframe1 = new KeyFrame()
{
Setters =
{
new Setter(Border.WidthProperty, 200d),
},
Cue = new Cue(1d)
};
var keyframe2 = new KeyFrame()
{
Setters =
{
new Setter(Border.WidthProperty, 100d),
},
Cue = new Cue(0d)
};
var animation = new Animation()
{
Duration = TimeSpan.FromSeconds(10),
Delay = TimeSpan.FromSeconds(0),
DelayBetweenIterations = TimeSpan.FromSeconds(0),
IterationCount = new IterationCount(1),
Children =
{
keyframe2,
keyframe1
}
};
var border = new Border()
{
Height = 100d,
Width = 100d
};
var propertyChangedCount = 0;
border.PropertyChanged += (sender, e) =>
{
if (e.Property == Control.WidthProperty)
{
propertyChangedCount++;
}
};
var clock = new TestClock();
var cancellationTokenSource = new CancellationTokenSource();
cancellationTokenSource.Cancel();
var animationRun = animation.RunAsync(border, clock, cancellationTokenSource.Token);
clock.Step(TimeSpan.FromSeconds(10));
Assert.Equal(0, propertyChangedCount);
Assert.True(animationRun.IsCompleted);
}
[Fact(Skip = "See #6111")]
public void Cancellation_Should_Stop_Animation()
{
var keyframe1 = new KeyFrame()
{
Setters =
{
new Setter(Border.WidthProperty, 200d),
},
Cue = new Cue(1d)
};
var keyframe2 = new KeyFrame()
{
Setters =
{
new Setter(Border.WidthProperty, 100d),
},
Cue = new Cue(0d)
};
var animation = new Animation()
{
Duration = TimeSpan.FromSeconds(10),
Delay = TimeSpan.FromSeconds(0),
DelayBetweenIterations = TimeSpan.FromSeconds(0),
IterationCount = new IterationCount(1),
Children =
{
keyframe2,
keyframe1
}
};
var border = new Border()
{
Height = 100d,
Width = 50d
};
var propertyChangedCount = 0;
border.PropertyChanged += (sender, e) =>
{
if (e.Property == Control.WidthProperty)
{
propertyChangedCount++;
}
};
var clock = new TestClock();
var cancellationTokenSource = new CancellationTokenSource();
var animationRun = animation.RunAsync(border, clock, cancellationTokenSource.Token);
Assert.Equal(0, propertyChangedCount);
clock.Step(TimeSpan.FromSeconds(0));
Assert.False(animationRun.IsCompleted);
Assert.Equal(1, propertyChangedCount);
cancellationTokenSource.Cancel();
clock.Step(TimeSpan.FromSeconds(1));
clock.Step(TimeSpan.FromSeconds(2));
clock.Step(TimeSpan.FromSeconds(3));
//Assert.Equal(2, propertyChangedCount);
animationRun.Wait();
clock.Step(TimeSpan.FromSeconds(6));
Assert.True(animationRun.IsCompleted);
Assert.Equal(2, propertyChangedCount);
}
[Fact]
public void Cancellation_Of_Completed_Animation_Does_Not_Fail()
{
var keyframe1 = new KeyFrame()
{
Setters =
{
new Setter(Border.WidthProperty, 200d),
},
Cue = new Cue(1d)
};
var keyframe2 = new KeyFrame()
{
Setters =
{
new Setter(Border.WidthProperty, 100d),
},
Cue = new Cue(0d)
};
var animation = new Animation()
{
Duration = TimeSpan.FromSeconds(10),
Delay = TimeSpan.FromSeconds(0),
DelayBetweenIterations = TimeSpan.FromSeconds(0),
IterationCount = new IterationCount(1),
Children =
{
keyframe2,
keyframe1
}
};
var border = new Border()
{
Height = 100d,
Width = 50d
};
var propertyChangedCount = 0;
border.PropertyChanged += (sender, e) =>
{
if (e.Property == Control.WidthProperty)
{
propertyChangedCount++;
}
};
var clock = new TestClock();
var cancellationTokenSource = new CancellationTokenSource();
var animationRun = animation.RunAsync(border, clock, cancellationTokenSource.Token);
Assert.Equal(0, propertyChangedCount);
clock.Step(TimeSpan.FromSeconds(0));
Assert.False(animationRun.IsCompleted);
Assert.Equal(1, propertyChangedCount);
clock.Step(TimeSpan.FromSeconds(10));
Assert.True(animationRun.IsCompleted);
Assert.Equal(2, propertyChangedCount);
cancellationTokenSource.Cancel();
animationRun.Wait();
}
} }
} }

Loading…
Cancel
Save