using System;
using System.Globalization;
using UnityEngine;
using UnityEngine.UIElements;
namespace Uigenx.Animation
{
///
/// A parsed USS value in the packed form a style setter applies. Four floats plus two unit
/// tags cover every property the animator drives, so keyframes hold no references and the
/// tick allocates nothing.
///
public readonly struct UgxStyleValue
{
public readonly float A;
public readonly float B;
public readonly float C;
public readonly float D;
/// 0 pixel, 1 percent for lengths. 0 degree, 1 gradian, 2 radian, 3 turn for angles.
public readonly byte UnitA;
public readonly byte UnitB;
public UgxStyleValue(float a, float b = 0f, float c = 0f, float d = 0f, byte unitA = 0, byte unitB = 0)
{
A = a;
B = b;
C = c;
D = d;
UnitA = unitA;
UnitB = unitB;
}
public static UgxStyleValue Number(float value) => new UgxStyleValue(value);
public static UgxStyleValue FromColor(Color c) => new UgxStyleValue(c.r, c.g, c.b, c.a);
public Color ToColor() => new Color(A, B, C, D);
public Length LengthA => new Length(A, UnitA == 1 ? LengthUnit.Percent : LengthUnit.Pixel);
public Length LengthB => new Length(B, UnitB == 1 ? LengthUnit.Percent : LengthUnit.Pixel);
public Angle AngleA => new Angle(A, (AngleUnit)UnitA);
///
/// Componentwise interpolation. When two keyframes disagree on a unit there is no correct
/// blend, so the value snaps to the later keyframe's unit at the midpoint.
///
public static UgxStyleValue Lerp(in UgxStyleValue a, in UgxStyleValue b, float t)
=> new UgxStyleValue(
Mathf.LerpUnclamped(a.A, b.A, t),
Mathf.LerpUnclamped(a.B, b.B, t),
Mathf.LerpUnclamped(a.C, b.C, t),
Mathf.LerpUnclamped(a.D, b.D, t),
t < 0.5f ? a.UnitA : b.UnitA,
t < 0.5f ? a.UnitB : b.UnitB);
}
/// Culture invariant parsing of the canonical USS value text the compiler emits.
public static class UgxValueParser
{
public static bool TryFloat(string text, out float value)
=> float.TryParse(Trim(text), NumberStyles.Float, CultureInfo.InvariantCulture, out value);
public static bool TryLength(string text, out float value, out byte unit)
{
unit = 0;
value = 0f;
var s = Trim(text);
if (s.Length == 0) return false;
if (s.EndsWith("%", StringComparison.Ordinal))
{
unit = 1;
return TryFloat(s.Substring(0, s.Length - 1), out value);
}
if (s.EndsWith("px", StringComparison.OrdinalIgnoreCase)) s = s.Substring(0, s.Length - 2);
return TryFloat(s, out value);
}
public static bool TryAngle(string text, out float value, out byte unit)
{
unit = 0;
value = 0f;
var s = Trim(text);
if (s.Length == 0) return false;
if (TryStrip(ref s, "deg")) unit = (byte)AngleUnit.Degree;
else if (TryStrip(ref s, "grad")) unit = (byte)AngleUnit.Gradian;
else if (TryStrip(ref s, "rad")) unit = (byte)AngleUnit.Radian;
else if (TryStrip(ref s, "turn")) unit = (byte)AngleUnit.Turn;
return TryFloat(s, out value);
}
/// `10px 20%` or a single value applied to both axes.
public static bool TryPair(string text, out float a, out byte unitA, out float b, out byte unitB)
{
a = b = 0f;
unitA = unitB = 0;
var s = Trim(text);
int split = s.IndexOf(' ');
if (split < 0)
{
if (!TryLength(s, out a, out unitA)) return false;
b = a;
unitB = unitA;
return true;
}
return TryLength(s.Substring(0, split), out a, out unitA) &&
TryLength(s.Substring(split + 1), out b, out unitB);
}
public static bool TryColor(string text, out Color color)
{
color = default;
var s = Trim(text);
if (s.Length == 0) return false;
if (s.StartsWith("rgba(", StringComparison.OrdinalIgnoreCase) ||
s.StartsWith("rgb(", StringComparison.OrdinalIgnoreCase))
return TryFunctionalColor(s, out color);
return ColorUtility.TryParseHtmlString(s[0] == '#' ? s : "#" + s, out color) ||
ColorUtility.TryParseHtmlString(s, out color);
}
static bool TryFunctionalColor(string s, out Color color)
{
color = default;
int open = s.IndexOf('(');
int close = s.LastIndexOf(')');
if (open < 0 || close <= open) return false;
var parts = s.Substring(open + 1, close - open - 1).Split(',');
if (parts.Length < 3) return false;
if (!TryChannel(parts[0], out float r) || !TryChannel(parts[1], out float g) ||
!TryChannel(parts[2], out float b)) return false;
float a = 1f;
if (parts.Length > 3 && !TryFloat(parts[3], out a)) return false;
color = new Color(r, g, b, a);
return true;
}
static bool TryChannel(string text, out float value)
{
var s = Trim(text);
if (s.EndsWith("%", StringComparison.Ordinal))
{
if (!TryFloat(s.Substring(0, s.Length - 1), out value)) return false;
value /= 100f;
return true;
}
if (!TryFloat(s, out value)) return false;
value /= 255f;
return true;
}
static bool TryStrip(ref string s, string suffix)
{
if (!s.EndsWith(suffix, StringComparison.OrdinalIgnoreCase)) return false;
s = s.Substring(0, s.Length - suffix.Length);
return true;
}
static string Trim(string text) => text == null ? string.Empty : text.Trim();
}
}