Extending Theme

../_images/theme-extended.png

You can extend Theme to add custom types (not only Color, Sprite, Texture, Font, etc.).

Sample for type with sprite and its rotation:

  1. Create a type for the value.

    namespace UIThemes.Samples
    {
            using System;
            using UnityEngine;
            using UnityEngine.UI;
    
            [Serializable]
            public struct RotatedSprite : IEquatable<RotatedSprite>
            {
                    [SerializeField]
                    public Sprite Sprite;
    
                    [SerializeField]
                    public float RotationZ;
    
                    public RotatedSprite(Image image)
                    {
                            if (image == null)
                            {
                                    Sprite = null;
                                    RotationZ = 0f;
                            }
                            else
                            {
                                    Sprite = image.sprite;
                                    RotationZ = image.transform.localRotation.eulerAngles.z;
                            }
                    }
    
                    public bool Equals(RotatedSprite other)
                    {
                            if (!Mathf.Approximately(RotationZ, other.RotationZ))
                            {
                                    return false;
                            }
    
                            return UnityObjectComparer<Sprite>.Instance.Equals(Sprite, other.Sprite);
                    }
    
                    public bool Set(Image image)
                    {
                            if (image == null)
                            {
                                    return false;
                            }
    
                            var rotation = image.transform.localRotation.eulerAngles;
                            if (UnityObjectComparer<Sprite>.Instance.Equals(image.sprite, Sprite) && Mathf.Approximately(rotation.z, RotationZ))
                            {
                                    return false;
                            }
    
                            image.sprite = Sprite;
                            rotation.z = RotationZ;
                            image.transform.localRotation = Quaternion.Euler(rotation);
    
                            return true;
                    }
            }
    }
    
  2. Create a class to create a VisualElement editor for this value.

    namespace UIThemes.Samples
    {
            using UnityEngine;
            using UnityEngine.UIElements;
    
            public class RotatedSpriteView : FieldView<RotatedSprite>
            {
                    public RotatedSpriteView(string undoName, Theme.ValuesWrapper<RotatedSprite> values)
                            : base(undoName, values)
                    {
                    }
    
                    protected override VisualElement CreateView(VariationId variationId, OptionId optionId, RotatedSprite value)
                    {
                            #if UNITY_EDITOR
                            var block = new VisualElement();
                            block.style.flexDirection = FlexDirection.Column;
    
                            var input = new UnityEditor.UIElements.ObjectField();
                            input.value = value.Sprite;
                            input.objectType = typeof(Sprite);
                            input.RegisterValueChangedCallback(x =>
                            {
                                    value.Sprite = x.newValue as Sprite;
                                    Save(variationId, optionId, value);
                            });
                            block.Add(input);
    
                            var rotation = new UnityEngine.UIElements.FloatField("Rotation.Z");
                            rotation.value = value.RotationZ;
                            rotation.RegisterValueChangedCallback(x =>
                            {
                                    value.RotationZ = x.newValue;
                                    Save(variationId, optionId, value);
                            });
                            block.Add(rotation);
    
                            return block;
                            #else
                            return null;
                            #endif
                    }
    
                    public override void UpdateValue(VisualElement view, RotatedSprite value)
                    {
                            #if UNITY_EDITOR
                            var block = new VisualElement();
                            block.style.flexDirection = FlexDirection.Column;
    
                            var input = view.ElementAt(0) as UnityEditor.UIElements.ObjectField;
                            if (input != null)
                            {
                                    input.value = value.Sprite;
                                    input.objectType = typeof(Sprite);
                            }
    
                            var rotation = view.ElementAt(1) as UnityEngine.UIElements.FloatField;
                            if (rotation != null)
                            {
                                    rotation.value = value.RotationZ;
                            }
                            #endif
                    }
            }
    }
    
  3. Create wrapper for the property

    namespace UIThemes.Samples
    {
            using System;
            using System.Collections.Generic;
            using UIThemes.Wrappers;
            using UnityEngine;
            using UnityEngine.UI;
    
            public class RotatedSpriteWrapper : IWrapper<RotatedSprite>
            {
                    public Type Type => typeof(Image);
    
                    public string Name => "Sprite + Rotation";
    
                    public RotatedSprite Get(Component component) => new RotatedSprite(component as Image);
    
                    public bool Set(Component component, RotatedSprite value, IEqualityComparer<RotatedSprite> comparer) => value.Set(component as Image);
    
                    public bool Active(Component component) => true;
    
                    public bool ShouldAttachValue(Component component) => (component as Image).sprite != null;
            }
    }
    
  4. Create derived Theme

    namespace UIThemes.Samples
    {
            using System;
            using UnityEngine;
            using UnityEngine.Scripting;
    
            [Serializable]
            [CreateAssetMenu(fileName = "UI Theme Extended", menuName = "UI Themes/Create Theme Extended")]
            public class ThemeExtended : Theme
            {
                    [SerializeField]
                    protected ValuesTable<RotatedSprite> RotatedSpritesTable = new ValuesTable<RotatedSprite>();
    
                    [UIThemes.PropertyGroup(typeof(RotatedSpriteView), "UI Themes: Change Rotated Sprite")]
                    public ValuesWrapper<RotatedSprite> RotatedSprites => new ValuesWrapper<RotatedSprite>(this, RotatedSpritesTable);
    
                    public override Type GetTargetType() => typeof(ThemeTargetExtended);
    
                    public override void Copy(Variation source, Variation destination)
                    {
                            base.Copy(source, destination);
                            RotatedSpritesTable.Copy(source.Id, destination.Id);
                    }
    
                    protected override void DeleteVariationValues(VariationId id)
                    {
                            base.DeleteVariationValues(id);
                            RotatedSpritesTable.DeleteVariation(id);
                    }
    
                    #if UITHEMES_ADDRESSABLE_SUPPORT
                    protected override void PreloadAddressableTasks(VariationId? variationId, List<Task> output)
                    {
                            base.PreloadAddressableTasks(variationId, output);
                            output.Add(RotatedSpritesTable.PreloadAddressable(variationId));
                    }
    
                    protected override void EnsureAddressableValues(Func<UnityEngine.Object, AddressableAsset> object2address, bool resetValues)
                    {
                            base.EnsureAddressableValues(object2address, resetValues);
                            RotatedSpritesTable.EnsureAddressable(object2address, resetValues);
                    }
                    #endif
    
                    [PropertiesRegistry, Preserve]
                    public static void AddProperties()
                    {
                            PropertyWrappers<RotatedSprite>.Add(new RotatedSpriteWrapper());
                    }
    
                    static List<string> disabledProperties = new List<string>()
                    {
                            nameof(Sprites),
                    };
    
                    public override bool IsActiveProperty(string name) => !disabledProperties.Contains(name);
            }
    }
    
  5. Create derived ThemeTarget

    namespace UIThemes.Samples
    {
            using System;
            using System.Collections.Generic;
            using UnityEngine;
    
            public class ThemeTargetExtended : ThemeTargetCustom<ThemeExtended>
            {
                    [SerializeField]
                    [ThemeProperty(nameof(ThemeExtended.RotatedSprites))]
                    protected List<Target> rotatedSprites = new List<Target>();
    
                    public IReadOnlyList<Target> RotatedSprites => rotatedSprites;
    
                    public override void SetPropertyOwner<TComponent>(Type propertyType, TComponent component, string property, Component owner)
                    {
                            if (propertyType == typeof(RotatedSprite))
                            {
                                    SetPropertyOwner(RotatedSprites, component, property, owner);
                            }
                            else
                            {
                                    base.SetPropertyOwner(propertyType, component, property, owner);
                            }
                    }
    
                    protected override void ThemeChanged(VariationId variationId)
                    {
                            base.ThemeChanged(variationId);
    
                            SetValue(variationId, Theme.RotatedSprites, rotatedSprites);
                    }
    
                    #if UNITY_EDITOR
                    protected override void FindTargets(List<Component> components, ExclusionList exclusion)
                    {
                            base.FindTargets(components, exclusion);
    
                            if (!IsDisabledProperty(nameof(Theme.RotatedSprites)))
                            {
                                    FindTargets<RotatedSprite>(components, rotatedSprites, exclusion);
                            }
                    }
                    #endif
            }
    }