using System.Collections.Generic;
using UnityEngine;
namespace Watermelon
{
///
/// Abstract base class for all tween animations.
/// Encapsulates lifecycle (start, pause, resume, kill, complete), easing, delay,
/// progress tracking, and callback registration.
///
/// Lifecycle:
///
/// - CreateInstantiate a concrete subclass and configure it with the fluent builder methods.
/// - StartCall to register the tween with and begin updating.
/// - Update calls and each frame.
/// - EndEither (applies the final value and fires callbacks) or (no final value, no callbacks).
///
///
///
/// Implementing a custom tween:
/// Override to interpolate the value each frame,
/// to snap to the final value on completion, and
/// to return false when the target object is no longer valid.
///
///
public abstract class TweenCase
{
// Managed by TweensHolder — internal set only
/// Index of this tween inside its array. -1 when inactive.
public int ActiveID { get; internal set; }
/// Whether the tween is currently registered and updating in a .
public bool IsActive { get; internal set; }
// Private: only read/written inside TweenCase
private float currentDelay;
private float delay;
private float duration;
private int updateMethodIndex;
private bool isPaused;
private bool isUnscaled;
private bool isCompleted;
private bool isKilling;
private Ease.IEasingFunction easeFunction;
private SimpleCallback completedCallback;
private SimpleCallback terminatedCallback;
private List callbackData;
// Protected: written by concrete case constructors
/// Optional target object. Used by in subclasses to detect destroyed Unity objects.
protected GameObject parentObject;
// Protected: read by concrete Invoke() implementations via Interpolate(state)
///
/// Normalized progress of the tween in the range [0, 1].
/// Read by concrete implementations via .
///
protected float state;
// Public read-only surface
/// Elapsed delay time (seconds) since the tween started. Counts up until it reaches .
public float CurrentDelay => currentDelay;
/// Initial delay (seconds) before the tween begins animating.
public float Delay => delay;
/// Normalized progress [0, 1]. Advances each frame after the delay has elapsed.
public float State => state;
/// Total animation duration in seconds (excluding delay).
public float Duration => duration;
/// Whether the tween is currently paused. Paused tweens are skipped by but remain registered.
public bool IsPaused => isPaused;
/// When true, the tween uses Time.unscaledDeltaTime and is unaffected by Time.timeScale.
public bool IsUnscaled => isUnscaled;
/// Whether the tween has finished animating (state reached 1, or was called).
public bool IsCompleted => isCompleted;
///
/// Whether the tween has been marked for removal but not yet physically removed from the array.
/// While true, is a no-op. Cleared by after removal.
///
public bool IsKilling => isKilling;
/// Array index of the owning inside .
public int UpdateMethodIndex => updateMethodIndex;
/// Unity update loop in which this tween runs.
public UpdateMethod UpdateMethod => (UpdateMethod)updateMethodIndex;
/// The GameObject this tween is attached to, or null for system (non-transform) tweens.
public GameObject ParentObject => parentObject;
/// Initialises the tween with a easing function.
public TweenCase()
{
SetEasing(Ease.Type.Linear);
}
/// Registers the tween with the global system and begins updating. Returns this for chaining.
public virtual TweenCase StartTween()
{
Tween.AddTween(this);
return this;
}
///
/// Called each frame to check whether the tween's target object is still valid.
/// Return false to automatically kill the tween (e.g. when a GameObject has been destroyed).
///
public abstract bool Validate();
// -----------------------------------------------------------
// Lifecycle
// -----------------------------------------------------------
///
/// Stops and removes the tween without applying the final value or firing .
/// Fires the internal terminated callback used by .
/// Idempotent — safe to call multiple times.
///
public TweenCase Kill()
{
if (!isKilling)
{
IsActive = false;
Tween.MarkForKilling(this);
isKilling = true;
completedCallback = null;
terminatedCallback?.Invoke();
terminatedCallback = null;
}
return this;
}
///
/// Immediately completes the tween: snaps to the final value via ,
/// fires the callback, then calls .
/// Idempotent — subsequent calls are no-ops.
///
public TweenCase Complete()
{
if (isKilling || isCompleted) return this;
if (isPaused) isPaused = false;
state = 1;
isCompleted = true;
DefaultComplete();
InvokeCompleteEvent();
Kill();
return this;
}
///
/// Restarts the tween from the beginning (resets progress and delay to 0).
/// If the tween has already ended, re-registers it with the system via .
/// Has no effect while is mid-processing ( is true).
///
public TweenCase Restart()
{
if (isKilling) return this;
state = 0;
currentDelay = 0;
isCompleted = false;
isPaused = false;
if (!IsActive)
StartTween();
return this;
}
/// Pauses the tween. The tween remains registered but is skipped by the update loop until is called.
public TweenCase Pause()
{
isPaused = true;
return this;
}
/// Resumes a paused tween so it continues updating from where it stopped.
public TweenCase Resume()
{
isPaused = false;
return this;
}
// -----------------------------------------------------------
// Builder (fluent API)
// -----------------------------------------------------------
/// Sets a delay (seconds) before the tween begins animating. Must be called before .
public TweenCase SetDelay(float delay)
{
this.delay = delay;
currentDelay = 0;
return this;
}
///
/// Sets which Unity update loop drives this tween (,
/// , or ).
/// Must be called before .
///
public TweenCase SetUpdateMethod(UpdateMethod updateMethod)
{
updateMethodIndex = (int)updateMethod;
return this;
}
///
/// When true, the tween uses Time.unscaledDeltaTime and ignores Time.timeScale.
/// Useful for UI animations that must run during paused gameplay.
///
public TweenCase SetUnscaledMode(bool isUnscaled)
{
this.isUnscaled = isUnscaled;
return this;
}
/// Sets one of the built-in easing curves from . Default is .
public TweenCase SetEasing(Ease.Type ease)
{
easeFunction = Ease.GetFunction(ease);
return this;
}
/// Sets a custom easing curve via an . The curve is sampled over its total time range.
public TweenCase SetCurveEasing(AnimationCurve easingCurve)
{
easeFunction = new AnimationCurveEasingFunction(easingCurve);
return this;
}
/// Sets a fully custom easing function implementing .
public TweenCase SetCustomEasing(Ease.IEasingFunction easeFunction)
{
this.easeFunction = easeFunction;
return this;
}
/// Sets the total animation duration in seconds (excluding delay).
public TweenCase SetDuration(float duration)
{
this.duration = duration;
return this;
}
// -----------------------------------------------------------
// Callbacks
// -----------------------------------------------------------
///
/// Registers a callback invoked when the tween completes naturally or via .
/// Only one callback is stored — each call replaces the previous one (last call wins).
/// Not fired when the tween is stopped via .
///
public TweenCase OnComplete(SimpleCallback callback)
{
completedCallback = callback;
return this;
}
///
/// Registers a callback fired when the tween ends for any reason — both and .
/// Used internally by . Always has at most one subscriber.
///
internal TweenCase OnTerminated(SimpleCallback callback)
{
terminatedCallback = callback;
return this;
}
///
/// Registers a one-shot callback fired when the normalized progress reaches (range 0–1).
/// Multiple thresholds can be registered; each fires exactly once and is then discarded.
///
/// Normalized time threshold in [0, 1].
/// The callback to invoke when the threshold is crossed.
public TweenCase OnTimeReached(float t, SimpleCallback callback)
{
callbackData ??= new List();
callbackData.Add(new CallbackData(t, callback));
return this;
}
// -----------------------------------------------------------
// Internal — called only by TweensHolder or TweenCase itself
// -----------------------------------------------------------
///
/// Applies the easing function to the raw progress value .
/// Call this inside to obtain the eased interpolation factor.
///
/// Raw normalized progress in [0, 1].
/// Eased value, typically in [0, 1] (may overshoot for elastic/back easings).
public float Interpolate(float p) => easeFunction.Interpolate(p);
/// Advances the delay timer by . Called by while the delay has not yet elapsed.
internal void UpdateDelay(float deltaTime)
{
currentDelay += deltaTime;
}
///
/// Advances by deltaTime / duration,
/// fires any callbacks whose threshold was crossed,
/// and sets when state reaches 1.
/// Called by every frame after the delay has elapsed.
///
internal void UpdateState(float deltaTime)
{
state += Mathf.Min(1.0f, deltaTime / duration);
// Check OnTimeReached callbacks independently — even if state jumped past 1.
if (!callbackData.IsNullOrEmpty())
{
for (int i = 0; i < callbackData.Count; i++)
{
var data = callbackData[i];
if (state >= data.t)
{
data.callback?.Invoke();
callbackData.RemoveAt(i);
i--;
}
}
}
if (state >= 1)
isCompleted = true;
}
/// Invokes the registered callback. Called by after the tween completes naturally.
internal void InvokeCompleteEvent()
{
completedCallback?.Invoke();
}
///
/// Called by after the tween has been fully removed from its array.
/// Resets so the tween can be safely ed.
///
internal void ResetKillingState()
{
isKilling = false;
}
// -----------------------------------------------------------
// Abstract — implemented by concrete tween cases
// -----------------------------------------------------------
///
/// Called every frame while the tween is active and not paused (after any delay has elapsed).
/// Implement the actual value interpolation here using ().
///
/// Elapsed time since the last frame (scaled or unscaled, matching ).
public abstract void Invoke(float deltaTime);
///
/// Called when the tween finishes naturally or via .
/// Snap the animated value to its final state here to guarantee the animation reaches its target.
/// Not called when the tween is stopped via .
///
public abstract void DefaultComplete();
// -----------------------------------------------------------
// Helpers
// -----------------------------------------------------------
private readonly struct CallbackData
{
public readonly float t;
public readonly SimpleCallback callback;
public CallbackData(float t, SimpleCallback callback)
{
this.t = t;
this.callback = callback;
}
}
}
}