Non generic interfaces

Hi, I recently created a small project that utilized UI Toolkit in runtime and I wanted to share some thoughts.

I needed to create an app with UI that the user in the runtime could modify.

  1. UI generation
    To solve the UI generation part I thought I could deserialize UXML to VisualElement.
    But as I found out there is no public API for that.
    To In the end I wrote a simple XML ↔ VisualElement system.

  2. Handling Input Fields
    App had to register special callbacks for each input field created by the user. This callback didn’t care for the type of the actual value being modified it just had to transform it into a string and save it.

I found myself with the following problems that I would like to give a spotlight to:

Non-generic BaseField<T>
BasseField<T> has properties like labelElement, label, showMixedValue, that have nothing to do with generic type and are impossible to access without it.

private void CreateUI(string[] ids, VisualElement root) 
{ 
    foreach (var id in ids) 
    { 
        BaseField field = CreateField(id); 
        field.label = "I wish I could do this";
        root.Add(field); 
    } 
} 
// Type `BaseField` does not exists
private BaseField CreateField<T>(string id) 
{ 
    return id switch 
    { 
        "0" => new Toggle(), 
        "1" => new IntegerField(), 
        "2" => new Slider(), 
    }; 
}

Non-generic ChangeEvent
It would also make a life easier if I could register callback for any ChangeEvent<T>.
Something like INotifyValueChanged<object> .

private void CreateUI(string[] ids, VisualElement root) 
{ 
    foreach (var id in ids) 
    { 
        BaseField field = CreateField(id); 
        field.label = "I wish I could do this"; 
        field.RegisterValueChangedCallback(evt => OnValueChange(evt.newValue)); 
        root.Add(field); 
    } 
} 

private void OnValueChange(object value) 
{ 
    Debug.Log($"I really wish I could log like that: {value}"); 
}

In the end I had to code something like this:

private static void TryRegisterValueChangedCallback(VisualElement e, 
    EventCallback<ChangeEvent<object>> callback) 
{ 
    switch (e) 
    { 
        case INotifyValueChanged<bool> n: 
            n.RegisterValueChangedCallback(ev => 
                callback?.Invoke(ChangeEvent<object>.GetPooled(ev.previousValue, ev.newValue))); 
            break;        case INotifyValueChanged<int> n: 
            n.RegisterValueChangedCallback(ev => 
                callback?.Invoke(ChangeEvent<object>.GetPooled(ev.previousValue, ev.newValue))); 
            break;        case INotifyValueChanged<float> n: 
            n.RegisterValueChangedCallback(ev => 
                callback?.Invoke(ChangeEvent<object>.GetPooled(ev.previousValue, ev.newValue))); 
            break;        case INotifyValueChanged<string> n: 
            n.RegisterValueChangedCallback(ev => 
                callback?.Invoke(ChangeEvent<object>.GetPooled(ev.previousValue, ev.newValue))); 
            break;        default: 
            return; 
    } 
}

UXML string to VisualElement in runtime
Is there a chance for UXML deserialization in runtime?

Having a non generic BaseField would mean that it has no value or not a strongly typed one. In general this is not desirable for performance reason.

This is similar to a non generic BaseField. Having a non generic ChangeEvent that is an object will lead to boxing and unnecessary GC allocs which is something we want to avoid.

UXML is parsed at import time in the editor. There is no plan at the moment to parse UXML at runtime.

What about having a base class with properties like label, labelElement, showMixedValue. Those fields do not depend on the generic type and are shared across the “field” classes.

That is an option but I’m wondering what problem that would help solve?

Imagine that you want to modify all labels of all of your input fields in the visual element.

Ideally, you could do something like this:

foreach(BaseField field in fields)
{
    field.label = field.name;
}

currently, there is no elegant way to do that. There’s no way to even build a UQuery to get all field visual elements.