diff --git a/src/Avalonia.Visuals/Animation/Animators/BaseBrushAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/BaseBrushAnimator.cs
index 508891fd72..be674269bf 100644
--- a/src/Avalonia.Visuals/Animation/Animators/BaseBrushAnimator.cs
+++ b/src/Avalonia.Visuals/Animation/Animators/BaseBrushAnimator.cs
@@ -4,6 +4,8 @@ using System.Reactive.Disposables;
using Avalonia.Logging;
using Avalonia.Media;
+#nullable enable
+
namespace Avalonia.Animation.Animators
{
///
@@ -12,9 +14,9 @@ namespace Avalonia.Animation.Animators
/// redirect them to the properly registered
/// animators in this class.
///
- public class BaseBrushAnimator : Animator
+ public class BaseBrushAnimator : Animator
{
- private IAnimator _targetAnimator;
+ private IAnimator? _targetAnimator;
private static readonly List<(Func Match, Type AnimatorType)> _brushAnimators =
new List<(Func Match, Type AnimatorType)>();
@@ -31,7 +33,7 @@ namespace Avalonia.Animation.Animators
/// The type of the animator to instantiate.
///
public static void RegisterBrushAnimator(Func condition)
- where TAnimator : IAnimator
+ where TAnimator : IAnimator, new()
{
_brushAnimators.Insert(0, (condition, typeof(TAnimator)));
}
@@ -40,20 +42,18 @@ namespace Avalonia.Animation.Animators
public override IDisposable Apply(Animation animation, Animatable control, IClock clock,
IObservable match, Action onComplete)
{
- foreach (var valueType in _brushAnimators)
- {
- if (!valueType.Match(this[0].Value.GetType())) continue;
-
- _targetAnimator = (IAnimator)Activator.CreateInstance(valueType.AnimatorType);
+ _targetAnimator = CreateAnimatorFromType(this[0].Value.GetType());
+ if (_targetAnimator != null)
+ {
foreach (var keyframe in this)
{
_targetAnimator.Add(keyframe);
}
_targetAnimator.Property = this.Property;
-
- return _targetAnimator.Apply(animation, control, clock, match, onComplete);
+
+ return _targetAnimator.Apply(animation, control, clock, match, onComplete);
}
Logger.TryGet(LogEventLevel.Error, LogArea.Animations)?.Log(
@@ -64,6 +64,19 @@ namespace Avalonia.Animation.Animators
}
///
- public override IBrush Interpolate(double progress, IBrush oldValue, IBrush newValue) => null;
+ public override IBrush? Interpolate(double progress, IBrush? oldValue, IBrush? newValue) => null;
+
+ internal static IAnimator? CreateAnimatorFromType(Type type)
+ {
+ foreach (var (match, animatorType) in _brushAnimators)
+ {
+ if (!match(type))
+ continue;
+
+ return (IAnimator)Activator.CreateInstance(animatorType);
+ }
+
+ return null;
+ }
}
}
diff --git a/src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs
new file mode 100644
index 0000000000..e51103b9b5
--- /dev/null
+++ b/src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs
@@ -0,0 +1,74 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using Avalonia.Data;
+using Avalonia.Media;
+using Avalonia.Media.Immutable;
+
+namespace Avalonia.Animation.Animators
+{
+ ///
+ /// Animator that handles values.
+ ///
+ public class IGradientBrushAnimator : Animator
+ {
+ private static readonly RelativePointAnimator s_relativePointAnimator = new RelativePointAnimator();
+ private static readonly DoubleAnimator s_doubleAnimator = new DoubleAnimator();
+
+ public override IGradientBrush Interpolate(double progress, IGradientBrush oldValue, IGradientBrush newValue)
+ {
+ if (oldValue is null || newValue is null
+ || oldValue.GradientStops.Count != oldValue.GradientStops.Count)
+ {
+ return progress >= 1 ? newValue : oldValue;
+ }
+
+ switch (oldValue)
+ {
+ case IRadialGradientBrush oldRadial when newValue is IRadialGradientBrush newRadial:
+ return new ImmutableRadialGradientBrush(
+ InterpolateStops(progress, oldValue.GradientStops, newValue.GradientStops),
+ s_doubleAnimator.Interpolate(progress, oldValue.Opacity, newValue.Opacity),
+ oldValue.SpreadMethod,
+ s_relativePointAnimator.Interpolate(progress, oldRadial.Center, newRadial.Center),
+ s_relativePointAnimator.Interpolate(progress, oldRadial.GradientOrigin, newRadial.GradientOrigin),
+ s_doubleAnimator.Interpolate(progress, oldRadial.Radius, newRadial.Radius));
+
+ case IConicGradientBrush oldConic when newValue is IConicGradientBrush newConic:
+ return new ImmutableConicGradientBrush(
+ InterpolateStops(progress, oldValue.GradientStops, newValue.GradientStops),
+ s_doubleAnimator.Interpolate(progress, oldValue.Opacity, newValue.Opacity),
+ oldValue.SpreadMethod,
+ s_relativePointAnimator.Interpolate(progress, oldConic.Center, newConic.Center),
+ s_doubleAnimator.Interpolate(progress, oldConic.Angle, newConic.Angle));
+
+ case ILinearGradientBrush oldLinear when newValue is ILinearGradientBrush newLinear:
+ return new ImmutableLinearGradientBrush(
+ InterpolateStops(progress, oldValue.GradientStops, newValue.GradientStops),
+ s_doubleAnimator.Interpolate(progress, oldValue.Opacity, newValue.Opacity),
+ oldValue.SpreadMethod,
+ s_relativePointAnimator.Interpolate(progress, oldLinear.StartPoint, newLinear.StartPoint),
+ s_relativePointAnimator.Interpolate(progress, oldLinear.EndPoint, newLinear.EndPoint));
+
+ default:
+ return progress >= 1 ? newValue : oldValue;
+ }
+ }
+
+ public override IDisposable BindAnimation(Animatable control, IObservable instance)
+ {
+ return control.Bind((AvaloniaProperty)Property, instance, BindingPriority.Animation);
+ }
+
+ private IReadOnlyList InterpolateStops(double progress, IReadOnlyList oldValue, IReadOnlyList newValue)
+ {
+ // pool
+ return oldValue
+ .Zip(newValue, (f, s) => new ImmutableGradientStop(
+ s_doubleAnimator.Interpolate(progress, f.Offset, s.Offset),
+ ColorAnimator.InterpolateCore(progress, f.Color, s.Color)))
+ .ToArray();
+ }
+ }
+}
diff --git a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs
index a56cc1de8c..ba2f2ae766 100644
--- a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs
+++ b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs
@@ -14,7 +14,7 @@ namespace Avalonia.Animation.Animators
{
if (oldValue is null || newValue is null)
{
- return oldValue;
+ return progress >= 1 ? newValue : oldValue;
}
return new ImmutableSolidColorBrush(ColorAnimator.InterpolateCore(progress, oldValue.Color, newValue.Color));
@@ -26,7 +26,7 @@ namespace Avalonia.Animation.Animators
}
}
- [Obsolete]
+ [Obsolete("Use ISolidColorBrushAnimator instead")]
public class SolidColorBrushAnimator : Animator
{
public override SolidColorBrush Interpolate(double progress, SolidColorBrush oldValue, SolidColorBrush newValue)
diff --git a/src/Avalonia.Visuals/Animation/Transitions/BrushTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/BrushTransition.cs
index cc5af1b4b1..7cc3f597b5 100644
--- a/src/Avalonia.Visuals/Animation/Transitions/BrushTransition.cs
+++ b/src/Avalonia.Visuals/Animation/Transitions/BrushTransition.cs
@@ -1,4 +1,6 @@
using System;
+using System.Linq;
+
using Avalonia.Animation.Animators;
using Avalonia.Animation.Easings;
using Avalonia.Media;
@@ -9,34 +11,34 @@ namespace Avalonia.Animation
{
///
/// Transition class that handles with type.
- /// Only values of will transition correctly at the moment.
///
public class BrushTransition : Transition
{
- private static readonly ISolidColorBrushAnimator s_animator = new ISolidColorBrushAnimator();
-
public override IObservable DoTransition(IObservable progress, IBrush? oldValue, IBrush? newValue)
{
- var oldSolidColorBrush = TryGetSolidColorBrush(oldValue);
- var newSolidColorBrush = TryGetSolidColorBrush(newValue);
+ var type = oldValue?.GetType() ?? newValue?.GetType();
+ if (type == null)
+ {
+ return new IncompatibleTransitionObservable(progress, Easing, oldValue, newValue);
+ }
- if (oldSolidColorBrush != null && newSolidColorBrush != null)
+ var animator = BaseBrushAnimator.CreateAnimatorFromType(type);
+ if (animator == null)
{
- return new AnimatorTransitionObservable(
- s_animator, progress, Easing, oldSolidColorBrush, newSolidColorBrush);
+ return new IncompatibleTransitionObservable(progress, Easing, oldValue, newValue);
}
- return new IncompatibleTransitionObservable(progress, Easing, oldValue, newValue);
- }
+ var animatorType = animator.GetType();
+ var animatorGenericArgument = animatorType.BaseType.GetGenericArguments().FirstOrDefault() ?? type;
- private static ISolidColorBrush? TryGetSolidColorBrush(IBrush? brush)
- {
- if (brush is null)
+ var observableType = typeof(AnimatorTransitionObservable<,>).MakeGenericType(animatorGenericArgument, animatorType);
+ var observable = Activator.CreateInstance(observableType, animator, progress, Easing, oldValue, newValue) as IObservable;
+ if (observable == null)
{
- return Brushes.Transparent;
+ return new IncompatibleTransitionObservable(progress, Easing, oldValue, newValue);
}
- return brush as ISolidColorBrush;
+ return observable;
}
private class IncompatibleTransitionObservable : TransitionObservableBase
diff --git a/src/Avalonia.Visuals/Media/GradientBrush.cs b/src/Avalonia.Visuals/Media/GradientBrush.cs
index 99923b8e06..4fb753a9de 100644
--- a/src/Avalonia.Visuals/Media/GradientBrush.cs
+++ b/src/Avalonia.Visuals/Media/GradientBrush.cs
@@ -2,6 +2,8 @@ using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
+
+using Avalonia.Animation.Animators;
using Avalonia.Collections;
using Avalonia.Metadata;
@@ -28,6 +30,7 @@ namespace Avalonia.Media
static GradientBrush()
{
+ BaseBrushAnimator.RegisterBrushAnimator(match => typeof(IGradientBrush).IsAssignableFrom(match));
GradientStopsProperty.Changed.Subscribe(GradientStopsChanged);
AffectsRender(SpreadMethodProperty);
}