When we can create drawer for entire Collection without hack?
Hello @JesOb . I’ve visited the code where PropertyAttributes are handled. From a quick look, it does seem to me that this feature could exist though I didn’t have time yet to assess all the risks that would come out of that solution.
Moreover, I’ve seen a number of cases where a collection drawer could make list solutions much more elegant. So my suggestion would be to send us a feature request on our roadmap page. Let me know once you do and I’ll go and link my today’s findings to it.
Currently it is able to create collection drawers by hacking unit internals a bit
There are many solution for this in a wild but it will be very handy to have supported not hacky solution for this
Out use case where we use it mostly is table drawer for array of structures
Adding attribute to any serialisable array or list field add table button in inspector:
This is toggle to table mode for drawing array and after pressing it we go into table mode:
So we use it in totally not destructive way in our case.
As Unity 2022 go into land of UITK for inspector I think it is time to consider allow Collection Drawer again
@TomasKucinskas thanks for interest I have submitted idea on roadmap with link to this thread
Perfect! Thank you!
That table mode is really neat! You mentioned there are many ways of hacking collection drawers in, would you mind sharing how you do it?
I’m aware of one way that is used by NaughtyAttributes where this asset completely overrides the default component drawer and that gives it the ability to implement its own collection drawer enabled via attribute amongst many other things.
We use similar solution to NaughtyAttributes currently and want to go out of it.
There are solution based on decorator drawer that actually will draw on top of entire list and hack to replace list drawer from there.
But this is most hackish solution
Actually with new UITK inspector it is very easy to implement ArrayDrawer:
public class ArrayDrawerTestInfo : ScriptableObject
{
[TestArray]
[SerializeField] TestStruct[] _data;
[SerializeField] String _str;
[SerializeField] Int32 _str234;
[Serializable]
public struct TestStruct
{
public String Name;
public Int32 Filed1;
public Single Filed2;
}
}
public class TestArrayAttribute: PropertyAttribute { }
[CustomPropertyDrawer(typeof(TestArrayAttribute))]
public class TestArrayDrawer : ArrayDrawer
{
protected override Boolean HideDefaultView => false;
protected override void OnArrayGUI(SerializedProperty arrayProperty)
{
GUILayout.Label( "Hello From Custom Array Drawer" );
}
}
public class ArrayDrawer : PropertyDrawer
{
private SerializedProperty _arrayProperty;
protected virtual Boolean HideDefaultView => true;
public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
// this is non destructive way if intention to fully replace old drawer return new empty VisualElement
var pf = new PropertyField( property );
if (property.propertyPath[^3] == '[' && property.propertyPath[^2] == '0')
{
var path = property.propertyPath[..^14];
_arrayProperty = property.serializedObject.FindProperty( path );
pf.RegisterCallback<AttachToPanelEvent>(AttachToPanel);
}
return pf;
}
protected virtual void OnArrayGUI( SerializedProperty arrayProperty ) { }
private void AttachToPanel(AttachToPanelEvent evt)
{
var arrayPropertyField = ((VisualElement)evt.target).hierarchy.parent.hierarchy.parent;
for( ; arrayPropertyField != null && arrayPropertyField is not PropertyField; arrayPropertyField = arrayPropertyField.hierarchy.parent );
var contentParent = arrayPropertyField.Q<Foldout>( "unity-list-view__foldout-header" );
var content = contentParent.Q("unity-content");
if( HideDefaultView )
content.hierarchy[0].style.display = DisplayStyle.None;
content.hierarchy[1].style.display = DisplayStyle.None;
// there we can continue to use UITK but for simplicity of sample use IMGUI
content.hierarchy.Add( new IMGUIContainer( DrawArray ) );
}
private void DrawArray( ) => OnArrayGUI( _arrayProperty.Copy( ) );
}
The only issue is empty array that will show default array drawer
And more clear way though decorator drawer
public class ArrayDrawerTestInfo : ScriptableObject
{
[HelloArray, XItems]
[SerializeField] TestStruct[] _data;
[SerializeField] String _str;
[SerializeField] Int32 _str234;
[Serializable]
public struct TestStruct
{
public String Name;
public Int32 Filed1;
public Single Filed2;
}
}
public class XItemsAttribute: PropertyAttribute { }
public class HelloArrayAttribute: PropertyAttribute { }
[CustomPropertyDrawer(typeof(HelloArrayAttribute))]
public class TestArrayDrawer : ArrayDrawer
{
protected override Boolean HideDefaultView => true;
protected override void OnArrayGUI(SerializedProperty arrayProperty)
{
GUILayout.Label( "Hello From Custom Array Drawer" );
}
}
[CustomPropertyDrawer(typeof(XItemsAttribute))]
public class ArrayDrawerX : PropertyDrawer
{
public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
return new VisualElement(){ style = { height = 0 }};
}
}
public class ArrayDrawer : DecoratorDrawer
{
private SerializedProperty _arrayProp;
protected virtual Boolean HideDefaultView => true;
public override VisualElement CreatePropertyGUI( )
{
var vi = new VisualElement { style = { height = 0 } };
vi.RegisterCallback<GeometryChangedEvent>(GeometryChcnaged);
return vi;
}
protected virtual void OnArrayGUI( SerializedProperty arrayProperty ) { }
private void GeometryChcnaged(GeometryChangedEvent evt)
{
var e = (VisualElement)evt.currentTarget;
e.UnregisterCallback<GeometryChangedEvent>( GeometryChcnaged );
var arrayPropertyField = e;
for( ; arrayPropertyField != null && arrayPropertyField is not PropertyField; arrayPropertyField = arrayPropertyField.hierarchy.parent );
_arrayProp = (SerializedProperty)arrayPropertyField.Q<ListView>( ).userData;
var contentParent = arrayPropertyField.Q<Foldout>( "unity-list-view__foldout-header" );
var content = contentParent.Q("unity-content");
if( content.hierarchy.childCount > 2 )
return;
if( HideDefaultView )
content.hierarchy[0].style.display = DisplayStyle.None;
content.hierarchy[1].style.display = DisplayStyle.None;
// there we can continue to use UITK but for simplicity of sample use IMGUI
content.hierarchy.Add( new IMGUIContainer( DrawArray ) );
}
private void DrawArray( ) => OnArrayGUI( _arrayProp.Copy( ) );
}
Definitely better than having to override the entire component drawer but still very hacky. Glad to see UIToolkit is making things a bit easier.