summaryrefslogtreecommitdiff
path: root/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening/Tweener.cs
blob: 61b55f9bf02c463da5b0d0eeea115c396710c9af (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
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<Tween> _activeTweens = new List<Tween>();

        public Tween<TMember> TweenTo<TTarget, TMember>(TTarget target, Expression<Func<TTarget, TMember>> expression, TMember toValue, float duration, float delay = 0f)
            where TTarget : class
            where TMember : struct
        {
            switch (toValue)
            {
                case Color toValueColor:
                    return (Tween<TMember>)(object)TweenTo<TTarget, Color, ColorTween>(target, expression as Expression<Func<TTarget, Color>>, toValueColor, duration, delay);
                default:
                    return TweenTo<TTarget, TMember, LinearTween<TMember>>(target, expression, toValue, duration, delay);
            }

        }

        public Tween<TMember> TweenTo<TTarget, TMember, TTween>(TTarget target, Expression<Func<TTarget, TMember>> expression, TMember toValue, float duration, float delay = 0f)
            where TTarget : class
            where TMember : struct
            where TTween : Tween<TMember>
        {
            var memberExpression = (MemberExpression)expression.Body;
            var memberInfo = memberExpression.Member;
            var member = GetMember<TMember>(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<TweenMemberKey, TweenMember> _memberCache = new Dictionary<TweenMemberKey, TweenMember>();

        private TweenMember<T> GetMember<T>(object target, string memberName)
            where T : struct
        {
            var key = new TweenMemberKey { Target = target, MemberName = memberName };

            if (_memberCache.TryGetValue(key, out var member))
                return (TweenMember<T>) member;

            member = CreateMember<T>(target, memberName);
            _memberCache.Add(key, member);
            return (TweenMember<T>) member;
        }

        private TweenMember<T> CreateMember<T>(object target, string memberName)
            where T : struct
        {
            AllocationCount++;

            var type = target.GetType();
            var property = type.GetTypeInfo().GetProperty(memberName);

            if (property != null)
                return new TweenPropertyMember<T>(target, property);

            var field = type.GetTypeInfo().GetField(memberName);

            if (field != null)
                return new TweenFieldMember<T>(target, field);

            throw new InvalidOperationException($"'{memberName}' is not a property or field of the target");
        }
    }
}