Skip to content

Commit

Permalink
Merge pull request #5886 from AvaloniaUI/animations/tryfix-scb-localv…
Browse files Browse the repository at this point in the history
…alue

Revamp IBrush animator selection + Stop SCBAnimator from setting localvalues on animation apply
  • Loading branch information
Dan Walmsley committed May 10, 2021
1 parent f2b9114 commit 98ab542
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 59 deletions.
11 changes: 11 additions & 0 deletions src/Avalonia.Animation/Animation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,17 @@ public string RepeatCount
( prop => typeof(decimal).IsAssignableFrom(prop.PropertyType), typeof(DecimalAnimator) ),
};

/// <summary>
/// Registers a <see cref="Animator{T}"/> that can handle
/// a value type that matches the specified condition.
/// </summary>
/// <param name="condition">
/// The condition to which the <see cref="Animator{T}"/>
/// is to be activated and used.
/// </param>
/// <typeparam name="TAnimator">
/// The type of the animator to instantiate.
/// </typeparam>
public static void RegisterAnimator<TAnimator>(Func<AvaloniaProperty, bool> condition)
where TAnimator : IAnimator
{
Expand Down
8 changes: 7 additions & 1 deletion src/Avalonia.Animation/Animators/Animator`1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ private int FindClosestBeforeKeyFrame(double time)
throw new Exception("Index time is out of keyframe time range.");
}

public virtual IDisposable BindAnimation(Animatable control, IObservable<T> instance)
{
return control.Bind((AvaloniaProperty<T>)Property, instance, BindingPriority.Animation);
}

/// <summary>
/// Runs the KeyFrames Animation.
/// </summary>
Expand All @@ -116,7 +121,8 @@ internal IDisposable Run(Animation animation, Animatable control, IClock clock,
clock ?? control.Clock ?? Clock.GlobalClock,
onComplete,
InterpolationHandler);
return control.Bind<T>((AvaloniaProperty<T>)Property, instance, BindingPriority.Animation);

return BindAnimation(control, instance);
}

/// <summary>
Expand Down
69 changes: 69 additions & 0 deletions src/Avalonia.Visuals/Animation/Animators/BaseBrushAnimator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
using Avalonia.Logging;
using Avalonia.Media;

namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles all animations on properties
/// with <see cref="IBrush"/> as their type and
/// redirect them to the properly registered
/// animators in this class.
/// </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)>();

/// <summary>
/// Register an <see cref="Animator{T}"/> that handles a specific
/// <see cref="IBrush"/>'s descendant value type.
/// </summary>
/// <param name="condition">
/// The condition to which the <see cref="Animator{T}"/>
/// is to be activated and used.
/// </param>
/// <typeparam name="TAnimator">
/// The type of the animator to instantiate.
/// </typeparam>
public static void RegisterBrushAnimator<TAnimator>(Func<Type, bool> condition)
where TAnimator : IAnimator
{
_brushAnimators.Insert(0, (condition, typeof(TAnimator)));
}

/// <inheritdoc/>
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);

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

/// <inheritdoc/>
public override IBrush Interpolate(double progress, IBrush oldValue, IBrush newValue) => null;
}
}
7 changes: 6 additions & 1 deletion src/Avalonia.Visuals/Animation/Animators/ColorAnimator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ private static double EOCF_sRGB(double srgb)
}

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;
Expand Down Expand Up @@ -59,7 +64,7 @@ public override Color Interpolate(double progress, Color oldValue, Color newValu
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;
Expand Down
71 changes: 16 additions & 55 deletions src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs
Original file line number Diff line number Diff line change
@@ -1,71 +1,32 @@
using System;
using System.Reactive.Disposables;
using Avalonia.Data;
using Avalonia.Media;
using Avalonia.Media.Immutable;

namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="SolidColorBrush"/>.
/// Animator that handles <see cref="SolidColorBrush"/> values.
/// </summary>
public class SolidColorBrushAnimator : Animator<SolidColorBrush>
public class ISolidColorBrushAnimator : Animator<ISolidColorBrush>
{
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<bool> match, Action onComplete)
public override IDisposable BindAnimation(Animatable control, IObservable<ISolidColorBrush> 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<IBrush>)Property, instance, BindingPriority.Animation);
}
}

[Obsolete]
public class SolidColorBrushAnimator : Animator<SolidColorBrush>
{
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;
}
}
2 changes: 2 additions & 0 deletions src/Avalonia.Visuals/Media/Brush.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.ComponentModel;
using Avalonia.Animation;
using Avalonia.Animation.Animators;

namespace Avalonia.Media
{
Expand All @@ -21,6 +22,7 @@ public abstract class Brush : Animatable, IMutableBrush

static Brush()
{
Animation.Animation.RegisterAnimator<BaseBrushAnimator>(prop => typeof(IBrush).IsAssignableFrom(prop.PropertyType));
AffectsRender<Brush>(OpacityProperty);
}

Expand Down
3 changes: 1 addition & 2 deletions src/Avalonia.Visuals/Media/SolidColorBrush.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using Avalonia.Animation;
using Avalonia.Animation.Animators;
using Avalonia.Media.Immutable;

Expand All @@ -17,7 +16,7 @@ public class SolidColorBrush : Brush, ISolidColorBrush

static SolidColorBrush()
{
Animation.Animation.RegisterAnimator<SolidColorBrushAnimator>(prop => typeof(IBrush).IsAssignableFrom(prop.PropertyType));
BaseBrushAnimator.RegisterBrushAnimator<ISolidColorBrushAnimator>(match => typeof(ISolidColorBrush).IsAssignableFrom(match));
AffectsRender<SolidColorBrush>(ColorProperty);
}

Expand Down

0 comments on commit 98ab542

Please sign in to comment.