Browse Source

Reflection free implementation with automatic convertion from solid color brush to gradient

Max Katz 4 years ago
parent
commit
a885e673c8

+ 33 - 1
samples/RenderDemo/Pages/AnimationsPage.xaml

@@ -168,6 +168,9 @@
                      IterationCount="Infinite"
                      PlaybackDirection="Alternate">
             <KeyFrame Cue="0%">
+              <Setter Property="Background" Value="Red" />
+            </KeyFrame>
+            <KeyFrame Cue="30%">
               <Setter Property="Background">
                 <LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
                   <GradientStop Offset="0" Color="Red"/>
@@ -175,6 +178,9 @@
                 </LinearGradientBrush>
               </Setter>
             </KeyFrame>
+            <KeyFrame Cue="60%">
+              <Setter Property="Background" Value="Blue" />
+            </KeyFrame>
             <KeyFrame Cue="100%">
               <Setter Property="Background">
                 <LinearGradientBrush StartPoint="100%,0%" EndPoint="0%,100%">
@@ -188,6 +194,31 @@
       </Style>
 
       <Style Selector="Border.Rect8">
+        <Style.Animations>
+          <Animation Duration="0:0:3"
+                     IterationCount="Infinite"
+                     PlaybackDirection="Alternate">
+            <KeyFrame Cue="0%">
+              <Setter Property="Background">
+                <LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
+                  <GradientStop Offset="0" Color="Red"/>
+                  <GradientStop Offset="1" Color="Blue"/>
+                </LinearGradientBrush>
+              </Setter>
+            </KeyFrame>
+            <KeyFrame Cue="100%">
+              <Setter Property="Background">
+                <LinearGradientBrush StartPoint="100%,0%" EndPoint="0%,100%">
+                  <GradientStop Offset="0" Color="Green"/>
+                  <GradientStop Offset="1" Color="Yellow"/>
+                </LinearGradientBrush>
+              </Setter>
+            </KeyFrame>
+          </Animation>
+        </Style.Animations>
+      </Style>
+
+      <Style Selector="Border.Rect9">
         <Style.Animations>
           <Animation Duration="0:0:3"
                      IterationCount="Infinite"
@@ -212,7 +243,7 @@
         </Style.Animations>
       </Style>
 
-      <Style Selector="Border.Rect9">
+      <Style Selector="Border.Rect10">
         <Style.Animations>
           <Animation Duration="0:0:3"
                      IterationCount="Infinite"
@@ -259,6 +290,7 @@
         <Border Classes="Test Rect7" Child="{x:Null}" />
         <Border Classes="Test Rect8" Child="{x:Null}" />
         <Border Classes="Test Rect9" Child="{x:Null}" />
+        <Border Classes="Test Rect10" Child="{x:Null}" />
       </WrapPanel>
     </StackPanel>
   </Grid>

+ 107 - 22
src/Avalonia.Visuals/Animation/Animators/BaseBrushAnimator.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
-using System.Reactive.Disposables;
+using System.Diagnostics.CodeAnalysis;
+
 using Avalonia.Logging;
 using Avalonia.Media;
 
