Browse Source

Merge branch 'master' into remove-panel-cliptobounds-override

pull/2175/head
Steven Kirk 7 years ago
committed by GitHub
parent
commit
bd4b596912
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 29
      Avalonia.sln
  2. 1
      nukebuild/Build.cs
  3. 35
      samples/RenderDemo/Pages/AnimationsPage.xaml
  4. 2
      samples/RenderDemo/Pages/ClippingPage.xaml
  5. 1
      src/Avalonia.Animation/Animatable.cs
  6. 199
      src/Avalonia.Animation/Animation.cs
  7. 204
      src/Avalonia.Animation/AnimationInstance`1.cs
  8. 73
      src/Avalonia.Animation/Animators/Animator`1.cs
  9. 21
      src/Avalonia.Animation/Animators/BoolAnimator.cs
  10. 24
      src/Avalonia.Animation/Animators/ByteAnimator.cs
  11. 17
      src/Avalonia.Animation/Animators/DecimalAnimator.cs
  12. 17
      src/Avalonia.Animation/Animators/DoubleAnimator.cs
  13. 17
      src/Avalonia.Animation/Animators/FloatAnimator.cs
  14. 24
      src/Avalonia.Animation/Animators/Int16Animator.cs
  15. 24
      src/Avalonia.Animation/Animators/Int32Animator.cs
  16. 24
      src/Avalonia.Animation/Animators/Int64Animator.cs
  17. 24
      src/Avalonia.Animation/Animators/UInt16Animator.cs
  18. 24
      src/Avalonia.Animation/Animators/UInt32Animator.cs
  19. 24
      src/Avalonia.Animation/Animators/UInt64Animator.cs
  20. 3
      src/Avalonia.Animation/DisposeAnimationInstanceSubject.cs
  21. 35
      src/Avalonia.Animation/DoubleAnimator.cs
  22. 0
      src/Avalonia.Animation/Easing/IEasing.cs
  23. 176
      src/Avalonia.Animation/IterationCount.cs
  24. 4
      src/Avalonia.Animation/IterationCountTypeConverter.cs
  25. 15
      src/Avalonia.Animation/KeyFrame.cs
  26. 33
      src/Avalonia.Animation/KeyFramePair`1.cs
  27. 33
      src/Avalonia.Animation/KeyFrames.cs
  28. 3
      src/Avalonia.Animation/Properties/AssemblyInfo.cs
  29. 199
      src/Avalonia.Animation/RepeatCount.cs
  30. 7
      src/Avalonia.Animation/Transitions/DoubleTransition.cs
  31. 0
      src/Avalonia.Animation/Transitions/FloatTransition.cs
  32. 0
      src/Avalonia.Animation/Transitions/IntegerTransition.cs
  33. 4
      src/Avalonia.Themes.Default/ProgressBar.xaml
  34. 70
      src/Avalonia.Visuals/Animation/Animators/ColorAnimator.cs
  35. 27
      src/Avalonia.Visuals/Animation/Animators/CornerRadiusAnimator.cs
  36. 17
      src/Avalonia.Visuals/Animation/Animators/PointAnimator.cs
  37. 23
      src/Avalonia.Visuals/Animation/Animators/RectAnimator.cs
  38. 17
      src/Avalonia.Visuals/Animation/Animators/SizeAnimator.cs
  39. 74
      src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs
  40. 17
      src/Avalonia.Visuals/Animation/Animators/ThicknessAnimator.cs
  41. 37
      src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs
  42. 17
      src/Avalonia.Visuals/Animation/Animators/VectorAnimator.cs
  43. 44
      src/Avalonia.Visuals/Animation/CrossFade.cs
  44. 89
      src/Avalonia.Visuals/Animation/PageSlide.cs
  45. 36
      src/Avalonia.Visuals/Animation/Transitions/CornerRadiusTransition.cs
  46. 7
      src/Avalonia.Visuals/Animation/Transitions/PointTransition.cs
  47. 25
      src/Avalonia.Visuals/Animation/Transitions/SizeTransition.cs
  48. 13
      src/Avalonia.Visuals/Animation/Transitions/ThicknessTransition.cs
  49. 25
      src/Avalonia.Visuals/Animation/Transitions/VectorTransition.cs
  50. 6
      src/Avalonia.Visuals/CornerRadius.cs
  51. 3
      src/Avalonia.Visuals/Media/Brush.cs
  52. 7
      src/Avalonia.Visuals/Media/Color.cs
  53. 3
      src/Avalonia.Visuals/Media/SolidColorBrush.cs
  54. 1
      src/Avalonia.Visuals/Media/Transform.cs
  55. 14
      src/Avalonia.Visuals/Point.cs
  56. 6
      src/Avalonia.Visuals/Rect.cs
  57. 6
      src/Avalonia.Visuals/Size.cs
  58. 39
      src/Avalonia.Visuals/Thickness.cs
  59. 10
      src/Avalonia.Visuals/Vector.cs
  60. 77
      tests/Avalonia.Animation.UnitTests/AnimationIterationTests.cs
  61. 25
      tests/Avalonia.Animation.UnitTests/Avalonia.Animation.UnitTests.csproj
  62. 10
      tests/Avalonia.Animation.UnitTests/Properties/AssemblyInfo.cs
  63. 28
      tests/Avalonia.Animation.UnitTests/TestClock.cs

29
Avalonia.sln

@ -1,4 +1,4 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27130.2027
MinimumVisualStudioVersion = 10.0.40219.1
@ -193,6 +193,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Build.Tasks", "src
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "nukebuild\_build.csproj", "{3F00BC43-5095-477F-93D8-E65B08179A00}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Animation.UnitTests", "tests\Avalonia.Animation.UnitTests\Avalonia.Animation.UnitTests.csproj", "{AF227847-E65C-4BE9-BCE9-B551357788E0}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
@ -1720,6 +1722,30 @@ Global
{BF28998D-072C-439A-AFBB-2FE5021241E0}.Release|iPhone.Build.0 = Release|Any CPU
{BF28998D-072C-439A-AFBB-2FE5021241E0}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{BF28998D-072C-439A-AFBB-2FE5021241E0}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.AppStore|iPhone.Build.0 = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Debug|iPhone.Build.0 = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Release|Any CPU.Build.0 = Release|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Release|iPhone.ActiveCfg = Release|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Release|iPhone.Build.0 = Release|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1773,6 +1799,7 @@ Global
{E1240B49-7B4B-4371-A00E-068778C5CF0B} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{D49233F8-F29C-47DD-9975-C4C9E4502720} = {E870DCD7-F46A-498D-83FC-D0FD13E0A11C}
{3C471044-3640-45E3-B1B2-16D2FF8399EE} = {E870DCD7-F46A-498D-83FC-D0FD13E0A11C}
{AF227847-E65C-4BE9-BCE9-B551357788E0} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}

1
nukebuild/Build.cs

@ -124,6 +124,7 @@ partial class Build : NukeBuild
.DependsOn(Compile)
.Executes(() =>
{
RunCoreTest("./tests/Avalonia.Animation.UnitTests", false);
RunCoreTest("./tests/Avalonia.Base.UnitTests", false);
RunCoreTest("./tests/Avalonia.Controls.UnitTests", false);
RunCoreTest("./tests/Avalonia.Input.UnitTests", false);

35
samples/RenderDemo/Pages/AnimationsPage.xaml

@ -43,7 +43,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style Selector="Border.Rect1:pointerover">
<Style.Animations>
<Animation Duration="0:0:2.5"
RepeatCount="4"
IterationCount="4"
FillMode="None"
PlaybackDirection="AlternateReverse"
Easing="SineEaseInOut">
@ -73,7 +73,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style.Animations>
<Animation Duration="0:0:0.5"
Easing="QuadraticEaseInOut"
RepeatCount="Loop">
IterationCount="Infinite">
<KeyFrame Cue="50%">
<Setter Property="ScaleTransform.ScaleX" Value="0.8"/>
<Setter Property="ScaleTransform.ScaleY" Value="0.8"/>
@ -87,6 +87,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Animation Duration="0:0:3" Easing="BounceEaseInOut">
<KeyFrame Cue="48%">
<Setter Property="TranslateTransform.Y" Value="-100"/>
<Setter Property="Background" Value="Magenta"/>
</KeyFrame>
</Animation>
</Style.Animations>
@ -103,6 +104,35 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
</Animation>
</Style.Animations>
</Style>
<Style Selector="Border.Rect6">
<Style.Animations>
<Animation Duration="0:0:3"
IterationCount="Infinite"
PlaybackDirection="Alternate">
<KeyFrame Cue="0%">
<Setter Property="Background" Value="Red"/>
</KeyFrame>
<KeyFrame Cue="15%">
<Setter Property="Background" Value="Yellow"/>
</KeyFrame>
<KeyFrame Cue="30%">
<Setter Property="Background" Value="Green"/>
</KeyFrame>
<KeyFrame Cue="45%">
<Setter Property="Background" Value="Cyan"/>
</KeyFrame>
<KeyFrame Cue="60%">
<Setter Property="Background" Value="Blue"/>
</KeyFrame>
<KeyFrame Cue="75%">
<Setter Property="Background" Value="Indigo"/>
</KeyFrame>
<KeyFrame Cue="90%">
<Setter Property="Background" Value="Violet"/>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
</Styles>
</UserControl.Styles>
<Grid>
@ -120,6 +150,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Border Classes="Test Rect3"/>
<Border Classes="Test Rect4" Background="Navy"/>
<Border Classes="Test Rect5" Background="SeaGreen"/>
<Border Classes="Test Rect6" Background="Red"/>
</WrapPanel>
</StackPanel>
</Grid>

2
samples/RenderDemo/Pages/ClippingPage.xaml

@ -8,7 +8,7 @@ xmlns="https://github.com/avaloniaui">
</Style>
<Style Selector="Border#clipChild">
<Style.Animations>
<Animation Duration="0:0:2" RepeatCount="Loop">
<Animation Duration="0:0:2" IterationCount="Infinite">
<KeyFrame Cue="100%">
<Setter Property="RotateTransform.Angle" Value="360"/>
</KeyFrame>

1
src/Avalonia.Animation/Animatable.cs

@ -7,6 +7,7 @@ using System.Linq;
using System.Reactive.Linq;
using Avalonia.Collections;
using Avalonia.Data;
using Avalonia.Animation.Animators;
namespace Avalonia.Animation
{

199
src/Avalonia.Animation/Animation.cs

@ -7,68 +7,209 @@ using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Avalonia.Animation.Animators;
using Avalonia.Animation.Easings;
using Avalonia.Collections;
using Avalonia.Data;
using Avalonia.Metadata;
namespace Avalonia.Animation
{
/// <summary>
/// Tracks the progress of an animation.
/// </summary>
public class Animation : AvaloniaList<KeyFrame>, IAnimation
public class Animation : AvaloniaObject, IAnimation
{
/// <summary>
/// Defines the <see cref="Duration"/> property.
/// </summary>
public static readonly DirectProperty<Animation, TimeSpan> DurationProperty =
AvaloniaProperty.RegisterDirect<Animation, TimeSpan>(
nameof(_duration),
o => o._duration,
(o, v) => o._duration = v);
/// <summary>
/// Defines the <see cref="IterationCount"/> property.
/// </summary>
public static readonly DirectProperty<Animation, IterationCount> IterationCountProperty =
AvaloniaProperty.RegisterDirect<Animation, IterationCount>(
nameof(_iterationCount),
o => o._iterationCount,
(o, v) => o._iterationCount = v);
/// <summary>
/// Defines the <see cref="PlaybackDirection"/> property.
/// </summary>
public static readonly DirectProperty<Animation, PlaybackDirection> PlaybackDirectionProperty =
AvaloniaProperty.RegisterDirect<Animation, PlaybackDirection>(
nameof(_playbackDirection),
o => o._playbackDirection,
(o, v) => o._playbackDirection = v);
/// <summary>
/// Defines the <see cref="FillMode"/> property.
/// </summary>
public static readonly DirectProperty<Animation, FillMode> FillModeProperty =
AvaloniaProperty.RegisterDirect<Animation, FillMode>(
nameof(_fillMode),
o => o._fillMode,
(o, v) => o._fillMode = v);
/// <summary>
/// Defines the <see cref="Easing"/> property.
/// </summary>
public static readonly DirectProperty<Animation, Easing> EasingProperty =
AvaloniaProperty.RegisterDirect<Animation, Easing>(
nameof(_easing),
o => o._easing,
(o, v) => o._easing = v);
/// <summary>
/// Defines the <see cref="Delay"/> property.
/// </summary>
public static readonly DirectProperty<Animation, TimeSpan> DelayProperty =
AvaloniaProperty.RegisterDirect<Animation, TimeSpan>(
nameof(_delay),
o => o._delay,
(o, v) => o._delay = v);
/// <summary>
/// Defines the <see cref="DelayBetweenIterations"/> property.
/// </summary>
public static readonly DirectProperty<Animation, TimeSpan> DelayBetweenIterationsProperty =
AvaloniaProperty.RegisterDirect<Animation, TimeSpan>(
nameof(_delayBetweenIterations),
o => o._delayBetweenIterations,
(o, v) => o._delayBetweenIterations = v);
/// <summary>
/// Defines the <see cref="SpeedRatio"/> property.
/// </summary>
public static readonly DirectProperty<Animation, double> SpeedRatioProperty =
AvaloniaProperty.RegisterDirect<Animation, double>(
nameof(_speedRatio),
o => o._speedRatio,
(o, v) => o._speedRatio = v,
defaultBindingMode: BindingMode.TwoWay);
private TimeSpan _duration;
private IterationCount _iterationCount = new IterationCount(1);
private PlaybackDirection _playbackDirection;
private FillMode _fillMode;
private Easing _easing = new LinearEasing();
private TimeSpan _delay = TimeSpan.Zero;
private TimeSpan _delayBetweenIterations = TimeSpan.Zero;
private double _speedRatio = 1d;
/// <summary>
/// Gets or sets the active time of this animation.
/// </summary>
public TimeSpan Duration { get; set; }
public TimeSpan Duration
{
get { return _duration; }
set { SetAndRaise(DurationProperty, ref _duration, value); }
}
/// <summary>
/// Gets or sets the repeat count for this animation.
/// </summary>
public RepeatCount RepeatCount { get; set; }
public IterationCount IterationCount
{
get { return _iterationCount; }
set { SetAndRaise(IterationCountProperty, ref _iterationCount, value); }
}
/// <summary>
/// Gets or sets the playback direction for this animation.
/// </summary>
public PlaybackDirection PlaybackDirection { get; set; }
public PlaybackDirection PlaybackDirection
{
get { return _playbackDirection; }
set { SetAndRaise(PlaybackDirectionProperty, ref _playbackDirection, value); }
}
/// <summary>
/// Gets or sets the value fill mode for this animation.
/// </summary>
public FillMode FillMode { get; set; }
/// </summary>
public FillMode FillMode
{
get { return _fillMode; }
set { SetAndRaise(FillModeProperty, ref _fillMode, value); }
}
/// <summary>
/// Gets or sets the easing function to be used for this animation.
/// </summary>
public Easing Easing { get; set; } = new LinearEasing();
/// <summary>
/// Gets or sets the speed multiple for this animation.
/// </summary>
public double SpeedRatio { get; set; } = 1d;
public Easing Easing
{
get { return _easing; }
set { SetAndRaise(EasingProperty, ref _easing, value); }
}
/// <summary>
/// Gets or sets the delay time for this animation.
/// Gets or sets the initial delay time for this animation.
/// </summary>
/// <remarks>
/// Describes a delay to be added before the animation starts, and optionally between
/// repeats of the animation if <see cref="DelayBetweenIterations"/> is set.
/// </remarks>
public TimeSpan Delay { get; set; }
public TimeSpan Delay
{
get { return _delay; }
set { SetAndRaise(DelayProperty, ref _delay, value); }
}
/// <summary>
/// Gets or sets a value indicating whether <see cref="Delay"/> will be applied between
/// iterations of the animation.
/// Gets or sets the delay time in between iterations.
/// </summary>
public TimeSpan DelayBetweenIterations
{
get { return _delayBetweenIterations; }
set { SetAndRaise(DelayBetweenIterationsProperty, ref _delayBetweenIterations, value); }
}
/// <summary>
/// Gets or sets the speed multiple for this animation.
/// </summary>
/// <remarks>
/// If this property is not set, then <see cref="Delay"/> will only be applied to the first
/// iteration of the animation.
/// </remarks>
public bool DelayBetweenIterations { get; set; }
public double SpeedRatio
{
get { return _speedRatio; }
set { SetAndRaise(SpeedRatioProperty, ref _speedRatio, value); }
}
/// <summary>
/// Obsolete: Do not use this property, use <see cref="IterationCount"/> instead.
/// </summary>
/// <value></value>
[Obsolete("This property has been superceded by IterationCount.")]
public string RepeatCount
{
get { return IterationCount.ToString(); }
set
{
var val = value.ToUpper();
val = val.Replace("LOOP", "INFINITE");
val = val.Replace("NONE", "1");
IterationCount = IterationCount.Parse(val);
}
}
/// <summary>
/// Gets the children of the <see cref="Animation"/>.
/// </summary>
[Content]
public KeyFrames Children { get; } = new KeyFrames();
private readonly static List<(Func<AvaloniaProperty, bool> Condition, Type Animator)> Animators = new List<(Func<AvaloniaProperty, bool>, Type)>
{
( prop => typeof(double).IsAssignableFrom(prop.PropertyType), typeof(DoubleAnimator) )
( prop => typeof(bool).IsAssignableFrom(prop.PropertyType), typeof(BoolAnimator) ),
( prop => typeof(byte).IsAssignableFrom(prop.PropertyType), typeof(ByteAnimator) ),
( prop => typeof(Int16).IsAssignableFrom(prop.PropertyType), typeof(Int16Animator) ),
( prop => typeof(Int32).IsAssignableFrom(prop.PropertyType), typeof(Int32Animator) ),
( prop => typeof(Int64).IsAssignableFrom(prop.PropertyType), typeof(Int64Animator) ),
( prop => typeof(UInt16).IsAssignableFrom(prop.PropertyType), typeof(UInt16Animator) ),
( prop => typeof(UInt32).IsAssignableFrom(prop.PropertyType), typeof(UInt32Animator) ),
( prop => typeof(UInt64).IsAssignableFrom(prop.PropertyType), typeof(UInt64Animator) ),
( prop => typeof(float).IsAssignableFrom(prop.PropertyType), typeof(FloatAnimator) ),
( prop => typeof(double).IsAssignableFrom(prop.PropertyType), typeof(DoubleAnimator) ),
( prop => typeof(decimal).IsAssignableFrom(prop.PropertyType), typeof(DecimalAnimator) ),
};
public static void RegisterAnimator<TAnimator>(Func<AvaloniaProperty, bool> condition)
@ -95,9 +236,9 @@ namespace Avalonia.Animation
var animatorKeyFrames = new List<AnimatorKeyFrame>();
var subscriptions = new List<IDisposable>();
foreach (var keyframe in this)
foreach (var keyframe in Children)
{
foreach (var setter in keyframe)
foreach (var setter in keyframe.Setters)
{
var handler = GetAnimatorType(setter.Property);
@ -179,7 +320,7 @@ namespace Avalonia.Animation
{
var run = new TaskCompletionSource<object>();
if (this.RepeatCount == RepeatCount.Loop)
if (this.IterationCount == IterationCount.Infinite)
run.SetException(new InvalidOperationException("Looping animations must not use the Run method."));
IDisposable subscriptions = null;

204
src/Avalonia.Animation/AnimationInstance`1.cs

