using System.Collections.Generic; using UnityEngine; using UnityEditor; using UNEB.Utility; namespace UNEB { /// /// The visual representation of a logic unit such as an object or function. /// public abstract class Node : ScriptableObject { public static readonly Vector2 kDefaultSize = new Vector2(140f, 110f); /// /// The space reserved between knobs. /// public const float kKnobOffset = 4f; /// /// The space reserved for the header (title) of the node. /// public const float kHeaderHeight = 15f; /// /// The max label width for a field in the body. /// public const float kBodyLabelWidth = 100f; /// /// The rect of the node in canvas space. /// [HideInInspector] public Rect bodyRect; /// /// How much additional offset to apply when resizing. /// public const float resizePaddingX = 20f; [SerializeField, HideInInspector] private List _outputs = new List(); [SerializeField, HideInInspector] private List _inputs = new List(); // Hides the node asset. // Sets up the name via type information. void OnEnable() { hideFlags = HideFlags.HideInHierarchy; name = GetType().Name; #if UNITY_EDITOR name = ObjectNames.NicifyVariableName(name); #endif } /// /// Always call the base OnDisable() to cleanup the connection objects. /// protected virtual void OnDestroy() { _inputs.RemoveAll( (input) => { ScriptableObject.DestroyImmediate(input, true); return true; }); _outputs.RemoveAll( (output) => { ScriptableObject.DestroyImmediate(output, true); return true; }); } /// /// Use this for initialization. /// public virtual void Init() { bodyRect.size = kDefaultSize; } public virtual void OnNodeGUI() { OnNodeHeaderGUI(); OnConnectionsGUI(); onBodyGuiInternal(); } /// /// Renders the node connections. By default, after the header. /// public virtual void OnConnectionsGUI() { int inputCount = _inputs.Count; int outputCount = _outputs.Count; int maxCount = (int)Mathf.Max(inputCount, outputCount); // The entire knob section is stacked rows of inputs and outputs. for (int i = 0; i < maxCount; ++i) { GUILayout.BeginHorizontal(); // Render the knob layout horizontally. if (i < inputCount) _inputs[i].OnConnectionGUI(i); if (i < outputCount) _outputs[i].OnConnectionGUI(i); GUILayout.EndHorizontal(); } } /// /// Render the title/header of the node. By default, renders on top of the node. /// public virtual void OnNodeHeaderGUI() { // Draw header GUILayout.Box(name, HeaderStyle); } /// /// Draws the body of the node. By default, after the connections. /// public virtual void OnBodyGUI() { } // Handles the coloring and layout of the body. // This is for convenience so the user does not need to worry about this boiler plate code. protected virtual void onBodyGuiInternal() { float oldLabelWidth = EditorGUIUtility.labelWidth; EditorGUIUtility.labelWidth = kBodyLabelWidth; // Cache the old label style. // Do this first before changing the EditorStyles.label style. // So the original values are kept. var oldLabelStyle = UnityLabelStyle; // Setup new values for the label style. EditorStyles.label.normal = DefaultStyle.normal; EditorStyles.label.active = DefaultStyle.active; EditorStyles.label.focused = DefaultStyle.focused; EditorGUILayout.BeginVertical(); GUILayout.Space(kKnobOffset); OnBodyGUI(); // Revert back to old label style. EditorStyles.label.normal = oldLabelStyle.normal; EditorStyles.label.active = oldLabelStyle.active; EditorStyles.label.focused = oldLabelStyle.focused; EditorGUIUtility.labelWidth = oldLabelWidth; EditorGUILayout.EndVertical(); } public NodeInput AddInput(string name = "input", bool multipleConnections = false) { var input = NodeInput.Create(this, multipleConnections); input.name = name; _inputs.Add(input); return input; } public NodeOutput AddOutput(string name = "output", bool multipleConnections = false) { var output = NodeOutput.Create(this, multipleConnections); output.name = name; _outputs.Add(output); return output; } /// /// Called when the output knob had an input connection removed. /// /// public virtual void OnInputConnectionRemoved(NodeInput removedInput) { } /// /// Called when the output knob made a connection to an input knob. /// /// public virtual void OnNewInputConnection(NodeInput addedInput) { } public IEnumerable Outputs { get { return _outputs; } } public IEnumerable Inputs { get { return _inputs; } } public int InputCount { get { return _inputs.Count; } } public int OutputCount { get { return _outputs.Count; } } public NodeInput GetInput(int index) { return _inputs[index]; } public NodeOutput GetOutput(int index) { return _outputs[index]; } /// /// Get the Y value of the top header. /// public float HeaderTop { get { return bodyRect.yMin + kHeaderHeight; } } /// /// Resize the node to fit the knobs. /// public void FitKnobs() { int maxCount = (int)Mathf.Max(_inputs.Count, _outputs.Count); float totalKnobsHeight = maxCount * NodeConnection.kMinSize.y; float totalOffsetHeight = (maxCount - 1) * kKnobOffset; float heightRequired = totalKnobsHeight + totalOffsetHeight + kHeaderHeight; // Add some extra height at the end. bodyRect.height = heightRequired + kHeaderHeight / 2f; } #region Styles and Contents private static GUIStyle _unityLabelStyle; /// /// Caches the default EditorStyle. /// There is a strange bug with it being overriden when opening an Animation window. /// public static GUIStyle UnityLabelStyle { get { if (_unityLabelStyle == null) { _unityLabelStyle = new GUIStyle(EditorStyles.label); } return _unityLabelStyle; } } private static GUIStyle _defStyle; public static GUIStyle DefaultStyle { get { if (_defStyle == null) { _defStyle = new GUIStyle(EditorStyles.label); _defStyle.normal.textColor = Color.white * 0.9f; _defStyle.active.textColor = ColorExtensions.From255(126, 186, 255) * 0.9f; _defStyle.focused.textColor = ColorExtensions.From255(126, 186, 255); } return _defStyle; } } private static GUIStyle _headerStyle; public GUIStyle HeaderStyle { get { if (_headerStyle == null) { _headerStyle = new GUIStyle(); _headerStyle.stretchWidth = true; _headerStyle.alignment = TextAnchor.MiddleLeft; _headerStyle.padding.left = 5; _headerStyle.normal.textColor = Color.white * 0.9f; _headerStyle.fixedHeight = kHeaderHeight; } return _headerStyle; } } #endregion } }