@@ -16,8 +17,6 @@ namespace Avalonia.Animation.Animators
     /// </summary>
     public class BaseBrushAnimator : Animator<IBrush?>
     {
-        private IAnimator? _targetAnimator;
-
         private static readonly List<(Func<Type, bool> Match, Type AnimatorType)> _brushAnimators =
             new List<(Func<Type, bool> Match, Type AnimatorType)>();
 
@@ -42,41 +41,127 @@ namespace Avalonia.Animation.Animators
         public override IDisposable Apply(Animation animation, Animatable control, IClock clock,
             IObservable<bool> match, Action onComplete)
         {
-            _targetAnimator = CreateAnimatorFromType(this[0].Value.GetType());
+            if (TryCreateCustomRegisteredAnimator(out var animator)
+                || TryCreateGradientAnimator(out animator)
+                || TryCreateSolidColorBrushAnimator(out animator))
+            {
+                return animator.Apply(animation, control, clock, match, onComplete);
+            }
+
+            Logger.TryGet(LogEventLevel.Error, LogArea.Animations)?.Log(
+                this,
+                "The animation's keyframe value types set is not supported.");
+
+            return base.Apply(animation, control, clock, match, onComplete);
+        }
+
+        /// <summary>
+        /// Fallback implementation of <see cref="IBrush"/> animation.
+        /// </summary>
+        public override IBrush? Interpolate(double progress, IBrush? oldValue, IBrush? newValue) => progress >= 0.5 ? newValue : oldValue;
 
-            if (_targetAnimator != null)
+        private bool TryCreateGradientAnimator([NotNullWhen(true)] out IAnimator? animator)
+        {
+            IGradientBrush? firstGradient = null;
+            foreach (var keyframe in this)
             {
-                foreach (var keyframe in this)
+                if (keyframe.Value is IGradientBrush gradientBrush)
                 {
-                    _targetAnimator.Add(keyframe);
+                    firstGradient = gradientBrush;
+                    break;
                 }
+            }
+
+            if (firstGradient is null)
+            {
+                animator = null;
+                return false;
+            }
 
-                _targetAnimator.Property = this.Property;
+            var gradientAnimator = new IGradientBrushAnimator();
+            gradientAnimator.Property = Property;
 
-                return _targetAnimator.Apply(animation, control, clock, match, onComplete);
+            foreach (var keyframe in this)
+            {
+                if (keyframe.Value is ISolidColorBrush solidColorBrush)
+                {
+                    gradientAnimator.Add(new AnimatorKeyFrame(typeof(IGradientBrushAnimator), keyframe.Cue, keyframe.KeySpline)
+                    {
+                        Value = IGradientBrushAnimator.ConvertSolidColorBrushToGradient(firstGradient, solidColorBrush)
+                    });
+                }
+                else if (keyframe.Value is IGradientBrush)
+                {
+                    gradientAnimator.Add(new AnimatorKeyFrame(typeof(IGradientBrushAnimator), keyframe.Cue, keyframe.KeySpline)
+                    {
+                        Value = keyframe.Value
+                    });
+                }
+                else
+                {
+                    animator = null;
+                    return false;
+                }
             }
 
-            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;
+            animator = gradientAnimator;
+            return true;
         }
 
-        /// <inheritdoc/>
-        public override IBrush? Interpolate(double progress, IBrush? oldValue, IBrush? newValue) => null;
+        private bool TryCreateSolidColorBrushAnimator([NotNullWhen(true)] out IAnimator? animator)
+        {
+            var solidColorBrushAnimator = new ISolidColorBrushAnimator();
+            solidColorBrushAnimator.Property = Property;
+
+            foreach (var keyframe in this)
+            {
+                if (keyframe.Value is ISolidColorBrush)
+                {
+                    solidColorBrushAnimator.Add(new AnimatorKeyFrame(typeof(ISolidColorBrushAnimator), keyframe.Cue, keyframe.KeySpline)
+                    {
+                        Value = keyframe.Value
+                    });
+                }
+                else
+                {
+                    animator = null;
+                    return false;
+                }
+            }
 
-        internal static IAnimator? CreateAnimatorFromType(Type type)
+            animator = solidColorBrushAnimator;
+            return true;
+        }
+
+        private bool TryCreateCustomRegisteredAnimator([NotNullWhen(true)] out IAnimator? animator)
         {
-            foreach (var (match, animatorType) in _brushAnimators)
+            if (_brushAnimators.Count > 0)
             {
-                if (!match(type))
-                    continue;
+                var firstKeyType = this[0].Value.GetType();
+                foreach (var (match, animatorType) in _brushAnimators)
+                {
+                    if (!match(firstKeyType))
+                        continue;
 
-                return (IAnimator)Activator.CreateInstance(animatorType);
+                    animator = (IAnimator)Activator.CreateInstance(animatorType);
+                    if (animator != null)
+                    {
+                        animator.Property = Property;
+                        foreach (var keyframe in this)
+                        {
+                            animator.Add(new AnimatorKeyFrame(animatorType, keyframe.Cue, keyframe.KeySpline)
+                            {
+                                Value = keyframe.Value
+                            });
+                        }
+
+                        return true;
+                    }
+                }
             }
 
-            return null;
+            animator = null;
+            return false;
         }
     }
 }

