Collection drawer not collection item

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.

1 Like

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 :slight_smile:

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 :slight_smile:

1 Like

@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 :slight_smile:

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 :slight_smile:

    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.