using System.Collections.Generic; using UnityEngine; using UnityEngine.UIElements; namespace Uigenx.Animation { /// /// Applies one animatable style property. Parsing happens once when a clip is prepared; /// runs every tick and must not allocate. /// public interface IUgxStyleSetter { /// Canonical USS property name, for example `background-color`. string Property { get; } bool TryParse(string text, out UgxStyleValue value); bool TryRead(VisualElement element, out UgxStyleValue value); void Apply(VisualElement element, in UgxStyleValue value); } /// Property name to setter table. Accepts USS names and their C# spellings. public static class UgxStyleSetters { static readonly Dictionary Table = Build(); public static bool TryGet(string property, out IUgxStyleSetter setter) { setter = null; if (string.IsNullOrEmpty(property)) return false; if (property.StartsWith("style.", System.StringComparison.Ordinal)) property = property.Substring(6); return Table.TryGetValue(property, out setter); } public static IEnumerable Properties => Table.Keys; static Dictionary Build() { var table = new Dictionary(32, System.StringComparer.Ordinal); Register(table, new OpacitySetter(), "opacity"); Register(table, new TranslateSetter(), "translate"); Register(table, new ScaleSetter(), "scale"); Register(table, new RotateSetter(), "rotate"); Register(table, new LengthSetter("width"), "width"); Register(table, new LengthSetter("height"), "height"); Register(table, new LengthSetter("left"), "left"); Register(table, new LengthSetter("top"), "top"); Register(table, new ColorSetter("background-color"), "background-color", "backgroundColor"); Register(table, new ColorSetter("color"), "color"); Register(table, new ColorSetter("-unity-background-image-tint-color"), "-unity-background-image-tint-color", "unityBackgroundImageTintColor"); return table; } static void Register(Dictionary table, IUgxStyleSetter setter, params string[] names) { foreach (var name in names) table[name] = setter; } sealed class OpacitySetter : IUgxStyleSetter { public string Property => "opacity"; public bool TryParse(string text, out UgxStyleValue value) { var ok = UgxValueParser.TryFloat(text, out var f); value = UgxStyleValue.Number(f); return ok; } public bool TryRead(VisualElement element, out UgxStyleValue value) { value = UgxStyleValue.Number(element.resolvedStyle.opacity); return true; } public void Apply(VisualElement element, in UgxStyleValue value) => element.style.opacity = value.A; } sealed class LengthSetter : IUgxStyleSetter { readonly string _property; internal LengthSetter(string property) => _property = property; public string Property => _property; public bool TryParse(string text, out UgxStyleValue value) { var ok = UgxValueParser.TryLength(text, out var v, out var unit); value = new UgxStyleValue(v, unitA: unit); return ok; } public bool TryRead(VisualElement element, out UgxStyleValue value) { var resolved = element.resolvedStyle; float v; switch (_property) { case "width": v = resolved.width; break; case "height": v = resolved.height; break; case "left": v = resolved.left; break; default: v = resolved.top; break; } value = new UgxStyleValue(v); return !float.IsNaN(v); } public void Apply(VisualElement element, in UgxStyleValue value) { var length = value.LengthA; switch (_property) { case "width": element.style.width = length; break; case "height": element.style.height = length; break; case "left": element.style.left = length; break; default: element.style.top = length; break; } } } sealed class ColorSetter : IUgxStyleSetter { readonly string _property; internal ColorSetter(string property) => _property = property; public string Property => _property; public bool TryParse(string text, out UgxStyleValue value) { var ok = UgxValueParser.TryColor(text, out var c); value = UgxStyleValue.FromColor(c); return ok; } public bool TryRead(VisualElement element, out UgxStyleValue value) { var resolved = element.resolvedStyle; Color c; switch (_property) { case "background-color": c = resolved.backgroundColor; break; case "color": c = resolved.color; break; default: c = resolved.unityBackgroundImageTintColor; break; } value = UgxStyleValue.FromColor(c); return true; } public void Apply(VisualElement element, in UgxStyleValue value) { var c = value.ToColor(); switch (_property) { case "background-color": element.style.backgroundColor = c; break; case "color": element.style.color = c; break; default: element.style.unityBackgroundImageTintColor = c; break; } } } sealed class TranslateSetter : IUgxStyleSetter { public string Property => "translate"; public bool TryParse(string text, out UgxStyleValue value) { var ok = UgxValueParser.TryPair(text, out var x, out var ux, out var y, out var uy); value = new UgxStyleValue(x, y, unitA: ux, unitB: uy); return ok; } public bool TryRead(VisualElement element, out UgxStyleValue value) { var t = element.resolvedStyle.translate; value = new UgxStyleValue(t.x, t.y); return true; } public void Apply(VisualElement element, in UgxStyleValue value) => element.style.translate = new Translate(value.LengthA, value.LengthB); } sealed class ScaleSetter : IUgxStyleSetter { public string Property => "scale"; public bool TryParse(string text, out UgxStyleValue value) { var s = text == null ? string.Empty : text.Trim(); int split = s.IndexOf(' '); if (split < 0) { var single = UgxValueParser.TryFloat(s, out var v); value = new UgxStyleValue(v, v); return single; } bool okX = UgxValueParser.TryFloat(s.Substring(0, split), out var x); bool okY = UgxValueParser.TryFloat(s.Substring(split + 1), out var y); value = new UgxStyleValue(x, y); return okX && okY; } public bool TryRead(VisualElement element, out UgxStyleValue value) { var s = element.resolvedStyle.scale.value; value = new UgxStyleValue(s.x, s.y); return true; } public void Apply(VisualElement element, in UgxStyleValue value) => element.style.scale = new Scale(new Vector2(value.A, value.B)); } sealed class RotateSetter : IUgxStyleSetter { public string Property => "rotate"; public bool TryParse(string text, out UgxStyleValue value) { var ok = UgxValueParser.TryAngle(text, out var v, out var unit); value = new UgxStyleValue(v, unitA: unit); return ok; } public bool TryRead(VisualElement element, out UgxStyleValue value) { var angle = element.resolvedStyle.rotate.angle; value = new UgxStyleValue(angle.value, unitA: (byte)angle.unit); return true; } public void Apply(VisualElement element, in UgxStyleValue value) => element.style.rotate = new Rotate(value.AngleA); } } }