diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs index 05142532e9..c42153ec4f 100644 --- a/src/Avalonia.Animation/Animation.cs +++ b/src/Avalonia.Animation/Animation.cs @@ -209,6 +209,17 @@ namespace Avalonia.Animation ( prop => typeof(decimal).IsAssignableFrom(prop.PropertyType), typeof(DecimalAnimator) ), }; + /// + /// Registers a that can handle + /// a value type that matches the specified condition. + /// + /// + /// The condition to which the + /// is to be activated and used. + /// + /// + /// The type of the animator to instantiate. + /// public static void RegisterAnimator(Func condition) where TAnimator : IAnimator { diff --git a/src/Avalonia.Animation/Animators/Animator`1.cs b/src/Avalonia.Animation/Animators/Animator`1.cs index 0660440e30..d784227620 100644 --- a/src/Avalonia.Animation/Animators/Animator`1.cs +++ b/src/Avalonia.Animation/Animators/Animator`1.cs @@ -104,6 +104,11 @@ namespace Avalonia.Animation.Animators throw new Exception("Index time is out of keyframe time range."); } + public virtual IDisposable BindAnimation(Animatable control, IObservable instance) + { + return control.Bind((AvaloniaProperty)Property, instance, BindingPriority.Animation); + } + /// /// Runs the KeyFrames Animation. /// @@ -116,7 +121,8 @@ namespace Avalonia.Animation.Animators clock ?? control.Clock ?? Clock.GlobalClock, onComplete, InterpolationHandler); - return control.Bind((AvaloniaProperty)Property, instance, BindingPriority.Animation); + + return BindAnimation(control, instance); } /// diff --git a/src/Avalonia.Visuals/Animation/Animators/BaseBrushAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/BaseBrushAnimator.cs new file mode 100644 index 0000000000..508891fd72 --- /dev/null +++ b/src/Avalonia.Visuals/Animation/Animators/BaseBrushAnimator.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Reactive.Disposables; +using Avalonia.Logging; +using Avalonia.Media; + +namespace Avalonia.Animation.Animators +{ + /// + /// Animator that handles all animations on properties + /// with as their type and + /// redirect them to the properly registered + /// animators in this class. + /// + public class BaseBrushAnimator : Animator + { + private IAnimator _targetAnimator; + + private static readonly List<(Func Match, Type AnimatorType)> _brushAnimators = + new List<(Func Match, Type AnimatorType)>(); + + /// + /// Register an that handles a specific + /// 's descendant value type. + /// + /// + /// The condition to which the + /// is to be activated and used. + /// + /// + /// The type of the animator to instantiate. + /// + public static void RegisterBrushAnimator(Func condition) + where TAnimator : IAnimator + { + _brushAnimators.Insert(0, (condition, typeof(TAnimator))); + } + + /// + 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); + + foreach (var keyframe in this) + { + _targetAnimator.Add(keyframe); + } + + _targetAnimator.Property = this.Property; + + return _targetAnimator.Apply(animation, control, clock, match, onComplete); + } + + Logger.TryGet(LogEventLevel.Error, LogArea.Animations)?.Log( + this, + "The animation's keyframe values didn't match any brush animators registered in BaseBrushAnimator."); + + return Disposable.Empty; + } + + /// + public override IBrush Interpolate(double progress, IBrush oldValue, IBrush newValue) => null; + } +} diff --git a/src/Avalonia.Visuals/Animation/Animators/ColorAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/ColorAnimator.cs index 6d1f6c39bd..1686cafeb9 100644 --- a/src/Avalonia.Visuals/Animation/Animators/ColorAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/ColorAnimator.cs @@ -31,6 +31,11 @@ namespace Avalonia.Animation.Animators } public override Color Interpolate(double progress, Color oldValue, Color newValue) + { + return InterpolateCore(progress, oldValue, newValue); + } + + internal static Color InterpolateCore(double progress, Color oldValue, Color newValue) { // normalize sRGB values. var oldA = oldValue.A / 255d; @@ -59,7 +64,7 @@ namespace Avalonia.Animation.Animators var b = oldB + progress * (newB - oldB); // convert back to sRGB in the [0..255] range - a = a * 255d; + a *= 255d; r = OECF_sRGB(r) * 255d; g = OECF_sRGB(g) * 255d; b = OECF_sRGB(b) * 255d; diff --git a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs index a8e618af27..cec96fecf8 100644 --- a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs @@ -1,71 +1,32 @@ using System; -using System.Reactive.Disposables; +using Avalonia.Data; using Avalonia.Media; using Avalonia.Media.Immutable; namespace Avalonia.Animation.Animators { /// - /// Animator that handles . + /// Animator that handles values. /// - public class SolidColorBrushAnimator : Animator + public class ISolidColorBrushAnimator : Animator { - private ColorAnimator _colorAnimator; - - private void InitializeColorAnimator() + public override ISolidColorBrush Interpolate(double progress, ISolidColorBrush oldValue, ISolidColorBrush newValue) { - _colorAnimator = new ColorAnimator(); - - foreach (AnimatorKeyFrame keyframe in this) - { - _colorAnimator.Add(keyframe); - } - - _colorAnimator.Property = SolidColorBrush.ColorProperty; + return new ImmutableSolidColorBrush(ColorAnimator.InterpolateCore(progress, oldValue.Color, newValue.Color)); } - public override IDisposable Apply(Animation animation, Animatable control, IClock clock, IObservable match, Action onComplete) + public override IDisposable BindAnimation(Animatable control, IObservable instance) { - // Preprocess keyframe values to Color if the xaml parser converts them to ISCB. - foreach (var keyframe in this) - { - if (keyframe.Value is ISolidColorBrush colorBrush) - { - keyframe.Value = colorBrush.Color; - } - else - { - return Disposable.Empty; - } - } - - SolidColorBrush finalTarget; - var targetVal = control.GetValue(Property); - if (targetVal is null) - { - finalTarget = new SolidColorBrush(Colors.Transparent); - control.SetValue(Property, finalTarget); - } - else if (targetVal is ImmutableSolidColorBrush immutableSolidColorBrush) - { - finalTarget = new SolidColorBrush(immutableSolidColorBrush.Color); - control.SetValue(Property, finalTarget); - } - else if (targetVal is ISolidColorBrush) - { - finalTarget = targetVal as SolidColorBrush; - } - else - { - return Disposable.Empty; - } - - if (_colorAnimator == null) - InitializeColorAnimator(); - - return _colorAnimator.Apply(animation, finalTarget, clock ?? control.Clock, match, onComplete); + return control.Bind((AvaloniaProperty)Property, instance, BindingPriority.Animation); + } + } + + [Obsolete] + public class SolidColorBrushAnimator : Animator + { + public override SolidColorBrush Interpolate(double progress, SolidColorBrush oldValue, SolidColorBrush newValue) + { + return new SolidColorBrush(ColorAnimator.InterpolateCore(progress, oldValue.Color, newValue.Color)); } - - public override SolidColorBrush Interpolate(double p, SolidColorBrush o, SolidColorBrush n) => null; } } diff --git a/src/Avalonia.Visuals/Media/Brush.cs b/src/Avalonia.Visuals/Media/Brush.cs index a19d5af8b7..fb03d19a4e 100644 --- a/src/Avalonia.Visuals/Media/Brush.cs +++ b/src/Avalonia.Visuals/Media/Brush.cs @@ -1,6 +1,7 @@ using System; using System.ComponentModel; using Avalonia.Animation; +using Avalonia.Animation.Animators; namespace Avalonia.Media { @@ -21,6 +22,7 @@ namespace Avalonia.Media static Brush() { + Animation.Animation.RegisterAnimator(prop => typeof(IBrush).IsAssignableFrom(prop.PropertyType)); AffectsRender(OpacityProperty); } diff --git a/src/Avalonia.Visuals/Media/SolidColorBrush.cs b/src/Avalonia.Visuals/Media/SolidColorBrush.cs index 8e30880489..fd94cbd214 100644 --- a/src/Avalonia.Visuals/Media/SolidColorBrush.cs +++ b/src/Avalonia.Visuals/Media/SolidColorBrush.cs @@ -1,4 +1,3 @@ -using Avalonia.Animation; using Avalonia.Animation.Animators; using Avalonia.Media.Immutable; @@ -17,7 +16,7 @@ namespace Avalonia.Media static SolidColorBrush() { - Animation.Animation.RegisterAnimator(prop => typeof(IBrush).IsAssignableFrom(prop.PropertyType)); + BaseBrushAnimator.RegisterBrushAnimator(match => typeof(ISolidColorBrush).IsAssignableFrom(match)); AffectsRender(ColorProperty); }