Search Unity

Resolved Collection drawer not collection item

Discussion in 'Authoring Dev Blitz Day 2023' started by JesOb, Jan 26, 2023.

  1. JesOb

    JesOb

    Joined:
    Sep 3, 2012
    Posts:
    1,109
    When we can create drawer for entire Collection without hack?
     
  2. TomasKucinskas

    TomasKucinskas

    Unity Technologies

    Joined:
    Dec 20, 2017
    Posts:
    60
    Hello @Jes28 . 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.
     
    JesOb likes this.
  3. JesOb

    JesOb

    Joined:
    Sep 3, 2012
    Posts:
    1,109
    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 [Table] to any serialisable array or list field add table button in inspector:
    upload_2023-1-27_18-50-51.png

    This is toggle to table mode for drawing array and after pressing it we go into table mode:

    upload_2023-1-27_18-52-7.png

    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 likes this.
  4. JesOb

    JesOb

    Joined:
    Sep 3, 2012
    Posts:
    1,109
    @TomasKucinskas thanks for interest I have submitted idea on roadmap with link to this thread




     
    Last edited: Jan 27, 2023
  5. TomasKucinskas

    TomasKucinskas

    Unity Technologies

    Joined:
    Dec 20, 2017
    Posts:
    60
    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.
     
  6. JesOb

    JesOb

    Joined:
    Sep 3, 2012
    Posts:
    1,109
  7. JesOb

    JesOb

    Joined:
    Sep 3, 2012
    Posts:
    1,109
    Actually with new UITK inspector it is very easy to implement ArrayDrawer:

    Code (CSharp):
    1.    public class ArrayDrawerTestInfo : ScriptableObject
    2.     {
    3.         [TestArray]
    4.         [SerializeField]  TestStruct[]  _data;
    5.         [SerializeField]  String  _str;
    6.         [SerializeField]  Int32  _str234;
    7.      
    8.         [Serializable]
    9.         public struct TestStruct
    10.         {
    11.             public String    Name;
    12.             public Int32    Filed1;
    13.             public Single    Filed2;
    14.         }
    15.     }
    16.  
    17.     public class TestArrayAttribute: PropertyAttribute { }
    18.  
    19.     [CustomPropertyDrawer(typeof(TestArrayAttribute))]
    20.     public class TestArrayDrawer : ArrayDrawer
    21.     {
    22.         protected override Boolean HideDefaultView => false;
    23.  
    24.         protected override void OnArrayGUI(SerializedProperty arrayProperty)
    25.         {
    26.             GUILayout.Label( "Hello From Custom Array Drawer" );
    27.         }
    28.     }
    29.  
    30.  
    31.     public class ArrayDrawer : PropertyDrawer
    32.     {
    33.         private SerializedProperty _arrayProperty;
    34.      
    35.         protected virtual Boolean HideDefaultView => true;
    36.      
    37.         public override VisualElement CreatePropertyGUI(SerializedProperty property)
    38.         {
    39.             // this is non destructive way if intention to fully replace old drawer return new empty VisualElement
    40.              var pf = new PropertyField( property );
    41.          
    42.             if (property.propertyPath[^3] == '[' && property.propertyPath[^2] == '0')
    43.             {
    44.                 var path        = property.propertyPath[..^14];
    45.                 _arrayProperty    = property.serializedObject.FindProperty( path );
    46.          
    47.                 pf.RegisterCallback<AttachToPanelEvent>(AttachToPanel);
    48.             }
    49.  
    50.             return pf;
    51.         }
    52.  
    53.         protected virtual void OnArrayGUI( SerializedProperty arrayProperty ) { }
    54.  
    55.         private void AttachToPanel(AttachToPanelEvent evt)
    56.         {
    57.             var arrayPropertyField = ((VisualElement)evt.target).hierarchy.parent.hierarchy.parent;
    58.             for( ; arrayPropertyField != null && arrayPropertyField is not PropertyField; arrayPropertyField = arrayPropertyField.hierarchy.parent );
    59.          
    60.             var contentParent    = arrayPropertyField.Q<Foldout>( "unity-list-view__foldout-header" );
    61.             var content            = contentParent.Q("unity-content");
    62.  
    63.             if( HideDefaultView )
    64.                 content.hierarchy[0].style.display = DisplayStyle.None;
    65.          
    66.             content.hierarchy[1].style.display = DisplayStyle.None;
    67.  
    68.             // there we can continue to use UITK but for simplicity of sample use IMGUI
    69.             content.hierarchy.Add( new IMGUIContainer( DrawArray ) );
    70.         }
    71.         private void DrawArray( ) => OnArrayGUI( _arrayProperty.Copy( ) );
    72.     }
    upload_2023-1-29_1-47-57.png

    The only issue is empty array that will show default array drawer
     
    Last edited: Jan 28, 2023
  8. JesOb

    JesOb

    Joined:
    Sep 3, 2012
    Posts:
    1,109
    And more clear way though decorator drawer :)

    Code (CSharp):
    1.     public class ArrayDrawerTestInfo : ScriptableObject
    2.     {
    3.         [HelloArray, XItems]
    4.         [SerializeField]  TestStruct[]  _data;
    5.         [SerializeField]  String  _str;
    6.         [SerializeField]  Int32  _str234;
    7.        
    8.         [Serializable]
    9.         public struct TestStruct
    10.         {
    11.             public String    Name;
    12.             public Int32    Filed1;
    13.             public Single    Filed2;
    14.         }
    15.     }
    16.    
    17.     public class XItemsAttribute: PropertyAttribute { }
    18.    
    19.     public class HelloArrayAttribute: PropertyAttribute { }
    20.    
    21.     [CustomPropertyDrawer(typeof(HelloArrayAttribute))]
    22.     public class TestArrayDrawer : ArrayDrawer
    23.     {
    24.         protected override Boolean HideDefaultView => true;
    25.  
    26.         protected override void OnArrayGUI(SerializedProperty arrayProperty)
    27.         {
    28.             GUILayout.Label( "Hello From Custom Array Drawer" );
    29.         }
    30.     }
    31.    
    32.     [CustomPropertyDrawer(typeof(XItemsAttribute))]
    33.     public class ArrayDrawerX : PropertyDrawer
    34.     {
    35.         public override VisualElement CreatePropertyGUI(SerializedProperty property)
    36.         {
    37.             return new VisualElement(){ style = { height = 0 }};
    38.         }
    39.     }
    40.    
    41.     public class ArrayDrawer : DecoratorDrawer
    42.     {
    43.         private SerializedProperty    _arrayProp;
    44.        
    45.         protected virtual Boolean HideDefaultView => true;
    46.        
    47.         public override VisualElement CreatePropertyGUI( )
    48.         {
    49.             var vi =  new VisualElement { style = { height = 0 } };
    50.             vi.RegisterCallback<GeometryChangedEvent>(GeometryChcnaged);
    51.             return vi;
    52.         }
    53.  
    54.         protected virtual void OnArrayGUI( SerializedProperty arrayProperty ) { }
    55.  
    56.         private void GeometryChcnaged(GeometryChangedEvent evt)
    57.         {
    58.             var e = (VisualElement)evt.currentTarget;
    59.             e.UnregisterCallback<GeometryChangedEvent>( GeometryChcnaged );
    60.        
    61.             var arrayPropertyField = e;
    62.             for( ; arrayPropertyField != null && arrayPropertyField is not PropertyField; arrayPropertyField = arrayPropertyField.hierarchy.parent );
    63.            
    64.             _arrayProp = (SerializedProperty)arrayPropertyField.Q<ListView>( ).userData;
    65.            
    66.             var contentParent    = arrayPropertyField.Q<Foldout>( "unity-list-view__foldout-header" );
    67.             var content            = contentParent.Q("unity-content");
    68.            
    69.             if( content.hierarchy.childCount > 2 )
    70.                 return;
    71.            
    72.             if( HideDefaultView )
    73.                 content.hierarchy[0].style.display = DisplayStyle.None;
    74.            
    75.             content.hierarchy[1].style.display = DisplayStyle.None;
    76.            
    77.             // there we can continue to use UITK but for simplicity of sample use IMGUI
    78.             content.hierarchy.Add( new IMGUIContainer( DrawArray ) );
    79.         }
    80.         private void DrawArray( ) => OnArrayGUI( _arrayProp.Copy( ) );
    81.     }
     
  9. TomasKucinskas

    TomasKucinskas

    Unity Technologies

    Joined:
    Dec 20, 2017
    Posts:
    60
    Definitely better than having to override the entire component drawer but still very hacky. Glad to see UIToolkit is making things a bit easier.