Browse Source

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

pull/6183/head
Max Katz 5 years ago
parent
commit
a885e673c8
  1. 34
      samples/RenderDemo/Pages/AnimationsPage.xaml
  2. 129
      src/Avalonia.Visuals/Animation/Animators/BaseBrushAnimator.cs
  3. 65
      src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs
  4. 18
      src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs
  5. 40
      src/Avalonia.Visuals/Animation/Transitions/BrushTransition.cs
  6. 1
      src/Avalonia.Visuals/Media/GradientBrush.cs
  7. 1
      src/Avalonia.Visuals/Media/SolidColorBrush.cs

34
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>

129
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;
}
}
}

65
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;
}
}
}
}

18
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));

40
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;
}
}
}

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);
}

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);
}

Loading…
Cancel
Save