Populating the drop-down add menu with types |
Utility functionality is provided to assist when building menus that contain a number of addable element types that implement some interface or base type; this is referred to as the element contract type.
Let's begin by defining the base class that each of our elements must be derived from:
// ExampleNode.cs using UnityEngine; public abstract class ExampleNode : ScriptableObject { [SerializeField] private string _displayName; public string DisplayName { get { return _displayName; } set { _displayName = value; } } }
We then need some sort of container wherein our node instances will be stored. In the case of this example this will be another custom ScriptableObject implementation; although the same principle can also be applied to a collection type.
// ExampleGraph.cs using System.Collections.Generic; using UnityEngine; public abstract class ExampleGraph : ScriptableObject { [SerializeField] private List<ExampleNode> _nodes = new List<ExampleNode>(); public void AddNode(ExampleNode node) { _nodes.Add(node); } }
We will also need to implement at least one type of node so that the add menu will contain at least one type to select from!
// NodeTypeA.cs public class NodeTypeA : ExampleNode { } // NodeTypeB.cs public class NodeTypeB : ExampleNode { }
An IElementAdderTContext implementation is also needed since this defines how nodes are to be created and how they are to be associated with their context object. In the case of this example the context object will be an instance of ExampleGraph.
using Rotorz.ReorderableList; using System; using UnityEngine; public class ExampleNodeElementAdder : IElementAdder<ExampleGraph> { public ExampleNodeElementAdder(ExampleGraph graph) { Object = graph; } public ExampleGraph Object { get; private set; } public bool CanAddElement(Type type) { return true; } public object AddElement(Type type) { var node = (ExampleNode)ScriptableObject.CreateInstance(type); Object.AddNode(node); return node; } }
The drop-down add menu can then be defined like shown below where we make use of an adder element menu builder to populate the menu with the relevant element types:
using Rotorz.ReorderableList; using UnityEditor; using UnityEngine; [CustomEditor(typeof(ExampleGraph))] public class ExampleGraphEditor : Editor { private ReorderableListControl _listControl; private IReorderableListAdaptor _listAdaptor; private void OnEnable() { // Create list control and pass flag into constructor so that the // regular add button is not displayed. _listControl = new ReorderableListControl(ReorderableListFlags.HideAddButton); // Subscribe to event for when add menu button is clicked which will // also indicate that the add menu button is to be presented. _listControl.AddMenuClicked += OnAddMenuClicked; // Create adaptor for example list. var nodesProperty = serializedObject.FindProperty("_nodes"); _listAdaptor = new SerializedPropertyAdaptor(nodesProperty); } private void OnDisable() { // Unsubscribe from event, good practice. if (_listControl != null) _listControl.AddMenuClicked -= OnAddMenuClicked; } private void OnAddMenuClicked(object sender, AddMenuClickedEventArgs args) { var graph = target as ExampleGraph; var elementAdder = new ExampleNodeElementAdder(graph); var builder = ElementAdderMenuBuilder.For<ExampleGraph>(typeof(ExampleNode)); builder.SetElementAdder(elementAdder); var menu = builder.GetMenu(); menu.DropDown(args.ButtonPosition); } private void OnGUI() { // Draw layout version of reorderable list control. _listControl.Draw(_listAdaptor); } }
With this approach custom commands can also be included by adding them directly using the menu builder.
Adder menus can also be extended with custom commands without needing to directly interact with the menu builder. This can be achieved by annotating custom command implementations with an attribute which defines the context in which the command will be included:
using Rotorz.ReorderableList; using UnityEngine; [ElementAdderMenuCommand(typeof(ExampleNode))] public class SpecialCommand : IElementAdderMenuCommand<ExampleGraph> { public SpecialCommand() { Content = new GUIContent("Special Command"); } public GUIContent Content { get; private set; } public bool CanExecute(IElementAdder<ExampleGraph> elementAdder) { return true; } public void Execute(IElementAdder<ExampleGraph> elementAdder) { // Execute some custom command here! // Such as bulk adding nodes! } }