@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Reactive.Linq;
using Avalonia.Animation.Animators;
using Avalonia.Animation.Utils;
using Avalonia.Data;
using Avalonia.Reactive;
@ -15,78 +16,77 @@ namespace Avalonia.Animation
{
private T _lastInterpValue;
private T _firstKFValue;
private long _repeatCount;
private double _currentIteration;
private bool _isLooping;
private ulong? _iterationCount;
private ulong _currentIteration;
private bool _gotFirstKFValue;
private bool _iterationDelay;
private FillMode _fillMode;
private PlaybackDirection _animationDirection;
private Animator<T> _parent;
private PlaybackDirection _playbackDirection;
private Animator<T> _animator;
private Animation _animation;
private Animatable _targetControl;
private T _neutralValue;
private double _speedRatio;
private TimeSpan _delay;
private double _speedRatioConv;
private TimeSpan _initialDelay;
private TimeSpan _iterationDelay;
private TimeSpan _duration;
private Easings.Easing _easeFunc;
private Action _onCompleteAction;
private Func<double, T, T> _interpolator;
private IDisposable _timerSubscription;
private IDisposable _timerSub;
private readonly IClock _baseClock;
private IClock _clock;
public AnimationInstance(Animation animation, Animatable control, Animator<T> animator, IClock baseClock, Action OnComplete, Func<double, T, T> Interpolator)
{
if (animation.SpeedRatio <= 0)
throw new InvalidOperationException("Speed ratio cannot be negative or zero.");
_animator = animator;
_animation = animation;
_targetControl = control;
_onCompleteAction = OnComplete;
_interpolator = Interpolator;
_baseClock = baseClock;
_neutralValue = (T)_targetControl.GetValue(_animator.Property);
if (animation.Duration.TotalSeconds <= 0)
throw new InvalidOperationException("Duration cannot be negative or zero.");
FetchProperties();
}
_parent = animator;
_easeFunc = animation.Easing;
_targetControl = control;
_neutralValue = (T)_targetControl.GetValue(_parent.Property);
private void FetchProperties()
{
if (_animation.SpeedRatio < 0d)
throw new ArgumentOutOfRangeException("SpeedRatio value should not be negative.");
_speedRatio = animation.SpeedRatio;
if (_animation.Duration.TotalSeconds <= 0)
throw new InvalidOperationException("Duration value cannot be negative or zero.");
_delay = animation.Delay;
_duration = animation.Duration;
_iterationDelay = animation.DelayBetweenIterations;
_easeFunc = _animation.Easing;
switch (animation.RepeatCount.RepeatType)
{
case RepeatType.None:
_repeatCount = 1;
break;
case RepeatType.Loop:
_isLooping = true;
break;
case RepeatType.Repeat:
_repeatCount = (long)animation.RepeatCount.Value;
break;
}
_speedRatioConv = 1d / _animation.SpeedRatio;
_animationDirection = animation.PlaybackDirection;
_fillMode = animation.FillMode;
_onCompleteAction = OnComplete;
_interpolator = Interpolator;
_baseClock = baseClock;
}
_initialDelay = _animation.Delay;
_duration = _animation.Duration;
_iterationDelay = _animation.DelayBetweenIterations;
if (_animation.IterationCount.RepeatType == IterationType.Many)
_iterationCount = _animation.IterationCount.Value;
else
_iterationCount = null;
_playbackDirection = _animation.PlaybackDirection;
_fillMode = _animation.FillMode;
}
protected override void Unsubscribed()
{
//Animation may have been stopped before it has finished
// Animation may have been stopped before it has finished.
ApplyFinalFill();
_timerSubscription?.Dispose();
_timerSub?.Dispose();
_clock.PlayState = PlayState.Stop;
}
protected override void Subscribed()
{
_clock = new Clock(_baseClock);
_timerSubscription = _clock.Subscribe(Step);
_timerSub = _clock.Subscribe(Step);
}
public void Step(TimeSpan frameTick)
@ -104,7 +104,7 @@ namespace Avalonia.Animation
private void ApplyFinalFill()
{
if (_fillMode == FillMode.Forward || _fillMode == FillMode.Both)
_targetControl.SetValue(_parent.Property, _lastInterpValue, BindingPriority.LocalValue);
_targetControl.SetValue(_animator.Property, _lastInterpValue, BindingPriority.LocalValue);
}
private void DoComplete()
@ -130,7 +130,7 @@ namespace Avalonia.Animation
if (!_gotFirstKFValue)
{
_firstKFValue = (T)_parent.First().Value;
_firstKFValue = (T)_animator.First().Value;
_gotFirstKFValue = true;
}
}
@ -138,75 +138,77 @@ namespace Avalonia.Animation
private void InternalStep(TimeSpan time)
{
DoPlayStates();
var delayEndpoint = _delay;
var iterationEndpoint = delayEndpoint + _duration;
var iterationTime = time;
//determine if time is currently in the first iteration.
if (time >= TimeSpan.Zero & time <= iterationEndpoint)
{
_currentIteration = 1;
}
else if (time > iterationEndpoint)
{
//Subtract first iteration to properly get the subsequent iteration time
iterationTime -= iterationEndpoint;
FetchProperties();
if (!_iterationDelay & delayEndpoint > TimeSpan.Zero)
{
delayEndpoint = TimeSpan.Zero;
iterationEndpoint = _duration;
}
//Calculate the current iteration number
_currentIteration = Math.Min(_repeatCount,(int)Math.Floor((double)((double)iterationTime.Ticks / iterationEndpoint.Ticks)) + 2);
}
else
{
return;
}
// Scale timebases according to speedratio.
var indexTime = time.Ticks;
var iterDuration = _duration.Ticks * _speedRatioConv;
var iterDelay = _iterationDelay.Ticks * _speedRatioConv;
var initDelay = _initialDelay.Ticks * _speedRatioConv;
// Determine if the current iteration should have its normalized time inverted.
bool isCurIterReverse = _animationDirection == PlaybackDirection.Normal ? false :
_animationDirection == PlaybackDirection.Alternate ? (_currentIteration % 2 == 0) ? false : true :
_animationDirection == PlaybackDirection.AlternateReverse ? (_currentIteration % 2 == 0) ? true : false :
_animationDirection == PlaybackDirection.Reverse ? true : false;
if (!_isLooping)
{
var totalTime = _iterationDelay ? _repeatCount * ( _duration.Ticks + _delay.Ticks) : _repeatCount * _duration.Ticks + _delay.Ticks;
if (time.Ticks >= totalTime)
{
var easedTime = _easeFunc.Ease(isCurIterReverse ? 0.0 : 1.0);
_lastInterpValue = _interpolator(easedTime, _neutralValue);
DoComplete();
return;
}
}
iterationTime = TimeSpan.FromTicks((long)(iterationTime.Ticks % iterationEndpoint.Ticks));
if (delayEndpoint > TimeSpan.Zero & iterationTime < delayEndpoint)
if (indexTime > 0 & indexTime <= initDelay)
{
DoDelay();
}
else
{
// Offset the delay time
iterationTime -= delayEndpoint;
iterationEndpoint -= delayEndpoint;
// Calculate timebases.
var iterationTime = iterDuration + iterDelay;
var opsTime = indexTime - initDelay;
var playbackTime = opsTime % iterationTime;
// Normalize time
var interpVal = (double)iterationTime.Ticks / iterationEndpoint.Ticks;
_currentIteration = (ulong)(opsTime / iterationTime);
if (isCurIterReverse)
interpVal = 1 - interpVal;
// Stop animation when the current iteration is beyond the iteration count.
if ((_currentIteration + 1) > _iterationCount)
DoComplete();
// Ease and interpolate
var easedTime = _easeFunc.Ease(interpVal);
_lastInterpValue = _interpolator(easedTime, _neutralValue);
if (playbackTime <= iterDuration)
{
// Normalize time for interpolation.
var normalizedTime = playbackTime / iterDuration;
// Check if normalized time needs to be reversed according to PlaybackDirection
bool playbackReversed;
switch (_playbackDirection)
{
case PlaybackDirection.Normal:
playbackReversed = false;
break;
case PlaybackDirection.Reverse:
playbackReversed = true;
break;
case PlaybackDirection.Alternate:
playbackReversed = (_currentIteration % 2 == 0) ? false : true;
break;
case PlaybackDirection.AlternateReverse:
playbackReversed = (_currentIteration % 2 == 0) ? true : false;
break;
default:
throw new InvalidOperationException($"Animation direction value is unknown: {_playbackDirection}");
}
if (playbackReversed)
normalizedTime = 1 - normalizedTime;
// Ease and interpolate
var easedTime = _easeFunc.Ease(normalizedTime);
_lastInterpValue = _interpolator(easedTime, _neutralValue);
PublishNext(_lastInterpValue);
PublishNext(_lastInterpValue);
}
else if (playbackTime > iterDuration &
playbackTime <= iterationTime &
iterDelay > 0)
{
// The last iteration's trailing delay should be skipped.
if ((_currentIteration + 1) < _iterationCount)
DoDelay();
else
DoComplete();
}
}
}
}

