A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

180 lines
7.2 KiB

using System;
using System.Collections.Generic;
using Avalonia.Animation;
using Avalonia.Rendering.Composition.Expressions;
using Avalonia.Rendering.Composition.Server;
// Special license applies, see //file: src/Avalonia.Base/Rendering/Composition/License.md
namespace Avalonia.Rendering.Composition.Animations
{
/// <summary>
/// Server-side counterpart of KeyFrameAnimation with values baked-in
/// </summary>
class KeyFrameAnimationInstance<T> : AnimationInstanceBase, IAnimationInstance where T : struct
{
private readonly IInterpolator<T> _interpolator;
private readonly ServerKeyFrame<T>[] _keyFrames;
private readonly ExpressionVariant? _finalValue;
private readonly AnimationDelayBehavior _delayBehavior;
private readonly TimeSpan _delayTime;
private readonly PlaybackDirection _direction;
private readonly TimeSpan _duration;
private readonly AnimationIterationBehavior _iterationBehavior;
private readonly int _iterationCount;
private readonly AnimationStopBehavior _stopBehavior;
private TimeSpan _startedAt;
private T _startingValue;
private readonly TimeSpan _totalDuration;
private bool _finished;
public KeyFrameAnimationInstance(
IInterpolator<T> interpolator, ServerKeyFrame<T>[] keyFrames,
PropertySetSnapshot snapshot, ExpressionVariant? finalValue,
ServerObject target,
AnimationDelayBehavior delayBehavior, TimeSpan delayTime,
PlaybackDirection direction, TimeSpan duration,
AnimationIterationBehavior iterationBehavior,
int iterationCount, AnimationStopBehavior stopBehavior) : base(target, snapshot)
{
_interpolator = interpolator;
_keyFrames = keyFrames;
_finalValue = finalValue;
_delayBehavior = delayBehavior;
_delayTime = delayTime;
_direction = direction;
_duration = duration;
_iterationBehavior = iterationBehavior;
_iterationCount = iterationCount;
_stopBehavior = stopBehavior;
if (_iterationBehavior == AnimationIterationBehavior.Count)
_totalDuration = delayTime.Add(TimeSpan.FromTicks(iterationCount * _duration.Ticks));
if (_keyFrames.Length == 0)
throw new InvalidOperationException("Animation has no key frames");
if(_duration.Ticks <= 0)
throw new InvalidOperationException("Invalid animation duration");
}
protected override ExpressionVariant EvaluateCore(TimeSpan now, ExpressionVariant currentValue)
{
var starting = ExpressionVariant.Create(_startingValue);
var ctx = new ExpressionEvaluationContext
{
Parameters = Parameters,
Target = TargetObject,
CurrentValue = currentValue,
FinalValue = _finalValue ?? starting,
StartingValue = starting,
ForeignFunctionInterface = BuiltInExpressionFfi.Instance
};
var elapsed = now - _startedAt;
var res = EvaluateImpl(elapsed, currentValue, ref ctx);
if (_iterationBehavior == AnimationIterationBehavior.Count
&& !_finished
&& elapsed > _totalDuration)
{
// Active check?
TargetObject.Compositor.RemoveFromClock(this);
_finished = true;
}
return res;
}
private ExpressionVariant EvaluateImpl(TimeSpan elapsed, ExpressionVariant currentValue, ref ExpressionEvaluationContext ctx)
{
if (elapsed < _delayTime)
{
if (_delayBehavior == AnimationDelayBehavior.SetInitialValueBeforeDelay)
return ExpressionVariant.Create(GetKeyFrame(ref ctx, _keyFrames[0]));
return currentValue;
}
elapsed -= _delayTime;
var iterationNumber = elapsed.Ticks / _duration.Ticks;
if (_iterationBehavior == AnimationIterationBehavior.Count
&& iterationNumber >= _iterationCount)
return ExpressionVariant.Create(GetKeyFrame(ref ctx, _keyFrames[_keyFrames.Length - 1]));
var evenIterationNumber = iterationNumber % 2 == 0;
elapsed = TimeSpan.FromTicks(elapsed.Ticks % _duration.Ticks);
var reverse =
_direction == PlaybackDirection.Alternate
? !evenIterationNumber
: _direction == PlaybackDirection.AlternateReverse
? evenIterationNumber
: _direction == PlaybackDirection.Reverse;
var iterationProgress = elapsed.TotalSeconds / _duration.TotalSeconds;
if (reverse)
iterationProgress = 1 - iterationProgress;
var left = new ServerKeyFrame<T>
{
Value = _startingValue
};
var right = _keyFrames[_keyFrames.Length - 1];
for (var c = 0; c < _keyFrames.Length; c++)
{
var kf = _keyFrames[c];
if (kf.Key < iterationProgress)
{
// this is the last frame
if (c == _keyFrames.Length - 1)
return ExpressionVariant.Create(GetKeyFrame(ref ctx, kf));
left = kf;
right = _keyFrames[c + 1];
break;
}
}
var keyProgress = Math.Max(0, Math.Min(1, (iterationProgress - left.Key) / (right.Key - left.Key)));
var easedKeyProgress = (float)right.EasingFunction.Ease(keyProgress);
if (float.IsNaN(easedKeyProgress) || float.IsInfinity(easedKeyProgress))
return currentValue;
return ExpressionVariant.Create(_interpolator.Interpolate(
GetKeyFrame(ref ctx, left),
GetKeyFrame(ref ctx, right),
easedKeyProgress
));
}
T GetKeyFrame(ref ExpressionEvaluationContext ctx, ServerKeyFrame<T> f)
{
if (f.Expression != null)
return f.Expression.Evaluate(ref ctx).CastOrDefault<T>();
else
return f.Value;
}
public override void Initialize(TimeSpan startedAt, ExpressionVariant startingValue, CompositionProperty property)
{
_startedAt = startedAt;
_startingValue = startingValue.CastOrDefault<T>();
var hs = new HashSet<(string, string)>();
// TODO: Update subscriptions based on the current keyframe rather than keeping subscriptions to all of them
foreach (var frame in _keyFrames)
frame.Expression?.CollectReferences(hs);
Initialize(property, hs);
}
public override void Activate()
{
TargetObject.Compositor.AddToClock(this);
base.Activate();
}
public override void Deactivate()
{
TargetObject.Compositor.RemoveFromClock(this);
base.Deactivate();
}
}
}