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.

336 lines
14 KiB
C#

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<Type, ConstructorInfo>();
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<NumericLerper>(numericTypes[i]);
}
/// <summary>
/// Associate a Lerper class with a property type.
/// </summary>
/// <typeparam name="TLerper">The Lerper class to use for properties of the given type.</typeparam>
/// <param name="type">The type of the property to associate the given Lerper with.</param>
public static void SetLerper<TLerper>(Type propertyType) where TLerper : GlideLerper, new() {
registeredLerpers[propertyType] = typeof(TLerper).GetConstructor(Type.EmptyTypes);
}
protected TweenerImpl() {
tweens = new Dictionary<object, List<Tween>>();
toRemove = new List<Tween>();
toAdd = new List<Tween>();
allTweens = new List<Tween>();
}
private static Dictionary<Type, ConstructorInfo> registeredLerpers;
private Dictionary<object, List<Tween>> tweens;
private List<Tween> toRemove, toAdd, allTweens;
/// <summary>
/// <para>Tweens a set of properties on an object.</para>
/// <para>To tween instance properties/fields, pass the object.</para>
/// <para>To tween static properties/fields, pass the type of the object, using typeof(ObjectType) or object.GetType().</para>
/// </summary>
/// <param name="target">The object or type to tween.</param>
/// <param name="values">The values to tween to, in an anonymous type ( new { prop1 = 100, prop2 = 0} ).</param>
/// <param name="duration">Duration of the tween in seconds.</param>
/// <param name="delay">Delay before the tween starts, in seconds.</param>
/// <param name="overwrite">Whether pre-existing tweens should be overwritten if this tween involves the same properties.</param>
/// <returns>The tween created, for setting properties on.</returns>
public Tween Tween<T>(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<Tween> 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;
}
/// <summary>
/// Starts a simple timer for setting up callback scheduling.
/// </summary>
/// <param name="duration">How long the timer will run for, in seconds.</param>
/// <param name="delay">How long to wait before starting the timer, in seconds.</param>
/// <returns>The tween created, for setting properties.</returns>
public Tween Timer(float duration, float delay = 0) {
var tween = new Tween(null, duration, delay, this);
AddAndRemove();
toAdd.Add(tween);
return tween;
}
/// <summary>
/// Remove tweens from the tweener without calling their complete functions.
/// </summary>
public void Cancel() {
toRemove.AddRange(allTweens);
}
/// <summary>
/// Assign tweens their final value and remove them from the tweener.
/// </summary>
public void CancelAndComplete() {
for (int i = 0; i < allTweens.Count; ++i)
allTweens[i].CancelAndComplete();
}
/// <summary>
/// Set tweens to pause. They won't update and their delays won't tick down.
/// </summary>
public void Pause() {
for (int i = 0; i < allTweens.Count; ++i) {
var tween = allTweens[i];
tween.Pause();
}
}
/// <summary>
/// Toggle tweens' paused value.
/// </summary>
public void PauseToggle() {
for (int i = 0; i < allTweens.Count; ++i) {
var tween = allTweens[i];
tween.PauseToggle();
}
}
/// <summary>
/// Resumes tweens from a paused state.
/// </summary>
public void Resume() {
for (int i = 0; i < allTweens.Count; ++i) {
var tween = allTweens[i];
tween.Resume();
}
}
/// <summary>
/// Updates the tweener and all objects it contains.
/// </summary>
/// <param name="secondsElapsed">Seconds elapsed since last update.</param>
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<Tween> list = null;
if (!tweens.TryGetValue(tween.Target, out list))
tweens[tween.Target] = list = new List<Tween>();
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<Tween> 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
/// <summary>
/// Cancel all tweens with the given target.
/// </summary>
/// <param name="target">The object being tweened that you want to cancel.</param>
public void TargetCancel(object target) {
List<Tween> list;
if (tweens.TryGetValue(target, out list)) {
for (int i = 0; i < list.Count; ++i)
list[i].Cancel();
}
}
/// <summary>
/// Cancel tweening named properties on the given target.
/// </summary>
/// <param name="target">The object being tweened that you want to cancel properties on.</param>
/// <param name="properties">The properties to cancel.</param>
public void TargetCancel(object target, params string[] properties) {
List<Tween> list;
if (tweens.TryGetValue(target, out list)) {
for (int i = 0; i < list.Count; ++i)
list[i].Cancel(properties);
}
}
/// <summary>
/// Cancel, complete, and call complete callbacks for all tweens with the given target..
/// </summary>
/// <param name="target">The object being tweened that you want to cancel and complete.</param>
public void TargetCancelAndComplete(object target) {
List<Tween> list;
if (tweens.TryGetValue(target, out list)) {
for (int i = 0; i < list.Count; ++i)
list[i].CancelAndComplete();
}
}
/// <summary>
/// Pause all tweens with the given target.
/// </summary>
/// <param name="target">The object being tweened that you want to pause.</param>
public void TargetPause(object target) {
List<Tween> list;
if (tweens.TryGetValue(target, out list)) {
for (int i = 0; i < list.Count; ++i)
list[i].Pause();
}
}
/// <summary>
/// Toggle the pause state of all tweens with the given target.
/// </summary>
/// <param name="target">The object being tweened that you want to toggle pause.</param>
public void TargetPauseToggle(object target) {
List<Tween> list;
if (tweens.TryGetValue(target, out list)) {
for (int i = 0; i < list.Count; ++i)
list[i].PauseToggle();
}
}
/// <summary>
/// Resume all tweens with the given target.
/// </summary>
/// <param name="target">The object being tweened that you want to resume.</param>
public void TargetResume(object target) {
List<Tween> 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);
}
}
}
}
}