using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; using Microsoft.Xna.Framework; namespace MonoGame.Extended.Tweening { public class Tweener : IDisposable { public Tweener() { } public void Dispose() { CancelAll(); _activeTweens.Clear(); _memberCache.Clear(); } public long AllocationCount { get; private set; } private readonly List _activeTweens = new List(); public Tween TweenTo(TTarget target, Expression> expression, TMember toValue, float duration, float delay = 0f) where TTarget : class where TMember : struct { switch (toValue) { case Color toValueColor: return (Tween)(object)TweenTo(target, expression as Expression>, toValueColor, duration, delay); default: return TweenTo>(target, expression, toValue, duration, delay); } } public Tween TweenTo(TTarget target, Expression> expression, TMember toValue, float duration, float delay = 0f) where TTarget : class where TMember : struct where TTween : Tween { var memberExpression = (MemberExpression)expression.Body; var memberInfo = memberExpression.Member; var member = GetMember(target, memberInfo.Name); var activeTween = FindTween(target, member.Name); activeTween?.Cancel(); AllocationCount++; var tween = (TTween)Activator.CreateInstance(typeof(TTween), BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, new object[]{target, duration, delay, member, toValue}, null); _activeTweens.Add(tween); return tween; } public void Update(float elapsedSeconds) { for (var i = _activeTweens.Count - 1; i >= 0; i--) { var tween = _activeTweens[i]; tween.Update(elapsedSeconds); if (!tween.IsAlive) _activeTweens.RemoveAt(i); } } public Tween FindTween(object target, string memberName) { return _activeTweens.FirstOrDefault(t => t.Target == target && t.MemberName == memberName); } public void CancelAll() { foreach (var tween in _activeTweens) tween.Cancel(); } public void CancelAndCompleteAll() { foreach (var tween in _activeTweens) tween.CancelAndComplete(); } private struct TweenMemberKey { #pragma warning disable 414 public object Target; public string MemberName; #pragma warning restore 414 } private readonly Dictionary _memberCache = new Dictionary(); private TweenMember GetMember(object target, string memberName) where T : struct { var key = new TweenMemberKey { Target = target, MemberName = memberName }; if (_memberCache.TryGetValue(key, out var member)) return (TweenMember) member; member = CreateMember(target, memberName); _memberCache.Add(key, member); return (TweenMember) member; } private TweenMember CreateMember(object target, string memberName) where T : struct { AllocationCount++; var type = target.GetType(); var property = type.GetTypeInfo().GetProperty(memberName); if (property != null) return new TweenPropertyMember(target, property); var field = type.GetTypeInfo().GetField(memberName); if (field != null) return new TweenFieldMember(target, field); throw new InvalidOperationException($"'{memberName}' is not a property or field of the target"); } } }