using System; using System.Collections.Generic; using System.Reflection; namespace Otter { public partial class Tween { [Flags] public enum RotationUnit { Degrees, Radians } #region Callbacks private Func ease; private Action begin, update, complete; #endregion #region Timing public bool Paused { get; private set; } private float Delay, repeatDelay; private float Duration; private float time; #endregion private bool firstUpdate; private int repeatCount, timesRepeated; private GlideLerper.Behavior behavior; private List vars; private List lerpers; private List start, end; private Dictionary varHash; private TweenerImpl Parent; private IRemoveTweens Remover; /// /// The time remaining before the tween ends or repeats. /// public float TimeRemaining { get { return Duration - time; } } /// /// A value between 0 and 1, where 0 means the tween has not been started and 1 means that it has completed. /// public float Completion { get { var c = time / Duration; return c < 0 ? 0 : (c > 1 ? 1 : c); } } /// /// Whether the tween is currently looping. /// public bool Looping { get { return repeatCount != 0; } } /// /// The object this tween targets. Will be null if the tween represents a timer. /// public object Target { get; private set; } private Tween(object target, float duration, float delay, Tween.TweenerImpl parent) { Target = target; Duration = duration; Delay = delay; Parent = parent; Remover = parent; firstUpdate = true; varHash = new Dictionary(); vars = new List(); lerpers = new List(); start = new List(); end = new List(); behavior = GlideLerper.Behavior.None; } private void AddLerp(GlideLerper lerper, GlideInfo info, object from, object to) { varHash.Add(info.PropertyName, vars.Count); vars.Add(info); start.Add(from); end.Add(to); lerpers.Add(lerper); } private void Update(float elapsed) { if (firstUpdate) { firstUpdate = false; var i = vars.Count; while (i-- > 0) { if (lerpers[i] != null) lerpers[i].Initialize(start[i], end[i], behavior); } } else { if (Paused) return; if (Delay > 0) { Delay -= elapsed; if (Delay > 0) return; } if (time == 0 && timesRepeated == 0 && begin != null) begin(); time += elapsed; float setTimeTo = time; float t = time / Duration; bool doComplete = false; if (time >= Duration) { if (repeatCount != 0) { setTimeTo = 0; Delay = repeatDelay; timesRepeated++; if (repeatCount > 0) --repeatCount; if (repeatCount < 0) doComplete = true; } else { time = Duration; t = 1; Remover.Remove(this); doComplete = true; } } if (ease != null) t = ease(t); int i = vars.Count; while (i-- > 0) { if (vars[i] != null) vars[i].Value = lerpers[i].Interpolate(t, vars[i].Value, behavior); } time = setTimeTo; // If the timer is zero here, we just restarted. // If reflect mode is on, flip start to end if (time == 0 && behavior.HasFlag(GlideLerper.Behavior.Reflect)) Reverse(); if (update != null) update(); if (doComplete && complete != null) complete(); } } #region Behavior /// /// Apply target values to a starting point before tweening. /// /// The values to apply, in an anonymous type ( new { prop1 = 100, prop2 = 0} ). /// A reference to this. public Tween From(object values) { var props = values.GetType().GetProperties(); for (int i = 0; i < props.Length; ++i) { var property = props[i]; var propValue = property.GetValue(values, null); int index = -1; if (varHash.TryGetValue(property.Name, out index)) { // if we're already tweening this value, adjust the range start[index] = propValue; } // if we aren't tweening this value, just set it var info = new GlideInfo(Target, property.Name, true); info.Value = propValue; } return this; } /// /// Set the easing function. /// /// The Easer to use. /// A reference to this. public Tween Ease(Func ease) { this.ease = ease; return this; } /// /// Set a function to call when the tween begins (useful when using delays). Can be called multiple times for compound callbacks. /// /// The function that will be called when the tween starts, after the delay. /// A reference to this. public Tween OnBegin(Action callback) { if (begin == null) begin = callback; else begin += callback; return this; } /// /// Set a function to call when the tween finishes. Can be called multiple times for compound callbacks. /// If the tween repeats infinitely, this will be called each time; otherwise it will only run when the tween is finished repeating. /// /// The function that will be called on tween completion. /// A reference to this. public Tween OnComplete(Action callback) { if (complete == null) complete = callback; else complete += callback; return this; } /// /// Set a function to call as the tween updates. Can be called multiple times for compound callbacks. /// /// The function to use. /// A reference to this. public Tween OnUpdate(Action callback) { if (update == null) update = callback; else update += callback; return this; } /// /// Enable repeating. /// /// Number of times to repeat. Leave blank or pass a negative number to repeat infinitely. /// A reference to this. public Tween Repeat(int times = -1) { repeatCount = times; return this; } /// /// Set a delay for when the tween repeats. /// /// How long to wait before repeating. /// A reference to this. public Tween RepeatDelay(float delay) { repeatDelay = delay; return this; } /// /// Sets the tween to reverse every other time it repeats. Repeating must be enabled for this to have any effect. /// /// A reference to this. public Tween Reflect() { behavior |= GlideLerper.Behavior.Reflect; return this; } /// /// Swaps the start and end values of the tween. /// /// A reference to this. public Tween Reverse() { int i = vars.Count; while (i-- > 0) { var s = start[i]; var e = end[i]; // Set start to end and end to start start[i] = e; end[i] = s; lerpers[i].Initialize(e, s, behavior); } return this; } /// /// Whether this tween handles rotation. /// /// A reference to this. public Tween Rotation(RotationUnit unit = RotationUnit.Degrees) { behavior |= GlideLerper.Behavior.Rotation; behavior |= (unit == RotationUnit.Degrees) ? GlideLerper.Behavior.RotationDegrees : GlideLerper.Behavior.RotationRadians; return this; } /// /// Whether tweened values should be rounded to integer values. /// /// A reference to this. public Tween Round() { behavior |= GlideLerper.Behavior.Round; return this; } #endregion #region Control /// /// Cancel tweening given properties. /// /// public void Cancel(params string[] properties) { var canceled = 0; for (int i = 0; i < properties.Length; ++i) { var index = 0; if (!varHash.TryGetValue(properties[i], out index)) continue; varHash.Remove(properties[i]); vars[index] = null; lerpers[index] = null; start[index] = null; end[index] = null; canceled++; } if (canceled == vars.Count) Cancel(); } /// /// Remove tweens from the tweener without calling their complete functions. /// public void Cancel() { Remover.Remove(this); } /// /// Assign tweens their final value and remove them from the tweener. /// public void CancelAndComplete() { time = Duration; update = null; Remover.Remove(this); } /// /// Set tweens to pause. They won't update and their delays won't tick down. /// public void Pause() { Paused = true; } /// /// Toggle tweens' paused value. /// public void PauseToggle() { Paused = !Paused; } /// /// Resumes tweens from a paused state. /// public void Resume() { Paused = false; } #endregion } }