NodeEditorGUILayout.cs 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Reflection;
  6. using UnityEditor;
  7. using UnityEditorInternal;
  8. using UnityEngine;
  9. namespace XNodeEditor {
  10. /// <summary> xNode-specific version of <see cref="EditorGUILayout"/> </summary>
  11. public static class NodeEditorGUILayout {
  12. private static readonly Dictionary<UnityEngine.Object, Dictionary<string, ReorderableList>> reorderableListCache = new Dictionary<UnityEngine.Object, Dictionary<string, ReorderableList>>();
  13. private static int reorderableListIndex = -1;
  14. /// <summary> Make a field for a serialized property. Automatically displays relevant node port. </summary>
  15. public static void PropertyField(SerializedProperty property, bool includeChildren = true, params GUILayoutOption[] options) {
  16. PropertyField(property, (GUIContent) null, includeChildren, options);
  17. }
  18. /// <summary> Make a field for a serialized property. Automatically displays relevant node port. </summary>
  19. public static void PropertyField(SerializedProperty property, GUIContent label, bool includeChildren = true, params GUILayoutOption[] options) {
  20. if (property == null) throw new NullReferenceException();
  21. XNode.Node node = property.serializedObject.targetObject as XNode.Node;
  22. XNode.NodePort port = node.GetPort(property.name);
  23. PropertyField(property, label, port, includeChildren);
  24. }
  25. /// <summary> Make a field for a serialized property. Manual node port override. </summary>
  26. public static void PropertyField(SerializedProperty property, XNode.NodePort port, bool includeChildren = true, params GUILayoutOption[] options) {
  27. PropertyField(property, null, port, includeChildren, options);
  28. }
  29. /// <summary> Make a field for a serialized property. Manual node port override. </summary>
  30. public static void PropertyField(SerializedProperty property, GUIContent label, XNode.NodePort port, bool includeChildren = true, params GUILayoutOption[] options) {
  31. if (property == null) throw new NullReferenceException();
  32. // If property is not a port, display a regular property field
  33. if (port == null) EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30));
  34. else {
  35. Rect rect = new Rect();
  36. List<PropertyAttribute> propertyAttributes = NodeEditorUtilities.GetCachedPropertyAttribs(port.node.GetType(), property.name);
  37. // If property is an input, display a regular property field and put a port handle on the left side
  38. if (port.direction == XNode.NodePort.IO.Input) {
  39. // Get data from [Input] attribute
  40. XNode.Node.ShowBackingValue showBacking = XNode.Node.ShowBackingValue.Unconnected;
  41. XNode.Node.InputAttribute inputAttribute;
  42. bool dynamicPortList = false;
  43. if (NodeEditorUtilities.GetCachedAttrib(port.node.GetType(), property.name, out inputAttribute)) {
  44. dynamicPortList = inputAttribute.dynamicPortList;
  45. showBacking = inputAttribute.backingValue;
  46. }
  47. bool usePropertyAttributes = dynamicPortList ||
  48. showBacking == XNode.Node.ShowBackingValue.Never ||
  49. (showBacking == XNode.Node.ShowBackingValue.Unconnected && port.IsConnected);
  50. float spacePadding = 0;
  51. foreach (var attr in propertyAttributes) {
  52. if (attr is SpaceAttribute) {
  53. if (usePropertyAttributes) GUILayout.Space((attr as SpaceAttribute).height);
  54. else spacePadding += (attr as SpaceAttribute).height;
  55. } else if (attr is HeaderAttribute) {
  56. if (usePropertyAttributes) {
  57. //GUI Values are from https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/ScriptAttributeGUI/Implementations/DecoratorDrawers.cs
  58. Rect position = GUILayoutUtility.GetRect(0, (EditorGUIUtility.singleLineHeight * 1.5f) - EditorGUIUtility.standardVerticalSpacing); //Layout adds standardVerticalSpacing after rect so we subtract it.
  59. position.yMin += EditorGUIUtility.singleLineHeight * 0.5f;
  60. position = EditorGUI.IndentedRect(position);
  61. GUI.Label(position, (attr as HeaderAttribute).header, EditorStyles.boldLabel);
  62. } else spacePadding += EditorGUIUtility.singleLineHeight * 1.5f;
  63. }
  64. }
  65. if (dynamicPortList) {
  66. Type type = GetType(property);
  67. XNode.Node.ConnectionType connectionType = inputAttribute != null ? inputAttribute.connectionType : XNode.Node.ConnectionType.Multiple;
  68. DynamicPortList(property.name, type, property.serializedObject, port.direction, connectionType);
  69. return;
  70. }
  71. switch (showBacking) {
  72. case XNode.Node.ShowBackingValue.Unconnected:
  73. // Display a label if port is connected
  74. if (port.IsConnected) EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName));
  75. // Display an editable property field if port is not connected
  76. else EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30));
  77. break;
  78. case XNode.Node.ShowBackingValue.Never:
  79. // Display a label
  80. EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName));
  81. break;
  82. case XNode.Node.ShowBackingValue.Always:
  83. // Display an editable property field
  84. EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30));
  85. break;
  86. }
  87. rect = GUILayoutUtility.GetLastRect();
  88. rect.position = rect.position - new Vector2(16, -spacePadding);
  89. // If property is an output, display a text label and put a port handle on the right side
  90. } else if (port.direction == XNode.NodePort.IO.Output) {
  91. // Get data from [Output] attribute
  92. XNode.Node.ShowBackingValue showBacking = XNode.Node.ShowBackingValue.Unconnected;
  93. XNode.Node.OutputAttribute outputAttribute;
  94. bool dynamicPortList = false;
  95. if (NodeEditorUtilities.GetCachedAttrib(port.node.GetType(), property.name, out outputAttribute)) {
  96. dynamicPortList = outputAttribute.dynamicPortList;
  97. showBacking = outputAttribute.backingValue;
  98. }
  99. bool usePropertyAttributes = dynamicPortList ||
  100. showBacking == XNode.Node.ShowBackingValue.Never ||
  101. (showBacking == XNode.Node.ShowBackingValue.Unconnected && port.IsConnected);
  102. float spacePadding = 0;
  103. foreach (var attr in propertyAttributes) {
  104. if (attr is SpaceAttribute) {
  105. if (usePropertyAttributes) GUILayout.Space((attr as SpaceAttribute).height);
  106. else spacePadding += (attr as SpaceAttribute).height;
  107. } else if (attr is HeaderAttribute) {
  108. if (usePropertyAttributes) {
  109. //GUI Values are from https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/ScriptAttributeGUI/Implementations/DecoratorDrawers.cs
  110. Rect position = GUILayoutUtility.GetRect(0, (EditorGUIUtility.singleLineHeight * 1.5f) - EditorGUIUtility.standardVerticalSpacing); //Layout adds standardVerticalSpacing after rect so we subtract it.
  111. position.yMin += EditorGUIUtility.singleLineHeight * 0.5f;
  112. position = EditorGUI.IndentedRect(position);
  113. GUI.Label(position, (attr as HeaderAttribute).header, EditorStyles.boldLabel);
  114. } else spacePadding += EditorGUIUtility.singleLineHeight * 1.5f;
  115. }
  116. }
  117. if (dynamicPortList) {
  118. Type type = GetType(property);
  119. XNode.Node.ConnectionType connectionType = outputAttribute != null ? outputAttribute.connectionType : XNode.Node.ConnectionType.Multiple;
  120. DynamicPortList(property.name, type, property.serializedObject, port.direction, connectionType);
  121. return;
  122. }
  123. switch (showBacking) {
  124. case XNode.Node.ShowBackingValue.Unconnected:
  125. // Display a label if port is connected
  126. if (port.IsConnected) EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName), NodeEditorResources.OutputPort, GUILayout.MinWidth(30));
  127. // Display an editable property field if port is not connected
  128. else EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30));
  129. break;
  130. case XNode.Node.ShowBackingValue.Never:
  131. // Display a label
  132. EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName), NodeEditorResources.OutputPort, GUILayout.MinWidth(30));
  133. break;
  134. case XNode.Node.ShowBackingValue.Always:
  135. // Display an editable property field
  136. EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30));
  137. break;
  138. }
  139. rect = GUILayoutUtility.GetLastRect();
  140. rect.position = rect.position + new Vector2(rect.width, spacePadding);
  141. }
  142. rect.size = new Vector2(16, 16);
  143. NodeEditor editor = NodeEditor.GetEditor(port.node, NodeEditorWindow.current);
  144. Color backgroundColor = editor.GetTint();
  145. Color col = NodeEditorWindow.current.graphEditor.GetPortColor(port);
  146. DrawPortHandle(rect, backgroundColor, col);
  147. // Register the handle position
  148. Vector2 portPos = rect.center;
  149. NodeEditor.portPositions[port] = portPos;
  150. }
  151. }
  152. private static System.Type GetType(SerializedProperty property) {
  153. System.Type parentType = property.serializedObject.targetObject.GetType();
  154. System.Reflection.FieldInfo fi = parentType.GetFieldInfo(property.name);
  155. return fi.FieldType;
  156. }
  157. /// <summary> Make a simple port field. </summary>
  158. public static void PortField(XNode.NodePort port, params GUILayoutOption[] options) {
  159. PortField(null, port, options);
  160. }
  161. /// <summary> Make a simple port field. </summary>
  162. public static void PortField(GUIContent label, XNode.NodePort port, params GUILayoutOption[] options) {
  163. if (port == null) return;
  164. if (options == null) options = new GUILayoutOption[] { GUILayout.MinWidth(30) };
  165. Vector2 position = Vector3.zero;
  166. GUIContent content = label != null ? label : new GUIContent(ObjectNames.NicifyVariableName(port.fieldName));
  167. // If property is an input, display a regular property field and put a port handle on the left side
  168. if (port.direction == XNode.NodePort.IO.Input) {
  169. // Display a label
  170. EditorGUILayout.LabelField(content, options);
  171. Rect rect = GUILayoutUtility.GetLastRect();
  172. position = rect.position - new Vector2(16, 0);
  173. }
  174. // If property is an output, display a text label and put a port handle on the right side
  175. else if (port.direction == XNode.NodePort.IO.Output) {
  176. // Display a label
  177. EditorGUILayout.LabelField(content, NodeEditorResources.OutputPort, options);
  178. Rect rect = GUILayoutUtility.GetLastRect();
  179. position = rect.position + new Vector2(rect.width, 0);
  180. }
  181. PortField(position, port);
  182. }
  183. /// <summary> Make a simple port field. </summary>
  184. public static void PortField(Vector2 position, XNode.NodePort port) {
  185. if (port == null) return;
  186. Rect rect = new Rect(position, new Vector2(16, 16));
  187. NodeEditor editor = NodeEditor.GetEditor(port.node, NodeEditorWindow.current);
  188. Color backgroundColor = editor.GetTint();
  189. Color col = NodeEditorWindow.current.graphEditor.GetPortColor(port);
  190. DrawPortHandle(rect, backgroundColor, col);
  191. // Register the handle position
  192. Vector2 portPos = rect.center;
  193. NodeEditor.portPositions[port] = portPos;
  194. }
  195. /// <summary> Add a port field to previous layout element. </summary>
  196. public static void AddPortField(XNode.NodePort port) {
  197. if (port == null) return;
  198. Rect rect = new Rect();
  199. // If property is an input, display a regular property field and put a port handle on the left side
  200. if (port.direction == XNode.NodePort.IO.Input) {
  201. rect = GUILayoutUtility.GetLastRect();
  202. rect.position = rect.position - new Vector2(16, 0);
  203. // If property is an output, display a text label and put a port handle on the right side
  204. } else if (port.direction == XNode.NodePort.IO.Output) {
  205. rect = GUILayoutUtility.GetLastRect();
  206. rect.position = rect.position + new Vector2(rect.width, 0);
  207. }
  208. rect.size = new Vector2(16, 16);
  209. NodeEditor editor = NodeEditor.GetEditor(port.node, NodeEditorWindow.current);
  210. Color backgroundColor = editor.GetTint();
  211. Color col = NodeEditorWindow.current.graphEditor.GetPortColor(port);
  212. DrawPortHandle(rect, backgroundColor, col);
  213. // Register the handle position
  214. Vector2 portPos = rect.center;
  215. NodeEditor.portPositions[port] = portPos;
  216. }
  217. /// <summary> Draws an input and an output port on the same line </summary>
  218. public static void PortPair(XNode.NodePort input, XNode.NodePort output) {
  219. GUILayout.BeginHorizontal();
  220. NodeEditorGUILayout.PortField(input, GUILayout.MinWidth(0));
  221. NodeEditorGUILayout.PortField(output, GUILayout.MinWidth(0));
  222. GUILayout.EndHorizontal();
  223. }
  224. public static void DrawPortHandle(Rect rect, Color backgroundColor, Color typeColor) {
  225. Color col = GUI.color;
  226. GUI.color = backgroundColor;
  227. GUI.DrawTexture(rect, NodeEditorResources.dotOuter);
  228. GUI.color = typeColor;
  229. GUI.DrawTexture(rect, NodeEditorResources.dot);
  230. GUI.color = col;
  231. }
  232. #region Obsolete
  233. [Obsolete("Use IsDynamicPortListPort instead")]
  234. public static bool IsInstancePortListPort(XNode.NodePort port) {
  235. return IsDynamicPortListPort(port);
  236. }
  237. [Obsolete("Use DynamicPortList instead")]
  238. 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) {
  239. DynamicPortList(fieldName, type, serializedObject, io, connectionType, typeConstraint, onCreation);
  240. }
  241. #endregion
  242. /// <summary> Is this port part of a DynamicPortList? </summary>
  243. public static bool IsDynamicPortListPort(XNode.NodePort port) {
  244. string[] parts = port.fieldName.Split(' ');
  245. if (parts.Length != 2) return false;
  246. Dictionary<string, ReorderableList> cache;
  247. if (reorderableListCache.TryGetValue(port.node, out cache)) {
  248. ReorderableList list;
  249. if (cache.TryGetValue(parts[0], out list)) return true;
  250. }
  251. return false;
  252. }
  253. /// <summary> Draw an editable list of dynamic ports. Port names are named as "[fieldName] [index]" </summary>
  254. /// <param name="fieldName">Supply a list for editable values</param>
  255. /// <param name="type">Value type of added dynamic ports</param>
  256. /// <param name="serializedObject">The serializedObject of the node</param>
  257. /// <param name="connectionType">Connection type of added dynamic ports</param>
  258. /// <param name="onCreation">Called on the list on creation. Use this if you want to customize the created ReorderableList</param>
  259. public static void DynamicPortList(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) {
  260. XNode.Node node = serializedObject.targetObject as XNode.Node;
  261. var indexedPorts = node.DynamicPorts.Select(x => {
  262. string[] split = x.fieldName.Split(' ');
  263. if (split != null && split.Length == 2 && split[0] == fieldName) {
  264. int i = -1;
  265. if (int.TryParse(split[1], out i)) {
  266. return new { index = i, port = x };
  267. }
  268. }
  269. return new { index = -1, port = (XNode.NodePort) null };
  270. }).Where(x => x.port != null);
  271. List<XNode.NodePort> dynamicPorts = indexedPorts.OrderBy(x => x.index).Select(x => x.port).ToList();
  272. ReorderableList list = null;
  273. Dictionary<string, ReorderableList> rlc;
  274. if (reorderableListCache.TryGetValue(serializedObject.targetObject, out rlc)) {
  275. if (!rlc.TryGetValue(fieldName, out list)) list = null;
  276. }
  277. // If a ReorderableList isn't cached for this array, do so.
  278. if (list == null) {
  279. SerializedProperty arrayData = serializedObject.FindProperty(fieldName);
  280. list = CreateReorderableList(fieldName, dynamicPorts, arrayData, type, serializedObject, io, connectionType, typeConstraint, onCreation);
  281. if (reorderableListCache.TryGetValue(serializedObject.targetObject, out rlc)) rlc.Add(fieldName, list);
  282. else reorderableListCache.Add(serializedObject.targetObject, new Dictionary<string, ReorderableList>() { { fieldName, list } });
  283. }
  284. list.list = dynamicPorts;
  285. list.DoLayoutList();
  286. }
  287. private static ReorderableList CreateReorderableList(string fieldName, List<XNode.NodePort> dynamicPorts, SerializedProperty arrayData, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType, XNode.Node.TypeConstraint typeConstraint, Action<ReorderableList> onCreation) {
  288. bool hasArrayData = arrayData != null && arrayData.isArray;
  289. XNode.Node node = serializedObject.targetObject as XNode.Node;
  290. ReorderableList list = new ReorderableList(dynamicPorts, null, true, true, true, true);
  291. string label = arrayData != null ? arrayData.displayName : ObjectNames.NicifyVariableName(fieldName);
  292. list.drawElementCallback =
  293. (Rect rect, int index, bool isActive, bool isFocused) => {
  294. XNode.NodePort port = node.GetPort(fieldName + " " + index);
  295. if (hasArrayData) {
  296. if (arrayData.arraySize <= index) {
  297. EditorGUI.LabelField(rect, "Array[" + index + "] data out of range");
  298. return;
  299. }
  300. SerializedProperty itemData = arrayData.GetArrayElementAtIndex(index);
  301. EditorGUI.PropertyField(rect, itemData, true);
  302. } else EditorGUI.LabelField(rect, port != null ? port.fieldName : "");
  303. if (port != null) {
  304. Vector2 pos = rect.position + (port.IsOutput?new Vector2(rect.width + 6, 0) : new Vector2(-36, 0));
  305. NodeEditorGUILayout.PortField(pos, port);
  306. }
  307. };
  308. list.elementHeightCallback =
  309. (int index) => {
  310. if (hasArrayData) {
  311. if (arrayData.arraySize <= index) return EditorGUIUtility.singleLineHeight;
  312. SerializedProperty itemData = arrayData.GetArrayElementAtIndex(index);
  313. return EditorGUI.GetPropertyHeight(itemData);
  314. } else return EditorGUIUtility.singleLineHeight;
  315. };
  316. list.drawHeaderCallback =
  317. (Rect rect) => {
  318. EditorGUI.LabelField(rect, label);
  319. };
  320. list.onSelectCallback =
  321. (ReorderableList rl) => {
  322. reorderableListIndex = rl.index;
  323. };
  324. list.onReorderCallback =
  325. (ReorderableList rl) => {
  326. // Move up
  327. if (rl.index > reorderableListIndex) {
  328. for (int i = reorderableListIndex; i < rl.index; ++i) {
  329. XNode.NodePort port = node.GetPort(fieldName + " " + i);
  330. XNode.NodePort nextPort = node.GetPort(fieldName + " " + (i + 1));
  331. port.SwapConnections(nextPort);
  332. // Swap cached positions to mitigate twitching
  333. Rect rect = NodeEditorWindow.current.portConnectionPoints[port];
  334. NodeEditorWindow.current.portConnectionPoints[port] = NodeEditorWindow.current.portConnectionPoints[nextPort];
  335. NodeEditorWindow.current.portConnectionPoints[nextPort] = rect;
  336. }
  337. }
  338. // Move down
  339. else {
  340. for (int i = reorderableListIndex; i > rl.index; --i) {
  341. XNode.NodePort port = node.GetPort(fieldName + " " + i);
  342. XNode.NodePort nextPort = node.GetPort(fieldName + " " + (i - 1));
  343. port.SwapConnections(nextPort);
  344. // Swap cached positions to mitigate twitching
  345. Rect rect = NodeEditorWindow.current.portConnectionPoints[port];
  346. NodeEditorWindow.current.portConnectionPoints[port] = NodeEditorWindow.current.portConnectionPoints[nextPort];
  347. NodeEditorWindow.current.portConnectionPoints[nextPort] = rect;
  348. }
  349. }
  350. // Apply changes
  351. serializedObject.ApplyModifiedProperties();
  352. serializedObject.Update();
  353. // Move array data if there is any
  354. if (hasArrayData) {
  355. arrayData.MoveArrayElement(reorderableListIndex, rl.index);
  356. }
  357. // Apply changes
  358. serializedObject.ApplyModifiedProperties();
  359. serializedObject.Update();
  360. NodeEditorWindow.current.Repaint();
  361. EditorApplication.delayCall += NodeEditorWindow.current.Repaint;
  362. };
  363. list.onAddCallback =
  364. (ReorderableList rl) => {
  365. // Add dynamic port postfixed with an index number
  366. string newName = fieldName + " 0";
  367. int i = 0;
  368. while (node.HasPort(newName)) newName = fieldName + " " + (++i);
  369. if (io == XNode.NodePort.IO.Output) node.AddDynamicOutput(type, connectionType, XNode.Node.TypeConstraint.None, newName);
  370. else node.AddDynamicInput(type, connectionType, typeConstraint, newName);
  371. serializedObject.Update();
  372. EditorUtility.SetDirty(node);
  373. if (hasArrayData) {
  374. arrayData.InsertArrayElementAtIndex(arrayData.arraySize);
  375. }
  376. serializedObject.ApplyModifiedProperties();
  377. };
  378. list.onRemoveCallback =
  379. (ReorderableList rl) => {
  380. var indexedPorts = node.DynamicPorts.Select(x => {
  381. string[] split = x.fieldName.Split(' ');
  382. if (split != null && split.Length == 2 && split[0] == fieldName) {
  383. int i = -1;
  384. if (int.TryParse(split[1], out i)) {
  385. return new { index = i, port = x };
  386. }
  387. }
  388. return new { index = -1, port = (XNode.NodePort) null };
  389. }).Where(x => x.port != null);
  390. dynamicPorts = indexedPorts.OrderBy(x => x.index).Select(x => x.port).ToList();
  391. int index = rl.index;
  392. if (dynamicPorts[index] == null) {
  393. Debug.LogWarning("No port found at index " + index + " - Skipped");
  394. } else if (dynamicPorts.Count <= index) {
  395. Debug.LogWarning("DynamicPorts[" + index + "] out of range. Length was " + dynamicPorts.Count + " - Skipped");
  396. } else {
  397. // Clear the removed ports connections
  398. dynamicPorts[index].ClearConnections();
  399. // Move following connections one step up to replace the missing connection
  400. for (int k = index + 1; k < dynamicPorts.Count(); k++) {
  401. for (int j = 0; j < dynamicPorts[k].ConnectionCount; j++) {
  402. XNode.NodePort other = dynamicPorts[k].GetConnection(j);
  403. dynamicPorts[k].Disconnect(other);
  404. dynamicPorts[k - 1].Connect(other);
  405. }
  406. }
  407. // Remove the last dynamic port, to avoid messing up the indexing
  408. node.RemoveDynamicPort(dynamicPorts[dynamicPorts.Count() - 1].fieldName);
  409. serializedObject.Update();
  410. EditorUtility.SetDirty(node);
  411. }
  412. if (hasArrayData) {
  413. if (arrayData.arraySize <= index) {
  414. Debug.LogWarning("Attempted to remove array index " + index + " where only " + arrayData.arraySize + " exist - Skipped");
  415. Debug.Log(rl.list[0]);
  416. return;
  417. }
  418. arrayData.DeleteArrayElementAtIndex(index);
  419. // Error handling. If the following happens too often, file a bug report at https://github.com/Siccity/xNode/issues
  420. if (dynamicPorts.Count <= arrayData.arraySize) {
  421. while (dynamicPorts.Count <= arrayData.arraySize) {
  422. arrayData.DeleteArrayElementAtIndex(arrayData.arraySize - 1);
  423. }
  424. UnityEngine.Debug.LogWarning("Array size exceeded dynamic ports size. Excess items removed.");
  425. }
  426. serializedObject.ApplyModifiedProperties();
  427. serializedObject.Update();
  428. }
  429. };
  430. if (hasArrayData) {
  431. int dynamicPortCount = dynamicPorts.Count;
  432. while (dynamicPortCount < arrayData.arraySize) {
  433. // Add dynamic port postfixed with an index number
  434. string newName = arrayData.name + " 0";
  435. int i = 0;
  436. while (node.HasPort(newName)) newName = arrayData.name + " " + (++i);
  437. if (io == XNode.NodePort.IO.Output) node.AddDynamicOutput(type, connectionType, typeConstraint, newName);
  438. else node.AddDynamicInput(type, connectionType, typeConstraint, newName);
  439. EditorUtility.SetDirty(node);
  440. dynamicPortCount++;
  441. }
  442. while (arrayData.arraySize < dynamicPortCount) {
  443. arrayData.InsertArrayElementAtIndex(arrayData.arraySize);
  444. }
  445. serializedObject.ApplyModifiedProperties();
  446. serializedObject.Update();
  447. }
  448. if (onCreation != null) onCreation(list);
  449. return list;
  450. }
  451. }
  452. }