Create Custom Inspector and Icon for VisualElement in UIBuilder

I noticed that the new EnumField has a custom inspector in UIBuilder.

  • The “Type” attribute in UIBuilder defines the available values for the “Value” dropdow
  • Both, “Type” and “Value” show a selection of available values that is filtered by the currently entered text

This is quite nice!
How can we write a such a custom inspector for our custom VisualElements?
I did not find anything in this direction in the UIBuilder documentation.

Also: How can we define the icons in UIBuilder for our custom VisualElements?

At the moment, all my custom VisualElement use the # icon because they come from C# scripts.
In contrast, the default VisualElements have dedicated icons for Label, Button, ScrollView, etc. I want this.
Is it possible to change the icon of custom VisualElements in UIBuilder?

It’s not currently possible to create a custom editor in the UI Builder, the code is all internal.
We do have some changes coming during the 2023.x cycle that will introduce support for custom property drawers that will work in the same way as the inspector. Keep an eye out for the UxmlElementsAttribute feature.

I don’t belive we support this at the moment. Ill speak to the team and see if it’s possible or if we could do something in the future.

Hi @karl_jones is custom editor/property drawer for UI Builder available now?

Yes if you are using UxmlElement then you can create custom property drawers, we don’t support custom editors.
There’s some examples here https://docs.unity3d.com/6000.0/Documentation/ScriptReference/UIElements.UxmlAttributeAttribute.html

@karl_jones Thanks. It helped me. I couldn’t solve a problem. I need to access the parent class value in that property drawer. In your link, it is MyDrawerExample class. I have a method in the parent class. I will call this method with reflection in the property drawer. When I do the same thing for unity inspector, serialized property is for the real data. I think in this case UIBuilder uses it’s own serialization system and uses data as generated for partial class.

I get “TargetException: Object does not match target type.” error when I call method.Invoke(parentValue, null).

In MyDrawerAttributePropertyDrawer, how can I get MyDrawerExample class value?

You can’t get access to the element, the serialized data is independent from the element, it doesn’t belong to any specific element so there’s no way to get it’s instance.
The only things that exists in the serialized data are the attribute fields. What is it your method does?

I have string field. I will show a dropdown for this field. My method returns a list for dropdown. My property attribute has method name. my property drawer will take this method name, access parent class and call this method to take dropdown data.

It will be like this;
https://odininspector.com/attributes/value-dropdown-attribute

So your method will return the list of items for the dropdown? Are these values dynamic or could they be added Into an attribute instead, like the Odin example? Attributes are copied across to the serialized version so you will still have access to that data.

@karl_jones Values are dynamic. So I have to access attribute container class. I did this for Unity inspector scripts. I think UIBuilder is not developer-friendly for now. It should expose more API functions to the developers. It is not possible to make a custom inspector for a custom control.

Can you just throw all the data you care about into a serializable C# class and serialise that into your visual element, and write a property drawer for said object.

You could even use Odin to make a PropertyTree and get your usual gucci features.

And I don’t think the issue here is UI Toolkit, the issue is that the SerializedObject/Property system is no longer fit for purpose and is overdue for a replacement that can handle more arbitrary data like Odin can.

I think the problem is;
Serialized property for unity inspector is related to the component class. So you can access the component hierarchy using the property path. Serialized property for UI Builder is related to the visual element created by UI builder. When you create a custom UI Toolkit control, you have to create it as partial, because UI Toolkit creates it’s own implementation for this control. I guess the serialization property is related to this implementation so I cannot access my component. I can just access single property data. Field info in property drawer does not match the boxed value in serialized property.

That’s not quite correct. A SerializedObject can be generated from any UnityEngine.Object type. SerializedProperty’s are for values serialised into an asset - any asset. PropertyDrawer’s are for serialized, non-Unity object types. You should not be writing property drawers for Unity object types; you should be writing UnityEditor.Editor’s.

