Community Made Tools

Have you made any useful utilities with Odin?

Login and submit your creations here

ListItemSelector attribute - Easily select items in lists

Authored by Bjarke
Shared 05-09-2019

I'll let the usage-code and gif speak for themselves.

Let me (Bjarke - Sirenix) know on Discord if you have any suggestions of feature requests.

Usage

[CreateAssetMenu]
public class SomeScriptableObject : ScriptableObject
{
    [ListItemSelector("SetSelected")]
    public List<Material> SomeList = new List<Material>();

    [BoxGroup("Preview")]
    [ShowInInspector, InlineEditor(InlineEditorObjectFieldModes.Hidden)]
    private Material selectedMaterial;

    public void SetSelected(int index)
    {
        this.selectedMaterial = index > 0 ? this.SomeList[index] : null;
    }
}

ListItemSelectorAttribute.cs

using System;

public class ListItemSelectorAttribute : Attribute
{
    public string SetSelectedMethod;

    public ListItemSelectorAttribute(string setSelectedMethod)
    {
        this.SetSelectedMethod = setSelectedMethod;
    }
}

ListItemSelectorAttributeDrawer.cs

using UnityEngine;
using System;
using Sirenix.OdinInspector.Editor;
using Sirenix.Utilities.Editor;
using UnityEditor;
using Sirenix.Utilities;

[DrawerPriority(0.01, 0, 0)]
public class ListItemSelectorAttributeDrawer : OdinAttributeDrawer<ListItemSelectorAttribute>
{
    private static Color selectedColor = new Color(0.301f, 0.563f, 1f, 0.497f);
    private bool isListElement;
    private InspectorProperty baseMemberProperty;
    private PropertyContext<InspectorProperty> globalSelectedProperty;
    private InspectorProperty selectedProperty;
    private Action<object, int> selectedIndexSetter;

    protected override void Initialize()
    {
        this.isListElement = this.Property.Parent != null && this.Property.Parent.ChildResolver is IOrderedCollectionResolver;
        var isList = !this.isListElement;
        var listProperty = isList ? this.Property : this.Property.Parent;
        this.baseMemberProperty = listProperty.FindParent(x => x.Info.PropertyType == PropertyType.Value, true);
        this.globalSelectedProperty = this.baseMemberProperty.Context.GetGlobal("selectedIndex" + this.baseMemberProperty.GetHashCode(), (InspectorProperty)null);

        if (isList)
        {
            var parentType = this.baseMemberProperty.ParentValues[0].GetType();
            this.selectedIndexSetter = EmitUtilities.CreateWeakInstanceMethodCaller<int>(parentType.GetMethod(this.Attribute.SetSelectedMethod, Flags.AllMembers));
        }
    }

    protected override void DrawPropertyLayout(GUIContent label)
    {
        var t = Event.current.type;

        if (this.isListElement)
        {
            if (t == EventType.Layout)
            {
                this.CallNextDrawer(label);
            }
            else
            {
                var rect = GUIHelper.GetCurrentLayoutRect();
                var isSelected = this.globalSelectedProperty.Value == this.Property;

                if (t == EventType.Repaint && isSelected)
                {
                    EditorGUI.DrawRect(rect, selectedColor);
                }
                else if (t == EventType.MouseDown && rect.Contains(Event.current.mousePosition))
                {
                    this.globalSelectedProperty.Value = this.Property;
                }

                this.CallNextDrawer(label);

            }
        }
        else
        {
            this.CallNextDrawer(label);

            if (Event.current.type != EventType.Layout)
            {
                var sel = this.globalSelectedProperty.Value;

                // Select
                if (sel != null && sel != this.selectedProperty)
                {
                    this.selectedProperty = sel;
                    this.Select(this.selectedProperty.Index);
                }
                // Deselect when destroyed
                else if (this.selectedProperty != null && this.selectedProperty.Index < this.Property.Children.Count && this.selectedProperty != this.Property.Children[this.selectedProperty.Index])
                {
                    var index = -1;
                    this.Select(index);
                    this.selectedProperty = null;
                    this.globalSelectedProperty.Value = null;
                }
            }
        }
    }

    private void Select(int index)
    {
        GUIHelper.RequestRepaint();
        this.Property.Tree.DelayAction(() =>
        {
            for (int i = 0; i < this.baseMemberProperty.ParentValues.Count; i++)
            {
                this.selectedIndexSetter(this.baseMemberProperty.ParentValues[i], index);
            }
        });
    }
}