You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

359 lines
12 KiB
C#

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<float, float> 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<GlideInfo> vars;
private List<GlideLerper> lerpers;
private List<object> start, end;
private Dictionary<string, int> varHash;
private TweenerImpl Parent;
private IRemoveTweens Remover;
/// <summary>
/// The time remaining before the tween ends or repeats.
/// </summary>
public float TimeRemaining { get { return Duration - time; } }
/// <summary>
/// A value between 0 and 1, where 0 means the tween has not been started and 1 means that it has completed.
/// </summary>
public float Completion { get { var c = time / Duration; return c < 0 ? 0 : (c > 1 ? 1 : c); } }
/// <summary>
/// Whether the tween is currently looping.
/// </summary>
public bool Looping { get { return repeatCount != 0; } }
/// <summary>
/// The object this tween targets. Will be null if the tween represents a timer.
/// </summary>
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<string, int>();
vars = new List<GlideInfo>();
lerpers = new List<GlideLerper>();
start = new List<object>();
end = new List<object>();
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
/// <summary>
/// Apply target values to a starting point before tweening.
/// </summary>
/// <param name="values">The values to apply, in an anonymous type ( new { prop1 = 100, prop2 = 0} ).</param>
/// <returns>A reference to this.</returns>
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;
}
/// <summary>
/// Set the easing function.
/// </summary>
/// <param name="ease">The Easer to use.</param>
/// <returns>A reference to this.</returns>
public Tween Ease(Func<float, float> ease) {
this.ease = ease;
return this;
}
/// <summary>
/// Set a function to call when the tween begins (useful when using delays). Can be called multiple times for compound callbacks.
/// </summary>
/// <param name="callback">The function that will be called when the tween starts, after the delay.</param>
/// <returns>A reference to this.</returns>
public Tween OnBegin(Action callback) {
if (begin == null) begin = callback;
else begin += callback;
return this;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="callback">The function that will be called on tween completion.</param>
/// <returns>A reference to this.</returns>
public Tween OnComplete(Action callback) {
if (complete == null) complete = callback;
else complete += callback;
return this;
}
/// <summary>
/// Set a function to call as the tween updates. Can be called multiple times for compound callbacks.
/// </summary>
/// <param name="callback">The function to use.</param>
/// <returns>A reference to this.</returns>
public Tween OnUpdate(Action callback) {
if (update == null) update = callback;
else update += callback;
return this;
}
/// <summary>
/// Enable repeating.
/// </summary>
/// <param name="times">Number of times to repeat. Leave blank or pass a negative number to repeat infinitely.</param>
/// <returns>A reference to this.</returns>
public Tween Repeat(int times = -1) {
repeatCount = times;
return this;
}
/// <summary>
/// Set a delay for when the tween repeats.
/// </summary>
/// <param name="delay">How long to wait before repeating.</param>
/// <returns>A reference to this.</returns>
public Tween RepeatDelay(float delay) {
repeatDelay = delay;
return this;
}
/// <summary>
/// Sets the tween to reverse every other time it repeats. Repeating must be enabled for this to have any effect.
/// </summary>
/// <returns>A reference to this.</returns>
public Tween Reflect() {
behavior |= GlideLerper.Behavior.Reflect;
return this;
}
/// <summary>
/// Swaps the start and end values of the tween.
/// </summary>
/// <returns>A reference to this.</returns>
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;
}
/// <summary>
/// Whether this tween handles rotation.
/// </summary>
/// <returns>A reference to this.</returns>
public Tween Rotation(RotationUnit unit = RotationUnit.Degrees) {
behavior |= GlideLerper.Behavior.Rotation;
behavior |= (unit == RotationUnit.Degrees) ? GlideLerper.Behavior.RotationDegrees : GlideLerper.Behavior.RotationRadians;
return this;
}
/// <summary>
/// Whether tweened values should be rounded to integer values.
/// </summary>
/// <returns>A reference to this.</returns>
public Tween Round() {
behavior |= GlideLerper.Behavior.Round;
return this;
}
#endregion
#region Control
/// <summary>
/// Cancel tweening given properties.
/// </summary>
/// <param name="properties"></param>
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();
}
/// <summary>
/// Remove tweens from the tweener without calling their complete functions.
/// </summary>
public void Cancel() {
Remover.Remove(this);
}
/// <summary>
/// Assign tweens their final value and remove them from the tweener.
/// </summary>
public void CancelAndComplete() {
time = Duration;
update = null;
Remover.Remove(this);
}
/// <summary>
/// Set tweens to pause. They won't update and their delays won't tick down.
/// </summary>
public void Pause() {
Paused = true;
}
/// <summary>
/// Toggle tweens' paused value.
/// </summary>
public void PauseToggle() {
Paused = !Paused;
}
/// <summary>
/// Resumes tweens from a paused state.
/// </summary>
public void Resume() {
Paused = false;
}
#endregion
}
}