Files
BetterLyrics/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/ValueTransition.cs
2025-07-10 23:08:29 -04:00

181 lines
6.1 KiB
C#

// 2025/6/23 by Zhe Fang
using System;
using System.Diagnostics;
using BetterLyrics.WinUI3.Enums;
namespace BetterLyrics.WinUI3.Helper
{
public class ValueTransition<T>
where T : struct
{
private T _currentValue;
private float _durationSeconds;
private EasingType? _easingType;
private Func<T, T, float, T> _interpolator;
private bool _isTransitioning;
private float _progress;
private T _startValue;
private T _targetValue;
public float DurationSeconds => _durationSeconds;
public bool IsTransitioning => _isTransitioning;
public T Value => _currentValue;
public T TargetValue => _targetValue;
public ValueTransition(T initialValue, float durationSeconds, Func<T, T, float, T>? interpolator = null, EasingType? easingType = null)
{
_currentValue = initialValue;
_startValue = initialValue;
_targetValue = initialValue;
_durationSeconds = durationSeconds;
_progress = 1f;
_isTransitioning = false;
if (interpolator != null)
{
_interpolator = interpolator;
_easingType = null;
}
else if (easingType.HasValue)
{
_easingType = easingType;
_interpolator = GetInterpolatorByEasingType(_easingType.Value);
}
else
{
_easingType = EasingType.Linear;
_interpolator = GetInterpolatorByEasingType(_easingType.Value);
}
}
public void SetDuration(float seconds)
{
if (seconds <= 0)
throw new ArgumentOutOfRangeException(nameof(seconds), "Duration must be positive.");
_durationSeconds = seconds;
}
private void JumpTo(T value)
{
_currentValue = value;
_startValue = value;
_targetValue = value;
_progress = 1f;
_isTransitioning = false;
}
public void Reset(T value)
{
_currentValue = value;
_startValue = value;
_targetValue = value;
_progress = 0f;
_isTransitioning = false;
}
public void StartTransition(T targetValue, bool jumpTo = false)
{
if (jumpTo)
{
JumpTo(targetValue);
return;
}
if (!targetValue.Equals(_currentValue))
{
_startValue = _currentValue;
_targetValue = targetValue;
_progress = 0f;
_isTransitioning = true;
}
}
public static bool Equals(double x, double y, double tolerance)
{
var diff = Math.Abs(x - y);
return diff <= tolerance || diff <= Math.Max(Math.Abs(x), Math.Abs(y)) * tolerance;
}
public void Update(TimeSpan elapsedTime)
{
if (!_isTransitioning) return;
_progress += (float)elapsedTime.TotalSeconds / _durationSeconds;
if (_progress >= 1f)
{
_progress = 1f;
_currentValue = _targetValue;
_isTransitioning = false;
}
else
{
_currentValue = _interpolator(_startValue, _targetValue, _progress);
}
}
private Func<T, T, float, T> GetInterpolatorByEasingType(EasingType type)
{
if (typeof(T) == typeof(float))
{
return (start, end, progress) =>
{
float s = (float)(object)start;
float e = (float)(object)end;
float t = progress;
switch (type)
{
case EasingType.EaseInOutSine:
t = EasingHelper.EaseInOutSine(t);
break;
case EasingType.EaseInOutQuad:
t = EasingHelper.EaseInOutQuad(t);
break;
case EasingType.EaseInOutCubic:
t = EasingHelper.EaseInOutCubic(t);
break;
case EasingType.EaseInOutQuart:
t = EasingHelper.EaseInOutQuart(t);
break;
case EasingType.EaseInOutQuint:
t = EasingHelper.EaseInOutQuint(t);
break;
case EasingType.EaseInOutExpo:
t = EasingHelper.EaseInOutExpo(t);
break;
case EasingType.EaseInOutCirc:
t = EasingHelper.EaseInOutCirc(t);
break;
case EasingType.EaseInOutBack:
t = EasingHelper.EaseInOutBack(t);
break;
case EasingType.EaseInOutElastic:
t = EasingHelper.EaseInOutElastic(t);
break;
case EasingType.EaseInOutBounce:
t = EasingHelper.EaseInOutBounce(t);
break;
case EasingType.SmoothStep:
t = EasingHelper.SmoothStep(t);
break;
case EasingType.Linear:
t = EasingHelper.Linear(t);
break;
default:
break;
}
return (T)(object)(s + (e - s) * t);
};
}
throw new NotSupportedException($"Easing type {type} is not supported for type {typeof(T)}.");
}
public void SetEasingType(EasingType easingType)
{
_easingType = easingType;
_interpolator = GetInterpolatorByEasingType(easingType);
}
}
}