Browse Source

Add IGradientBrushAnimator

pull/6183/head
Max Katz 5 years ago
parent
commit
ab071f1ba0
  1. 35
      src/Avalonia.Visuals/Animation/Animators/BaseBrushAnimator.cs
  2. 74
      src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs
  3. 4
      src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs
  4. 32
      src/Avalonia.Visuals/Animation/Transitions/BrushTransition.cs
  5. 3
      src/Avalonia.Visuals/Media/GradientBrush.cs

35
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
{
/// <summary>
@ -12,9 +14,9 @@ namespace Avalonia.Animation.Animators
/// redirect them to the properly registered
/// animators in this class.
/// </summary>
public class BaseBrushAnimator : Animator<IBrush>
public class BaseBrushAnimator : Animator<IBrush?>
{
private IAnimator _targetAnimator;
private IAnimator? _targetAnimator;
private static readonly List<(Func<Type, bool> Match, Type AnimatorType)> _brushAnimators =
new List<(Func<Type, bool> Match, Type AnimatorType)>();
@ -31,7 +33,7 @@ namespace Avalonia.Animation.Animators
/// The type of the animator to instantiate.
/// </typeparam>
public static void RegisterBrushAnimator<TAnimator>(Func<Type, bool> 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<bool> 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
}
/// <inheritdoc/>
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;
}
}
}

74
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
{
/// <summary>
/// Animator that handles <see cref="SolidColorBrush"/> values.
/// </summary>
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)
{
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<IGradientBrush> instance)
{
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();
}
}
}

4
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<SolidColorBrush>
{
public override SolidColorBrush Interpolate(double progress, SolidColorBrush oldValue, SolidColorBrush newValue)

32
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
{
/// <summary>
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="IBrush"/> type.
/// Only values of <see cref="ISolidColorBrush"/> will transition correctly at the moment.
/// </summary>
public class BrushTransition : Transition<IBrush?>
{
private static readonly ISolidColorBrushAnimator s_animator = new ISolidColorBrushAnimator();
public override IObservable<IBrush?> DoTransition(IObservable<double> 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<ISolidColorBrush, ISolidColorBrushAnimator>(
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<IBrush>;
if (observable == null)
{
return Brushes.Transparent;
return new IncompatibleTransitionObservable(progress, Easing, oldValue, newValue);
}
return brush as ISolidColorBrush;
return observable;
}
private class IncompatibleTransitionObservable : TransitionObservableBase<IBrush?>

3
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<IGradientBrushAnimator>(match => typeof(IGradientBrush).IsAssignableFrom(match));
GradientStopsProperty.Changed.Subscribe(GradientStopsChanged);
AffectsRender<LinearGradientBrush>(SpreadMethodProperty);
}

Loading…
Cancel
Save