Skip to content

Instantly share code, notes, and snippets.

@LeMartinParadis
Last active June 28, 2023 01:49
Show Gist options
  • Save LeMartinParadis/076be259f33acd944efb22876cb02c08 to your computer and use it in GitHub Desktop.
Save LeMartinParadis/076be259f33acd944efb22876cb02c08 to your computer and use it in GitHub Desktop.
Part 4: Type conversions.

This is the fourth part in the series: UI Toolkit runtime bindings system

This part will provide an overview of the type conversion that can be used in the UI Toolkit runtime bindings system.

Type Conversion

There are two types of conversion possible:

  • Global conversions
  • Per-binding conversions

Global Conversion

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));

Per Binding Conversion

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.

Using type converters through code

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.

Using type converters through UXML

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" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment