/// <summary> Update static ports to reflect class fields. </summary> public static void UpdatePorts(Node node, Dictionary <string, NodePort> ports) { if (!Initialized) { BuildCache(); } Dictionary <string, NodePort> staticPorts = new Dictionary <string, NodePort>(); System.Type nodeType = node.GetType(); List <NodePort> typePortCache; if (portDataCache.TryGetValue(nodeType, out typePortCache)) { for (int i = 0; i < typePortCache.Count; i++) { staticPorts.Add(typePortCache[i].fieldName, portDataCache[nodeType][i]); } } // Cleanup port dict - Remove nonexisting static ports - update static port types // Loop through current node ports foreach (NodePort port in ports.Values.ToList()) { // If port still exists, check it it has been changed NodePort staticPort; if (staticPorts.TryGetValue(port.fieldName, out staticPort)) { // If port exists but with wrong settings, remove it. Re-add it later. if (port.connectionType != staticPort.connectionType || port.IsDynamic || port.direction != staticPort.direction || port.typeConstraint != staticPort.typeConstraint) { ports.Remove(port.fieldName); } else { port.ValueType = staticPort.ValueType; } } // If port doesn't exist anymore, remove it else if (port.IsStatic) { ports.Remove(port.fieldName); } } // Add missing ports foreach (NodePort staticPort in staticPorts.Values) { if (!ports.ContainsKey(staticPort.fieldName)) { ports.Add(staticPort.fieldName, new NodePort(staticPort, node)); } } }
/// <summary> Update static ports to reflect class fields. </summary> public static void UpdatePorts(Node node, Dictionary <string, NodePort> ports) { if (!Initialized) { BuildCache(); } Dictionary <string, NodePort> staticPorts = new Dictionary <string, NodePort>(); System.Type nodeType = node.GetType(); if (portDataCache.ContainsKey(nodeType)) { for (int i = 0; i < portDataCache[nodeType].Count; i++) { staticPorts.Add(portDataCache[nodeType][i].fieldName, portDataCache[nodeType][i]); } } // Cleanup port dict - Remove nonexisting static ports - update static port types foreach (NodePort port in ports.Values.ToList()) { if (staticPorts.ContainsKey(port.fieldName)) { NodePort staticPort = staticPorts[port.fieldName]; if (port.IsDynamic || port.direction != staticPort.direction) { ports.Remove(port.fieldName); } else { port.ValueType = staticPort.ValueType; } } else if (port.IsStatic) { ports.Remove(port.fieldName); } } // Add missing ports foreach (NodePort staticPort in staticPorts.Values) { if (!ports.ContainsKey(staticPort.fieldName)) { ports.Add(staticPort.fieldName, new NodePort(staticPort, node)); } } }
/// <summary> Automatically delete Node sub-assets before deleting their script. /// <para/> This is important to do, because you can't delete null sub assets. </summary> private static AssetDeleteResult OnWillDeleteAsset(string path, RemoveAssetOptions options) { // Get the object that is requested for deletion UnityEngine.Object obj = AssetDatabase.LoadAssetAtPath <UnityEngine.Object> (path); // If we aren't deleting a script, return if (!(obj is UnityEditor.MonoScript)) { return(AssetDeleteResult.DidNotDelete); } // Check script type. Return if deleting a non-node script UnityEditor.MonoScript script = obj as UnityEditor.MonoScript; System.Type scriptType = script.GetClass(); if (scriptType == null || (scriptType != typeof(XNode.Node) && !scriptType.IsSubclassOf(typeof(XNode.Node)))) { return(AssetDeleteResult.DidNotDelete); } // Find all ScriptableObjects using this script string[] guids = AssetDatabase.FindAssets("t:" + scriptType); for (int i = 0; i < guids.Length; i++) { string assetpath = AssetDatabase.GUIDToAssetPath(guids[i]); Object[] objs = AssetDatabase.LoadAllAssetRepresentationsAtPath(assetpath); for (int k = 0; k < objs.Length; k++) { XNode.Node node = objs[k] as XNode.Node; if (node.GetType() == scriptType) { if (node != null && node.graph != null) { // Delete the node and notify the user Debug.LogWarning(node.name + " of " + node.graph + " depended on deleted script and has been removed automatically.", node.graph); node.graph.RemoveNode(node); } } } } // We didn't actually delete the script. Tell the internal system to carry on with normal deletion procedure return(AssetDeleteResult.DidNotDelete); }
public static AssetDeleteResult OnWillDeleteAsset(string path, RemoveAssetOptions options) { UnityEngine.Object obj = AssetDatabase.LoadAssetAtPath <UnityEngine.Object>(path); if (!(obj is UnityEditor.MonoScript)) { return(AssetDeleteResult.DidNotDelete); } UnityEditor.MonoScript script = obj as UnityEditor.MonoScript; System.Type scriptType = script.GetClass(); if (scriptType != typeof(XNode.Node) && !scriptType.IsSubclassOf(typeof(XNode.Node))) { return(AssetDeleteResult.DidNotDelete); } //Find ScriptableObjects using this script string[] guids = AssetDatabase.FindAssets("t:" + scriptType); for (int i = 0; i < guids.Length; i++) { string assetpath = AssetDatabase.GUIDToAssetPath(guids[i]); Object[] objs = AssetDatabase.LoadAllAssetRepresentationsAtPath(assetpath); for (int k = 0; k < objs.Length; k++) { XNode.Node node = objs[k] as XNode.Node; if (node.GetType() == scriptType) { if (node != null && node.graph != null) { Debug.LogWarning(node.name + " of " + node.graph + " depended on deleted script and has been removed automatically.", node.graph); node.graph.RemoveNode(node); } } } } return(AssetDeleteResult.DidNotDelete); }
/// <summary> Update static ports and dynamic ports managed by DynamicPortLists to reflect class fields. </summary> public static void UpdatePorts(Node node, Dictionary <string, NodePort> ports) { if (!Initialized) { BuildCache(); } Dictionary <string, NodePort> staticPorts = new Dictionary <string, NodePort>(); Dictionary <string, List <NodePort> > removedPorts = new Dictionary <string, List <NodePort> >(); System.Type nodeType = node.GetType(); Dictionary <string, string> formerlySerializedAs = null; if (formerlySerializedAsCache != null) { formerlySerializedAsCache.TryGetValue(nodeType, out formerlySerializedAs); } List <NodePort> dynamicListPorts = new List <NodePort>(); List <NodePort> typePortCache; if (portDataCache.TryGetValue(nodeType, out typePortCache)) { for (int i = 0; i < typePortCache.Count; i++) { staticPorts.Add(typePortCache[i].fieldName, portDataCache[nodeType][i]); } } // Cleanup port dict - Remove nonexisting static ports - update static port types // AND update dynamic ports (albeit only those in lists) too, in order to enforce proper serialisation. // Loop through current node ports foreach (NodePort port in ports.Values.ToList()) { // If port still exists, check it it has been changed NodePort staticPort; if (staticPorts.TryGetValue(port.fieldName, out staticPort)) { // If port exists but with wrong settings, remove it. Re-add it later. if (port.IsDynamic || port.direction != staticPort.direction || port.connectionType != staticPort.connectionType || port.typeConstraint != staticPort.typeConstraint) { // If port is not dynamic and direction hasn't changed, add it to the list so we can try reconnecting the ports connections. if (!port.IsDynamic && port.direction == staticPort.direction) { removedPorts.Add(port.fieldName, port.GetConnections()); } port.ClearConnections(); ports.Remove(port.fieldName); } else { port.ValueType = staticPort.ValueType; } } // If port doesn't exist anymore, remove it else if (port.IsStatic) { //See if the field is tagged with FormerlySerializedAs, if so add the port with its new field name to removedPorts // so it can be reconnected in missing ports stage. string newName = null; if (formerlySerializedAs != null && formerlySerializedAs.TryGetValue(port.fieldName, out newName)) { removedPorts.Add(newName, port.GetConnections()); } port.ClearConnections(); ports.Remove(port.fieldName); } // If the port is dynamic and is managed by a dynamic port list, flag it for reference updates else if (IsDynamicListPort(port)) { dynamicListPorts.Add(port); } } // Add missing ports foreach (NodePort staticPort in staticPorts.Values) { if (!ports.ContainsKey(staticPort.fieldName)) { NodePort port = new NodePort(staticPort, node); //If we just removed the port, try re-adding the connections List <NodePort> reconnectConnections; if (removedPorts.TryGetValue(staticPort.fieldName, out reconnectConnections)) { for (int i = 0; i < reconnectConnections.Count; i++) { NodePort connection = reconnectConnections[i]; if (connection == null) { continue; } if (port.CanConnectTo(connection)) { port.Connect(connection); } } } ports.Add(staticPort.fieldName, port); } } // Finally, make sure dynamic list port settings correspond to the settings of their "backing port" foreach (NodePort listPort in dynamicListPorts) { // At this point we know that ports here are dynamic list ports // which have passed name/"backing port" checks, ergo we can proceed more safely. string backingPortName = listPort.fieldName.Split(' ')[0]; NodePort backingPort = staticPorts[backingPortName]; // Update port constraints. Creating a new port instead will break the editor, mandating the need for setters. listPort.ValueType = GetBackingValueType(backingPort.ValueType); listPort.direction = backingPort.direction; listPort.connectionType = backingPort.connectionType; listPort.typeConstraint = backingPort.typeConstraint; } }
private void DrawNodes() { Event e = Event.current; if (e.type == EventType.Layout) { selectionCache = new List <UnityEngine.Object>(Selection.objects); } System.Reflection.MethodInfo onValidate = null; if (Selection.activeObject != null && Selection.activeObject is XNode.Node) { onValidate = Selection.activeObject.GetType().GetMethod("OnValidate"); if (onValidate != null) { EditorGUI.BeginChangeCheck(); } } BeginZoomed(position, zoom, topPadding); Vector2 mousePos = Event.current.mousePosition; if (e.type != EventType.Layout) { hoveredNode = null; hoveredPort = null; hoveredLink = null; } List <UnityEngine.Object> preSelection = preBoxSelection != null ? new List <UnityEngine.Object>(preBoxSelection) : new List <UnityEngine.Object>(); // Selection box stuff Vector2 boxStartPos = GridToWindowPositionNoClipped(dragBoxStart); Vector2 boxSize = mousePos - boxStartPos; if (boxSize.x < 0) { boxStartPos.x += boxSize.x; boxSize.x = Mathf.Abs(boxSize.x); } if (boxSize.y < 0) { boxStartPos.y += boxSize.y; boxSize.y = Mathf.Abs(boxSize.y); } Rect selectionBox = new Rect(boxStartPos, boxSize); //Save guiColor so we can revert it Color guiColor = GUI.color; List <XNode.NodePort> removeEntries = new List <XNode.NodePort>(); if (e.type == EventType.Layout) { culledNodes = new List <XNode.Node>(); } Action <NodeLinkPort> selectLinkIfHovered = link => { if (link == null || !linkConnectionPoints.ContainsKey(link)) { return; } Rect r = GridToWindowRectNoClipped(linkConnectionPoints[link]); if (r.Contains(mousePos)) { hoveredLink = link; } }; for (int n = 0; n < graph.nodes.Count; n++) { // Skip null nodes. The user could be in the process of renaming scripts, so removing them at this point is not advisable. if (graph.nodes[n] == null) { continue; } if (n >= graph.nodes.Count) { return; } XNode.Node node = graph.nodes[n]; // Culling if (e.type == EventType.Layout) { // Cull unselected nodes outside view if (!Selection.Contains(node) && ShouldBeCulled(node)) { culledNodes.Add(node); continue; } } else if (culledNodes.Contains(node)) { continue; } if (e.type == EventType.Repaint) { removeEntries.Clear(); foreach (var kvp in portConnectionPoints) { if (kvp.Key.node == node) { removeEntries.Add(kvp.Key); } } foreach (var k in removeEntries) { portConnectionPoints.Remove(k); } } NodeEditor nodeEditor = NodeEditor.GetEditor(node, this); NodeEditor.portPositions.Clear(); // Set default label width. This is potentially overridden in OnBodyGUI EditorGUIUtility.labelWidth = 84; //Get node position Vector2 nodePos = GridToWindowPositionNoClipped(node.position); GUILayout.BeginArea(new Rect(nodePos, new Vector2(nodeEditor.GetWidth(), 4000))); bool selected = selectionCache.Contains(graph.nodes[n]); if (selected) { GUIStyle style = new GUIStyle(nodeEditor.GetBodyStyle()); GUIStyle highlightStyle = new GUIStyle(nodeEditor.GetBodyHighlightStyle()); highlightStyle.padding = style.padding; style.padding = new RectOffset(); GUI.color = nodeEditor.GetTint(); GUILayout.BeginVertical(style); GUI.color = NodeEditorPreferences.GetSettings().highlightColor; GUILayout.BeginVertical(new GUIStyle(highlightStyle)); } else { GUIStyle style = new GUIStyle(nodeEditor.GetBodyStyle()); GUI.color = nodeEditor.GetTint(); GUILayout.BeginVertical(style); } GUI.color = guiColor; EditorGUI.BeginChangeCheck(); //Draw node contents nodeEditor.OnHeaderGUI(); nodeEditor.OnBodyGUI(); //If user changed a value, notify other scripts through onUpdateNode if (EditorGUI.EndChangeCheck()) { if (NodeEditor.onUpdateNode != null) { NodeEditor.onUpdateNode(node); } EditorUtility.SetDirty(node); nodeEditor.serializedObject.ApplyModifiedProperties(); } GUILayout.EndVertical(); //Cache data about the node for next frame if (e.type == EventType.Repaint) { Vector2 size = GUILayoutUtility.GetLastRect().size; if (nodeSizes.ContainsKey(node)) { nodeSizes[node] = size; } else { nodeSizes.Add(node, size); } foreach (var kvp in NodeEditor.portPositions) { Vector2 portHandlePos = kvp.Value; portHandlePos += node.position; Rect rect = new Rect(portHandlePos.x - 8, portHandlePos.y - 8, 16, 16); portConnectionPoints[kvp.Key] = rect; } foreach (var kvp in NodeEditor.linkPositions) { if (kvp.Key.Node != node) { continue; } Vector2 portHandlePos = kvp.Value; portHandlePos += node.position; Rect rect = new Rect(portHandlePos.x - 8, portHandlePos.y - 8, 16, 16); linkConnectionPoints[kvp.Key] = rect; } } if (selected) { GUILayout.EndVertical(); } if (e.type != EventType.Layout) { //Check if we are hovering this node Vector2 nodeSize = GUILayoutUtility.GetLastRect().size; Rect windowRect = new Rect(nodePos, nodeSize); if (windowRect.Contains(mousePos)) { hoveredNode = node; } //If dragging a selection box, add nodes inside to selection if (currentActivity == NodeActivity.DragGrid) { if (windowRect.Overlaps(selectionBox)) { preSelection.Add(node); } } //Check if we are hovering any of this nodes ports //Check input ports foreach (XNode.NodePort input in node.Inputs) { //Check if port rect is available if (!portConnectionPoints.ContainsKey(input)) { continue; } Rect r = GridToWindowRectNoClipped(portConnectionPoints[input]); if (r.Contains(mousePos)) { hoveredPort = input; } } //Check all output ports foreach (XNode.NodePort output in node.Outputs) { //Check if port rect is available if (!portConnectionPoints.ContainsKey(output)) { continue; } Rect r = GridToWindowRectNoClipped(portConnectionPoints[output]); if (r.Contains(mousePos)) { hoveredPort = output; } } var links = NodeDataCache.GetLinks(node.GetType()).Select(x => new NodeLinkPort(node, x)); foreach (NodeLinkPort link in links) { selectLinkIfHovered(link); } } var selectedLink = Selection.activeObject as NodeLink; selectLinkIfHovered(selectedLink?.GetFromPort()); selectLinkIfHovered(selectedLink?.GetToPort()); GUILayout.EndArea(); } if (e.type != EventType.Layout && currentActivity == NodeActivity.DragGrid) { Selection.objects = preSelection.ToArray(); } EndZoomed(position, zoom, topPadding); //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, //and thus, the code should not be included in build. if (onValidate != null && EditorGUI.EndChangeCheck()) { onValidate.Invoke(Selection.activeObject, null); } }
/// <summary> Update static ports to reflect class fields. </summary> public static void UpdatePorts(Node node, Dictionary <string, NodePort> ports) { if (!Initialized) { BuildCache(); } Dictionary <string, NodePort> staticPorts = new Dictionary <string, NodePort>(); Dictionary <string, List <NodePort> > removedPorts = new Dictionary <string, List <NodePort> >(); System.Type nodeType = node.GetType(); List <NodePort> typePortCache; if (portDataCache.TryGetValue(nodeType, out typePortCache)) { for (int i = 0; i < typePortCache.Count; i++) { staticPorts.Add(typePortCache[i].fieldName, portDataCache[nodeType][i]); } } // Cleanup port dict - Remove nonexisting static ports - update static port types // Loop through current node ports foreach (NodePort port in ports.Values.ToList()) { // If port still exists, check it it has been changed NodePort staticPort; if (staticPorts.TryGetValue(port.fieldName, out staticPort)) { // If port exists but with wrong settings, remove it. Re-add it later. if (port.IsDynamic || port.direction != staticPort.direction || port.connectionType != staticPort.connectionType || port.typeConstraint != staticPort.typeConstraint) { // If port is not dynamic and direction hasn't changed, add it to the list so we can try reconnecting the ports connections. if (!port.IsDynamic && port.direction == staticPort.direction) { removedPorts.Add(port.fieldName, port.GetConnections()); } port.ClearConnections(); ports.Remove(port.fieldName); } else { port.ValueType = staticPort.ValueType; } } // If port doesn't exist anymore, remove it else if (port.IsStatic) { port.ClearConnections(); ports.Remove(port.fieldName); } } // Add missing ports foreach (NodePort staticPort in staticPorts.Values) { if (!ports.ContainsKey(staticPort.fieldName)) { NodePort port = new NodePort(staticPort, node); //If we just removed the port, try re-adding the connections List <NodePort> reconnectConnections; if (removedPorts.TryGetValue(staticPort.fieldName, out reconnectConnections)) { for (int i = 0; i < reconnectConnections.Count; i++) { NodePort connection = reconnectConnections[i]; if (connection == null) { continue; } if (port.CanConnectTo(connection)) { port.Connect(connection); } } } ports.Add(staticPort.fieldName, port); } } }
/// <summary> /// Checks if the output/input field is the same type as <paramref name="connectingNode"/> /// </summary> /// <param name="connectingNode"></param> /// <returns></returns> private bool IsBackingValueTypeSameAsConnectingNode(Node connectingNode) { return(connectingNode.GetType().IsSubclassOf(Type.GetType(_typeQualifiedName))); }