Files
barotrauma-localmods/Quick Interactions/CSharp/Client/CrabUI/Animations/CUIAnimation.cs

260 lines
6.1 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Diagnostics;
using System.IO;
using Barotrauma;
using Microsoft.Xna.Framework;
namespace QICrabUI
{
/// <summary>
/// WIP, can animate any property on any object
/// Can run back and forth in [0..1] interval and
/// interpolate any property between StartValue and EndValue
/// </summary>
public class CUIAnimation
{
internal static void InitStatic()
{
CUI.OnDispose += () => ActiveAnimations.Clear();
}
public static HashSet<CUIAnimation> ActiveAnimations = new();
/// <summary>
/// This is called in CUIUpdate
/// </summary>
internal static void UpdateAllAnimations(double time)
{
foreach (CUIAnimation animation in ActiveAnimations)
{
animation.Step(time);
}
}
public bool Debug { get; set; }
public static float StartLambda = 0.0f;
public static float EndLambda = 1.0f;
private object target;
/// <summary>
/// Object containing animated property
/// </summary>
public object Target
{
get => target;
set
{
target = value;
UpdateSetter();
}
}
private bool active;
public bool Active
{
get => active;
set
{
if (Blocked || active == value) return;
active = value;
if (active) ActiveAnimations.Add(this);
else ActiveAnimations.Remove(this);
ApplyValue();
}
}
/// <summary>
/// In seconds
/// </summary>
public double Duration
{
get => 1.0 / Speed * Timing.Step;
set
{
double steps = value / Timing.Step;
Speed = 1.0 / steps;
}
}
public double ReverseDuration
{
get => 1.0 / (BackSpeed ?? 0) * Timing.Step;
set
{
double steps = value / Timing.Step;
BackSpeed = 1.0 / steps;
}
}
/// <summary>
/// Will prevent it from starting
/// </summary>
public bool Blocked { get; set; }
/// <summary>
/// Progress of animation [0..1]
/// </summary>
public double Lambda { get; set; }
/// <summary>
/// Lambda increase per update step, calculated when you set Duration
/// </summary>
public double Speed { get; set; } = 0.01;
public double? BackSpeed { get; set; }
/// <summary>
/// If true animation won't stop when reaching end, it will change direction
/// </summary>
public bool Bounce { get; set; }
/// <summary>
/// Straight, Reverse
/// </summary>
public CUIDirection Direction { get; set; }
/// <summary>
/// Value will be interpolated between these values
/// </summary>
public object StartValue { get; set; }
public object EndValue { get; set; }
private string property;
private Action<object> setter;
private Type propertyType;
/// <summary>
/// Property name that is animated
/// </summary>
public string Property
{
get => property;
set
{
property = value;
UpdateSetter();
}
}
public event Action<CUIDirection> OnStop;
/// <summary>
/// You can set custon Interpolate function
/// </summary>
public Func<float, object> Interpolate
{
get => interpolate;
set
{
customInterpolate = value;
UpdateSetter();
}
}
private Func<float, object> customInterpolate;
private Func<float, object> interpolate;
//...
public Action<object> Convert<T>(Action<T> myActionT)
{
if (myActionT == null) return null;
else return new Action<object>(o => myActionT((T)o));
}
private void UpdateSetter()
{
if (Target != null && Property != null)
{
PropertyInfo pi = Target.GetType().GetProperty(Property);
if (pi == null)
{
CUI.Warning($"CUIAnimation couldn't find {Property} in {Target}");
return;
}
propertyType = pi.PropertyType;
interpolate = customInterpolate ?? ((l) => CUIInterpolate.Interpolate[propertyType].Invoke(StartValue, EndValue, l));
// https://coub.com/view/1mast0
if (propertyType == typeof(float))
{
setter = Convert<float>(pi.GetSetMethod()?.CreateDelegate<Action<float>>(Target));
}
if (propertyType == typeof(Color))
{
setter = Convert<Color>(pi.GetSetMethod()?.CreateDelegate<Action<Color>>(Target));
}
}
}
/// <summary>
/// Resumes animation in the same direction
/// </summary>
public void Start() => Active = true;
public void Stop()
{
Active = false;
OnStop?.Invoke(Direction);
}
/// <summary>
/// Set Direction to Straight and Start
/// </summary>
public void Forward()
{
Direction = CUIDirection.Straight;
Active = true;
}
/// <summary>
/// Set Direction to Reverse and Start
/// </summary>
public void Back()
{
Direction = CUIDirection.Reverse;
Active = true;
}
/// <summary>
/// Set Lambda to 0
/// </summary>
public void SetToStart() => Lambda = StartLambda;
/// <summary>
/// Set Lambda to 1
/// </summary>
public void SetToEnd() => Lambda = EndLambda;
private void UpdateState()
{
if (Direction == CUIDirection.Straight && Lambda >= EndLambda)
{
Lambda = EndLambda;
if (Bounce) Direction = CUIDirection.Reverse;
else Stop();
}
if (Direction == CUIDirection.Reverse && Lambda <= StartLambda)
{
Lambda = StartLambda;
if (Bounce) Direction = CUIDirection.Straight;
else Stop();
}
}
public void ApplyValue()
{
if (interpolate == null) return;
object value = interpolate.Invoke((float)Lambda);
setter?.Invoke(value);
}
public void Step(double time)
{
UpdateState();
ApplyValue();
Lambda += Direction == CUIDirection.Straight ? Speed : -(BackSpeed ?? Speed);
if (Debug) LogStatus();
}
public void LogStatus() => CUI.Log($"Active:{Active} Direction:{Direction} Lambda:{Lambda}");
}
}