73
src/Avalonia.Animation/Animator`1.cs → src/Avalonia.Animation/Animators/Animator`1.cs

@ -10,10 +10,10 @@ using Avalonia.Collections;
using Avalonia.Data;
using Avalonia.Reactive;
namespace Avalonia.Animation
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Base class for KeyFrames objects
/// Base class for <see cref="Animator{T}"/> objects
/// </summary>
public abstract class Animator<T> : AvaloniaList<AnimatorKeyFrame>, IAnimator
{
@ -45,17 +45,10 @@ namespace Avalonia.Animation
return match.Subscribe(subject);
}
/// <summary>
/// Get the nearest pair of cue-time ordered keyframes
/// according to the given time parameter that is relative to the
/// total animation time and the normalized intra-keyframe pair time
/// (i.e., the normalized time between the selected keyframes, relative to the
/// time parameter).
/// </summary>
/// <param name="animationTime">The time parameter, relative to the total animation time</param>
protected (double IntraKFTime, KeyFramePair<T> KFPair) GetKFPairAndIntraKFTime(double animationTime)
protected T InterpolationHandler(double animationTime, T neutralValue)
{
AnimatorKeyFrame firstKeyframe, lastKeyframe;
int kvCount = _convertedKeyframes.Count;
if (kvCount > 2)
{
@ -84,38 +77,31 @@ namespace Avalonia.Animation
double t0 = firstKeyframe.Cue.CueValue;
double t1 = lastKeyframe.Cue.CueValue;
var intraframeTime = (animationTime - t0) / (t1 - t0);
var firstFrameData = (firstKeyframe.GetTypedValue<T>(), firstKeyframe.isNeutral);
var lastFrameData = (lastKeyframe.GetTypedValue<T>(), lastKeyframe.isNeutral);
return (intraframeTime, new KeyFramePair<T>(firstFrameData, lastFrameData));
double progress = (animationTime - t0) / (t1 - t0);
T oldValue, newValue;
if (firstKeyframe.isNeutral)
oldValue = neutralValue;
else
oldValue = (T)firstKeyframe.Value;
if (lastKeyframe.isNeutral)
newValue = neutralValue;
else
newValue = (T)lastKeyframe.Value;
return Interpolate(progress, oldValue, newValue);
}
private int FindClosestBeforeKeyFrame(double time)
{
int FindClosestBeforeKeyFrame(int startIndex, int length)
{
if (length == 0 || length == 1)
{
return startIndex;
}
for (int i = 0; i < _convertedKeyframes.Count; i++)
if (_convertedKeyframes[i].Cue.CueValue > time)
return i - 1;
int middle = startIndex + (length / 2);
if (_convertedKeyframes[middle].Cue.CueValue < time)
{
return FindClosestBeforeKeyFrame(middle, length - middle);
}
else if (_convertedKeyframes[middle].Cue.CueValue > time)
{
return FindClosestBeforeKeyFrame(startIndex, middle - startIndex);
}
else
{
return middle;
}
}
return FindClosestBeforeKeyFrame(0, _convertedKeyframes.Count);
throw new Exception("Index time is out of keyframe time range.");
}
/// <summary>
@ -129,18 +115,15 @@ namespace Avalonia.Animation
this,
clock ?? control.Clock ?? Clock.GlobalClock,
onComplete,
DoInterpolation);
InterpolationHandler);
return control.Bind<T>((AvaloniaProperty<T>)Property, instance, BindingPriority.Animation);
}
/// <summary>
/// Interpolates a value given the desired time.
/// Interpolates in-between two key values given the desired progress time.
/// </summary>
protected abstract T DoInterpolation(double time, T neutralValue);
public abstract T Interpolate(double progress, T oldValue, T newValue);
/// <summary>
/// Verifies, converts and sorts keyframe values according to this class's target type.
/// </summary>
private void VerifyConvertKeyFrames()
{
foreach (AnimatorKeyFrame keyframe in this)
@ -188,4 +171,4 @@ namespace Avalonia.Animation
}
}
}
}
}

