using System; using System.Collections.Generic; using System.Reflection; namespace Otter { public class Tweener : Tween.TweenerImpl { }; public partial class Tween { private interface IRemoveTweens // lol get it { void Remove(Tween t); } public class TweenerImpl : IRemoveTweens { static TweenerImpl() { registeredLerpers = new Dictionary(); var numericTypes = new Type[] { typeof(Int16), typeof(Int32), typeof(Int64), typeof(UInt16), typeof(UInt32), typeof(UInt64), typeof(Single), typeof(Double) }; for (int i = 0; i < numericTypes.Length; i++) SetLerper(numericTypes[i]); } /// /// Associate a Lerper class with a property type. /// /// The Lerper class to use for properties of the given type. /// The type of the property to associate the given Lerper with. public static void SetLerper(Type propertyType) where TLerper : GlideLerper, new() { registeredLerpers[propertyType] = typeof(TLerper).GetConstructor(Type.EmptyTypes); } protected TweenerImpl() { tweens = new Dictionary>(); toRemove = new List(); toAdd = new List(); allTweens = new List(); } private static Dictionary registeredLerpers; private Dictionary> tweens; private List toRemove, toAdd, allTweens; /// /// Tweens a set of properties on an object. /// To tween instance properties/fields, pass the object. /// To tween static properties/fields, pass the type of the object, using typeof(ObjectType) or object.GetType(). /// /// The object or type to tween. /// The values to tween to, in an anonymous type ( new { prop1 = 100, prop2 = 0} ). /// Duration of the tween in seconds. /// Delay before the tween starts, in seconds. /// Whether pre-existing tweens should be overwritten if this tween involves the same properties. /// The tween created, for setting properties on. public Tween Tween(T target, object values, float duration, float delay = 0, bool overwrite = false) where T : class { if (target == null) throw new ArgumentNullException("target"); // Prevent tweening on structs if you cheat by casting target as Object var targetType = target.GetType(); if (targetType.IsValueType) throw new Exception("Target of tween cannot be a struct!"); var tween = new Tween(target, duration, delay, this); AddAndRemove(); toAdd.Add(tween); if (values == null) // valid in case of manual timer return tween; var props = values.GetType().GetProperties(); for (int i = 0; i < props.Length; ++i) { List library = null; if (overwrite && tweens.TryGetValue(target, out library)) { for (int j = 0; j < library.Count; j++) library[j].Cancel(props[i].Name); } var property = props[i]; var info = new GlideInfo(target, property.Name); var to = new GlideInfo(values, property.Name, false); var lerper = CreateLerper(info.PropertyType); tween.AddLerp(lerper, info, info.Value, to.Value); } return tween; } /// /// Starts a simple timer for setting up callback scheduling. /// /// How long the timer will run for, in seconds. /// How long to wait before starting the timer, in seconds. /// The tween created, for setting properties. public Tween Timer(float duration, float delay = 0) { var tween = new Tween(null, duration, delay, this); AddAndRemove(); toAdd.Add(tween); return tween; } /// /// Remove tweens from the tweener without calling their complete functions. /// public void Cancel() { toRemove.AddRange(allTweens); } /// /// Assign tweens their final value and remove them from the tweener. /// public void CancelAndComplete() { for (int i = 0; i < allTweens.Count; ++i) allTweens[i].CancelAndComplete(); } /// /// Set tweens to pause. They won't update and their delays won't tick down. /// public void Pause() { for (int i = 0; i < allTweens.Count; ++i) { var tween = allTweens[i]; tween.Pause(); } } /// /// Toggle tweens' paused value. /// public void PauseToggle() { for (int i = 0; i < allTweens.Count; ++i) { var tween = allTweens[i]; tween.PauseToggle(); } } /// /// Resumes tweens from a paused state. /// public void Resume() { for (int i = 0; i < allTweens.Count; ++i) { var tween = allTweens[i]; tween.Resume(); } } /// /// Updates the tweener and all objects it contains. /// /// Seconds elapsed since last update. public void Update(float secondsElapsed) { for (int i = 0; i < allTweens.Count; ++i) allTweens[i].Update(secondsElapsed); AddAndRemove(); } private GlideLerper CreateLerper(Type propertyType) { ConstructorInfo lerper = null; if (!registeredLerpers.TryGetValue(propertyType, out lerper)) throw new Exception(string.Format("No Lerper found for type {0}.", propertyType.FullName)); return (GlideLerper)lerper.Invoke(null); } void IRemoveTweens.Remove(Tween tween) { toRemove.Add(tween); } private void AddAndRemove() { for (int i = 0; i < toAdd.Count; ++i) { var tween = toAdd[i]; allTweens.Add(tween); if (tween.Target == null) continue; // don't sort timers by target List list = null; if (!tweens.TryGetValue(tween.Target, out list)) tweens[tween.Target] = list = new List(); list.Add(tween); } for (int i = 0; i < toRemove.Count; ++i) { var tween = toRemove[i]; allTweens.Remove(tween); if (tween.Target == null) continue; // see above List list = null; if (tweens.TryGetValue(tween.Target, out list)) { list.Remove(tween); if (list.Count == 0) { tweens.Remove(tween.Target); } } allTweens.Remove(tween); } toAdd.Clear(); toRemove.Clear(); } #region Target control /// /// Cancel all tweens with the given target. /// /// The object being tweened that you want to cancel. public void TargetCancel(object target) { List list; if (tweens.TryGetValue(target, out list)) { for (int i = 0; i < list.Count; ++i) list[i].Cancel(); } } /// /// Cancel tweening named properties on the given target. /// /// The object being tweened that you want to cancel properties on. /// The properties to cancel. public void TargetCancel(object target, params string[] properties) { List list; if (tweens.TryGetValue(target, out list)) { for (int i = 0; i < list.Count; ++i) list[i].Cancel(properties); } } /// /// Cancel, complete, and call complete callbacks for all tweens with the given target.. /// /// The object being tweened that you want to cancel and complete. public void TargetCancelAndComplete(object target) { List list; if (tweens.TryGetValue(target, out list)) { for (int i = 0; i < list.Count; ++i) list[i].CancelAndComplete(); } } /// /// Pause all tweens with the given target. /// /// The object being tweened that you want to pause. public void TargetPause(object target) { List list; if (tweens.TryGetValue(target, out list)) { for (int i = 0; i < list.Count; ++i) list[i].Pause(); } } /// /// Toggle the pause state of all tweens with the given target. /// /// The object being tweened that you want to toggle pause. public void TargetPauseToggle(object target) { List list; if (tweens.TryGetValue(target, out list)) { for (int i = 0; i < list.Count; ++i) list[i].PauseToggle(); } } /// /// Resume all tweens with the given target. /// /// The object being tweened that you want to resume. public void TargetResume(object target) { List list; if (tweens.TryGetValue(target, out list)) { for (int i = 0; i < list.Count; ++i) list[i].Resume(); } } #endregion private class NumericLerper : GlideLerper { float from, to, range; public override void Initialize(object fromValue, object toValue, GlideLerper.Behavior behavior) { from = Convert.ToSingle(fromValue); to = Convert.ToSingle(toValue); range = to - from; if (behavior.HasFlag(GlideLerper.Behavior.Rotation)) { float angle = from; if (behavior.HasFlag(GlideLerper.Behavior.RotationRadians)) angle *= Util.DEG_TO_RAD; if (angle < 0) angle = 360 + angle; float r = angle + range; float d = r - angle; float a = (float)Math.Abs(d); if (a >= 180) range = (360 - a) * (d > 0 ? -1 : 1); else range = d; } } public override object Interpolate(float t, object current, GlideLerper.Behavior behavior) { var value = from + range * t; if (behavior.HasFlag(GlideLerper.Behavior.Rotation)) { if (behavior.HasFlag(GlideLerper.Behavior.RotationRadians)) value *= Util.DEG_TO_RAD; value %= 360.0f; if (value < 0) value += 360.0f; if (behavior.HasFlag(GlideLerper.Behavior.RotationRadians)) value *= Util.RAD_TO_DEG; } if (behavior.HasFlag(GlideLerper.Behavior.Round)) value = (float)Math.Round(value); var type = current.GetType(); return Convert.ChangeType(value, type); } } } } }