mirror of
https://github.com/Siccity/xNode.git
synced 2026-03-26 22:49:02 +08:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
13031b570d
@ -18,6 +18,14 @@ If your feature aims to cover something not related to editing nodes, it general
|
|||||||
Skim through the code and you'll get the hang of it quickly.
|
Skim through the code and you'll get the hang of it quickly.
|
||||||
* Methods, Types and properties PascalCase
|
* Methods, Types and properties PascalCase
|
||||||
* Variables camelCase
|
* Variables camelCase
|
||||||
* Public methods XML commented
|
* Public methods XML commented. Params described if not obvious
|
||||||
|
* Explicit usage of brackets when doing multiple math operations on the same line
|
||||||
|
|
||||||
|
## Formatting
|
||||||
|
I use VSCode with the C# FixFormat extension and the following setting overrides:
|
||||||
|
```json
|
||||||
|
"csharpfixformat.style.spaces.beforeParenthesis": false,
|
||||||
|
"csharpfixformat.style.indent.regionIgnored": true
|
||||||
|
```
|
||||||
* Open braces on same line as condition
|
* Open braces on same line as condition
|
||||||
* 4 spaces for indentation.
|
* 4 spaces for indentation.
|
||||||
|
|||||||
@ -24,6 +24,7 @@ With a minimal footprint, it is ideal as a base for custom state machines, dialo
|
|||||||
* No runtime reflection (unless you need to edit/build node graphs at runtime. In this case, all reflection is cached.)
|
* No runtime reflection (unless you need to edit/build node graphs at runtime. In this case, all reflection is cached.)
|
||||||
* Does not rely on any 3rd party plugins
|
* Does not rely on any 3rd party plugins
|
||||||
* Custom node inspector code is very similar to regular custom inspector code
|
* Custom node inspector code is very similar to regular custom inspector code
|
||||||
|
* Supported from Unity 5.3 and up
|
||||||
|
|
||||||
### Wiki
|
### Wiki
|
||||||
* [Getting started](https://github.com/Siccity/xNode/wiki/Getting%20Started) - create your very first node node and graph
|
* [Getting started](https://github.com/Siccity/xNode/wiki/Getting%20Started) - create your very first node node and graph
|
||||||
|
|||||||
@ -24,12 +24,21 @@ namespace XNodeEditor {
|
|||||||
string enumName = "";
|
string enumName = "";
|
||||||
if (property.enumValueIndex >= 0 && property.enumValueIndex < property.enumDisplayNames.Length) enumName = property.enumDisplayNames[property.enumValueIndex];
|
if (property.enumValueIndex >= 0 && property.enumValueIndex < property.enumDisplayNames.Length) enumName = property.enumDisplayNames[property.enumValueIndex];
|
||||||
|
|
||||||
|
#if UNITY_2017_1_OR_NEWER
|
||||||
// Display dropdown
|
// Display dropdown
|
||||||
if (EditorGUI.DropdownButton(position, new GUIContent(enumName), FocusType.Passive)) {
|
if (EditorGUI.DropdownButton(position, new GUIContent(enumName), FocusType.Passive)) {
|
||||||
// Position is all wrong if we show the dropdown during the node draw phase.
|
// Position is all wrong if we show the dropdown during the node draw phase.
|
||||||
// Instead, add it to onLateGUI to display it later.
|
// Instead, add it to onLateGUI to display it later.
|
||||||
NodeEditorWindow.current.onLateGUI += () => ShowContextMenuAtMouse(property);
|
NodeEditorWindow.current.onLateGUI += () => ShowContextMenuAtMouse(property);
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
// Display dropdown
|
||||||
|
if (GUI.Button(position, new GUIContent(enumName), "MiniPopup")) {
|
||||||
|
// Position is all wrong if we show the dropdown during the node draw phase.
|
||||||
|
// Instead, add it to onLateGUI to display it later.
|
||||||
|
NodeEditorWindow.current.onLateGUI += () => ShowContextMenuAtMouse(property);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
EditorGUI.EndProperty();
|
EditorGUI.EndProperty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -106,6 +106,7 @@ namespace XNodeEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void Rename(string newName) {
|
public void Rename(string newName) {
|
||||||
|
if (newName == null || newName.Trim() == "") newName = UnityEditor.ObjectNames.NicifyVariableName(target.GetType().Name);
|
||||||
target.name = newName;
|
target.name = newName;
|
||||||
AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target));
|
AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,12 +22,11 @@ namespace XNodeEditor {
|
|||||||
[NonSerialized] private List<Vector2> draggedOutputReroutes = new List<Vector2>();
|
[NonSerialized] private List<Vector2> draggedOutputReroutes = new List<Vector2>();
|
||||||
private RerouteReference hoveredReroute = new RerouteReference();
|
private RerouteReference hoveredReroute = new RerouteReference();
|
||||||
private List<RerouteReference> selectedReroutes = new List<RerouteReference>();
|
private List<RerouteReference> selectedReroutes = new List<RerouteReference>();
|
||||||
private Rect nodeRects; // Is this used?
|
|
||||||
private Vector2 dragBoxStart;
|
private Vector2 dragBoxStart;
|
||||||
private UnityEngine.Object[] preBoxSelection;
|
private UnityEngine.Object[] preBoxSelection;
|
||||||
private RerouteReference[] preBoxSelectionReroute;
|
private RerouteReference[] preBoxSelectionReroute;
|
||||||
private Rect selectionBox;
|
private Rect selectionBox;
|
||||||
private bool isDoubleClick = false;
|
private bool isDoubleClick = false;
|
||||||
|
|
||||||
private struct RerouteReference {
|
private struct RerouteReference {
|
||||||
public XNode.NodePort port;
|
public XNode.NodePort port;
|
||||||
@ -61,7 +60,7 @@ namespace XNodeEditor {
|
|||||||
case EventType.MouseDrag:
|
case EventType.MouseDrag:
|
||||||
if (e.button == 0) {
|
if (e.button == 0) {
|
||||||
if (IsDraggingPort) {
|
if (IsDraggingPort) {
|
||||||
if (IsHoveringPort && hoveredPort.IsInput) {
|
if (IsHoveringPort && hoveredPort.IsInput && draggedOutput.CanConnectTo(hoveredPort)) {
|
||||||
if (!draggedOutput.IsConnectedTo(hoveredPort)) {
|
if (!draggedOutput.IsConnectedTo(hoveredPort)) {
|
||||||
draggedOutputTarget = hoveredPort;
|
draggedOutputTarget = hoveredPort;
|
||||||
}
|
}
|
||||||
@ -170,7 +169,7 @@ namespace XNodeEditor {
|
|||||||
} else if (e.control || e.shift) DeselectNode(hoveredNode);
|
} else if (e.control || e.shift) DeselectNode(hoveredNode);
|
||||||
|
|
||||||
// Cache double click state, but only act on it in MouseUp - Except ClickCount only works in mouseDown.
|
// Cache double click state, but only act on it in MouseUp - Except ClickCount only works in mouseDown.
|
||||||
isDoubleClick = ( e.clickCount == 2 );
|
isDoubleClick = (e.clickCount == 2);
|
||||||
|
|
||||||
e.Use();
|
e.Use();
|
||||||
currentActivity = NodeActivity.HoldNode;
|
currentActivity = NodeActivity.HoldNode;
|
||||||
@ -231,6 +230,7 @@ namespace XNodeEditor {
|
|||||||
// If click outside node, release field focus
|
// If click outside node, release field focus
|
||||||
if (!isPanning) {
|
if (!isPanning) {
|
||||||
EditorGUI.FocusTextInControl(null);
|
EditorGUI.FocusTextInControl(null);
|
||||||
|
EditorGUIUtility.editingTextField = false;
|
||||||
}
|
}
|
||||||
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
|
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
|
||||||
}
|
}
|
||||||
@ -241,9 +241,8 @@ namespace XNodeEditor {
|
|||||||
SelectNode(hoveredNode, false);
|
SelectNode(hoveredNode, false);
|
||||||
|
|
||||||
// Double click to center node
|
// Double click to center node
|
||||||
if ( isDoubleClick )
|
if (isDoubleClick) {
|
||||||
{
|
Vector2 nodeDimension = nodeSizes.ContainsKey(hoveredNode) ? nodeSizes[hoveredNode] / 2 : Vector2.zero;
|
||||||
Vector2 nodeDimension = nodeSizes.ContainsKey( hoveredNode ) ? nodeSizes[ hoveredNode ] / 2 : new Vector2(0f, 0f);
|
|
||||||
panOffset = -hoveredNode.position - nodeDimension;
|
panOffset = -hoveredNode.position - nodeDimension;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -287,7 +286,7 @@ namespace XNodeEditor {
|
|||||||
case EventType.KeyDown:
|
case EventType.KeyDown:
|
||||||
if (EditorGUIUtility.editingTextField) break;
|
if (EditorGUIUtility.editingTextField) break;
|
||||||
else if (e.keyCode == KeyCode.F) Home();
|
else if (e.keyCode == KeyCode.F) Home();
|
||||||
if (SystemInfo.operatingSystemFamily == OperatingSystemFamily.MacOSX) {
|
if (IsMac()) {
|
||||||
if (e.keyCode == KeyCode.Return) RenameSelectedNode();
|
if (e.keyCode == KeyCode.Return) RenameSelectedNode();
|
||||||
} else {
|
} else {
|
||||||
if (e.keyCode == KeyCode.F2) RenameSelectedNode();
|
if (e.keyCode == KeyCode.F2) RenameSelectedNode();
|
||||||
@ -298,7 +297,7 @@ namespace XNodeEditor {
|
|||||||
if (e.commandName == "SoftDelete") {
|
if (e.commandName == "SoftDelete") {
|
||||||
if (e.type == EventType.ExecuteCommand) RemoveSelectedNodes();
|
if (e.type == EventType.ExecuteCommand) RemoveSelectedNodes();
|
||||||
e.Use();
|
e.Use();
|
||||||
} else if (SystemInfo.operatingSystemFamily == OperatingSystemFamily.MacOSX && e.commandName == "Delete") {
|
} else if (IsMac() && e.commandName == "Delete") {
|
||||||
if (e.type == EventType.ExecuteCommand) RemoveSelectedNodes();
|
if (e.type == EventType.ExecuteCommand) RemoveSelectedNodes();
|
||||||
e.Use();
|
e.Use();
|
||||||
} else if (e.commandName == "Duplicate") {
|
} else if (e.commandName == "Duplicate") {
|
||||||
@ -317,6 +316,14 @@ namespace XNodeEditor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsMac() {
|
||||||
|
#if UNITY_2017_1_OR_NEWER
|
||||||
|
return SystemInfo.operatingSystemFamily == OperatingSystemFamily.MacOSX;
|
||||||
|
#else
|
||||||
|
return SystemInfo.operatingSystem.StartsWith("Mac");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
private void RecalculateDragOffsets(Event current) {
|
private void RecalculateDragOffsets(Event current) {
|
||||||
dragOffset = new Vector2[Selection.objects.Length + selectedReroutes.Count];
|
dragOffset = new Vector2[Selection.objects.Length + selectedReroutes.Count];
|
||||||
// Selected nodes
|
// Selected nodes
|
||||||
@ -420,7 +427,7 @@ namespace XNodeEditor {
|
|||||||
Rect fromRect;
|
Rect fromRect;
|
||||||
if (!_portConnectionPoints.TryGetValue(draggedOutput, out fromRect)) return;
|
if (!_portConnectionPoints.TryGetValue(draggedOutput, out fromRect)) return;
|
||||||
Vector2 from = fromRect.center;
|
Vector2 from = fromRect.center;
|
||||||
col.a = 0.6f;
|
col.a = draggedOutputTarget != null ? 1.0f : 0.6f;
|
||||||
Vector2 to = Vector2.zero;
|
Vector2 to = Vector2.zero;
|
||||||
for (int i = 0; i < draggedOutputReroutes.Count; i++) {
|
for (int i = 0; i < draggedOutputReroutes.Count; i++) {
|
||||||
to = draggedOutputReroutes[i];
|
to = draggedOutputReroutes[i];
|
||||||
|
|||||||
@ -241,12 +241,10 @@ namespace XNodeEditor {
|
|||||||
selectionCache = new List<UnityEngine.Object>(Selection.objects);
|
selectionCache = new List<UnityEngine.Object>(Selection.objects);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Active node is hashed before and after node GUI to detect changes
|
|
||||||
int nodeHash = 0;
|
|
||||||
System.Reflection.MethodInfo onValidate = null;
|
System.Reflection.MethodInfo onValidate = null;
|
||||||
if (Selection.activeObject != null && Selection.activeObject is XNode.Node) {
|
if (Selection.activeObject != null && Selection.activeObject is XNode.Node) {
|
||||||
onValidate = Selection.activeObject.GetType().GetMethod("OnValidate");
|
onValidate = Selection.activeObject.GetType().GetMethod("OnValidate");
|
||||||
if (onValidate != null) nodeHash = Selection.activeObject.GetHashCode();
|
if (onValidate != null) EditorGUI.BeginChangeCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
BeginZoomed(position, zoom, topPadding);
|
BeginZoomed(position, zoom, topPadding);
|
||||||
@ -383,12 +381,10 @@ namespace XNodeEditor {
|
|||||||
if (e.type != EventType.Layout && currentActivity == NodeActivity.DragGrid) Selection.objects = preSelection.ToArray();
|
if (e.type != EventType.Layout && currentActivity == NodeActivity.DragGrid) Selection.objects = preSelection.ToArray();
|
||||||
EndZoomed(position, zoom, topPadding);
|
EndZoomed(position, zoom, topPadding);
|
||||||
|
|
||||||
//If a change in hash is detected in the selected node, call OnValidate method.
|
//If a change in is detected in the selected node, call OnValidate method.
|
||||||
//This is done through reflection because OnValidate is only relevant in editor,
|
//This is done through reflection because OnValidate is only relevant in editor,
|
||||||
//and thus, the code should not be included in build.
|
//and thus, the code should not be included in build.
|
||||||
if (nodeHash != 0) {
|
if (onValidate != null && EditorGUI.EndChangeCheck()) onValidate.Invoke(Selection.activeObject, null);
|
||||||
if (onValidate != null && nodeHash != Selection.activeObject.GetHashCode()) onValidate.Invoke(Selection.activeObject, null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ShouldBeCulled(XNode.Node node) {
|
private bool ShouldBeCulled(XNode.Node node) {
|
||||||
|
|||||||
@ -154,7 +154,7 @@ namespace XNodeEditor {
|
|||||||
|
|
||||||
private static System.Type GetType(SerializedProperty property) {
|
private static System.Type GetType(SerializedProperty property) {
|
||||||
System.Type parentType = property.serializedObject.targetObject.GetType();
|
System.Type parentType = property.serializedObject.targetObject.GetType();
|
||||||
System.Reflection.FieldInfo fi = parentType.GetField(property.propertyPath);
|
System.Reflection.FieldInfo fi = NodeEditorWindow.GetFieldInfo(property.serializedObject.targetObject.GetType(), property.name);
|
||||||
return fi.FieldType;
|
return fi.FieldType;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,7 +260,7 @@ namespace XNodeEditor {
|
|||||||
/// <param name="serializedObject">The serializedObject of the node</param>
|
/// <param name="serializedObject">The serializedObject of the node</param>
|
||||||
/// <param name="connectionType">Connection type of added instance ports</param>
|
/// <param name="connectionType">Connection type of added instance ports</param>
|
||||||
/// <param name="onCreation">Called on the list on creation. Use this if you want to customize the created ReorderableList</param>
|
/// <param name="onCreation">Called on the list on creation. Use this if you want to customize the created ReorderableList</param>
|
||||||
public static void InstancePortList(string fieldName, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType = XNode.Node.ConnectionType.Multiple, Action<ReorderableList> onCreation = null) {
|
public static void InstancePortList(string fieldName, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType = XNode.Node.ConnectionType.Multiple, XNode.Node.TypeConstraint typeConstraint = XNode.Node.TypeConstraint.None, Action<ReorderableList> onCreation = null) {
|
||||||
XNode.Node node = serializedObject.targetObject as XNode.Node;
|
XNode.Node node = serializedObject.targetObject as XNode.Node;
|
||||||
|
|
||||||
Predicate<string> isMatchingInstancePort =
|
Predicate<string> isMatchingInstancePort =
|
||||||
@ -279,7 +279,7 @@ namespace XNodeEditor {
|
|||||||
// If a ReorderableList isn't cached for this array, do so.
|
// If a ReorderableList isn't cached for this array, do so.
|
||||||
if (list == null) {
|
if (list == null) {
|
||||||
SerializedProperty arrayData = serializedObject.FindProperty(fieldName);
|
SerializedProperty arrayData = serializedObject.FindProperty(fieldName);
|
||||||
list = CreateReorderableList(fieldName, instancePorts, arrayData, type, serializedObject, io, connectionType, onCreation);
|
list = CreateReorderableList(fieldName, instancePorts, arrayData, type, serializedObject, io, connectionType, typeConstraint, onCreation);
|
||||||
if (reorderableListCache.TryGetValue(serializedObject.targetObject, out rlc)) rlc.Add(fieldName, list);
|
if (reorderableListCache.TryGetValue(serializedObject.targetObject, out rlc)) rlc.Add(fieldName, list);
|
||||||
else reorderableListCache.Add(serializedObject.targetObject, new Dictionary<string, ReorderableList>() { { fieldName, list } });
|
else reorderableListCache.Add(serializedObject.targetObject, new Dictionary<string, ReorderableList>() { { fieldName, list } });
|
||||||
}
|
}
|
||||||
@ -287,7 +287,7 @@ namespace XNodeEditor {
|
|||||||
list.DoLayoutList();
|
list.DoLayoutList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ReorderableList CreateReorderableList(string fieldName, List<XNode.NodePort> instancePorts, SerializedProperty arrayData, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType, Action<ReorderableList> onCreation) {
|
private static ReorderableList CreateReorderableList(string fieldName, List<XNode.NodePort> instancePorts, SerializedProperty arrayData, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType, XNode.Node.TypeConstraint typeConstraint, Action<ReorderableList> onCreation) {
|
||||||
bool hasArrayData = arrayData != null && arrayData.isArray;
|
bool hasArrayData = arrayData != null && arrayData.isArray;
|
||||||
int arraySize = hasArrayData ? arrayData.arraySize : 0;
|
int arraySize = hasArrayData ? arrayData.arraySize : 0;
|
||||||
XNode.Node node = serializedObject.targetObject as XNode.Node;
|
XNode.Node node = serializedObject.targetObject as XNode.Node;
|
||||||
@ -375,8 +375,8 @@ namespace XNodeEditor {
|
|||||||
int i = 0;
|
int i = 0;
|
||||||
while (node.HasPort(newName)) newName = fieldName + " " + (++i);
|
while (node.HasPort(newName)) newName = fieldName + " " + (++i);
|
||||||
|
|
||||||
if (io == XNode.NodePort.IO.Output) node.AddInstanceOutput(type, connectionType, newName);
|
if (io == XNode.NodePort.IO.Output) node.AddInstanceOutput(type, connectionType, XNode.Node.TypeConstraint.None, newName);
|
||||||
else node.AddInstanceInput(type, connectionType, newName);
|
else node.AddInstanceInput(type, connectionType, typeConstraint, newName);
|
||||||
serializedObject.Update();
|
serializedObject.Update();
|
||||||
EditorUtility.SetDirty(node);
|
EditorUtility.SetDirty(node);
|
||||||
if (hasArrayData) arrayData.InsertArrayElementAtIndex(arraySize);
|
if (hasArrayData) arrayData.InsertArrayElementAtIndex(arraySize);
|
||||||
@ -422,8 +422,8 @@ namespace XNodeEditor {
|
|||||||
string newName = arrayData.name + " 0";
|
string newName = arrayData.name + " 0";
|
||||||
int i = 0;
|
int i = 0;
|
||||||
while (node.HasPort(newName)) newName = arrayData.name + " " + (++i);
|
while (node.HasPort(newName)) newName = arrayData.name + " " + (++i);
|
||||||
if (io == XNode.NodePort.IO.Output) node.AddInstanceOutput(type, connectionType, newName);
|
if (io == XNode.NodePort.IO.Output) node.AddInstanceOutput(type, connectionType, typeConstraint, newName);
|
||||||
else node.AddInstanceInput(type, connectionType, newName);
|
else node.AddInstanceInput(type, connectionType, typeConstraint, newName);
|
||||||
EditorUtility.SetDirty(node);
|
EditorUtility.SetDirty(node);
|
||||||
instancePortCount++;
|
instancePortCount++;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -81,7 +81,20 @@ namespace XNodeEditor {
|
|||||||
return settings[lastKey];
|
return settings[lastKey];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if UNITY_2019_1_OR_NEWER
|
||||||
|
[SettingsProvider]
|
||||||
|
public static SettingsProvider CreateXNodeSettingsProvider() {
|
||||||
|
SettingsProvider provider = new SettingsProvider("Preferences/Node Editor", SettingsScope.User) {
|
||||||
|
guiHandler = (searchContext) => { XNodeEditor.NodeEditorPreferences.PreferencesGUI(); },
|
||||||
|
keywords = new HashSet<string>(new [] { "xNode", "node", "editor", "graph", "connections", "noodles", "ports" })
|
||||||
|
};
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if !UNITY_2019_1_OR_NEWER
|
||||||
[PreferenceItem("Node Editor")]
|
[PreferenceItem("Node Editor")]
|
||||||
|
#endif
|
||||||
private static void PreferencesGUI() {
|
private static void PreferencesGUI() {
|
||||||
VerifyLoaded();
|
VerifyLoaded();
|
||||||
Settings settings = NodeEditorPreferences.settings[lastKey];
|
Settings settings = NodeEditorPreferences.settings[lastKey];
|
||||||
|
|||||||
@ -42,9 +42,9 @@ namespace XNodeEditor {
|
|||||||
public static Dictionary<Type, Color> GetNodeTint() {
|
public static Dictionary<Type, Color> GetNodeTint() {
|
||||||
Dictionary<Type, Color> tints = new Dictionary<Type, Color>();
|
Dictionary<Type, Color> tints = new Dictionary<Type, Color>();
|
||||||
for (int i = 0; i < nodeTypes.Length; i++) {
|
for (int i = 0; i < nodeTypes.Length; i++) {
|
||||||
var attribs = nodeTypes[i].GetCustomAttributes(typeof(XNode.Node.NodeTint), true);
|
var attribs = nodeTypes[i].GetCustomAttributes(typeof(XNode.Node.NodeTintAttribute), true);
|
||||||
if (attribs == null || attribs.Length == 0) continue;
|
if (attribs == null || attribs.Length == 0) continue;
|
||||||
XNode.Node.NodeTint attrib = attribs[0] as XNode.Node.NodeTint;
|
XNode.Node.NodeTintAttribute attrib = attribs[0] as XNode.Node.NodeTintAttribute;
|
||||||
tints.Add(nodeTypes[i], attrib.color);
|
tints.Add(nodeTypes[i], attrib.color);
|
||||||
}
|
}
|
||||||
return tints;
|
return tints;
|
||||||
@ -53,14 +53,23 @@ namespace XNodeEditor {
|
|||||||
public static Dictionary<Type, int> GetNodeWidth() {
|
public static Dictionary<Type, int> GetNodeWidth() {
|
||||||
Dictionary<Type, int> widths = new Dictionary<Type, int>();
|
Dictionary<Type, int> widths = new Dictionary<Type, int>();
|
||||||
for (int i = 0; i < nodeTypes.Length; i++) {
|
for (int i = 0; i < nodeTypes.Length; i++) {
|
||||||
var attribs = nodeTypes[i].GetCustomAttributes(typeof(XNode.Node.NodeWidth), true);
|
var attribs = nodeTypes[i].GetCustomAttributes(typeof(XNode.Node.NodeWidthAttribute), true);
|
||||||
if (attribs == null || attribs.Length == 0) continue;
|
if (attribs == null || attribs.Length == 0) continue;
|
||||||
XNode.Node.NodeWidth attrib = attribs[0] as XNode.Node.NodeWidth;
|
XNode.Node.NodeWidthAttribute attrib = attribs[0] as XNode.Node.NodeWidthAttribute;
|
||||||
widths.Add(nodeTypes[i], attrib.width);
|
widths.Add(nodeTypes[i], attrib.width);
|
||||||
}
|
}
|
||||||
return widths;
|
return widths;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary> Get FieldInfo of a field, including those that are private and/or inherited </summary>
|
||||||
|
public static FieldInfo GetFieldInfo(Type type, string fieldName) {
|
||||||
|
// If we can't find field in the first run, it's probably a private field in a base class.
|
||||||
|
FieldInfo field = type.GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
|
// Search base classes for private fields only. Public fields are found above
|
||||||
|
while (field == null && (type = type.BaseType) != typeof(XNode.Node)) field = type.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary> Get all classes deriving from baseType via reflection </summary>
|
/// <summary> Get all classes deriving from baseType via reflection </summary>
|
||||||
public static Type[] GetDerivedTypes(Type baseType) {
|
public static Type[] GetDerivedTypes(Type baseType) {
|
||||||
List<System.Type> types = new List<System.Type>();
|
List<System.Type> types = new List<System.Type>();
|
||||||
|
|||||||
@ -36,9 +36,7 @@ namespace XNodeEditor {
|
|||||||
|
|
||||||
public static bool GetAttrib<T>(Type classType, string fieldName, out T attribOut) where T : Attribute {
|
public static bool GetAttrib<T>(Type classType, string fieldName, out T attribOut) where T : Attribute {
|
||||||
// If we can't find field in the first run, it's probably a private field in a base class.
|
// If we can't find field in the first run, it's probably a private field in a base class.
|
||||||
FieldInfo field = classType.GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
FieldInfo field = NodeEditorWindow.GetFieldInfo(classType, fieldName);
|
||||||
// Search base classes for private fields only. Public fields are found above
|
|
||||||
while (field == null && (classType = classType.BaseType) != typeof(XNode.Node)) field = classType.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
|
|
||||||
// This shouldn't happen. Ever.
|
// This shouldn't happen. Ever.
|
||||||
if (field == null) {
|
if (field == null) {
|
||||||
Debug.LogWarning("Field " + fieldName + " couldnt be found");
|
Debug.LogWarning("Field " + fieldName + " couldnt be found");
|
||||||
|
|||||||
@ -41,6 +41,16 @@ namespace XNode {
|
|||||||
Override,
|
Override,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary> Tells which types of input to allow </summary>
|
||||||
|
public enum TypeConstraint {
|
||||||
|
/// <summary> Allow all types of input</summary>
|
||||||
|
None,
|
||||||
|
/// <summary> Allow similar and inherited types </summary>
|
||||||
|
Inherited,
|
||||||
|
/// <summary> Allow only similar types </summary>
|
||||||
|
Strict,
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary> Iterate over all ports on this node. </summary>
|
/// <summary> Iterate over all ports on this node. </summary>
|
||||||
public IEnumerable<NodePort> Ports { get { foreach (NodePort port in ports.Values) yield return port; } }
|
public IEnumerable<NodePort> Ports { get { foreach (NodePort port in ports.Values) yield return port; } }
|
||||||
/// <summary> Iterate over all outputs on this node. </summary>
|
/// <summary> Iterate over all outputs on this node. </summary>
|
||||||
@ -63,7 +73,6 @@ namespace XNode {
|
|||||||
/// <summary> Used during node instantiation to fix null/misconfigured graph during OnEnable/Init. Set it before instantiating a node. Will automatically be unset during OnEnable </summary>
|
/// <summary> Used during node instantiation to fix null/misconfigured graph during OnEnable/Init. Set it before instantiating a node. Will automatically be unset during OnEnable </summary>
|
||||||
public static NodeGraph graphHotfix;
|
public static NodeGraph graphHotfix;
|
||||||
|
|
||||||
|
|
||||||
protected void OnEnable() {
|
protected void OnEnable() {
|
||||||
if (graphHotfix != null) graph = graphHotfix;
|
if (graphHotfix != null) graph = graphHotfix;
|
||||||
graphHotfix = null;
|
graphHotfix = null;
|
||||||
@ -88,21 +97,21 @@ namespace XNode {
|
|||||||
/// <summary> Convenience function. </summary>
|
/// <summary> Convenience function. </summary>
|
||||||
/// <seealso cref="AddInstancePort"/>
|
/// <seealso cref="AddInstancePort"/>
|
||||||
/// <seealso cref="AddInstanceOutput"/>
|
/// <seealso cref="AddInstanceOutput"/>
|
||||||
public NodePort AddInstanceInput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, string fieldName = null) {
|
public NodePort AddInstanceInput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) {
|
||||||
return AddInstancePort(type, NodePort.IO.Input, connectionType, fieldName);
|
return AddInstancePort(type, NodePort.IO.Input, connectionType, typeConstraint, fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Convenience function. </summary>
|
/// <summary> Convenience function. </summary>
|
||||||
/// <seealso cref="AddInstancePort"/>
|
/// <seealso cref="AddInstancePort"/>
|
||||||
/// <seealso cref="AddInstanceInput"/>
|
/// <seealso cref="AddInstanceInput"/>
|
||||||
public NodePort AddInstanceOutput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, string fieldName = null) {
|
public NodePort AddInstanceOutput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) {
|
||||||
return AddInstancePort(type, NodePort.IO.Output, connectionType, fieldName);
|
return AddInstancePort(type, NodePort.IO.Output, connectionType, typeConstraint, fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Add a dynamic, serialized port to this node. </summary>
|
/// <summary> Add a dynamic, serialized port to this node. </summary>
|
||||||
/// <seealso cref="AddInstanceInput"/>
|
/// <seealso cref="AddInstanceInput"/>
|
||||||
/// <seealso cref="AddInstanceOutput"/>
|
/// <seealso cref="AddInstanceOutput"/>
|
||||||
private NodePort AddInstancePort(Type type, NodePort.IO direction, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, string fieldName = null) {
|
private NodePort AddInstancePort(Type type, NodePort.IO direction, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) {
|
||||||
if (fieldName == null) {
|
if (fieldName == null) {
|
||||||
fieldName = "instanceInput_0";
|
fieldName = "instanceInput_0";
|
||||||
int i = 0;
|
int i = 0;
|
||||||
@ -111,7 +120,7 @@ namespace XNode {
|
|||||||
Debug.LogWarning("Port '" + fieldName + "' already exists in " + name, this);
|
Debug.LogWarning("Port '" + fieldName + "' already exists in " + name, this);
|
||||||
return ports[fieldName];
|
return ports[fieldName];
|
||||||
}
|
}
|
||||||
NodePort port = new NodePort(fieldName, type, direction, connectionType, this);
|
NodePort port = new NodePort(fieldName, type, direction, connectionType, typeConstraint, this);
|
||||||
ports.Add(fieldName, port);
|
ports.Add(fieldName, port);
|
||||||
return port;
|
return port;
|
||||||
}
|
}
|
||||||
@ -206,24 +215,25 @@ namespace XNode {
|
|||||||
foreach (NodePort port in Ports) port.ClearConnections();
|
foreach (NodePort port in Ports) port.ClearConnections();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override int GetHashCode() {
|
#region Attributes
|
||||||
return JsonUtility.ToJson(this).GetHashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary> Mark a serializable field as an input port. You can access this through <see cref="GetInputPort(string)"/> </summary>
|
/// <summary> Mark a serializable field as an input port. You can access this through <see cref="GetInputPort(string)"/> </summary>
|
||||||
[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
|
[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
|
||||||
public class InputAttribute : Attribute {
|
public class InputAttribute : Attribute {
|
||||||
public ShowBackingValue backingValue;
|
public ShowBackingValue backingValue;
|
||||||
public ConnectionType connectionType;
|
public ConnectionType connectionType;
|
||||||
public bool instancePortList;
|
public bool instancePortList;
|
||||||
|
public TypeConstraint typeConstraint;
|
||||||
|
|
||||||
/// <summary> Mark a serializable field as an input port. You can access this through <see cref="GetInputPort(string)"/> </summary>
|
/// <summary> Mark a serializable field as an input port. You can access this through <see cref="GetInputPort(string)"/> </summary>
|
||||||
/// <param name="backingValue">Should we display the backing value for this port as an editor field? </param>
|
/// <param name="backingValue">Should we display the backing value for this port as an editor field? </param>
|
||||||
/// <param name="connectionType">Should we allow multiple connections? </param>
|
/// <param name="connectionType">Should we allow multiple connections? </param>
|
||||||
public InputAttribute(ShowBackingValue backingValue = ShowBackingValue.Unconnected, ConnectionType connectionType = ConnectionType.Multiple, bool instancePortList = false) {
|
/// <param name="typeConstraint">Constrains which input connections can be made to this port </param>
|
||||||
|
/// <param name="instancePortList">If true, will display a reorderable list of inputs instead of a single port. Will automatically add and display values for lists and arrays </param>
|
||||||
|
public InputAttribute(ShowBackingValue backingValue = ShowBackingValue.Unconnected, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None, bool instancePortList = false) {
|
||||||
this.backingValue = backingValue;
|
this.backingValue = backingValue;
|
||||||
this.connectionType = connectionType;
|
this.connectionType = connectionType;
|
||||||
this.instancePortList = instancePortList;
|
this.instancePortList = instancePortList;
|
||||||
|
this.typeConstraint = typeConstraint;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -237,6 +247,7 @@ namespace XNode {
|
|||||||
/// <summary> Mark a serializable field as an output port. You can access this through <see cref="GetOutputPort(string)"/> </summary>
|
/// <summary> Mark a serializable field as an output port. You can access this through <see cref="GetOutputPort(string)"/> </summary>
|
||||||
/// <param name="backingValue">Should we display the backing value for this port as an editor field? </param>
|
/// <param name="backingValue">Should we display the backing value for this port as an editor field? </param>
|
||||||
/// <param name="connectionType">Should we allow multiple connections? </param>
|
/// <param name="connectionType">Should we allow multiple connections? </param>
|
||||||
|
/// <param name="instancePortList">If true, will display a reorderable list of outputs instead of a single port. Will automatically add and display values for lists and arrays </param>
|
||||||
public OutputAttribute(ShowBackingValue backingValue = ShowBackingValue.Never, ConnectionType connectionType = ConnectionType.Multiple, bool instancePortList = false) {
|
public OutputAttribute(ShowBackingValue backingValue = ShowBackingValue.Never, ConnectionType connectionType = ConnectionType.Multiple, bool instancePortList = false) {
|
||||||
this.backingValue = backingValue;
|
this.backingValue = backingValue;
|
||||||
this.connectionType = connectionType;
|
this.connectionType = connectionType;
|
||||||
@ -255,19 +266,19 @@ namespace XNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
|
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
|
||||||
public class NodeTint : Attribute {
|
public class NodeTintAttribute : Attribute {
|
||||||
public Color color;
|
public Color color;
|
||||||
/// <summary> Specify a color for this node type </summary>
|
/// <summary> Specify a color for this node type </summary>
|
||||||
/// <param name="r"> Red [0.0f .. 1.0f] </param>
|
/// <param name="r"> Red [0.0f .. 1.0f] </param>
|
||||||
/// <param name="g"> Green [0.0f .. 1.0f] </param>
|
/// <param name="g"> Green [0.0f .. 1.0f] </param>
|
||||||
/// <param name="b"> Blue [0.0f .. 1.0f] </param>
|
/// <param name="b"> Blue [0.0f .. 1.0f] </param>
|
||||||
public NodeTint(float r, float g, float b) {
|
public NodeTintAttribute(float r, float g, float b) {
|
||||||
color = new Color(r, g, b);
|
color = new Color(r, g, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Specify a color for this node type </summary>
|
/// <summary> Specify a color for this node type </summary>
|
||||||
/// <param name="hex"> HEX color value </param>
|
/// <param name="hex"> HEX color value </param>
|
||||||
public NodeTint(string hex) {
|
public NodeTintAttribute(string hex) {
|
||||||
ColorUtility.TryParseHtmlString(hex, out color);
|
ColorUtility.TryParseHtmlString(hex, out color);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -275,20 +286,21 @@ namespace XNode {
|
|||||||
/// <param name="r"> Red [0 .. 255] </param>
|
/// <param name="r"> Red [0 .. 255] </param>
|
||||||
/// <param name="g"> Green [0 .. 255] </param>
|
/// <param name="g"> Green [0 .. 255] </param>
|
||||||
/// <param name="b"> Blue [0 .. 255] </param>
|
/// <param name="b"> Blue [0 .. 255] </param>
|
||||||
public NodeTint(byte r, byte g, byte b) {
|
public NodeTintAttribute(byte r, byte g, byte b) {
|
||||||
color = new Color32(r, g, b, byte.MaxValue);
|
color = new Color32(r, g, b, byte.MaxValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
|
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
|
||||||
public class NodeWidth : Attribute {
|
public class NodeWidthAttribute : Attribute {
|
||||||
public int width;
|
public int width;
|
||||||
/// <summary> Specify a width for this node type </summary>
|
/// <summary> Specify a width for this node type </summary>
|
||||||
/// <param name="width"> Width </param>
|
/// <param name="width"> Width </param>
|
||||||
public NodeWidth(int width) {
|
public NodeWidthAttribute(int width) {
|
||||||
this.width = width;
|
this.width = width;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
[Serializable] private class NodePortDictionary : Dictionary<string, NodePort>, ISerializationCallbackReceiver {
|
[Serializable] private class NodePortDictionary : Dictionary<string, NodePort>, ISerializationCallbackReceiver {
|
||||||
[SerializeField] private List<string> keys = new List<string>();
|
[SerializeField] private List<string> keys = new List<string>();
|
||||||
|
|||||||
@ -30,7 +30,7 @@ namespace XNode {
|
|||||||
NodePort staticPort;
|
NodePort staticPort;
|
||||||
if (staticPorts.TryGetValue(port.fieldName, out staticPort)) {
|
if (staticPorts.TryGetValue(port.fieldName, out staticPort)) {
|
||||||
// If port exists but with wrong settings, remove it. Re-add it later.
|
// If port exists but with wrong settings, remove it. Re-add it later.
|
||||||
if (port.connectionType != staticPort.connectionType || port.IsDynamic || port.direction != staticPort.direction) ports.Remove(port.fieldName);
|
if (port.connectionType != staticPort.connectionType || port.IsDynamic || port.direction != staticPort.direction || port.typeConstraint != staticPort.typeConstraint) ports.Remove(port.fieldName);
|
||||||
else port.ValueType = staticPort.ValueType;
|
else port.ValueType = staticPort.ValueType;
|
||||||
}
|
}
|
||||||
// If port doesn't exist anymore, remove it
|
// If port doesn't exist anymore, remove it
|
||||||
|
|||||||
@ -21,6 +21,7 @@ namespace XNode {
|
|||||||
|
|
||||||
public IO direction { get { return _direction; } }
|
public IO direction { get { return _direction; } }
|
||||||
public Node.ConnectionType connectionType { get { return _connectionType; } }
|
public Node.ConnectionType connectionType { get { return _connectionType; } }
|
||||||
|
public Node.TypeConstraint typeConstraint { get { return _typeConstraint; } }
|
||||||
|
|
||||||
/// <summary> Is this port connected to anytihng? </summary>
|
/// <summary> Is this port connected to anytihng? </summary>
|
||||||
public bool IsConnected { get { return connections.Count != 0; } }
|
public bool IsConnected { get { return connections.Count != 0; } }
|
||||||
@ -49,6 +50,7 @@ namespace XNode {
|
|||||||
[SerializeField] private List<PortConnection> connections = new List<PortConnection>();
|
[SerializeField] private List<PortConnection> connections = new List<PortConnection>();
|
||||||
[SerializeField] private IO _direction;
|
[SerializeField] private IO _direction;
|
||||||
[SerializeField] private Node.ConnectionType _connectionType;
|
[SerializeField] private Node.ConnectionType _connectionType;
|
||||||
|
[SerializeField] private Node.TypeConstraint _typeConstraint;
|
||||||
[SerializeField] private bool _dynamic;
|
[SerializeField] private bool _dynamic;
|
||||||
|
|
||||||
/// <summary> Construct a static targetless nodeport. Used as a template. </summary>
|
/// <summary> Construct a static targetless nodeport. Used as a template. </summary>
|
||||||
@ -61,6 +63,7 @@ namespace XNode {
|
|||||||
if (attribs[i] is Node.InputAttribute) {
|
if (attribs[i] is Node.InputAttribute) {
|
||||||
_direction = IO.Input;
|
_direction = IO.Input;
|
||||||
_connectionType = (attribs[i] as Node.InputAttribute).connectionType;
|
_connectionType = (attribs[i] as Node.InputAttribute).connectionType;
|
||||||
|
_typeConstraint = (attribs[i] as Node.InputAttribute).typeConstraint;
|
||||||
} else if (attribs[i] is Node.OutputAttribute) {
|
} else if (attribs[i] is Node.OutputAttribute) {
|
||||||
_direction = IO.Output;
|
_direction = IO.Output;
|
||||||
_connectionType = (attribs[i] as Node.OutputAttribute).connectionType;
|
_connectionType = (attribs[i] as Node.OutputAttribute).connectionType;
|
||||||
@ -75,17 +78,19 @@ namespace XNode {
|
|||||||
_direction = nodePort.direction;
|
_direction = nodePort.direction;
|
||||||
_dynamic = nodePort._dynamic;
|
_dynamic = nodePort._dynamic;
|
||||||
_connectionType = nodePort._connectionType;
|
_connectionType = nodePort._connectionType;
|
||||||
|
_typeConstraint = nodePort._typeConstraint;
|
||||||
_node = node;
|
_node = node;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Construct a dynamic port. Dynamic ports are not forgotten on reimport, and is ideal for runtime-created ports. </summary>
|
/// <summary> Construct a dynamic port. Dynamic ports are not forgotten on reimport, and is ideal for runtime-created ports. </summary>
|
||||||
public NodePort(string fieldName, Type type, IO direction, Node.ConnectionType connectionType, Node node) {
|
public NodePort(string fieldName, Type type, IO direction, Node.ConnectionType connectionType, Node.TypeConstraint typeConstraint, Node node) {
|
||||||
_fieldName = fieldName;
|
_fieldName = fieldName;
|
||||||
this.ValueType = type;
|
this.ValueType = type;
|
||||||
_direction = direction;
|
_direction = direction;
|
||||||
_node = node;
|
_node = node;
|
||||||
_dynamic = true;
|
_dynamic = true;
|
||||||
_connectionType = connectionType;
|
_connectionType = connectionType;
|
||||||
|
_typeConstraint = typeConstraint;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Checks all connections for invalid references, and removes them. </summary>
|
/// <summary> Checks all connections for invalid references, and removes them. </summary>
|
||||||
@ -240,6 +245,23 @@ namespace XNode {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary> Returns true if this port can connect to specified port </summary>
|
||||||
|
public bool CanConnectTo(NodePort port) {
|
||||||
|
// Figure out which is input and which is output
|
||||||
|
NodePort input = null, output = null;
|
||||||
|
if (IsInput) input = this;
|
||||||
|
else output = this;
|
||||||
|
if (port.IsInput) input = port;
|
||||||
|
else output = port;
|
||||||
|
// If there isn't one of each, they can't connect
|
||||||
|
if (input == null || output == null) return false;
|
||||||
|
// Check type constraints
|
||||||
|
if (input.typeConstraint == XNode.Node.TypeConstraint.Inherited && !input.ValueType.IsAssignableFrom(output.ValueType)) return false;
|
||||||
|
if (input.typeConstraint == XNode.Node.TypeConstraint.Strict && input.ValueType != output.ValueType) return false;
|
||||||
|
// Success
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary> Disconnect this port from another port </summary>
|
/// <summary> Disconnect this port from another port </summary>
|
||||||
public void Disconnect(NodePort port) {
|
public void Disconnect(NodePort port) {
|
||||||
// Remove this ports connection to the other
|
// Remove this ports connection to the other
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user