This is the fourth part in the series: UI Toolkit runtime bindings system
- Part 1: Introduction to the runtime binding system.
- Part 2: Overview of the available binding types.
- Part 3: Instrumenting your types and custom elements for runtime bindings.
- Part 4: Type conversions. (This post)
- Part 5: Tips & performance considerations.
- Part 6: Current limitations & what's next.
This part will provide an overview of the type conversion that can be used in the UI Toolkit runtime bindings system.
There are two types of conversion possible:
- Global conversions
- Per-binding conversions
Global conversions are type converters that are statically registered and are always available to the binding system. These should only be used when you want them to apply to all binding instances, in all binding modes.
The binding system comes with many global converters already registered. This was necessary for convenience. For example, we added converters between the style values and their underlying data representation, so that you could bind a normal float
or a StyleKeyword
to a StyleFloat
property instead of having to use StyleFloat
in your data source.
Here is an example to register a global converter:
public struct TextureHandle
{
public static Texture2D ResolveTexture(TextureHandle handle)
{
return /* Actual texture */;
}
public static FromTexture(Texture2D texture)
{
return new TextureHandle { handle = /* Compute handle */ };
}
public int handle;
}
// Somewhere else
ConverterGroups.RegisterGlobalConverter((ref TextureHandle handle) => TextureHandle.ResolveTexture(handle));
ConverterGroups.RegisterGlobalConverter((ref Texture2D texture) => TextureHandle.FromTexture(texture));
Under the hood, per-binding conversions are exactly the same thing as global conversions, but they only apply to specific binding instances. We have created an API that allows you to register individual converters to a DataBinding
object and an API that allows you to register multiple converters at once to a DataBinding
object.
You can register individual converters to a DataBinding
object like this:
// Converting between radians and degrees for a binding instance
var binding = new DataBinding();
binding.sourceToUiConverters.AddConverter((ref float radian) => Mathf.RadToDeg * radian);
binding.uiToSourceConverters.AddConverter((ref float degree) => Mathf.DegToRad * degree);
You can register a group of converters like this:
var group = new ConverterGroup("Inverters");
group.AddConverter((ref int v) => return -v);
group.AddConverter((ref float v) => return -v);
group.AddConverter((ref double v) => return -v);
ConverterGroups.RegisterConverterGroup(group);
You can add to an existing converter group like this:
if (ConverterGroups.TryGetConverterGroup("Inverters", out var group))
{
group.AddConverter((ref short v) => -v);
}
And finally, you can apply a converter group to a DataBinding
instance like this:
var binding = new DataBinding();
if (ConverterGroups.TryGetConverterGroup("Inverters", out var group))
{
binding.ApplyConverterGroupToUI(group);
binding.ApplyConverterGroupToSource(group);
}
Applying a converter group onto another one will copy all the registered converter groups contained in the source group to the destination gone, potentially overriding existing converters. Note that applying a converter group onto another one is a fire-and-forget operation.
Since type converters cannot be defined in UXML directly, you must use a converter group to register them, like this:
<ui:DataBinding source-to-ui-converters="Inverters" ui-to-source-converters="Inverters" />