How to align the fields in a custom property drawer with the built in ones?

407 Views Asked by At

I have a general purpose Couple class that serializes two generic properties like a tuple.
In the inspector a custom property drawer packs them in a single line.
I implemented it using IMGUI, where the label looks like the built in labels and the input fields are aligned with the input fields of the built in properties.

Now I want to migrate to UI Toolkit and got it working in principle, but I can't get the fields to always align with the built in ones.

Unity Editor Alignment Problem


My progress so far:

I found out here that the unity-base-field__aligned uss class is responsible to stretch the width of the label to create the alignment.
I tried to replicate the element tree structure and applied classes, but I guess the unity-base-field__aligned class only works for subclasses of BaseField<T>.
Moreover, this seems to compute a width value in some script and apply it inline, but I would prefer a solution using uss styles and classes.

The unity-base-field__label class sets a min-width to the label, which produces the desired result if the Inspector panel is small enough.

Here's my code:

using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;

[CustomPropertyDrawer(typeof(Couple<,>))]
public class CoupleEditor : PropertyDrawer {

    public override VisualElement CreatePropertyGUI(SerializedProperty property) {
        // Create property container element
        var container = new VisualElement();
        container.style.flexDirection = FlexDirection.Row;

        // Create label
        var label = new Label(property.displayName);
        label.AddToClassList("unity-base-field__label"); // sets min-width to 120px

        // Create input container
        var input = new VisualElement();
        input.style.flexDirection = FlexDirection.Row;
        input.style.flexGrow = 1f;

        // Create property fields
        var item1 = new PropertyField(property.FindPropertyRelative(nameof(Couple<int, int>.Item1)), "");
        var item2 = new PropertyField(property.FindPropertyRelative(nameof(Couple<int, int>.Item2)), "");
        item1.style.flexGrow = 1f;
        item2.style.flexGrow = 1f;

        // Add fields to the container
        container.Add(label);
        input.Add(item1);
        input.Add(item2);
        container.Add(input);

        return container;
    }

    // For reference: This is the working IMGUI version
    public override void OnGUI(Rect rect, SerializedProperty property, GUIContent label) {
        // Using BeginProperty / EndProperty on the parent property means that
        // prefab override logic works on the entire property.
        _ = EditorGUI.BeginProperty(rect, label, property);

        // Draw label
        rect = EditorGUI.PrefixLabel(rect, GUIUtility.GetControlID(FocusType.Passive), label);

        // Disable indentation for inline fields
        var indent = EditorGUI.indentLevel;
        EditorGUI.indentLevel = 0;

        // Calculate rects
        var fieldGap = 8f;
        var fieldWidth = (rect.width - fieldGap) / 2f;
        var rect1 = new Rect(rect.x, rect.y, fieldWidth, rect.height);
        var rect2 = new Rect(rect.x + fieldWidth + fieldGap, rect.y, fieldWidth, rect.height);

        // Draw fields - pass GUIContent.none to each so they are drawn without labels
        _ = EditorGUI.PropertyField(rect1, property.FindPropertyRelative(nameof(Couple<int, int>.Item1)), GUIContent.none);
        _ = EditorGUI.PropertyField(rect2, property.FindPropertyRelative(nameof(Couple<int, int>.Item2)), GUIContent.none);

        // Restore indent
        EditorGUI.indentLevel = indent;

        EditorGUI.EndProperty();
    }
}

1

There are 1 best solutions below

0
Lieke On

I had the same problem a while back so I reversed engineered the USS that they use by just copy and pasting all the class USS and finetuning it. I'm not 100% sure if this works all the time since I haven't used it in production. But it might get you where you need to go.

I made an example that can be found here: https://github.com/LiekeVanmulken/Unity-UIToolkit-Base-Field-Example/

The USS used to style a base field:

.custom-unity-field-container {
    /* class: .unity-base-field     */
    flex-direction: row;
    flex-shrink:0;
    
    overflow: hidden;
    
    margin-top: 1px;
    margin-bottom: 1px;
    margin-left: 3px;
    margin-right: 3px;
    
    /* class: .unity-base-text-field  */
    white-space: nowrap;
    /*--unity-selection-color:rgba(0.239, 0.502, 0.875, 0.651);*/
    /*--unity-cursor-color: rgba(0.706, 0.706, 0.706, 1);*/
    
    /*      .unity-base-field__inspector-field*/
    margin-right: -2px;
    
    /*    Custom */
    margin-top:0px;
    margin-bottom: 0px;
}

.custom-unity-field-label{
    /* class: .unity-base-field__label    */
    min-width: 150px;
    padding-left: 2px;
    padding-top: 2px;
    padding-bottom: 0px;
    
    margin-top:0px;
    margin-bottom: 0px;
    margin-right: 2px;
    /*color: rgba(0.769, 0.769, 0.769, 1);*/
    
    /* class: .unity-base-field__label--with-dragger*/
    /*cursor: slide-arrow;*/
    
    /* class: .unity-label*/
    flex: 0 0 auto;
    margin-top:0px;
    padding-left: 1px;
    padding-right: 2px;
    white-space: nowrap;
    
    /*    Custom*/
    -unity-overflow-clip-box: content-box;
    margin-right: 0px;
    padding-right: 0px;
}

.custom-unity-field-value{
    /* class: .unity-base-field__input  */
    flex: 1 0 0;
    overflow: hidden;
    margin-top: 0;
    margin-right: 0;
    margin-bottom: 0;
    
    /* class: .unity-base-text-field__input*/
    padding-left: 2px;
    padding-right: 2px;
    padding-top: 1px;
    padding-bottom: 0px;
    /*cursor: text;*/
    -unity-overflow-clip-box: content-box;
    flex: 1 1 auto;
    /*background-color: rgba(0.165,0.165,0.165,1);*/
    border-width: 1px;
    /*border-left-color: rgba(0.129,0.129,0.129,1);*/
    /*border-top-color: rgba(0.051, 0.051, 0.051, 1);*/
    /*border-right-color: rgba(0.129, 0.129, 0.129, 1);*/
    border-radius: 3px;
    /*color: rgba(0.824, 0.824,0.824,1);*/
    /*-unity-sync-text-editor: true;*/

    /* class: .unity-base-text-field__input--single-line */
    -unity-text-align: middle-left;
    
    /* custom code   */    
    margin-left: 0px;
    padding-left: 1px;
    padding-right: 0px;
    padding-top: 0px;
    padding-bottom: 0px;
}

I commented what wasn't necessary but was in the classes. Just to keep track of what was in there. This contains mainly cursors and colors.