The issue is that Unity’s inspector system is incredibly tied to serialisation. And a SerializedProperty both manages the structure (in an incredibly unintuitive way) and the serialization/data, when these should be distinct (such as how Odin’s InspectorProperty’s work). The fact that a serialized property’s value type is an enum is a huge red flag. But the system has been around since before Unity 5 so I’m looking at it with hindsight.

Right now we have no way to generate an inspector from an arbitrary data object like we can with Odin. We really need a replacement system that separates the visual layout from the serialisation/data, and it’s not out of the question that this could handle a UI Toolkit visual tree at the same time.

by saying “Serialized property for unity inspector is related to the component class”, I mean that;

For property drawer
On unity inspector;
Serialized property uses your component (Monobehaviour class). so you can access all fields and class using serialized property path. you can iterate to children and parent.

On UI Builder
Serialized property uses uxml structure. so you cannot access your custom visual element (class derived visualelement)

When you have a custom editor for your component (mono behaviour class), you can access target class. For now you cannot create a custom editor for UI builder, so you cannot access your custom controller (class derived visualelement)

I hope it is clear now. I hope I am wrong and there is a solution for my problem :slight_smile:

The element you see in the builder is a temporary one created from the UxmlSerializedData. So all the information you need should be available in the serialized data.
What does the contents of your method do, how does it determine the items that should be returned?

I developed some tools for unity. one of them is game event system. when a button clicked it will send a game event. i wrote a custom button control derived from UI Element button class. a method in my control will return game event category names. another method in my control will return game event names related to selected game event category. I can write a property drawer just for game event but I want a general solution. I also have an audio library tool like wwise. There will be sounds for button click, hover. So I need to access my custom control. I want abilities like I have for unity custom inspector.

Unfortunately, it can’t work that way, as I said you are editing the serialized data, not the element. The builder has to do a lot of extras for each field so a custom inspector/editor is not possible at the moment and it would still only be editing the serialized data.
We do support property drawers for UxmlObjects which can contain multiple fields and are more flexible than a single UxmlAttribute, maybe that would work better? You are still editing a serialized version of the UxmlObject though.
It is possible to get the serialized data, and then use that to create a temporary instance and apply the serialized data to it, you could then use it, however, if it depends on having access to the hierarchy then it won’t work.

For Audioclips you can just have them as attributes and drag the files into the builder as object references.

I think this might be a language thing but a serialized property is not related to MonoBehaviour at all. A SerializedProperty is just a structural and serialisation representation of a serialized value within a UnityEngine.Object. It’s just one part of a SerializedObject, which, again, you can create out of any UnityEngine.Object.

So functionally there is no difference between what is going on in a regular component inspector compared to the UI Builder. They are both using the exact same mechanism. The only difference on the latter is that the UI Builder is serializing an object that has been built by source generators (in newer versions at least). Otherwise they’re doing the same thing.

And again it’s all just data. And half the point of UI Toolkit is to separate your data from your visual representation, whereas with MonoBehaviours we’re generally mixing up both model and view. This is a pretty common thing that trips up folks using UI Toolkit because it does go against what’s natural with the usual component workflow.

So the solution here is to probably start to define some distinction between your data and visuals.

This is my problem :slight_smile:

For UI Builder serializationProperty path is like “m_VisualElementAssets.Array.data[5].m_SerializedData.GameEventName”. So I cannot access container class (UIButton). In my
UIListSourceDrawer I can get GameEventName value. I need UIButton value to call GetEventItemList with reflection.I can do this when using serializedproperty for unity custom editor/inspector/propertydrawer.

I think I cannot explain it with my bad English. If there is a way to call GetEventItemList method in UIListSourceDrawer it will solve my problem.

 [UxmlElement]
public partial class UIButton : Button
{
     [UIListSource("GetEventItemList"), UxmlAttribute]
     public string GameEventName;

     private List<ListSourceItem> GetEventItemList()
     {
           ........
     }
}

You would use a uxml object like Karl suggested and write a property drawer for said object instead. Then everything is together in the one place.

Basically just your usual encapsulation process.