+ 51 - 14
src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs

@@ -1,27 +1,28 @@
 using System;
 using System.Collections.Generic;
-using System.Linq;
 
 using Avalonia.Data;
 using Avalonia.Media;
 using Avalonia.Media.Immutable;
 
+#nullable enable
+
 namespace Avalonia.Animation.Animators
 {
     /// <summary>
     /// Animator that handles <see cref="SolidColorBrush"/> values. 
     /// </summary>
-    public class IGradientBrushAnimator : Animator<IGradientBrush>
+    public class IGradientBrushAnimator : Animator<IGradientBrush?>
     {
         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)
+        public override IGradientBrush? Interpolate(double progress, IGradientBrush? oldValue, IGradientBrush? newValue)
         {
             if (oldValue is null || newValue is null
-                || oldValue.GradientStops.Count != oldValue.GradientStops.Count)
+                || oldValue.GradientStops.Count != newValue.GradientStops.Count)
             {
-                return progress >= 1 ? newValue : oldValue;
+                return progress >= 0.5 ? newValue : oldValue;
             }
 
             switch (oldValue)
@@ -52,23 +53,59 @@ namespace Avalonia.Animation.Animators
                         s_relativePointAnimator.Interpolate(progress, oldLinear.EndPoint, newLinear.EndPoint));
 
                 default:
-                    return progress >= 1 ? newValue : oldValue;
+                    return progress >= 0.5 ? newValue : oldValue;
             }
         }
 
-        public override IDisposable BindAnimation(Animatable control, IObservable<IGradientBrush> instance)
+        public override IDisposable BindAnimation(Animatable control, IObservable<IGradientBrush?> instance)
         {
-            return control.Bind((AvaloniaProperty<IBrush>)Property, instance, BindingPriority.Animation);
+            return control.Bind((AvaloniaProperty<IBrush?>)Property, instance, BindingPriority.Animation);
         }
 
         private IReadOnlyList<ImmutableGradientStop> InterpolateStops(double progress, IReadOnlyList<IGradientStop> oldValue, IReadOnlyList<IGradientStop> 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();
+            var stops = new ImmutableGradientStop[oldValue.Count];
+            for (int index = 0; index < oldValue.Count; index++)
+            {
+                stops[index] = new ImmutableGradientStop(
+                    s_doubleAnimator.Interpolate(progress, oldValue[index].Offset, newValue[index].Offset),
+                    ColorAnimator.InterpolateCore(progress, oldValue[index].Color, newValue[index].Color));
+            }
+            return stops;
+        }
+
+        internal static IGradientBrush ConvertSolidColorBrushToGradient(IGradientBrush gradientBrush, ISolidColorBrush solidColorBrush)
+        {
+            switch (gradientBrush)
+            {
+                case IRadialGradientBrush oldRadial:
+                    return new ImmutableRadialGradientBrush(
+                        CreateStopsFromSolidColorBrush(solidColorBrush, oldRadial), solidColorBrush.Opacity,
+                        oldRadial.SpreadMethod, oldRadial.Center, oldRadial.GradientOrigin, oldRadial.Radius);
+
+                case IConicGradientBrush oldConic:
+                    return new ImmutableConicGradientBrush(
+                        CreateStopsFromSolidColorBrush(solidColorBrush, oldConic), solidColorBrush.Opacity,
+                        oldConic.SpreadMethod, oldConic.Center, oldConic.Angle);
+
+                case ILinearGradientBrush oldLinear:
+                    return new ImmutableLinearGradientBrush(
+                        CreateStopsFromSolidColorBrush(solidColorBrush, oldLinear), solidColorBrush.Opacity,
+                        oldLinear.SpreadMethod, oldLinear.StartPoint, oldLinear.EndPoint);
+
+                default:
+                    throw new NotSupportedException($"Gradient of type {gradientBrush?.GetType()} is not supported");
+            }
+
+            static IReadOnlyList<ImmutableGradientStop> CreateStopsFromSolidColorBrush(ISolidColorBrush solidColorBrush, IGradientBrush baseGradient)
+            {
+                var stops = new ImmutableGradientStop[baseGradient.GradientStops.Count];
+                for (int index = 0; index < baseGradient.GradientStops.Count; index++)
+                {
+                    stops[index] = new ImmutableGradientStop(baseGradient.GradientStops[index].Offset, solidColorBrush.Color);
+                }
+                return stops;
+            }
         }
     }
 }

