Wrappers for the Custom Properties

UI Themes uses reflection to read and write properties and fields of components, this causes memory allocation.
Memory allocation can be avoided by using wrappers to access component properties; for properties and fields of standard components, such wrappers are available for default components and memory allocation does not occur for them.
Memory allocation by UI Themes when toggle Theme variations without reflection wrappers for properties and fields is zero.
You can create your own wrappers for custom components.
You can check properties and fields which are accessed via reflection in Window / UI Themes / Reflection Wrappers.
In this window possible to generate wrappers for those properties and fields.
Recommended to toggle Theme variations before using because wrappers created on request.

Note

Wrappers created via reflection only for the direct public fields and properties.
For the nested properties like Selectable.colors.normalColor wrappers should be create manually.
../_images/theme-reflection.png

Sample Widget Code

using UIThemes;
using UIThemes.Wrappers;
using UnityEngine;
using UnityEngine.Scripting;
using UnityEngine.UI;

// this widget changes graphics color when the switch value is changed
public class CustomWidget : MonoBehaviour, ITargetOwner
{
        public Toggle Toggle;

        public Image Image;

        [SerializeField]
        Color colorOn = Color.white;

        public Color ColorOn
        {
                get => colorOn;
                set
                {
                        colorOn = value;
                        UpdateColor();
                }
        }

        [SerializeField]
        Color colorOff = Color.white;

        public Color ColorOff
        {
                get => colorOff;
                set
                {
                        colorOff = value;
                        UpdateColor();
                }
        }

        protected void Start()
        {
                SetTargetOwner();
                Toggle.onValueChanged.AddListener(UpdateColor);
                UpdateColor();
        }

        protected void OnDestroy() => Toggle.onValueChanged.RemoveListener(UpdateColor);

        public void SetTargetOwner() => UIThemes.Utilities.SetTargetOwner<Graphic>(typeof(Color), Image, nameof(Image.color), this);

        void UpdateColor() => UpdateColor(Toggle.isOn);

        void UpdateColor(bool isOn) => Image.color = isOn ? colorOn : colorOff;
}

Wrapper

class CustomEffectColorOn : Wrapper<Color, CustomWidget>
{
        // name used by ThemeTarget, it should be unique per type
        public CustomEffectColorOn() => Name = nameof(CustomWidget.ColorOn);

        protected override Color Get(CustomWidget widget) => widget.ColorOn;

        protected override void Set(CustomWidget widget, Color value) => widget.ColorOn = value;
}

class CustomEffectColorOff : Wrapper<Color, CustomWidget>
{
        public CustomEffectColorOff() => Name = nameof(CustomWidget.ColorOff);

        protected override Color Get(CustomWidget widget) => widget.ColorOff;

        protected override void Set(CustomWidget widget, Color value) => widget.ColorOff = value;
}

[PropertiesRegistry, Preserve]
public static void AddWrappers()
{
        PropertyWrappers<Color>.Add(new CustomEffectColorOn());
        PropertyWrappers<Color>.Add(new CustomEffectColorOff());
}

Additional Information

Wrappers should implements IWrapper<TValue> interface, which has two additional methods:

  • bool Active(Component component)

    Check is property active.
    If false then the property will not be available to the ThemeTarget list.
    Example: Selectable sprites properties should be available only if Selectable.transition is SpriteSwap.
  • bool ShouldAttachValue(Component component)

    If true then try to find or create value in options (only when using menu “Attach Theme”).
    If false then the ThemeTarget option will be None.
    Example: if Image component sprite is null then it should not be controlled by ThemeTarget by default.