committed by
GitHub
19 changed files with 276 additions and 146 deletions
@ -0,0 +1,20 @@ |
|||
using System; |
|||
using Avalonia.Animation.Animators; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
/// <summary>
|
|||
/// <see cref="Transition{T}"/> using an <see cref="Animator{T}"/> to transition between values.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">Type of the transitioned value.</typeparam>
|
|||
/// <typeparam name="TAnimator">Type of the animator.</typeparam>
|
|||
public abstract class AnimatorDrivenTransition<T, TAnimator> : Transition<T> where TAnimator : Animator<T>, new() |
|||
{ |
|||
private static readonly TAnimator s_animator = new TAnimator(); |
|||
|
|||
public override IObservable<T> DoTransition(IObservable<double> progress, T oldValue, T newValue) |
|||
{ |
|||
return new AnimatorTransitionObservable<T, TAnimator>(s_animator, progress, Easing, oldValue, newValue); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
using System; |
|||
using Avalonia.Animation.Animators; |
|||
using Avalonia.Animation.Easings; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
/// <summary>
|
|||
/// Transition observable based on an <see cref="Animator{T}"/> producing a value.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">Type of the transitioned value.</typeparam>
|
|||
/// <typeparam name="TAnimator">Type of the animator.</typeparam>
|
|||
public class AnimatorTransitionObservable<T, TAnimator> : TransitionObservableBase<T> where TAnimator : Animator<T> |
|||
{ |
|||
private readonly TAnimator _animator; |
|||
private readonly Easing _easing; |
|||
private readonly T _oldValue; |
|||
private readonly T _newValue; |
|||
|
|||
public AnimatorTransitionObservable(TAnimator animator, IObservable<double> progress, Easing easing, T oldValue, T newValue) : base(progress, easing) |
|||
{ |
|||
_animator = animator; |
|||
_easing = easing; |
|||
_oldValue = oldValue; |
|||
_newValue = newValue; |
|||
} |
|||
|
|||
protected override T ProduceValue(double progress) |
|||
{ |
|||
return _animator.Interpolate(progress, _oldValue, _newValue); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,58 @@ |
|||
using System; |
|||
using Avalonia.Animation.Easings; |
|||
using Avalonia.Reactive; |
|||
|
|||
#nullable enable |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
/// <summary>
|
|||
/// Provides base for observables implementing transitions.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">Type of the transitioned value.</typeparam>
|
|||
public abstract class TransitionObservableBase<T> : SingleSubscriberObservableBase<T>, IObserver<double> |
|||
{ |
|||
private readonly Easing _easing; |
|||
private readonly IObservable<double> _progress; |
|||
private IDisposable? _progressSubscription; |
|||
|
|||
protected TransitionObservableBase(IObservable<double> progress, Easing easing) |
|||
{ |
|||
_progress = progress; |
|||
_easing = easing; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Produces value at given progress time point.
|
|||
/// </summary>
|
|||
/// <param name="progress">Transition progress.</param>
|
|||
protected abstract T ProduceValue(double progress); |
|||
|
|||
protected override void Subscribed() |
|||
{ |
|||
_progressSubscription = _progress.Subscribe(this); |
|||
} |
|||
|
|||
protected override void Unsubscribed() |
|||
{ |
|||
_progressSubscription?.Dispose(); |
|||
} |
|||
|
|||
void IObserver<double>.OnCompleted() |
|||
{ |
|||
PublishCompleted(); |
|||
} |
|||
|
|||
void IObserver<double>.OnError(Exception error) |
|||
{ |
|||
PublishError(error); |
|||
} |
|||
|
|||
void IObserver<double>.OnNext(double value) |
|||
{ |
|||
double progress = _easing.Ease(value); |
|||
|
|||
PublishNext(ProduceValue(progress)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
using Avalonia.Animation.Animators; |
|||
using Avalonia.Media; |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
/// <summary>
|
|||
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="Color"/> type.
|
|||
/// </summary>
|
|||
public class ColorTransition : AnimatorDrivenTransition<Color, ColorAnimator> |
|||
{ |
|||
} |
|||
} |
|||
@ -1,28 +1,26 @@ |
|||
using System; |
|||
using System.Reactive.Linq; |
|||
using Avalonia.Animation.Animators; |
|||
using Avalonia.Media; |
|||
using Avalonia.Media.Transformation; |
|||
|
|||
#nullable enable |
|||
|
|||
namespace Avalonia.Animation |
|||
{ |
|||
public class TransformOperationsTransition : Transition<ITransform> |
|||
{ |
|||
private static readonly TransformOperationsAnimator _operationsAnimator = new TransformOperationsAnimator(); |
|||
private static readonly TransformOperationsAnimator s_operationsAnimator = new TransformOperationsAnimator(); |
|||
|
|||
public override IObservable<ITransform> DoTransition(IObservable<double> progress, |
|||
public override IObservable<ITransform> DoTransition( |
|||
IObservable<double> progress, |
|||
ITransform oldValue, |
|||
ITransform newValue) |
|||
{ |
|||
var oldTransform = TransformOperationsAnimator.EnsureOperations(oldValue); |
|||
var newTransform = TransformOperationsAnimator.EnsureOperations(newValue); |
|||
|
|||
return progress |
|||
.Select(p => |
|||
{ |
|||
var f = Easing.Ease(p); |
|||
|
|||
return _operationsAnimator.Interpolate(f, oldTransform, newTransform); |
|||
}); |
|||
return new AnimatorTransitionObservable<TransformOperations, TransformOperationsAnimator>( |
|||
s_operationsAnimator, progress, Easing, oldTransform, newTransform); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,78 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics; |
|||
using System.Reactive.Linq; |
|||
using System.Reactive.Subjects; |
|||
using System.Runtime.CompilerServices; |
|||
using Avalonia.Animation; |
|||
using Avalonia.Animation.Animators; |
|||
using Avalonia.Layout; |
|||
using BenchmarkDotNet.Attributes; |
|||
|
|||
namespace Avalonia.Benchmarks.Animations |
|||
{ |
|||
[MemoryDiagnoser] |
|||
public class TransitionBenchmark |
|||
{ |
|||
private readonly AddValueObserver _observer; |
|||
private readonly List<double> _producedValues; |
|||
private readonly Subject<double> _timeProducer; |
|||
private readonly DoubleTransition _transition; |
|||
|
|||
public TransitionBenchmark() |
|||
{ |
|||
_transition = new DoubleTransition |
|||
{ |
|||
Duration = TimeSpan.FromMilliseconds(FrameCount), Property = Layoutable.WidthProperty |
|||
}; |
|||
|
|||
_timeProducer = new Subject<double>(); |
|||
_producedValues = new List<double>(FrameCount); |
|||
|
|||
_observer = new AddValueObserver(_producedValues); |
|||
} |
|||
|
|||
[Params(10, 100)] public int FrameCount { get; set; } |
|||
|
|||
[Benchmark] |
|||
[MethodImpl(MethodImplOptions.NoInlining)] |
|||
public void NewTransition() |
|||
{ |
|||
var transitionObs = _transition.DoTransition(_timeProducer, 0, 1); |
|||
|
|||
_producedValues.Clear(); |
|||
|
|||
using var transitionSub = transitionObs.Subscribe(_observer); |
|||
|
|||
for (int i = 0; i < FrameCount; i++) |
|||
{ |
|||
_timeProducer.OnNext(i / 1000d); |
|||
} |
|||
|
|||
Debug.Assert(_producedValues.Count == FrameCount); |
|||
} |
|||
|
|||
private class AddValueObserver : IObserver<double> |
|||
{ |
|||
private readonly List<double> _values; |
|||
|
|||
public AddValueObserver(List<double> values) |
|||
{ |
|||
_values = values; |
|||
} |
|||
|
|||
public void OnCompleted() |
|||
{ |
|||
} |
|||
|
|||
public void OnError(Exception error) |
|||
{ |
|||
} |
|||
|
|||
public void OnNext(double value) |
|||
{ |
|||
_values.Add(value); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue