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.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.Iffalse
then the property will not be available to the ThemeTarget list.Example:Selectable
sprites properties should be available only ifSelectable.transition
isSpriteSwap
.bool ShouldAttachValue(Component component)
Iftrue
then try to find or create value in options (only when using menu “Attach Theme”).Iffalse
then the ThemeTarget option will be None.Example: if Image component sprite is null then it should not be controlled by ThemeTarget by default.