+ 10 - 8
src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs

@@ -3,37 +3,39 @@ using Avalonia.Data;
 using Avalonia.Media;
 using Avalonia.Media.Immutable;
 
+#nullable enable
+
 namespace Avalonia.Animation.Animators
 {
     /// <summary>
     /// Animator that handles <see cref="SolidColorBrush"/> values. 
     /// </summary>
-    public class ISolidColorBrushAnimator : Animator<ISolidColorBrush>
+    public class ISolidColorBrushAnimator : Animator<ISolidColorBrush?>
     {
-        public override ISolidColorBrush Interpolate(double progress, ISolidColorBrush oldValue, ISolidColorBrush newValue)
+        public override ISolidColorBrush? Interpolate(double progress, ISolidColorBrush? oldValue, ISolidColorBrush? newValue)
         {
             if (oldValue is null || newValue is null)
             {
-                return progress >= 1 ? newValue : oldValue;
+                return progress >= 0.5 ? newValue : oldValue;
             }
 
             return new ImmutableSolidColorBrush(ColorAnimator.InterpolateCore(progress, oldValue.Color, newValue.Color));
         }
 
-        public override IDisposable BindAnimation(Animatable control, IObservable<ISolidColorBrush> instance)
+        public override IDisposable BindAnimation(Animatable control, IObservable<ISolidColorBrush?> instance)
         {
-            return control.Bind((AvaloniaProperty<IBrush>)Property, instance, BindingPriority.Animation);
+            return control.Bind((AvaloniaProperty<IBrush?>)Property, instance, BindingPriority.Animation);
         }
     }
     
     [Obsolete("Use ISolidColorBrushAnimator instead")]
-    public class SolidColorBrushAnimator : Animator<SolidColorBrush>
+    public class SolidColorBrushAnimator : Animator<SolidColorBrush?>
     {    
-        public override SolidColorBrush Interpolate(double progress, SolidColorBrush oldValue, SolidColorBrush newValue)
+        public override SolidColorBrush? Interpolate(double progress, SolidColorBrush? oldValue, SolidColorBrush? newValue)
         {
             if (oldValue is null || newValue is null)
             {
-                return oldValue;
+                return progress >= 0.5 ? newValue : oldValue;
             }
 
             return new SolidColorBrush(ColorAnimator.InterpolateCore(progress, oldValue.Color, newValue.Color));

+ 24 - 16
src/Avalonia.Visuals/Animation/Transitions/BrushTransition.cs

@@ -1,5 +1,4 @@
 using System;
-using System.Linq;
 
 using Avalonia.Animation.Animators;
 using Avalonia.Animation.Easings;
@@ -14,34 +13,43 @@ namespace Avalonia.Animation
     /// </summary>
     public class BrushTransition : Transition<IBrush?>
     {
+        private static readonly IGradientBrushAnimator s_gradientAnimator = new IGradientBrushAnimator();
+        private static readonly ISolidColorBrushAnimator s_solidColorBrushAnimator = new ISolidColorBrushAnimator();
+
         public override IObservable<IBrush?> DoTransition(IObservable<double> progress, IBrush? oldValue, IBrush? newValue)
         {
-            var type = oldValue?.GetType() ?? newValue?.GetType();
-            if (type == null)
+            if (oldValue is null || newValue is null)
             {
                 return new IncompatibleTransitionObservable(progress, Easing, oldValue, newValue);
             }
 
-            var animator = BaseBrushAnimator.CreateAnimatorFromType(type);
-            if (animator == null)
+            if (oldValue is IGradientBrush oldGradient)
             {
-                return new IncompatibleTransitionObservable(progress, Easing, oldValue, newValue);
+                if (newValue is IGradientBrush newGradient)
+                {
+                    return new AnimatorTransitionObservable<IGradientBrush?, IGradientBrushAnimator>(s_gradientAnimator, progress, Easing, oldGradient, newGradient);
+                }
+                else if (newValue is ISolidColorBrush newSolidColorBrushToConvert)
+                {
+                    var convertedSolidColorBrush = IGradientBrushAnimator.ConvertSolidColorBrushToGradient(oldGradient, newSolidColorBrushToConvert);
+                    return new AnimatorTransitionObservable<IGradientBrush?, IGradientBrushAnimator>(s_gradientAnimator, progress, Easing, oldGradient, convertedSolidColorBrush);
+                }
+            }
+            else if (newValue is IGradientBrush newGradient && oldValue is ISolidColorBrush oldSolidColorBrushToConvert)
+            {
+                var convertedSolidColorBrush = IGradientBrushAnimator.ConvertSolidColorBrushToGradient(newGradient, oldSolidColorBrushToConvert);
+                return new AnimatorTransitionObservable<IGradientBrush?, IGradientBrushAnimator>(s_gradientAnimator, progress, Easing, convertedSolidColorBrush, newGradient);
             }
 
-            var animatorType = animator.GetType();
-            var animatorGenericArgument = animatorType.BaseType.GetGenericArguments().FirstOrDefault() ?? type;
-
-            var observableType = typeof(AnimatorTransitionObservable<,>).MakeGenericType(animatorGenericArgument, animatorType);
-            var observable = Activator.CreateInstance(observableType, animator, progress, Easing, oldValue, newValue) as IObservable<IBrush>;
-            if (observable == null)
+            if (oldValue is ISolidColorBrush oldSolidColorBrush && newValue is ISolidColorBrush newSolidColorBrush)
             {
-                return new IncompatibleTransitionObservable(progress, Easing, oldValue, newValue);
+                return new AnimatorTransitionObservable<ISolidColorBrush?, ISolidColorBrushAnimator>(s_solidColorBrushAnimator, progress, Easing, oldSolidColorBrush, newSolidColorBrush);
             }
 
-            return observable;
+            return new IncompatibleTransitionObservable(progress, Easing, oldValue, newValue);
         }
 
-        private class IncompatibleTransitionObservable : TransitionObservableBase<IBrush?>
+        private sealed class IncompatibleTransitionObservable : TransitionObservableBase<IBrush?>
         {
             private readonly IBrush? _from;
             private readonly IBrush? _to;
@@ -54,7 +62,7 @@ namespace Avalonia.Animation
 
             protected override IBrush? ProduceValue(double progress)
             {
-                return progress < 0.5 ? _from : _to;
+                return progress >= 0.5 ? _to : _from;
             }
         }
     }

+ 0 - 1
src/Avalonia.Visuals/Media/GradientBrush.cs

@@ -30,7 +30,6 @@ namespace Avalonia.Media
 
         static GradientBrush()
         {
-            BaseBrushAnimator.RegisterBrushAnimator<IGradientBrushAnimator>(match => typeof(IGradientBrush).IsAssignableFrom(match));
             GradientStopsProperty.Changed.Subscribe(GradientStopsChanged);
             AffectsRender<LinearGradientBrush>(SpreadMethodProperty);
         }

+ 0 - 1
src/Avalonia.Visuals/Media/SolidColorBrush.cs

@@ -16,7 +16,6 @@ namespace Avalonia.Media
 
         static SolidColorBrush()
         {
-            BaseBrushAnimator.RegisterBrushAnimator<ISolidColorBrushAnimator>(match => typeof(ISolidColorBrush).IsAssignableFrom(match));
             AffectsRender<SolidColorBrush>(ColorProperty);
         }