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 { /// /// 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 /// public class CUIAnimation { internal static void InitStatic() { CUI.OnDispose += () => ActiveAnimations.Clear(); } public static HashSet ActiveAnimations = new(); /// /// This is called in CUIUpdate /// 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; /// /// Object containing animated property /// 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(); } } /// /// In seconds /// 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; } } /// /// Will prevent it from starting /// public bool Blocked { get; set; } /// /// Progress of animation [0..1] /// public double Lambda { get; set; } /// /// Lambda increase per update step, calculated when you set Duration /// public double Speed { get; set; } = 0.01; public double? BackSpeed { get; set; } /// /// If true animation won't stop when reaching end, it will change direction /// public bool Bounce { get; set; } /// /// Straight, Reverse /// public CUIDirection Direction { get; set; } /// /// Value will be interpolated between these values /// public object StartValue { get; set; } public object EndValue { get; set; } private string property; private Action setter; private Type propertyType; /// /// Property name that is animated /// public string Property { get => property; set { property = value; UpdateSetter(); } } public event Action OnStop; /// /// You can set custon Interpolate function /// public Func Interpolate { get => interpolate; set { customInterpolate = value; UpdateSetter(); } } private Func customInterpolate; private Func interpolate; //... public Action Convert(Action myActionT) { if (myActionT == null) return null; else return new Action(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(pi.GetSetMethod()?.CreateDelegate>(Target)); } if (propertyType == typeof(Color)) { setter = Convert(pi.GetSetMethod()?.CreateDelegate>(Target)); } } } /// /// Resumes animation in the same direction /// public void Start() => Active = true; public void Stop() { Active = false; OnStop?.Invoke(Direction); } /// /// Set Direction to Straight and Start /// public void Forward() { Direction = CUIDirection.Straight; Active = true; } /// /// Set Direction to Reverse and Start /// public void Back() { Direction = CUIDirection.Reverse; Active = true; } /// /// Set Lambda to 0 /// public void SetToStart() => Lambda = StartLambda; /// /// Set Lambda to 1 /// 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}"); } }