Skip to content

Instantly share code, notes, and snippets.

@wesleywh
Created January 15, 2024 03:23
Show Gist options
  • Save wesleywh/b167e0bc001b5c7b378517a87ed0748b to your computer and use it in GitHub Desktop.
Save wesleywh/b167e0bc001b5c7b378517a87ed0748b to your computer and use it in GitHub Desktop.
Unity Custom Right Click Menu To List
#region Editor
public static class PCGSettingsCopier
{
public static PCGSettings obj = null;
public static List<int> indexes = new();
}
[CustomEditor(typeof(PCGSettings), true)]
public class PCGSettingsEditor : Editor
{
#region Properties
SerializedProperty itemList;
ReorderableList reorderableList;
#endregion
#region Enable
protected virtual void OnEnable()
{
itemList = serializedObject.FindProperty("items");
reorderableList = new ReorderableList(serializedObject, itemList, true, true, true, true);
reorderableList.multiSelect = true;
reorderableList.drawHeaderCallback = rect => {
EditorGUI.LabelField(rect, itemList.displayName);
};
reorderableList.drawElementCallback += DrawIndividualElement;
reorderableList.elementHeightCallback = index => {
var element = itemList.GetArrayElementAtIndex(index);
return EditorGUI.GetPropertyHeight(element, GUIContent.none, true) + 6f;
};
reorderableList.drawElementBackgroundCallback += DrawElementBackground;
}
protected virtual void DrawElementBackground(Rect rect, int index, bool isActive, bool isFocused)
{
var element = itemList.GetArrayElementAtIndex(index);
if (isActive)
EditorGUI.DrawRect(rect, new Color(0.5f, 0.5f, 0.5f, 0.2f));
}
protected virtual void DrawIndividualElement(Rect rect, int index, bool isActive, bool isFocused)
{
SerializedProperty element = itemList.GetArrayElementAtIndex(index);
SerializedProperty nextElement = null;
if (index < itemList.arraySize-1)
nextElement = itemList.GetArrayElementAtIndex(index+1);
rect.y += 2;
EditorGUI.indentLevel++;
// EditorGUI.PropertyField(new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight), element, new GUIContent(element.displayName));
element.isExpanded = EditorGUI.Foldout(new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight), element.isExpanded, new GUIContent(element.displayName));
if (Event.current.type == EventType.ContextClick && rect.Contains(Event.current.mousePosition))
{
if (!isActive)
reorderableList.index = index; // Select the index since it wasn't (will override a list of selections if it was)
OnContextClick();
}
// If the foldout is expanded, display sub-properties
if (element.isExpanded)
{
EditorGUI.indentLevel++;
float row = EditorGUIUtility.singleLineHeight + 2;
// Iterate through all child properties of the element
SerializedProperty childProperty = element.Copy();
bool enterChildren = true;
int count = 1;
while (childProperty.NextVisible(enterChildren))
{
if (nextElement != null && childProperty.displayName == nextElement.displayName) break;
enterChildren = true;
if (childProperty.propertyType == SerializedPropertyType.Vector2 ||
childProperty.propertyType == SerializedPropertyType.Vector3 ||
childProperty.propertyType == SerializedPropertyType.Vector4)
{
EditorGUI.PropertyField(new Rect(rect.x, rect.y+(row*count), rect.width, EditorGUIUtility.singleLineHeight), childProperty, true);
enterChildren = false;
count++;
}
// Exclude the main element property
else if (childProperty.name != "Array" && childProperty.name != "data")
{
EditorGUI.PropertyField(new Rect(rect.x, rect.y+(row*count), rect.width, EditorGUIUtility.singleLineHeight), childProperty, true);
count++;
}
}
enterChildren = false;
EditorGUI.indentLevel--;
}
EditorGUI.indentLevel--;
}
protected virtual void OnContextClick()
{
GenericMenu menu = new GenericMenu();
menu.AddItem(new GUIContent("Delete"), false, Delete);
menu.AddItem(new GUIContent(""), false, ()=>{});
menu.AddItem(new GUIContent("Copy"), false, Copy);
menu.AddItem(new GUIContent("Paste"), false, Paste);
menu.AddItem(new GUIContent(""), false, ()=>{});
if (reorderableList.selectedIndices.Count == 1)
{
menu.AddItem(new GUIContent("Duplicate"), false, Duplicate);
menu.AddItem(new GUIContent(""), false, ()=>{});
}
menu.AddItem(new GUIContent("Set Always Up - True"), false, () => { SetAlwaysUp(true); });
menu.AddItem(new GUIContent("Set Always Up - False"), false, () => { SetAlwaysUp(false); });
menu.ShowAsContext();
Event.current.Use();
}
#endregion
#region GUI
public override void OnInspectorGUI()
{
serializedObject.Update();
reorderableList.DoLayoutList();
serializedObject.ApplyModifiedProperties();
}
#endregion
#region Default Context Actions
protected virtual void Copy()
{
PCGSettingsCopier.obj = (PCGSettings)target;
PCGSettingsCopier.indexes.Clear();
foreach(int index in reorderableList.selectedIndices)
PCGSettingsCopier.indexes.Add(index);
}
protected virtual void Paste()
{
List<PCGItem> tmp = new List<PCGItem>();
foreach(int index in PCGSettingsCopier.indexes)
tmp.Add(PCGSettingsCopier.obj.items[index]);
Undo.RegisterCompleteObjectUndo(target, "Paste Items");
((PCGSettings)target).items.AddRange(tmp);
itemList.serializedObject.ApplyModifiedProperties();
itemList.serializedObject.Update();
}
protected virtual void Duplicate()
{
itemList.GetArrayElementAtIndex(reorderableList.selectedIndices[0]).DuplicateCommand();
itemList.serializedObject.ApplyModifiedProperties();
itemList.serializedObject.Update();
serializedObject.ApplyModifiedProperties();
}
protected virtual void Delete()
{
for (int i = reorderableList.selectedIndices.Count-1; i > -1; i--)
{
itemList.DeleteArrayElementAtIndex(reorderableList.selectedIndices[i]);
}
serializedObject.ApplyModifiedProperties();
}
#endregion
#region Custom Context Actions
protected virtual void SetAlwaysUp(bool value)
{
foreach(int selectedIndex in reorderableList.selectedIndices)
itemList.GetArrayElementAtIndex(selectedIndex).FindPropertyRelative("alwaysUp").boolValue = value;
serializedObject.ApplyModifiedProperties();
}
#endregion
}
#endregion
@wesleywh
Copy link
Author

wesleywh commented Jan 15, 2024

Unfortunately unity has no built in way that I can see to make a custom right click item target multiple items in a list. There is a way to target individual items like so:

[CustomEditor([typeof(PCGSettings))]
    public class PCGSettingsEditor : Editor
    {
        protected virtual void OnEnable()
        {
            EditorApplication.contextualPropertyMenu += OnPropertyContextMenu;
        }
 
        void OnDestroy()
        {
            EditorApplication.contextualPropertyMenu -= OnPropertyContextMenu;
        }
 
        void OnPropertyContextMenu(GenericMenu menu, SerializedProperty property)
        {
            menu.AddItem(new GUIContent("TEST"), false, () =>
            {
                Debug.Log(JsonUtility.ToJson(property.objectReferenceValue));
                Debug.Log(property.objectReferenceValue);
            });
            return;
        }
    }

That will allow you to add a new entry into the existing list, but only allow you to target the selected item.

The above rewrites the "ReorderableList" to include two new right click menus while attempting to maintain the original list. I might have missed a few things visually but it's good enough for my needs. This should allow you to copy this code and paste it, modify the targeting component (along with the custom right click menus) and you should be good to go.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment