UnityEditor.Graphs - GraphGUI and NodeGUI questions

I previously sent this to Unity’s support email address, but was told I should post it here instead.


I’m currently taking a look at implementing a tool using the public but undocumented GraphGUI. I understand that I could write my own GUI tools or use something 3rd party, but if at all possible I’d rather keep things consistent with what’s already in Unity and avoid additional dependencies.

I’ve managed to create a graph and get it drawing. I’ve hit three things which I haven’t yet solved, which I thought it certainly can’t hurt to ask about. It would be amazing if you could please help me out with any of the following:

  1. How do I set the size of a Node in the graph? I can set width and height in the position when I create the Node, but this seems to then get overwritten. If I set a width and height by overriding GraphGUI.NodeGUI(…) it works intermittently.

  2. How do I detect clicks on Nodes? At this stage I am assuming that in an overridden GraphGUI.NodeGUI(…) I can examine Event.current, but haven’t been able to test this because my Nodes keep reverting to an unclickable default size width.

  3. How can I make and use a custom GUIStyle for a Node? I’ve found Styles.GetNodeStyle(…), which I assume is that the “style” string on a Node is used for, but I haven’t found how to add a new style.

Edit, in case someone from Unity sees this: The UnityEditor.Graphs namespace is completely undocumented despite being exposed. Is this because we’re not meant to be using it, or is it because nobody’s got around to documenting it yet? (Given the number of workflows and tools that benefit from this kind of UI it’d be awesome if the built-in one was available for use, rather than everyone and their dogs writing their own or bringing in 3rd party dependencies.)

The graph framework is unsupported API. It is likely to be removed (or at least deprecated) in favor a more robust/performant framework in the future. Unfortunately, it was one of the first external (outside of UnityEngine.dll and UnityEditor.dll) assemblies we shipped along with Unity and at the time we weren’t as strict about having public undocumented API (we’re still not perfect today either). Using this API (or any undocumented API) is at your own risk.

With that said, in the interest of learning/sharing/etc…

Nodes will automatically be the size needed to fit whatever content is within them. It relies on the layout system to figure out how large they should be. If you use anything in GUILayout/EditorGUILayout, then it should “just work”. If you want to do something fancy and handle your own layout within a node, you can simply request a hardcoded width and height from the layout system calling GUILayoutUtility.GetRect at the beginning of your NodeGUI.

Correct, this should work as expected.

If you want to change the styling of nodes you have two options. You can override GraphGUI.OnGraphGUI and reimplement the drawing of the nodes yourself (passing your own style). Or you can set GUI.skin to your own GUISkin and then follow the pattern that Styles.GetNodeStyle uses for looking up the style by name.

The default implementation of OnGraphGUI for drawing the nodes looks like this:

foreach (Node n in m_Graph.nodes)
{
    bool on = selection.Contains(n);

    // show invalid nodes in red
    Styles.Color color = n.nodeIsInvalid ? Styles.Color.Red : n.color;

    n.position = GUILayout.Window(n.GetInstanceID(), n.position, delegate { NodeGUI(n); }, n.title, Styles.GetNodeStyle(n.style, color, on), GUILayout.Width(0), GUILayout.Height(0));
}

Styles.GetNodeStyle uses this format to look up the GUIStyle by name:

string.Format ("flow {0} {1}{2}", styleName, (int)color, (on ? " on" : ""));
2 Likes

I suspected that this would be the case, thanks for clearing that up. It’s awkward, because what’s there seems like a reasonable starting point, but it’s sitting there gathering dust and we’re being encouraged to either roll our own or bring in 3rd party dependencies.

Still, thanks for the heads up, and for the detailed responses on the rest - hugely appreciated!

Hey @shawn_1 !

I’d like to take a dive into the node API, being fully aware that it might get replaced at any time. What’s the difference between a datanode and a flownode? Also, ANY further information/advice/hint are really appreciated.

Thanks in advance!

And another question, @shawn_1 ,

In my class inheriting from Graphs.GraphGUI, I have the following function, meant to draw all input slots or all outputslots at once, due to styling reasons. Now I want to be able to give each output slot only one edge, but allow any number of edges in an input slot.

private void DrawSlotRow ( Node n, bool isInRow )
        {
            bool isOn = selection.Contains ( n );
            Styles.Color nodeColor = n.nodeIsInvalid ? Styles.Color.Red : n.color;

            List<Slot> slotList = new List<Slot> ( isInRow ? n.inputSlots : n.outputSlots );
            int slotCount = slotList.Count;
            GUILayoutUtility.GetRect ( SLOT_AREA, SLOT_AREA * slotCount + SLOT_PADDING_TOP + SLOT_PADDING_BOTTOM );
            float x = isInRow ? 0.0f : n.position.width - SLOT_SIZE;
            for ( int slotIndex = 0; slotIndex < slotCount; ++slotIndex )
            {
                Slot ( new Rect ( x, SLOT_AREA * slotIndex + SLOT_PADDING_TOP, SLOT_SIZE, SLOT_SIZE )
                     , slotList [ slotIndex ].title
                     , slotList [ slotIndex ]
                     , !isInRow
                     , isInRow
                     , isInRow
                     , Styles.GetNodeStyle ( n.style, nodeColor, isOn ) );
            }
        }

The problem is, that I can still create multiple edges for my output slots. I tried doing it the other way round, allowing startDrag on input slots and disallow it on output slots, but that turns the edges inside out, meaning they go through the nodes instead of out from them while drawing.

So I tried to remove excess edges on output slots by hand (this is from my node class):

public override void InputEdgeChanged ( Edge edge )
        {
            base.InputEdgeChanged ( edge );

            Debug.Log ( "Before: " + edge.fromSlot.edges.Count.ToString () );
            Slot fromSlot = edge.fromSlot;
            if ( edge.fromSlot.edges.Count > 1 )
            {
                for ( int i = 0; i < fromSlot.edges.Count; ++i )
                {
                    Edge other = fromSlot.edges [ i ];
                    if ( ( other != null ) && ( other != edge ) )
                    {
                        fromSlot.RemoveEdge ( other );
                        other.toSlot.RemoveEdge ( other );
                        i--;
                    }
                }
            }
            Debug.Log ( "After: " + edge.fromSlot.edges.Count.ToString () );
        }

But this simply doesn’t work. The debug log states that if I attempt to create another edge, that I have 2 ‘before’ and 1 ‘after’, but the graph still shows two edges, as if nothing happened.

Any idea how I can solve this? Is this maybe somehow connected to this flow/data node thing?