Code/Animations/AnimationBase.cs
using System;

namespace BetterUI.Animations;

/// <summary>
/// Base class for animations, providing common functionality for animating components.
/// </summary>
public abstract class AnimationBase : Component
{
	private int _currentIteration;

	/// <summary>
	/// Gets the start position of the animation.
	/// </summary>
	protected Vector3 StartPosition { get; private set; }

	/// <summary>
	/// Gets the elapsed time since the animation started.
	/// </summary>
	protected float ElapsedTime { get; private set; }

	/// <summary>
	/// Gets the normalized time of the animation, calculated as elapsed time over duration.
	/// </summary>
	protected float NormalizedTime => ElapsedTime / Duration;

	/// <summary>
	/// Gets or sets the duration of the animation in seconds.
	/// </summary>
	[Property, Category( "Animation" )]
	public float Duration { get; set; } = 1f;

	/// <summary>
	/// Gets or sets the axis of the animation.
	/// </summary>
	[Property, Category( "Animation" )]
	public virtual Vector3 Axis { get; set; } = Vector3.Up;

	/// <summary>
	/// Gets or sets the easing mode of the animation.
	/// </summary>
	[Property, Category( "Animation" )]
	public EasingMode Easing { get; set; } = EasingMode.Linear;

	/// <summary>
	/// Gets or sets the iteration type of the animation.
	/// </summary>
	[Property, Category( "Animation" )]
	public IterationType Iteration { get; set; } = IterationType.Loop;

	/// <summary>
	/// Gets or sets the number of iterations for manual iteration type.
	/// </summary>
	[Property, Category( "Animation" ), ShowIf( nameof(Iteration), IterationType.Manual )]
	public int Iterations { get; set; } = 1;

	/// <summary>
	/// Called when the animation starts, setting the initial position.
	/// </summary>
	protected override void OnStart()
	{
		StartPosition = WorldPosition;
	}

	/// <summary>
	/// Updates the animation, progressing the animation state.
	/// </summary>
	protected override void OnUpdate()
	{
		Animate();
	}

	/// <summary>
	/// Handles the animation logic, progressing time and calling OnAnimate.
	/// </summary>
	private void Animate()
	{
		if ( Iteration is IterationType.Once && _currentIteration > 0 ) return;
		if ( Iteration is IterationType.Manual && _currentIteration >= Iterations ) return;

		ElapsedTime += Time.Delta;

		var t = ElapsedTime / Duration;
		t = ApplyEasing( t, Easing );

		OnAnimate( t );

		if ( !(ElapsedTime >= Duration) )
			return;

		ElapsedTime = 0f;

		if ( Iteration is not IterationType.Loop )
			_currentIteration++;
	}

	/// <summary>
	/// Abstract method to be implemented by derived classes for specific animation logic.
	/// </summary>
	/// <param name="t">The normalized time for the animation.</param>
	protected abstract void OnAnimate( float t );

	/// <summary>
	/// Applies easing to the normalized time based on the specified easing mode.
	/// </summary>
	/// <param name="t">The normalized time to apply easing to.</param>
	/// <param name="easing">The easing mode to apply.</param>
	/// <returns>The eased time.</returns>
	protected static float ApplyEasing( float t, EasingMode easing ) => easing switch
	{
		EasingMode.Linear => t,
		EasingMode.EaseIn => t * t,
		EasingMode.EaseOut => 1 - MathF.Pow( 1 - t, 2 ),
		EasingMode.EaseInOut => t < 0.5f
			? 2 * t * t
			: 1 - MathF.Pow( -2 * t + 2, 2 ) / 2,
		_ => t
	};
}