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
}
}