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(); } }