21
src/Avalonia.Animation/Animators/BoolAnimator.cs

@ -0,0 +1,21 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="bool"/> properties.
/// </summary>
public class BoolAnimator : Animator<bool>
{
/// <inheritdocs/>
public override bool Interpolate(double progress, bool oldValue, bool newValue)
{
if(progress >= 1d)
return newValue;
if(progress >= 0)
return oldValue;
return oldValue;
}
}
}

24
src/Avalonia.Animation/Animators/ByteAnimator.cs

@ -0,0 +1,24 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="byte"/> properties.
/// </summary>
public class ByteAnimator : Animator<byte>
{
const double maxVal = (double)byte.MaxValue;
/// <inheritdocs/>
public override byte Interpolate(double progress, byte oldValue, byte newValue)
{
var normOV = oldValue / maxVal;
var normNV = newValue / maxVal;
var deltaV = normNV - normOV;
return (byte)Math.Round(maxVal * ((deltaV * progress) + normOV));
}
}
}

17
src/Avalonia.Animation/Animators/DecimalAnimator.cs

@ -0,0 +1,17 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="decimal"/> properties.
/// </summary>
public class DecimalAnimator : Animator<decimal>
{
/// <inheritdocs/>
public override decimal Interpolate(double progress, decimal oldValue, decimal newValue)
{
return ((newValue - oldValue) * (decimal)progress) + oldValue;
}
}
}

17
src/Avalonia.Animation/Animators/DoubleAnimator.cs

@ -0,0 +1,17 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="double"/> properties.
/// </summary>
public class DoubleAnimator : Animator<double>
{
/// <inheritdocs/>
public override double Interpolate(double progress, double oldValue, double newValue)
{
return ((newValue - oldValue) * progress) + oldValue;
}
}
}

17
src/Avalonia.Animation/Animators/FloatAnimator.cs

@ -0,0 +1,17 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="float"/> properties.
/// </summary>
public class FloatAnimator : Animator<float>
{
/// <inheritdocs/>
public override float Interpolate(double progress, float oldValue, float newValue)
{
return (float)(((newValue - oldValue) * progress) + oldValue);
}
}
}

24
src/Avalonia.Animation/Animators/Int16Animator.cs

@ -0,0 +1,24 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="Int16"/> properties.
/// </summary>
public class Int16Animator : Animator<Int16>
{
const double maxVal = (double)Int16.MaxValue;
/// <inheritdocs/>
public override Int16 Interpolate(double progress, Int16 oldValue, Int16 newValue)
{
var normOV = oldValue / maxVal;
var normNV = newValue / maxVal;
var deltaV = normNV - normOV;
return (Int16)Math.Round(maxVal * ((deltaV * progress) + normOV));
}
}
}

24
src/Avalonia.Animation/Animators/Int32Animator.cs

@ -0,0 +1,24 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="Int32"/> properties.
/// </summary>
public class Int32Animator : Animator<Int32>
{
const double maxVal = (double)Int32.MaxValue;
/// <inheritdocs/>
public override Int32 Interpolate(double progress, Int32 oldValue, Int32 newValue)
{
var normOV = oldValue / maxVal;
var normNV = newValue / maxVal;
var deltaV = normNV - normOV;
return (Int32)Math.Round(maxVal * ((deltaV * progress) + normOV));
}
}
}

24
src/Avalonia.Animation/Animators/Int64Animator.cs

@ -0,0 +1,24 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="Int64"/> properties.
/// </summary>
public class Int64Animator : Animator<Int64>
{
const double maxVal = (double)Int64.MaxValue;
/// <inheritdocs/>
public override Int64 Interpolate(double progress, Int64 oldValue, Int64 newValue)
{
var normOV = oldValue / maxVal;
var normNV = newValue / maxVal;
var deltaV = normNV - normOV;
return (Int64)Math.Round(maxVal * ((deltaV * progress) + normOV));
}
}
}

24
src/Avalonia.Animation/Animators/UInt16Animator.cs

@ -0,0 +1,24 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="UInt16"/> properties.
/// </summary>
public class UInt16Animator : Animator<UInt16>
{
const double maxVal = (double)UInt16.MaxValue;
/// <inheritdocs/>
public override UInt16 Interpolate(double progress, UInt16 oldValue, UInt16 newValue)
{
var normOV = oldValue / maxVal;
var normNV = newValue / maxVal;
var deltaV = normNV - normOV;
return (UInt16)Math.Round(maxVal * ((deltaV * progress) + normOV));
}
}
}

24
src/Avalonia.Animation/Animators/UInt32Animator.cs

@ -0,0 +1,24 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="UInt32"/> properties.
/// </summary>
public class UInt32Animator : Animator<UInt32>
{
const double maxVal = (double)UInt32.MaxValue;
/// <inheritdocs/>
public override UInt32 Interpolate(double progress, UInt32 oldValue, UInt32 newValue)
{
var normOV = oldValue / maxVal;
var normNV = newValue / maxVal;
var deltaV = normNV - normOV;
return (UInt32)Math.Round(maxVal * ((deltaV * progress) + normOV));
}
}
}

24
src/Avalonia.Animation/Animators/UInt64Animator.cs

@ -0,0 +1,24 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="UInt64"/> properties.
/// </summary>
public class UInt64Animator : Animator<UInt64>
{
const double maxVal = (double)UInt64.MaxValue;
/// <inheritdocs/>
public override UInt64 Interpolate(double progress, UInt64 oldValue, UInt64 newValue)
{
var normOV = oldValue / maxVal;
var normNV = newValue / maxVal;
var deltaV = normNV - normOV;
return (UInt64)Math.Round(maxVal * ((deltaV * progress) + normOV));
}
}
}

3
src/Avalonia.Animation/DisposeAnimationInstanceSubject.cs

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using Avalonia.Animation.Animators;
using Avalonia.Animation.Utils;
using Avalonia.Collections;
using Avalonia.Data;
@ -33,8 +34,6 @@ namespace Avalonia.Animation
this._onComplete = onComplete;
this._clock = clock;
}
public void Dispose()
{
_lastInstance?.Dispose();

35
src/Avalonia.Animation/DoubleAnimator.cs

@ -1,35 +0,0 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Avalonia.Animation
{
/// <summary>
/// Animator that handles <see cref="double"/> properties.
/// </summary>
public class DoubleAnimator : Animator<double>
{
/// <inheritdocs/>
protected override double DoInterpolation(double t, double neutralValue)
{
var pair = GetKFPairAndIntraKFTime(t);
double y0, y1;
var firstKF = pair.KFPair.FirstKeyFrame;
var secondKF = pair.KFPair.SecondKeyFrame;
if (firstKF.isNeutral)
y0 = neutralValue;
else
y0 = firstKF.TargetValue;
if (secondKF.isNeutral)
y1 = neutralValue;
else
y1 = secondKF.TargetValue;
// Do linear parametric interpolation
return y0 + (pair.IntraKFTime) * (y1 - y0);
}
}
}

0
src/Avalonia.Animation/IEasing.cs → src/Avalonia.Animation/Easing/IEasing.cs

176
src/Avalonia.Animation/IterationCount.cs

@ -0,0 +1,176 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.ComponentModel;
using System.Globalization;
namespace Avalonia.Animation
{
/// <summary>
/// Defines the valid modes for a <see cref="IterationCount"/>.
/// </summary>
public enum IterationType
{
Many,
Infinite
}
/// <summary>
/// Determines the number of iterations of an animation.
/// Also defines its repeat behavior.
/// </summary>
[TypeConverter(typeof(IterationCountTypeConverter))]
public struct IterationCount : IEquatable<IterationCount>
{
private readonly IterationType _type;
private readonly ulong _value;
/// <summary>
/// Initializes a new instance of the <see cref="IterationCount"/> struct.
/// </summary>
/// <param name="value">The number of iterations of an animation.</param>
public IterationCount(ulong value)
: this(value, IterationType.Many)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="IterationCount"/> struct.
/// </summary>
/// <param name="value">The size of the IterationCount.</param>
/// <param name="type">The unit of the IterationCount.</param>
public IterationCount(ulong value, IterationType type)
{
if (type > IterationType.Infinite)
{
throw new ArgumentException("Invalid value", "type");
}
_type = type;
_value = value;
}
/// <summary>
/// Gets an instance of <see cref="IterationCount"/> that indicates that an animation
/// should repeat forever.
/// </summary>
public static IterationCount Infinite => new IterationCount(0, IterationType.Infinite);
/// <summary>
/// Gets the unit of the <see cref="IterationCount"/>.
/// </summary>
public IterationType RepeatType => _type;
/// <summary>
/// Gets a value that indicates whether the <see cref="IterationCount"/> is set to loop.
/// </summary>
public bool IsInfinite => _type == IterationType.Infinite;
/// <summary>
/// Gets the number of repeat iterations.
/// </summary>
public ulong Value => _value;
/// <summary>
/// Compares two IterationCount structures for equality.
/// </summary>
/// <param name="a">The first IterationCount.</param>
/// <param name="b">The second IterationCount.</param>
/// <returns>True if the structures are equal, otherwise false.</returns>
public static bool operator ==(IterationCount a, IterationCount b)
{
return (a.IsInfinite && b.IsInfinite)
|| (a._value == b._value && a._type == b._type);
}
/// <summary>
/// Compares two IterationCount structures for inequality.
/// </summary>
/// <param name="rc1">The first IterationCount.</param>
/// <param name="rc2">The first IterationCount.</param>
/// <returns>True if the structures are unequal, otherwise false.</returns>
public static bool operator !=(IterationCount rc1, IterationCount rc2)
{
return !(rc1 == rc2);
}
/// <summary>
/// Determines whether the <see cref="IterationCount"/> is equal to the specified object.
/// </summary>
/// <param name="o">The object with which to test equality.</param>
/// <returns>True if the objects are equal, otherwise false.</returns>
public override bool Equals(object o)
{
if (o == null)
{
return false;
}
if (!(o is IterationCount))
{
return false;
}
return this == (IterationCount)o;
}
/// <summary>
/// Compares two IterationCount structures for equality.
/// </summary>
/// <param name="IterationCount">The structure with which to test equality.</param>
/// <returns>True if the structures are equal, otherwise false.</returns>
public bool Equals(IterationCount IterationCount)
{
return this == IterationCount;
}
/// <summary>
/// Gets a hash code for the IterationCount.
/// </summary>
/// <returns>The hash code.</returns>
public override int GetHashCode()
{
return _value.GetHashCode() ^ _type.GetHashCode();
}
/// <summary>
/// Gets a string representation of the <see cref="IterationCount"/>.
/// </summary>
/// <returns>The string representation.</returns>
public override string ToString()
{
if (IsInfinite)
{
return "Infinite";
}
string s = _value.ToString();
return s;
}
/// <summary>
/// Parses a string to return a <see cref="IterationCount"/>.
/// </summary>
/// <param name="s">The string.</param>
/// <returns>The <see cref="IterationCount"/>.</returns>
public static IterationCount Parse(string s)
{
s = s.ToUpperInvariant().Trim();
if (s.EndsWith("INFINITE"))
{
return Infinite;
}
else
{
if (s.StartsWith("-"))
throw new InvalidCastException("IterationCount can't be a negative number.");
var value = ulong.Parse(s, CultureInfo.InvariantCulture);
return new IterationCount(value);
}
}
}
}

4
src/Avalonia.Animation/RepeatCountTypeConverter.cs → src/Avalonia.Animation/IterationCountTypeConverter.cs

@ -7,7 +7,7 @@ using System.Globalization;
namespace Avalonia.Animation
{
public class RepeatCountTypeConverter : TypeConverter
public class IterationCountTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
@ -16,7 +16,7 @@ namespace Avalonia.Animation
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
return RepeatCount.Parse((string)value);
return IterationCount.Parse((string)value);
}
}
}

