/// <summary> Return false for nodes that can't be removed </summary> public virtual bool CanRemove(XNode.Node node) { // Check graph attributes to see if this node is required Type graphType = target.GetType(); XNode.NodeGraph.RequireNodeAttribute[] attribs = Array.ConvertAll( graphType.GetCustomAttributes(typeof(XNode.NodeGraph.RequireNodeAttribute), true), x => x as XNode.NodeGraph.RequireNodeAttribute); if (attribs.Any(x => x.Requires(node.GetType()))) { if (target.nodes.Count(x => x.GetType() == node.GetType()) <= 1) { return false; } } return true; }
/// <summary> Automatically delete Node sub-assets before deleting their script. /// This is important to do, because you can't delete null sub assets. /// <para/> For another workaround, see: https://gitlab.com/RotaryHeart-UnityShare/subassetmissingscriptdelete </summary> private static AssetDeleteResult OnWillDeleteAsset(string path, RemoveAssetOptions options) { // Skip processing anything without the .cs extension if (Path.GetExtension(path) != ".cs") { return(AssetDeleteResult.DidNotDelete); } // 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); }
/// <summary> Attempt to connect dragged output to target node </summary> public void AutoConnect(XNode.Node node) { if (autoConnectOutput != null) { // Find input port of same type XNode.NodePort inputPort = node.Ports.FirstOrDefault(x => x.IsInput && x.ValueType == autoConnectOutput.ValueType); // Fallback to input port if (inputPort == null) { inputPort = node.Ports.FirstOrDefault(x => x.IsInput); } // Autoconnect if (inputPort != null) { autoConnectOutput.Connect(inputPort); } // Save changes EditorUtility.SetDirty(graph); if (NodeEditorPreferences.GetSettings().autoSave) { AssetDatabase.SaveAssets(); } autoConnectOutput = null; } if (autoConnectOutputLink != null) { var linkType = autoConnectOutputLink.Link.LinkType; var inputLinks = XNode.NodeDataCache.GetInputLinks(node.GetType()); var inputPort = inputLinks.FirstOrDefault(x => x.LinkType == linkType); inputPort = inputPort ?? inputLinks.FirstOrDefault(x => x.LinkType.IsAssignableFrom(linkType)); if (inputPort != null) { XNode.NodeLink link = autoConnectOutputLink.Connect(new XNode.NodeLinkPort(node, inputPort)); SelectObject(link, false); EditorUtility.SetDirty(graph); if (NodeEditorPreferences.GetSettings().autoSave) { AssetDatabase.SaveAssets(); } } autoConnectOutputLink = null; } }
/// <summary> Make a field for a serialized property. Automatically displays relevant node port. </summary> public static void PropertyField(SerializedProperty property, GUIContent label, bool includeChildren = true, params GUILayoutOption[] options) { if (property == null) { throw new NullReferenceException(); } XNode.Node node = property.serializedObject.targetObject as XNode.Node; XNode.NodeLinkDefinition link = XNode.NodeDataCache.GetLinkCacheInfo(node.GetType(), property.name); XNode.NodePort port = node.GetPort(property.name); if (link != null) { PropertyField(property, label, new XNode.NodeLinkPort(node, link)); } else { PropertyField(property, label, port, includeChildren); } }
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); }
private void InsertDuplicateNodes(XNode.Node[] nodes, Vector2 topLeft) { if (nodes == null || nodes.Length == 0) { return; } // Get top-left node Vector2 topLeftNode = nodes.Select(x => x.position).Aggregate((x, y) => new Vector2(Mathf.Min(x.x, y.x), Mathf.Min(x.y, y.y))); Vector2 offset = topLeft - topLeftNode; UnityEngine.Object[] newNodes = new UnityEngine.Object[nodes.Length]; Dictionary <XNode.Node, XNode.Node> substitutes = new Dictionary <XNode.Node, XNode.Node>(); for (int i = 0; i < nodes.Length; i++) { XNode.Node srcNode = nodes[i]; if (srcNode == null) { continue; } // Check if user is allowed to add more of given node type XNode.Node.DisallowMultipleNodesAttribute disallowAttrib; Type nodeType = srcNode.GetType(); if (NodeEditorUtilities.GetAttrib(nodeType, out disallowAttrib)) { int typeCount = graph.nodes.Count(x => x.GetType() == nodeType); if (typeCount >= disallowAttrib.max) { continue; } } XNode.Node newNode = graphEditor.CopyNode(srcNode); substitutes.Add(srcNode, newNode); newNode.position = srcNode.position + offset; newNodes[i] = newNode; } // Walk through the selected nodes again, recreate connections, using the new nodes for (int i = 0; i < nodes.Length; i++) { XNode.Node srcNode = nodes[i]; if (srcNode == null) { continue; } foreach (XNode.NodePort port in srcNode.Ports) { for (int c = 0; c < port.ConnectionCount; c++) { XNode.NodePort inputPort = port.direction == XNode.NodePort.IO.Input ? port : port.GetConnection(c); XNode.NodePort outputPort = port.direction == XNode.NodePort.IO.Output ? port : port.GetConnection(c); XNode.Node newNodeIn, newNodeOut; if (substitutes.TryGetValue(inputPort.node, out newNodeIn) && substitutes.TryGetValue(outputPort.node, out newNodeOut)) { newNodeIn.UpdatePorts(); newNodeOut.UpdatePorts(); inputPort = newNodeIn.GetInputPort(inputPort.fieldName); outputPort = newNodeOut.GetOutputPort(outputPort.fieldName); } if (!inputPort.IsConnectedTo(outputPort)) { inputPort.Connect(outputPort); } } } } // Select the new nodes Selection.objects = newNodes; }
/// <summary> Make a field for a serialized property. Manual node port override. </summary> public static void PropertyField(SerializedProperty property, GUIContent label, XNode.NodePort port, bool includeChildren = true, params GUILayoutOption[] options) { if (property == null) { throw new NullReferenceException(); } // If property is not a port, display a regular property field if (port == null) { XNode.Node node = property.serializedObject.targetObject as XNode.Node; FieldInfo field = node.GetType().GetField(property.name); XNode.Node.NodeLabelTextAttribute labelTextAttr = field.GetCustomAttribute <XNode.Node.NodeLabelTextAttribute>(); XNode.Node.NodeLabelWidthAttribute labelWidthAttr = field.GetCustomAttribute <XNode.Node.NodeLabelWidthAttribute>(); var origWidth = EditorGUIUtility.labelWidth; label = labelTextAttr != null ? new GUIContent(labelTextAttr.labelText) : label; EditorGUIUtility.labelWidth = labelWidthAttr != null ? labelWidthAttr.labelWidth : origWidth; EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30)); EditorGUIUtility.labelWidth = origWidth; } else { Rect rect = new Rect(); List <PropertyAttribute> propertyAttributes = NodeEditorUtilities.GetCachedPropertyAttribs(port.node.GetType(), property.name); // If property is an input, display a regular property field and put a port handle on the left side if (port.direction == XNode.NodePort.IO.Input) { // Get data from [Input] attribute XNode.Node.ShowBackingValue showBacking = XNode.Node.ShowBackingValue.Unconnected; XNode.Node.InputAttribute inputAttribute; bool dynamicPortList = false; if (NodeEditorUtilities.GetCachedAttrib(port.node.GetType(), property.name, out inputAttribute)) { dynamicPortList = inputAttribute.dynamicPortList; showBacking = inputAttribute.backingValue; } bool usePropertyAttributes = dynamicPortList || showBacking == XNode.Node.ShowBackingValue.Never || (showBacking == XNode.Node.ShowBackingValue.Unconnected && port.IsConnected); float spacePadding = 0; foreach (var attr in propertyAttributes) { if (attr is SpaceAttribute) { if (usePropertyAttributes) { GUILayout.Space((attr as SpaceAttribute).height); } else { spacePadding += (attr as SpaceAttribute).height; } } else if (attr is HeaderAttribute) { if (usePropertyAttributes) { //GUI Values are from https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/ScriptAttributeGUI/Implementations/DecoratorDrawers.cs Rect position = GUILayoutUtility.GetRect(0, (EditorGUIUtility.singleLineHeight * 1.5f) - EditorGUIUtility.standardVerticalSpacing); //Layout adds standardVerticalSpacing after rect so we subtract it. position.yMin += EditorGUIUtility.singleLineHeight * 0.5f; position = EditorGUI.IndentedRect(position); GUI.Label(position, (attr as HeaderAttribute).header, EditorStyles.boldLabel); } else { spacePadding += EditorGUIUtility.singleLineHeight * 1.5f; } } } if (dynamicPortList) { Type type = GetType(property); XNode.Node.ConnectionType connectionType = inputAttribute != null ? inputAttribute.connectionType : XNode.Node.ConnectionType.Multiple; DynamicPortList(property.name, type, property.serializedObject, port.direction, connectionType); return; } switch (showBacking) { case XNode.Node.ShowBackingValue.Unconnected: // Display a label if port is connected if (port.IsConnected) { EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName)); } // Display an editable property field if port is not connected else { EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30)); } break; case XNode.Node.ShowBackingValue.Never: // Display a label EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName)); break; case XNode.Node.ShowBackingValue.Always: // Display an editable property field EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30)); break; } rect = GUILayoutUtility.GetLastRect(); rect.position = rect.position - new Vector2(16, -spacePadding); // If property is an output, display a text label and put a port handle on the right side } else if (port.direction == XNode.NodePort.IO.Output) { // Get data from [Output] attribute XNode.Node.ShowBackingValue showBacking = XNode.Node.ShowBackingValue.Unconnected; XNode.Node.OutputAttribute outputAttribute; bool dynamicPortList = false; if (NodeEditorUtilities.GetCachedAttrib(port.node.GetType(), property.name, out outputAttribute)) { dynamicPortList = outputAttribute.dynamicPortList; showBacking = outputAttribute.backingValue; } bool usePropertyAttributes = dynamicPortList || showBacking == XNode.Node.ShowBackingValue.Never || (showBacking == XNode.Node.ShowBackingValue.Unconnected && port.IsConnected); float spacePadding = 0; foreach (var attr in propertyAttributes) { if (attr is SpaceAttribute) { if (usePropertyAttributes) { GUILayout.Space((attr as SpaceAttribute).height); } else { spacePadding += (attr as SpaceAttribute).height; } } else if (attr is HeaderAttribute) { if (usePropertyAttributes) { //GUI Values are from https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/ScriptAttributeGUI/Implementations/DecoratorDrawers.cs Rect position = GUILayoutUtility.GetRect(0, (EditorGUIUtility.singleLineHeight * 1.5f) - EditorGUIUtility.standardVerticalSpacing); //Layout adds standardVerticalSpacing after rect so we subtract it. position.yMin += EditorGUIUtility.singleLineHeight * 0.5f; position = EditorGUI.IndentedRect(position); GUI.Label(position, (attr as HeaderAttribute).header, EditorStyles.boldLabel); } else { spacePadding += EditorGUIUtility.singleLineHeight * 1.5f; } } } if (dynamicPortList) { Type type = GetType(property); XNode.Node.ConnectionType connectionType = outputAttribute != null ? outputAttribute.connectionType : XNode.Node.ConnectionType.Multiple; DynamicPortList(property.name, type, property.serializedObject, port.direction, connectionType); return; } switch (showBacking) { case XNode.Node.ShowBackingValue.Unconnected: // Display a label if port is connected if (port.IsConnected) { EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName), NodeEditorResources.OutputPort, GUILayout.MinWidth(30)); } // Display an editable property field if port is not connected else { EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30)); } break; case XNode.Node.ShowBackingValue.Never: // Display a label EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName), NodeEditorResources.OutputPort, GUILayout.MinWidth(30)); break; case XNode.Node.ShowBackingValue.Always: // Display an editable property field EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30)); break; } rect = GUILayoutUtility.GetLastRect(); rect.position = rect.position + new Vector2(rect.width, spacePadding); } rect.size = new Vector2(16, 16); NodeEditor editor = NodeEditor.GetEditor(port.node, NodeEditorWindow.current); Color backgroundColor = editor.GetTint(); Color col = NodeEditorWindow.current.graphEditor.GetPortColor(port); DrawPortHandle(rect, backgroundColor, col); // Register the handle position Vector2 portPos = rect.center; NodeEditor.portPositions[port] = portPos; } }
/// <summary> Make a field for a serialized property. Manual node port override. </summary> public static void PropertyField(SerializedProperty property, GUIContent label, XNode.NodeLinkPort con) { Rect rect = new Rect(); XNode.Node node = con.Node; XNode.NodeLinkDefinition link = con.Link; XNode.Node.InputAttribute inputAttribute = link.InputAttribute; XNode.Node.OutputAttribute outputAttribute = link.OutputAttribute; List <PropertyAttribute> propertyAttributes = NodeEditorUtilities.GetCachedPropertyAttribs(node.GetType(), property.name); float spacePadding = 0; foreach (var attr in propertyAttributes) { if (attr is SpaceAttribute) { spacePadding += (attr as SpaceAttribute).height; } } if (inputAttribute != null) { EditorGUILayout.LabelField(label ?? new GUIContent(property.displayName)); rect = GUILayoutUtility.GetLastRect(); rect.position = rect.position - new Vector2(16, -spacePadding); } // If property is an output, display a text label and put a port handle on the right side else if (outputAttribute != null) { // Get data from [Output] attribute bool dynamicPortList = outputAttribute.dynamicPortList; XNode.Node.ShowBackingValue showBacking = outputAttribute.backingValue; EditorGUILayout.LabelField(label ?? new GUIContent(property.displayName), NodeEditorResources.OutputPort, GUILayout.MinWidth(30)); rect = GUILayoutUtility.GetLastRect(); rect.position = rect.position + new Vector2(rect.width, spacePadding); } rect.size = new Vector2(16, 16); NodeEditor editor = NodeEditor.GetEditor(node, NodeEditorWindow.current); Color backgroundColor = editor.GetTint(); Color col = NodeEditorWindow.current.graphEditor.GetLinkColor(link); DrawPortHandle(rect, backgroundColor, col); // Register the handle position Vector2 portPos = rect.center; NodeEditor.linkPositions[con] = portPos; }