How can I use `UnityEngine.UIElements.PopupWindow` for a custom PropertyDrawer targeting an array with UI Toolkit?
I'm following the "Create a Custom Inspector" car and tire example that Unity has in their documentation for UI Toolkit, but I'm trying to modify it in a specific way and I'm coming up short (whether or not it is a "good" way is beyond the scope of the question). Essentially, I'd like to take the editor display for an array and make it accessible by a single button that opens a PopupWindow in the editor, displaying the information for all four tires. I'd also like to make sure that the array is not reorderable, and if possible, I'd like to have it not act like a foldout in the PopupWindow.
I've managed to get this so far:
In Car.cs
, I have the following:
using UnityEngine;
using UnityEngine.UIElements;
using UnityEditor;
using UnityEditor.UIElements;
public class Car : MonoBehaviour
{
public string myMake = "Ford";
public int myYearBuilt = 2001;
public Color myColor = new Color(0.5725f, 0.525f, 0.3875f, 1.0f);
public TireSpecsArrayWrapper myTires = new TireSpecsArrayWrapper()
{
Tires = new TireSpecs[4]
};
public ActualTireDetails tireOptions;
}
In TireSpecs.cs
, I have the following:
using System;
using UnityEngine;
[Serializable]
public class TireSpecs
{
public float myAirPressure = 35.0f;
public int myProfileDepth = 4;
public TireSpecs(float airPressure, int profileDepth)
{
myAirPressure = airPressure;
myProfileDepth = profileDepth;
}
}
In TireSpecsArrayWrapper.cs
, I have the following:
using System;
using UnityEngine;
[Serializable]
public class TireSpecsArrayWrapper
{
public TireSpecs[] Tires;
}
And my PropertyDrawer, TirePropertyDrawer.cs
, is structured like this (has been edited to reflect changes below):
using UnityEngine;
using UnityEngine.UIElements;
using UnityEditor;
using UnityEditor.UIElements;
using System.Collections.Generic;
[CustomPropertyDrawer(typeof(TireSpecsArrayWrapper))]
public class TirePropertyDrawer : PropertyDrawer
{
public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
var container = new VisualElement();
SerializedProperty tireProperties = property.FindPropertyRelative("Tires");
var button = new Button(() => ShowPopup(container, tireProperties)) { text = "View Tire Specifications" };
container.Add(button);
return container;
}
void ShowPopup(VisualElement anchor, SerializedProperty property)
{
UnityEngine.UIElements.PopupWindow popup = new UnityEngine.UIElements.PopupWindow();
popup.style.flexGrow = 200;
popup.style.height = new StyleLength(Length.Percent(100));
popup.style.flexDirection = FlexDirection.Column;
if (property != null && property.isArray)
{
Debug.Log($"Array Size: {property.arraySize}");
for (int i = 0; i < property.arraySize; i++)
{
Debug.Log($"Array index = {i}");
SerializedProperty arrayElement = property.GetArrayElementAtIndex(i);
VisualElement arrayElementContainer = new VisualElement();
arrayElementContainer.style.flexDirection = FlexDirection.Column;
arrayElementContainer.Add(new Label($"Tire #{i + 1}: "));
CreatePropertyField(arrayElementContainer, arrayElement, "airPressure", "Air Pressure (psi):");
CreatePropertyField(arrayElementContainer, arrayElement, "profileDepth", "Profile Depth (mm):");
arrayElementContainer.style.paddingTop = 8;
arrayElementContainer.style.paddingRight = 8;
arrayElementContainer.style.paddingBottom = 8;
arrayElementContainer.style.paddingLeft = 8;
popup.Add(arrayElementContainer);
}
}
var button = new Button(() => anchor.Remove(popup)) { text = "Close Popup" };
popup.Add(button);
anchor.Add(popup);
}
private void CreatePropertyField(VisualElement elementContainer, SerializedProperty parentProperty, string propertyName, string label)
{
SerializedProperty property = parentProperty.FindPropertyRelative(propertyName);
PropertyField field = new PropertyField(property, label);
field.BindProperty(property);
field.style.flexGrow = 1;
field.style.paddingTop = 2;
field.style.paddingRight = 2;
field.style.paddingBottom = 2;
field.style.paddingLeft = 2;
field.style.minHeight = 16;
field.style.flexDirection = FlexDirection.Column;
elementContainer.Add(field);
}
}
EDIT: After consulting with some folks, I've managed to get it to this stage:
EDIT 2: I've now managed to get close to what I'm looking for visually and functionally, but I'm not certain that what I have is behaving entirely as intended. Getting to this point involved creating a wrapper class for TireSpecs.cs
to allow the PropertyDrawer
to target the array directly. Scripts have been updated to reflect changes; the styling especially could use some work, but outside of that, is there a way to improve my script(s)?