Access index or component type in list

Issue #29 resolved
Ivan Wang created an issue

I'd like to customize a list of elements to show a bold title that delineates each element. The list is of an abstract class, and each element might have a different subclass.

Ideally, the title would have the index and the class/type name and replace the reference picker. The HideReferenceObjectPicker is nice, but the Title attribute has to be a static string.

Maybe you could add an option to display the index in lists? Or support arbitrary functions in the Title attribute, and getting the index or class type within lists somehow?

Comments (6)

  1. Bjarke Elias

    Nice use-case you have. It is exactly cases like that that Odin needs to shine.

    You can get some of the behavior by adding:

    [ListDrawerSettings(ShowLabels = true)]
    public int[] MyArary;
    

    However, in 0.9.0.3, It shows the label name again, followed by the index. This is changed in the next patch to only show the index. In the next version it looks like this: 0S4TOgwnWaFY5JlkMPm5WOP9se11FXr_EztrI-iNTHxq7hNGkxrXuZKqVzv3E8FZcSEtHof_p2FmJFTlKHF2kFNlSmh0egFbdyHzE6LvoMAI4av1wcwKsmUDuTGzHRrg.png

    Alternatively, you could do something slightly messier: Y6bJeMXsDnvfLiHqqDGfaalSDoZQAVXNO3mbWVF0ngTVN2q6dI1njkICpBtwP8dqSZLFAxeLaCFBxiFsNjHPYVNlSmh0egFbdyHzE6LvoMAI4av1wcwKsmUDuTGzHRrg.png

    Though I don't think it's the cleanest of solutions. I'll talk with the other about adding the option you suggested where you can inject GUI into each element. Something like this right?

    [ListDrawerSettings(OnListElementGUI = "DrawLabelForListElement")]
    public SomeClass[] MyArary;
    
    private void DrawLabelForListElement(SomeClass item, int index)
    {
          GUILayout.Label(...);
    }
    
  2. Ivan Wang reporter

    That suggested solution would be fantastic, but for now I'll use the messy suggestion. Thanks :)

  3. Johannes P.

    Since the suggested approach has not been implemented yet, is there a way to modify the provided workaround to also still show an object picker?

    So for example, to have the Label header of each element look like this: [Index] : [Field/Property value on object (if not null)] [Object Picker]

    If not, at least I would like to make sure that when the suggested approach is implemented, that this kind of a header format is possible. In other words, OnListElementGUI should still play nice with ShowIndexLabels and the Object picker.

  4. Bjarke Elias

    Sadly there is no pretty way of doing this at the moment. It could look something like this:

    public class MyComponent : SerializedMonoBehaviour
    {
        [ListElementLabelText("Name", true)]
        public List<MyClass> Test;
    }
    
    public class MyClass
    {
        public string Name;
    }
    
    public class ListElementLabelTextAttribute : Attribute
    {
        public readonly string LabelMemberName;
        public readonly bool PrependListElementIndex;
    
        public ListElementLabelTextAttribute(string labelMemberName, bool prependListElementIndex)
        {
            this.LabelMemberName = labelMemberName;
            this.PrependListElementIndex = prependListElementIndex;
        }
    }
    
    [OdinDrawer]
    [DrawerPriority(0, 100, 0)]
    public class ListElementLabelTextAttributeDrawer : OdinAttributeDrawer<ListElementLabelTextAttribute>
    {
        public override bool CanDrawTypeFilter(Type type)
        {
            return !type.InheritsFrom<IList>();
        }
    
        private class Context
        {
            public StringMemberHelper StringMemberHelper;
            public GUIContent Label;
        }
    
        protected override void DrawPropertyLayout(InspectorProperty property, ListElementLabelTextAttribute attribute, GUIContent label)
        {
            Context context;
    
            if (property.Context.Get<Context>(this, "Context", out context))
            {
                context.StringMemberHelper = new StringMemberHelper(property.ValueEntry.TypeOfValue, "$" + attribute.LabelMemberName);
                context.Label = new GUIContent();
            }
    
            if (context.StringMemberHelper.ErrorMessage != null)
            {
                SirenixEditorGUI.ErrorMessageBox(context.StringMemberHelper.ErrorMessage);
                this.CallNextDrawer(property, label);
            }
            else
            {
                var instance = property.ValueEntry.WeakValues[0];
                context.Label.text = attribute.PrependListElementIndex ? property.Index.ToString() + " : " : "";
                context.Label.text += instance == null ? "Null" : context.StringMemberHelper.GetString(instance);
    
                // If you want the object picker to be collapsed, then you could uncomment the code below. 
                // It will also be applied for all child elements, so it's not the pretties way of doing it. Need to think a bit about that one.
    
                // var tmp = GeneralDrawerConfig.Instance.ExpandFoldoutByDefault;
                // GeneralDrawerConfig.Instance.ExpandFoldoutByDefault = false; 
                this.CallNextDrawer(property, context.Label);
                // GeneralDrawerConfig.Instance.ExpandFoldoutByDefault = tmp;
            }
        }
    }
    

    rfa.png

    I don't think we'll implement this exact attribute. But something like that should be via ListDrawerSettings attribute in the future.

  5. Bjarke Elias
    • Added support for injecting GUI before and after each element is drawin inside a list. This is achived using the [ListDrawerSettingsAttribute(OnBeginListElementGUI = "OnBeginListElementGUIMethodName", OnEndListElementGUI = "OnEndListElementGUIMethodName")].
    • Added support to the ListDrawerSettings attribute for defining custom label text for each list element. This is achived using the [ListDrawerSettingsAttribute(ListElementLabelName = "ListElementLabelName")].

    ListDrawer-begin-end.png

  6. Log in to comment