Extending Theme
You can extend Theme
to add custom types (not only Color
, Sprite
, Texture
, Font
, etc.).
Sample for type with sprite and its rotation:
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; } } }
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 } } }
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; } }
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); } }
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 } }