@ -28,57 +28,70 @@ namespace Avalonia.Animation.Animators
if ( Count = = 0 )
return neutralValue ;
var ( beforeKeyFrame , afterKeyFrame ) = Find KeyFrames( animationTime ) ;
var ( from , to ) = Get KeyFrames( animationTime , neutralValu e ) ;
double beforeTime , afterTime ;
T beforeValue , afterValue ;
var progress = ( animationTime - from . Time ) / ( to . Time - from . Time ) ;
if ( beforeKeyFrame is null )
{
beforeTime = 0.0 ;
beforeValue = afterKeyFrame is { FillBefore : true , Value : T fillValue } ? fillValue : neutralValue ;
}
else
{
beforeTime = beforeKeyFrame . Cue . CueValue ;
beforeValue = beforeKeyFrame . Value is T value ? value : neutralValue ;
}
if ( afterKeyFrame is null )
{
afterTime = 1.0 ;
afterValue = beforeKeyFrame is { FillAfter : true , Value : T fillValue } ? fillValue : neutralValue ;
}
else
{
afterTime = afterKeyFrame . Cue . CueValue ;
afterValue = afterKeyFrame . Value is T value ? value : neutralValue ;
}
var progress = ( animationTime - beforeTime ) / ( afterTime - beforeTime ) ;
if ( afterKeyFrame ? . KeySpline is { } keySpline )
if ( to . KeySpline is { } keySpline )
progress = keySpline . GetSplineProgress ( progress ) ;
return Interpolate ( progress , beforeValue , after Value) ;
return Interpolate ( progress , from . Value , to . Value ) ;
}
private ( AnimatorKeyFrame ? Before , AnimatorKeyFrame ? After ) FindKeyFrames ( double tim e)
private ( KeyFrameInfo From , KeyFrameInfo To ) GetKeyFrames ( double time , T neutralValue )
{
Debug . Assert ( Count > = 1 ) ;
for ( var i = 0 ; i < Count ; i + + )
// Before or right at the first frame which isn't at time 0.0: interpolate between 0.0 and the first frame.
var firstFrame = this [ 0 ] ;
var firstTime = firstFrame . Cue . CueValue ;
if ( time < = firstTime & & firstTime > 0.0 )
{
var keyFrame = this [ i ] ;
var keyFrameTime = keyFrame . Cue . CueValue ;
var beforeValue = firstFrame . FillBefore ? GetTypedValue ( firstFrame . Value , neutralValue ) : neutralValue ;
return (
new KeyFrameInfo ( 0.0 , beforeValue , firstFrame . KeySpline ) ,
KeyFrameInfo . FromKeyFrame ( firstFrame , neutralValue ) ) ;
}
if ( time < keyFrameTime | | keyFrameTime = = 1.0 )
return ( i > 0 ? this [ i - 1 ] : null , keyFrame ) ;
// Between two frames: interpolate between the previous frame and the next frame.
for ( var i = 1 ; i < Count ; + + i )
{
var frame = this [ i ] ;
if ( time < = frame . Cue . CueValue )
{
return (
KeyFrameInfo . FromKeyFrame ( this [ i - 1 ] , neutralValue ) ,
KeyFrameInfo . FromKeyFrame ( this [ i ] , neutralValue ) ) ;
}
}
// Past the last frame which is at time 1.0: interpolate between the last two frames.
var lastFrame = this [ Count - 1 ] ;
if ( lastFrame . Cue . CueValue > = 1.0 )
{
if ( Count = = 1 )
{
var beforeValue = lastFrame . FillBefore ? GetTypedValue ( lastFrame . Value , neutralValue ) : neutralValue ;
return (
new KeyFrameInfo ( 0.0 , beforeValue , lastFrame . KeySpline ) ,
KeyFrameInfo . FromKeyFrame ( lastFrame , neutralValue ) ) ;
}
return (
KeyFrameInfo . FromKeyFrame ( this [ Count - 2 ] , neutralValue ) ,
KeyFrameInfo . FromKeyFrame ( lastFrame , neutralValue ) ) ;
}
return ( this [ Count - 1 ] , null ) ;
// Past the last frame which isn't at time 1.0: interpolate between the last frame and 1.0.
var afterValue = lastFrame . FillAfter ? GetTypedValue ( lastFrame . Value , neutralValue ) : neutralValue ;
return (
KeyFrameInfo . FromKeyFrame ( lastFrame , neutralValue ) ,
new KeyFrameInfo ( 1.0 , afterValue , lastFrame . KeySpline ) ) ;
}
private static T GetTypedValue ( object? untypedValue , T neutralValue )
= > untypedValue is T value ? value : neutralValue ;
public virtual IDisposable BindAnimation ( Animatable control , IObservable < T > instance )
{
if ( Property is null )
@ -107,5 +120,15 @@ namespace Avalonia.Animation.Animators
/// Interpolates in-between two key values given the desired progress time.
/// </summary>
public abstract T Interpolate ( double progress , T oldValue , T newValue ) ;
private readonly struct KeyFrameInfo ( double time , T value , KeySpline ? keySpline )
{
public readonly double Time = time ;
public readonly T Value = value ;
public readonly KeySpline ? KeySpline = keySpline ;
public static KeyFrameInfo FromKeyFrame ( AnimatorKeyFrame source , T neutralValue )
= > new ( source . Cue . CueValue , GetTypedValue ( source . Value , neutralValue ) , source . KeySpline ) ;
}
}
}