Understanding drawers

The single most important part of understanding how Odin's drawing system works, is understanding the drawing chain. In Odin, any single property is not drawn by a single drawer; instead each property has many drawers assigned to it. When Odin is told to draw a property (using either InspectorProperty.Draw or InspectorUtilities.DrawProperty), it fetches the drawer chain for that property, and then invokes only the first drawer in the chain.

Note that there is a difference between what Odin calls a property - an inspector property, if you will - and C#'s concept of properties with getters and setters. Properties in Odin are more complex than that, and represent the general concept of a "thing" in the inspector - an int field, a button for a method, a list. To keep things simple, whenever the word 'property' is used in this section, it will mean 'Odin inspector property' rather than 'C# property', unless specifically stated otherwise. You can find more details on properties in the section The property system.

That drawer may then at some point decide to call the next drawer in the chain, and thus the drawing call propagates down the chain of drawers until either there are no more drawers left (prompting a somewhat deceptive message that there are no drawers defined for that type - worded like that for simplicity's sake), or a drawer decides not to call the next drawer in line.

To demonstrate, let's look at the drawer chain for the simplest of properties: an int field. We can easily inspect the drawer chain for any member by decorating the member with the [ShowDrawerChain] attribute, like so:

public class MyMonoBehaviour : MonoBehaviour
{
    [ShowDrawerChain]
    public int intField;
}

This will give a result approximately like this:

The [ShowDrawerChain] attribute is excellent for getting an overview over which drawers are being used to draw a given attribute, and for determining which order drawers are called in.

If you're following along, and don't get the exact same list of drawers as in the picture, that's okay. Odin changes sometimes, and drawers may appear in or disappear from the chain as patches and tweaks roll out - this image was taken in a working copy of 1.0.5.0. Your drawer chain should at the very least, however, look roughly like this. So, let's take a look at what's going on, here.

First off, we can see the ShowDrawerChainAttributeDrawer at the top - the first drawer in the chain. What this drawer does is very simple - it fetches the drawer chain for the property being drawn, renders some GUI for the drawer chain (which we're looking at right now), and then it calls the next drawer in line, which here is PropertyContextMenuDrawer<int>.

PropertyContextMenuDrawer<int> actually doesn't do any drawing at all - it merely calls the next drawer in line immediately, and then checks for right-click events in the property's area to determine whether to show a context menu.

Then comes the PrimitiveValueConflictDrawer<int>, which is in the chain of all types that can have "primitive" value conflicts (IE, differing numbers or strings, etc). It checks for a value conflict on the property, which can happen when there are many selected objects, and if there is a conflict, it does things like enabling EditorGUI.showMixedValue before calling the next drawer in line, so that you get the visual appearance of a value conflict.

And finally, of course, there is the Int32Drawer, which is very simple, and merely draws the int value itself.

Now let's try an interesting and instructive trick:

public class MyMonoBehaviour : MonoBehaviour
{
    [HideInInspector]
    [ShowDrawerChain]
    public int intField;
}

As you can see, we've added the [HideInInspector] attribute to the member, which is a Unity attribute that Odin supports, which prevents properties from showing up in the inspector. And sure enough, the Int Field property is no longer being shown - but the drawer chain is still being shown for it - what's going on, here?

The answer can (as is often the case) be seen in the drawer chain. Adding [HideInInspector] has introduced a new drawer into our chain, the HideInInspectorAttributeDrawer. This drawer is extremely simple. In fact, it's a drawer that does nothing at all - it doesn't even call the next drawer in the chain. This results in the property itself not being drawn, as nothing from PropertyContextMenuDrawer and down is even being called!

However, as we can see by the numbers to the right of each drawer in the chain, drawers are ordered by priority. HideInInspectorAttributeDrawer has a very high priority, but not as high as ShowDrawerChainAttributeDrawer, so the drawer chain will be rendered before [HideInInspector] cuts off the drawer chain.

Having the drawing system arranged like this is extremely convenient. It makes it trivial to insert custom wrapping logic or extra drawing anywhere in the process of drawing a property, or to simply take over the drawing entirely at any point one wishes.

So how do we define a drawer, and how do we specify where exactly it applies and where it belongs in the drawing chain? That's the subject of the next section, Drawer types.