15
src/Avalonia.Animation/KeyFrame.cs

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using Avalonia.Collections;
using Avalonia.Metadata;
namespace Avalonia.Animation
{
@ -17,7 +18,7 @@ namespace Avalonia.Animation
/// Stores data regarding a specific key
/// point and value in an animation.
/// </summary>
public class KeyFrame : AvaloniaList<IAnimationSetter>
public class KeyFrame : AvaloniaObject
{
private TimeSpan _ktimeSpan;
private Cue _kCue;
@ -26,13 +27,11 @@ namespace Avalonia.Animation
{
}
public KeyFrame(IEnumerable<IAnimationSetter> items) : base(items)
{
}
public KeyFrame(params IAnimationSetter[] items) : base(items)
{
}
/// <summary>
/// Gets the setters of <see cref="KeyFrame"/>.
/// </summary>
[Content]
public AvaloniaList<IAnimationSetter> Setters { get; } = new AvaloniaList<IAnimationSetter>();
internal KeyFrameTimingMode TimingMode { get; private set; }

33
src/Avalonia.Animation/KeyFramePair`1.cs

@ -1,33 +0,0 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Avalonia.Animation
{
/// <summary>
/// Represents a pair of keyframe, usually the
/// Start and End keyframes of a <see cref="Animator{T}"/> object.
/// </summary>
public struct KeyFramePair<T>
{
/// <summary>
/// Initializes this <see cref="KeyFramePair{T}"/>
/// </summary>
/// <param name="FirstKeyFrame"></param>
/// <param name="LastKeyFrame"></param>
public KeyFramePair((T TargetValue, bool isNeutral) FirstKeyFrame, (T TargetValue, bool isNeutral) LastKeyFrame) : this()
{
this.FirstKeyFrame = FirstKeyFrame;
this.SecondKeyFrame = LastKeyFrame;
}
/// <summary>
/// First <see cref="KeyFrame"/> object.
/// </summary>
public (T TargetValue, bool isNeutral) FirstKeyFrame { get; }
/// <summary>
/// Second <see cref="KeyFrame"/> object.
/// </summary>
public (T TargetValue, bool isNeutral) SecondKeyFrame { get; }
}
}

33
src/Avalonia.Animation/KeyFrames.cs

@ -0,0 +1,33 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
using Avalonia.Collections;
namespace Avalonia.Animation
{
/// <summary>
/// A collection of <see cref="KeyFrame"/>s.
/// </summary>
public class KeyFrames : AvaloniaList<KeyFrame>
{
/// <summary>
/// Initializes a new instance of the <see cref="KeyFrames"/> class.
/// </summary>
public KeyFrames()
{
ResetBehavior = ResetBehavior.Remove;
}
/// <summary>
/// Initializes a new instance of the <see cref="KeyFrames"/> class.
/// </summary>
/// <param name="items">The initial items in the collection.</param>
public KeyFrames(IEnumerable<KeyFrame> items)
: base(items)
{
ResetBehavior = ResetBehavior.Remove;
}
}
}

3
src/Avalonia.Animation/Properties/AssemblyInfo.cs

@ -5,4 +5,5 @@ using Avalonia.Metadata;
using System.Reflection;
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation.Easings")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation.Easings")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation.Animators")]

199
src/Avalonia.Animation/RepeatCount.cs

@ -1,199 +0,0 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.ComponentModel;
using System.Globalization;
namespace Avalonia.Animation
{
/// <summary>
/// Defines the valid modes for a <see cref="RepeatCount"/>.
/// </summary>
public enum RepeatType
{
None,
Repeat,
Loop
}
/// <summary>
/// Determines the number of iterations of an animation.
/// Also defines its repeat behavior.
/// </summary>
[TypeConverter(typeof(RepeatCountTypeConverter))]
public struct RepeatCount : IEquatable<RepeatCount>
{
private readonly RepeatType _type;
private readonly ulong _value;
/// <summary>
/// Initializes a new instance of the <see cref="RepeatCount"/> struct.
/// </summary>
/// <param name="value">The number of iterations of an animation.</param>
public RepeatCount(ulong value)
: this(value, RepeatType.Repeat)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RepeatCount"/> struct.
/// </summary>
/// <param name="value">The size of the RepeatCount.</param>
/// <param name="type">The unit of the RepeatCount.</param>
public RepeatCount(ulong value, RepeatType type)
{
if (type < RepeatType.None || type > RepeatType.Loop)
{
throw new ArgumentException("Invalid value", "type");
}
_type = type;
_value = value;
}
/// <summary>
/// Gets an instance of <see cref="RepeatCount"/> that indicates that an animation
/// should repeat forever.
/// </summary>
public static RepeatCount Loop => new RepeatCount(0, RepeatType.Loop);
/// <summary>
/// Gets an instance of <see cref="RepeatCount"/> that indicates that an animation
/// should not repeat.
/// </summary>
public static RepeatCount None => new RepeatCount(0, RepeatType.None);
/// <summary>
/// Gets the unit of the <see cref="RepeatCount"/>.
/// </summary>
public RepeatType RepeatType => _type;
/// <summary>
/// Gets a value that indicates whether the <see cref="RepeatCount"/> is set to loop.
/// </summary>
public bool IsLoop => _type == RepeatType.Loop;
/// <summary>
/// Gets a value that indicates whether the <see cref="RepeatCount"/> is set to not repeat.
/// </summary>
public bool IsNone => _type == RepeatType.None;
/// <summary>
/// Gets the number of repeat iterations.
/// </summary>
public ulong Value => _value;
/// <summary>
/// Compares two RepeatCount structures for equality.
/// </summary>
/// <param name="a">The first RepeatCount.</param>
/// <param name="b">The second RepeatCount.</param>
/// <returns>True if the structures are equal, otherwise false.</returns>
public static bool operator ==(RepeatCount a, RepeatCount b)
{
return (a.IsNone && b.IsNone) && (a.IsLoop && b.IsLoop)
|| (a._value == b._value && a._type == b._type);
}
/// <summary>
/// Compares two RepeatCount structures for inequality.
/// </summary>
/// <param name="rc1">The first RepeatCount.</param>
/// <param name="rc2">The first RepeatCount.</param>
/// <returns>True if the structures are unequal, otherwise false.</returns>
public static bool operator !=(RepeatCount rc1, RepeatCount rc2)
{
return !(rc1 == rc2);
}
/// <summary>
/// Determines whether the <see cref="RepeatCount"/> is equal to the specified object.
/// </summary>
/// <param name="o">The object with which to test equality.</param>
/// <returns>True if the objects are equal, otherwise false.</returns>
public override bool Equals(object o)
{
if (o == null)
{
return false;
}
if (!(o is RepeatCount))
{
return false;
}
return this == (RepeatCount)o;
}
/// <summary>
/// Compares two RepeatCount structures for equality.
/// </summary>
/// <param name="RepeatCount">The structure with which to test equality.</param>
/// <returns>True if the structures are equal, otherwise false.</returns>
public bool Equals(RepeatCount RepeatCount)
{
return this == RepeatCount;
}
/// <summary>
/// Gets a hash code for the RepeatCount.
/// </summary>
/// <returns>The hash code.</returns>
public override int GetHashCode()
{
return _value.GetHashCode() ^ _type.GetHashCode();
}
/// <summary>
/// Gets a string representation of the <see cref="RepeatCount"/>.
/// </summary>
/// <returns>The string representation.</returns>
public override string ToString()
{
if (IsLoop)
{
return "Auto";
}
else if (IsNone)
{
return "None";
}
string s = _value.ToString();
return s;
}
/// <summary>
/// Parses a string to return a <see cref="RepeatCount"/>.
/// </summary>
/// <param name="s">The string.</param>
/// <returns>The <see cref="RepeatCount"/>.</returns>
public static RepeatCount Parse(string s)
{
s = s.ToUpperInvariant().Trim();
if (s == "NONE")
{
return None;
}
else if (s.EndsWith("LOOP"))
{
return Loop;
}
else
{
if(s.StartsWith("-"))
throw new InvalidCastException("RepeatCount can't be a negative number.");
var value = ulong.Parse(s, CultureInfo.InvariantCulture);
if (value == 1)
return None;
return new RepeatCount(value);
}
}
}
}

7
src/Avalonia.Animation/DoubleTransition.cs → src/Avalonia.Animation/Transitions/DoubleTransition.cs

@ -14,9 +14,12 @@ namespace Avalonia.Animation
/// <inheritdocs/>
public override IObservable<double> DoTransition(IObservable<double> progress, double oldValue, double newValue)
{
var delta = newValue - oldValue;
return progress
.Select(p => Easing.Ease(p) * delta + oldValue);
.Select(p =>
{
var f = Easing.Ease(p);
return ((newValue - oldValue) * f) + oldValue;
});
}
}
}

0
src/Avalonia.Animation/FloatTransition.cs → src/Avalonia.Animation/Transitions/FloatTransition.cs

0
src/Avalonia.Animation/IntegerTransition.cs → src/Avalonia.Animation/Transitions/IntegerTransition.cs

4
src/Avalonia.Themes.Default/ProgressBar.xaml

@ -33,7 +33,7 @@
<Style Selector="ProgressBar:horizontal:indeterminate /template/ Border#PART_Indicator">
<Style.Animations>
<Animation Duration="0:0:3"
RepeatCount="Loop"
IterationCount="Infinite"
Easing="LinearEasing">
<KeyFrame Cue="0%">
<Setter Property="TranslateTransform.X"
@ -49,7 +49,7 @@
<Style Selector="ProgressBar:vertical:indeterminate /template/ Border#PART_Indicator">
<Style.Animations>
<Animation Duration="0:0:3"
RepeatCount="Loop"
IterationCount="Infinite"
Easing="LinearEasing">
<KeyFrame Cue="0%">
<Setter Property="TranslateTransform.Y"

70
src/Avalonia.Visuals/Animation/Animators/ColorAnimator.cs

@ -0,0 +1,70 @@
// Original color interpolation code was written by Romain Guy and Francois Blavoet
// and adopted from LottieSharp Project (https://github.com/ascora/LottieSharp).
using System;
using System.Reactive.Disposables;
using Avalonia.Logging;
using Avalonia.Media;
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that interpolates <see cref="Color"/> through
/// gamma sRGB color space for better visual result.
/// </summary>
public class ColorAnimator : Animator<Color>
{
// Opto-electronic conversion function for the sRGB color space
// Takes a gamma-encoded sRGB value and converts it to a linear sRGB value
private static double OECF_sRGB(double linear)
{
// IEC 61966-2-1:1999
return linear <= 0.0031308d ? linear * 12.92d : (double)(Math.Pow(linear, 1.0d / 2.4d) * 1.055d - 0.055d);
}
// Electro-optical conversion function for the sRGB color space
// Takes a linear sRGB value and converts it to a gamma-encoded sRGB value
private static double EOCF_sRGB(double srgb)
{
// IEC 61966-2-1:1999
return srgb <= 0.04045d ? srgb / 12.92d : (double)Math.Pow((srgb + 0.055d) / 1.055d, 2.4d);
}
public override Color Interpolate(double progress, Color oldValue, Color newValue)
{
// normalize sRGB values.
var oldA = oldValue.A / 255d;
var oldR = oldValue.R / 255d;
var oldG = oldValue.G / 255d;
var oldB = oldValue.B / 255d;
var newA = newValue.A / 255d;
var newR = newValue.R / 255d;
var newG = newValue.G / 255d;
var newB = newValue.B / 255d;
// convert from sRGB to linear
oldR = EOCF_sRGB(oldR);
oldG = EOCF_sRGB(oldG);
oldB = EOCF_sRGB(oldB);
newR = EOCF_sRGB(newR);
newG = EOCF_sRGB(newG);
newB = EOCF_sRGB(newB);
// compute the interpolated color in linear space
var a = oldA + progress * (newA - oldA);
var r = oldR + progress * (newR - oldR);
var g = oldG + progress * (newG - oldG);
var b = oldB + progress * (newB - oldB);
// convert back to sRGB in the [0..255] range
a = a * 255d;
r = OECF_sRGB(r) * 255d;
g = OECF_sRGB(g) * 255d;
b = OECF_sRGB(b) * 255d;
return new Color((byte)Math.Round(a), (byte)Math.Round(r), (byte)Math.Round(g), (byte)Math.Round(b));
}
}
}

27
src/Avalonia.Visuals/Animation/Animators/CornerRadiusAnimator.cs

@ -0,0 +1,27 @@
using System;
using Avalonia.Logging;
using Avalonia.Media;
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="CornerRadius"/> properties.
/// </summary>
public class CornerRadiusAnimator : Animator<CornerRadius>
{
public override CornerRadius Interpolate(double progress, CornerRadius oldValue, CornerRadius newValue)
{
var deltaTL = newValue.TopLeft - oldValue.TopLeft;
var deltaTR = newValue.TopRight - oldValue.TopRight;
var deltaBR = newValue.BottomRight - oldValue.BottomRight;
var deltaBL = newValue.BottomLeft - oldValue.BottomLeft;
var nTL = progress * deltaTL + oldValue.TopLeft;
var nTR = progress * deltaTR + oldValue.TopRight;
var nBR = progress * deltaBR + oldValue.BottomRight;
var nBL = progress * deltaBL + oldValue.BottomLeft;
return new CornerRadius(nTL, nTR, nBR, nBL);
}
}
}

17
src/Avalonia.Visuals/Animation/Animators/PointAnimator.cs

@ -0,0 +1,17 @@
using System;
using Avalonia.Logging;
using Avalonia.Media;
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="Point"/> properties.
/// </summary>
public class PointAnimator : Animator<Point>
{
public override Point Interpolate(double progress, Point oldValue, Point newValue)
{
return ((newValue - oldValue) * progress) + oldValue;
}
}
}

23
src/Avalonia.Visuals/Animation/Animators/RectAnimator.cs

@ -0,0 +1,23 @@
using System;
using Avalonia.Logging;
using Avalonia.Media;
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="Rect"/> properties.
/// </summary>
public class RectAnimator : Animator<Rect>
{
public override Rect Interpolate(double progress, Rect oldValue, Rect newValue)
{
var deltaPos = newValue.Position - oldValue.Position;
var deltaSize = newValue.Size - oldValue.Size;
var newPos = (deltaPos * progress) + oldValue.Position;
var newSize = (deltaSize * progress) + oldValue.Size;
return new Rect(newPos, newSize);
}
}
}

17
src/Avalonia.Visuals/Animation/Animators/SizeAnimator.cs

@ -0,0 +1,17 @@
using System;
using Avalonia.Logging;
using Avalonia.Media;
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="Size"/> properties.
/// </summary>
public class SizeAnimator : Animator<Size>
{
public override Size Interpolate(double progress, Size oldValue, Size newValue)
{
return ((newValue - oldValue) * progress) + oldValue;
}
}
}

74
src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs

@ -0,0 +1,74 @@
using System;
using System.Reactive.Disposables;
using Avalonia.Logging;
using Avalonia.Media;
using Avalonia.Media.Immutable;
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="SolidColorBrush"/>.
/// </summary>
public class SolidColorBrushAnimator : Animator<SolidColorBrush>
{
ColorAnimator _colorAnimator;
void InitializeColorAnimator()
{
_colorAnimator = new ColorAnimator();
foreach (AnimatorKeyFrame keyframe in this)
{
_colorAnimator.Add(keyframe);
}
_colorAnimator.Property = SolidColorBrush.ColorProperty;
}
public override IDisposable Apply(Animation animation, Animatable control, IClock clock, IObservable<bool> match, Action onComplete)
{
foreach (var keyframe in this)
{
if (keyframe.Value as ISolidColorBrush == null)
return Disposable.Empty;
// Preprocess keyframe values to Color if the xaml parser converts them to ISCB.
if (keyframe.Value.GetType() == typeof(ImmutableSolidColorBrush))
{
keyframe.Value = ((ImmutableSolidColorBrush)keyframe.Value).Color;
}
}
// Add SCB if the target prop is empty.
if (control.GetValue(Property) == null)
control.SetValue(Property, new SolidColorBrush(Colors.Transparent));
var targetVal = control.GetValue(Property);
// Continue if target prop is not empty & is a SolidColorBrush derivative.
if (typeof(ISolidColorBrush).IsAssignableFrom(targetVal.GetType()))
{
if (_colorAnimator == null)
InitializeColorAnimator();
SolidColorBrush finalTarget;
// If it's ISCB, change it back to SCB.
if (targetVal.GetType() == typeof(ImmutableSolidColorBrush))
{
var col = (ImmutableSolidColorBrush)targetVal;
targetVal = new SolidColorBrush(col.Color);
control.SetValue(Property, targetVal);
}
finalTarget = targetVal as SolidColorBrush;
return _colorAnimator.Apply(animation, finalTarget, clock ?? control.Clock, match, onComplete);
}
return Disposable.Empty;
}
public override SolidColorBrush Interpolate(double p, SolidColorBrush o, SolidColorBrush n) => null;
}
}

17
src/Avalonia.Visuals/Animation/Animators/ThicknessAnimator.cs

@ -0,0 +1,17 @@
using System;
using Avalonia.Logging;
using Avalonia.Media;
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="Thickness"/> properties.
/// </summary>
public class ThicknessAnimator : Animator<Thickness>
{
public override Thickness Interpolate(double progress, Thickness oldValue, Thickness newValue)
{
return ((newValue - oldValue) * progress) + oldValue;
}
}
}

37
src/Avalonia.Visuals/Animation/TransformAnimator.cs → src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs

@ -2,14 +2,14 @@
using Avalonia.Logging;
using Avalonia.Media;
namespace Avalonia.Animation
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="Transform"/> properties.
/// </summary>
public class TransformAnimator : Animator<double>
{
DoubleAnimator childAnimator;
DoubleAnimator _doubleAnimator;
/// <inheritdoc/>
public override IDisposable Apply(Animation animation, Animatable control, IClock clock, IObservable<bool> obsMatch, Action onComplete)
@ -27,7 +27,7 @@ namespace Avalonia.Animation
// default RenderTransform order.
normalTransform.Children.Add(new ScaleTransform());
normalTransform.Children.Add(new SkewTransform());
normalTransform.Children.Add(new SkewTransform());
normalTransform.Children.Add(new RotateTransform());
normalTransform.Children.Add(new TranslateTransform());
@ -36,15 +36,22 @@ namespace Avalonia.Animation
var renderTransformType = ctrl.RenderTransform.GetType();
if (childAnimator == null)
if (_doubleAnimator == null)
{
InitializeChildAnimator();
_doubleAnimator = new DoubleAnimator();
foreach (AnimatorKeyFrame keyframe in this)
{
_doubleAnimator.Add(keyframe);
}
_doubleAnimator.Property = Property;
}
// It's a transform object so let's target that.
if (renderTransformType == Property.OwnerType)
{
return childAnimator.Apply(animation, ctrl.RenderTransform, clock ?? control.Clock, obsMatch, onComplete);
return _doubleAnimator.Apply(animation, ctrl.RenderTransform, clock ?? control.Clock, obsMatch, onComplete);
}
// It's a TransformGroup and try finding the target there.
else if (renderTransformType == typeof(TransformGroup))
@ -53,7 +60,7 @@ namespace Avalonia.Animation
{
if (transform.GetType() == Property.OwnerType)
{
return childAnimator.Apply(animation, transform, clock ?? control.Clock, obsMatch, onComplete);
return _doubleAnimator.Apply(animation, transform, clock ?? control.Clock, obsMatch, onComplete);
}
}
}
@ -73,19 +80,7 @@ namespace Avalonia.Animation
return null;
}
void InitializeChildAnimator()
{
childAnimator = new DoubleAnimator();
foreach (AnimatorKeyFrame keyframe in this)
{
childAnimator.Add(keyframe);
}
childAnimator.Property = Property;
}
/// <inheritdocs/>
protected override double DoInterpolation(double time, double neutralValue) => 0;
/// <inheritdocs/>
public override double Interpolate(double p, double o, double n) => 0;
}
}

17
src/Avalonia.Visuals/Animation/Animators/VectorAnimator.cs

@ -0,0 +1,17 @@
using System;
using Avalonia.Logging;
using Avalonia.Media;
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="Vector"/> properties.
/// </summary>
public class VectorAnimator : Animator<Vector>
{
public override Vector Interpolate(double progress, Vector oldValue, Vector newValue)
{
return ((newValue - oldValue) * progress) + oldValue;
}
}
}

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

@ -21,7 +21,7 @@ namespace Avalonia.Animation
/// Initializes a new instance of the <see cref="CrossFade"/> class.
/// </summary>
public CrossFade()
:this(TimeSpan.Zero)
: this(TimeSpan.Zero)
{
}
@ -33,30 +33,40 @@ namespace Avalonia.Animation
{
_fadeOutAnimation = new Animation
{
new KeyFrame
(
new Setter
Children =
{
new KeyFrame()
{
Property = Visual.OpacityProperty,
Value = 0d
Setters =
{
new Setter
{
Property = Visual.OpacityProperty,
Value = 0d
}
},
Cue = new Cue(1d)
}
)
{
Cue = new Cue(1d)
}
};
_fadeInAnimation = new Animation
{
new KeyFrame
(
new Setter
Children =
{
new KeyFrame()
{
Property = Visual.OpacityProperty,
Value = 0d
Setters =
{
new Setter
{
Property = Visual.OpacityProperty,
Value = 0d
}
},
Cue = new Cue(0d)
}
)
{
Cue = new Cue(0d)
}
};
_fadeOutAnimation.Duration = _fadeInAnimation.Duration = duration;

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

@ -74,34 +74,36 @@ namespace Avalonia.Animation
var distance = Orientation == SlideAxis.Horizontal ? parent.Bounds.Width : parent.Bounds.Height;
var translateProperty = Orientation == SlideAxis.Horizontal ? TranslateTransform.XProperty : TranslateTransform.YProperty;
// TODO: Implement relevant transition logic here (or discard this class)
// in favor of XAML based transition for pages
if (from != null)
{
var animation = new Animation
{
new KeyFrame
(
new Setter
{
Property = translateProperty,
Value = 0d
}
)
Children =
{
Cue = new Cue(0d)
},
new KeyFrame
(
new Setter
new KeyFrame
{
Property = translateProperty,
Value = forward ? -distance : distance
}
)
{
Cue = new Cue(1d)
Setters =
{
new Setter
{
Property = translateProperty,
Value = 0d
}
},
Cue = new Cue(0d)
},
new KeyFrame
{
Setters =
{
new Setter
{
Property = translateProperty,
Value = forward ? -distance : distance
}
},
Cue = new Cue(1d)
}
}
};
animation.Duration = Duration;
@ -113,29 +115,34 @@ namespace Avalonia.Animation
to.IsVisible = true;
var animation = new Animation
{
Children =
{
new KeyFrame
(
new Setter
new KeyFrame
{
Property = translateProperty,
Value = forward ? distance : -distance
}
)
{
Cue = new Cue(0d)
},
new KeyFrame
(
new Setter
Setters =
{
new Setter
{
Property = translateProperty,
Value = forward ? distance : -distance
}
},
Cue = new Cue(0d)
},
new KeyFrame
{
Property = translateProperty,
Value = 0d
Setters =
{
new Setter
{
Property = translateProperty,
Value = 0d
}
},
Cue = new Cue(1d)
}
)
{
Cue = new Cue(1d)
},
}
};
animation.Duration = Duration;
tasks.Add(animation.RunAsync(to));

36
src/Avalonia.Visuals/Animation/Transitions/CornerRadiusTransition.cs

@ -0,0 +1,36 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Reactive.Linq;
namespace Avalonia.Animation
{
/// <summary>
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="CornerRadius"/> type.
/// </summary>
public class CornerRadiusTransition : Transition<CornerRadius>
{
/// <inheritdocs/>
public override IObservable<CornerRadius> DoTransition(IObservable<double> progress, CornerRadius oldValue, CornerRadius newValue)
{
return progress
.Select(p =>
{
var f = Easing.Ease(p);
var deltaTL = newValue.TopLeft - oldValue.TopLeft;
var deltaTR = newValue.TopRight - oldValue.TopRight;
var deltaBR = newValue.BottomRight - oldValue.BottomRight;
var deltaBL = newValue.BottomLeft - oldValue.BottomLeft;
var nTL = f * deltaTL + oldValue.TopLeft;
var nTR = f * deltaTR + oldValue.TopRight;
var nBR = f * deltaBR + oldValue.BottomRight;
var nBL = f * deltaBL + oldValue.BottomLeft;
return new CornerRadius(nTL, nTR, nBR, nBL);
});
}
}
}

7
src/Avalonia.Visuals/Animation/PointTransition.cs → src/Avalonia.Visuals/Animation/Transitions/PointTransition.cs

@ -14,16 +14,11 @@ namespace Avalonia.Animation
/// <inheritdocs/>
public override IObservable<Point> DoTransition(IObservable<double> progress, Point oldValue, Point newValue)
{
var deltaX = newValue.X - oldValue.Y;
var deltaY = newValue.X - oldValue.Y;
return progress
.Select(p =>
{
var f = Easing.Ease(p);
var nX = f * deltaX + oldValue.X;
var nY = f * deltaY + oldValue.Y;
return new Point(nX, nY);
return ((newValue - oldValue) * f) + oldValue;
});
}
}

25
src/Avalonia.Visuals/Animation/Transitions/SizeTransition.cs

@ -0,0 +1,25 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Reactive.Linq;
namespace Avalonia.Animation
{
/// <summary>
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="Size"/> type.
/// </summary>
public class SizeTransition : Transition<Size>
{
/// <inheritdocs/>
public override IObservable<Size> DoTransition(IObservable<double> progress, Size oldValue, Size newValue)
{
return progress
.Select(p =>
{
var f = Easing.Ease(p);
return ((newValue - oldValue) * f) + oldValue;
});
}
}
}

13
src/Avalonia.Visuals/Animation/ThicknessTransition.cs → src/Avalonia.Visuals/Animation/Transitions/ThicknessTransition.cs

@ -14,20 +14,11 @@ namespace Avalonia.Animation
/// <inheritdocs/>
public override IObservable<Thickness> DoTransition(IObservable<double> progress, Thickness oldValue, Thickness newValue)
{
var deltaL = newValue.Left - oldValue.Left;
var deltaT = newValue.Top - oldValue.Top;
var deltaR = newValue.Right - oldValue.Right;
var deltaB = newValue.Bottom - oldValue.Bottom;
return progress
.Select(p =>
.Select(p =>
{
var f = Easing.Ease(p);
var nL = f * deltaL + oldValue.Left;
var nT = f * deltaT + oldValue.Right;
var nR = f * deltaR + oldValue.Top;
var nB = f * deltaB + oldValue.Bottom;
return new Thickness(nL, nT, nR, nB);
return ((newValue - oldValue) * f) + oldValue;
});
}
}

25
src/Avalonia.Visuals/Animation/Transitions/VectorTransition.cs

@ -0,0 +1,25 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Reactive.Linq;
namespace Avalonia.Animation
{
/// <summary>
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="Vector"/> type.
/// </summary>
public class VectorTransition : Transition<Vector>
{
/// <inheritdocs/>
public override IObservable<Vector> DoTransition(IObservable<double> progress, Vector oldValue, Vector newValue)
{
return progress
.Select(p =>
{
var f = Easing.Ease(p);
return ((newValue - oldValue) * f) + oldValue;
});
}
}
}

6
src/Avalonia.Visuals/CornerRadius.cs

@ -3,12 +3,18 @@
using System;
using System.Globalization;
using Avalonia.Animation.Animators;
using Avalonia.Utilities;
namespace Avalonia
{
public struct CornerRadius
{
static CornerRadius()
{
Animation.Animation.RegisterAnimator<CornerRadiusAnimator>(prop => typeof(CornerRadius).IsAssignableFrom(prop.PropertyType));
}
public CornerRadius(double uniformRadius)
{
TopLeft = TopRight = BottomLeft = BottomRight = uniformRadius;

3
src/Avalonia.Visuals/Media/Brush.cs

@ -3,6 +3,7 @@
using System;
using System.ComponentModel;
using Avalonia.Animation;
namespace Avalonia.Media
{
@ -10,7 +11,7 @@ namespace Avalonia.Media
/// Describes how an area is painted.
/// </summary>
[TypeConverter(typeof(BrushConverter))]
public abstract class Brush : AvaloniaObject, IMutableBrush
public abstract class Brush : Animatable, IMutableBrush
{
/// <summary>
/// Defines the <see cref="Opacity"/> property.

7
src/Avalonia.Visuals/Media/Color.cs

@ -3,6 +3,8 @@
using System;
using System.Globalization;
using Avalonia.Animation;
using Avalonia.Animation.Animators;
namespace Avalonia.Media
{
@ -11,6 +13,11 @@ namespace Avalonia.Media
/// </summary>
public readonly struct Color
{
static Color()
{
Animation.Animation.RegisterAnimator<ColorAnimator>(prop => typeof(Color).IsAssignableFrom(prop.PropertyType));
}
/// <summary>
/// Gets or sets the Alpha component of the color.
/// </summary>

3
src/Avalonia.Visuals/Media/SolidColorBrush.cs

@ -1,6 +1,8 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Animation;
using Avalonia.Animation.Animators;
using Avalonia.Media.Immutable;
namespace Avalonia.Media
@ -18,6 +20,7 @@ namespace Avalonia.Media
static SolidColorBrush()
{
Animation.Animation.RegisterAnimator<SolidColorBrushAnimator>(prop => typeof(IBrush).IsAssignableFrom(prop.PropertyType));
AffectsRender<SolidColorBrush>(ColorProperty);
}

1
src/Avalonia.Visuals/Media/Transform.cs

@ -3,6 +3,7 @@
using System;
using Avalonia.Animation;
using Avalonia.Animation.Animators;
using Avalonia.VisualTree;
namespace Avalonia.Media

14
src/Avalonia.Visuals/Point.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Globalization;
using Avalonia.Animation.Animators;
using Avalonia.Utilities;
namespace Avalonia
@ -11,6 +12,11 @@ namespace Avalonia
/// </summary>
public readonly struct Point
{
static Point()
{
Animation.Animation.RegisterAnimator<PointAnimator>(prop => typeof(Point).IsAssignableFrom(prop.PropertyType));
}
/// <summary>
/// The X position.
/// </summary>
@ -133,7 +139,7 @@ namespace Avalonia
/// <param name="p">Point to multiply</param>
/// <param name="k">Factor</param>
/// <returns>Points having its coordinates multiplied</returns>
public static Point operator *(Point p, double k) => new Point(p.X*k, p.Y*k);
public static Point operator *(Point p, double k) => new Point(p.X * k, p.Y * k);
/// <summary>
/// Multiplies a point by a factor coordinate-wise
@ -141,7 +147,7 @@ namespace Avalonia
/// <param name="p">Point to multiply</param>
/// <param name="k">Factor</param>
/// <returns>Points having its coordinates multiplied</returns>
public static Point operator *(double k, Point p) => new Point(p.X*k, p.Y*k);
public static Point operator *(double k, Point p) => new Point(p.X * k, p.Y * k);
/// <summary>
/// Divides a point by a factor coordinate-wise
@ -149,8 +155,8 @@ namespace Avalonia
/// <param name="p">Point to divide by</param>
/// <param name="k">Factor</param>
/// <returns>Points having its coordinates divided</returns>
public static Point operator /(Point p, double k) => new Point(p.X/k, p.Y/k);
public static Point operator /(Point p, double k) => new Point(p.X / k, p.Y / k);
/// <summary>
/// Applies a matrix to a point.
/// </summary>

6
src/Avalonia.Visuals/Rect.cs

@ -3,6 +3,7 @@
using System;
using System.Globalization;
using Avalonia.Animation.Animators;
using Avalonia.Utilities;
namespace Avalonia
@ -12,6 +13,11 @@ namespace Avalonia
/// </summary>
public readonly struct Rect
{
static Rect()
{
Animation.Animation.RegisterAnimator<RectAnimator>(prop => typeof(Rect).IsAssignableFrom(prop.PropertyType));
}
/// <summary>
/// An empty rectangle.
/// </summary>

6
src/Avalonia.Visuals/Size.cs

@ -3,6 +3,7 @@
using System;
using System.Globalization;
using Avalonia.Animation.Animators;
using Avalonia.Utilities;
namespace Avalonia
@ -12,6 +13,11 @@ namespace Avalonia
/// </summary>
public readonly struct Size
{
static Size()
{
Animation.Animation.RegisterAnimator<SizeAnimator>(prop => typeof(Size).IsAssignableFrom(prop.PropertyType));
}
/// <summary>
/// A size representing infinity.
/// </summary>

39
src/Avalonia.Visuals/Thickness.cs

@ -3,6 +3,8 @@
using System;
using System.Globalization;
using Avalonia.Animation;
using Avalonia.Animation.Animators;
using Avalonia.Utilities;
namespace Avalonia
@ -12,6 +14,11 @@ namespace Avalonia
/// </summary>
public readonly struct Thickness
{
static Thickness()
{
Animation.Animation.RegisterAnimator<ThicknessAnimator>(prop => typeof(Thickness).IsAssignableFrom(prop.PropertyType));
}
/// <summary>
/// The thickness on the left.
/// </summary>
@ -134,6 +141,36 @@ namespace Avalonia
a.Bottom + b.Bottom);
}
/// <summary>
/// Subtracts two Thicknesses.
/// </summary>
/// <param name="a">The first thickness.</param>
/// <param name="b">The second thickness.</param>
/// <returns>The equality.</returns>
public static Thickness operator -(Thickness a, Thickness b)
{
return new Thickness(
a.Left - b.Left,
a.Top - b.Top,
a.Right - b.Right,
a.Bottom - b.Bottom);
}
/// <summary>
/// Multiplies a Thickness to a scalar.
/// </summary>
/// <param name="a">The thickness.</param>
/// <param name="b">The scalar.</param>
/// <returns>The equality.</returns>
public static Thickness operator *(Thickness a, double b)
{
return new Thickness(
a.Left * b,
a.Top * b,
a.Right * b,
a.Bottom * b);
}
/// <summary>
/// Adds a Thickness to a Size.
/// </summary>
@ -169,7 +206,7 @@ namespace Avalonia
{
using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Thickness"))
{
if(tokenizer.TryReadDouble(out var a))
if (tokenizer.TryReadDouble(out var a))
{
if (tokenizer.TryReadDouble(out var b))
{

10
src/Avalonia.Visuals/Vector.cs

@ -3,6 +3,7 @@
using System;
using System.Globalization;
using Avalonia.Animation.Animators;
using JetBrains.Annotations;
namespace Avalonia
@ -12,6 +13,11 @@ namespace Avalonia
/// </summary>
public readonly struct Vector
{
static Vector()
{
Animation.Animation.RegisterAnimator<VectorAnimator>(prop => typeof(Vector).IsAssignableFrom(prop.PropertyType));
}
/// <summary>
/// The X vector.
/// </summary>
@ -60,7 +66,7 @@ namespace Avalonia
/// <returns>The dot product</returns>
public static double operator *(Vector a, Vector b)
{
return a.X*b.X + a.Y*b.Y;
return a.X * b.X + a.Y * b.Y;
}
/// <summary>
@ -88,7 +94,7 @@ namespace Avalonia
/// <summary>
/// Length of the vector
/// </summary>
public double Length => Math.Sqrt(X*X + Y*Y);
public double Length => Math.Sqrt(X * X + Y * Y);
/// <summary>
/// Negates a vector.

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

@ -0,0 +1,77 @@
using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Styling;
using Avalonia.UnitTests;
using Avalonia.Data;
using Xunit;
namespace Avalonia.Animation.UnitTests
{
public class AnimationIterationTests
{
[Fact]
public void Check_Initial_Inter_and_Trailing_Delay_Values()
{
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(3),
Delay = TimeSpan.FromSeconds(3),
DelayBetweenIterations = TimeSpan.FromSeconds(3),
IterationCount = new IterationCount(2),
Children =
{
keyframe2,
keyframe1
}
};
var border = new Border()
{
Height = 100d,
Width = 100d
};
var clock = new TestClock();
var animationRun = animation.RunAsync(border, clock);
clock.Step(TimeSpan.Zero);
// Initial Delay.
clock.Step(TimeSpan.FromSeconds(1));
Assert.Equal(border.Width, 0d);
clock.Step(TimeSpan.FromSeconds(6));
// First Inter-Iteration delay.
clock.Step(TimeSpan.FromSeconds(8));
Assert.Equal(border.Width, 200d);
// Trailing Delay should be non-existent.
clock.Step(TimeSpan.FromSeconds(14));
Assert.True(animationRun.Status == TaskStatus.RanToCompletion);
Assert.Equal(border.Width, 100d);
}
}
}

25
tests/Avalonia.Animation.UnitTests/Avalonia.Animation.UnitTests.csproj

@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.0</TargetFrameworks>
<OutputType>Library</OutputType>
</PropertyGroup>
<Import Project="..\..\build\UnitTests.NetCore.targets" />
<Import Project="..\..\build\Moq.props" />
<Import Project="..\..\build\XUnit.props" />
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\Microsoft.Reactive.Testing.props" />
<ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Animation\Avalonia.Animation.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Controls\Avalonia.Controls.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Input\Avalonia.Input.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Interactivity\Avalonia.Interactivity.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Layout\Avalonia.Layout.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Visuals\Avalonia.Visuals.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Styling\Avalonia.Styling.csproj" />
<ProjectReference Include="..\Avalonia.UnitTests\Avalonia.UnitTests.csproj" />
</ItemGroup>
<ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup>
</Project>

10
tests/Avalonia.Animation.UnitTests/Properties/AssemblyInfo.cs

@ -0,0 +1,10 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Reflection;
using Xunit;
[assembly: AssemblyTitle("Avalonia.Animation.UnitTests")]
// Don't run tests in parallel.
[assembly: CollectionBehavior(DisableTestParallelization = true)]

28
tests/Avalonia.Animation.UnitTests/TestClock.cs

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
namespace Avalonia.Animation.UnitTests
{
internal class TestClock : IClock, IDisposable
{
private IObserver<TimeSpan> _observer;
public PlayState PlayState { get; set; } = PlayState.Run;
public void Dispose()
{
_observer?.OnCompleted();
}
public void Step(TimeSpan time)
{
_observer?.OnNext(time);
}
public IDisposable Subscribe(IObserver<TimeSpan> observer)
{
_observer = observer;
return this;
}
}
}
Loading…
Cancel
Save