using System; using System.Collections.Generic; using UnityEditor; using UnityEditor.Callbacks; using UnityEngine; using Object = UnityEngine.Object; namespace XNodeEditor { [InitializeOnLoad, CustomNodeGraphWindow(typeof(XNode.NodeGraph))] public partial class NodeGraphWindow : EditorWindow { /// Stores node positions for all nodePorts. public Dictionary portConnectionPoints { get { return _portConnectionPoints; } } private Dictionary _portConnectionPoints = new Dictionary(); [SerializeField] private NodePortReference[] _references = new NodePortReference[0]; [SerializeField] private Rect[] _rects = new Rect[0]; private Func isDocked { get { if (_isDocked == null) _isDocked = this.GetIsDockedDelegate(); return _isDocked; } } private Func _isDocked; [System.Serializable] private class NodePortReference { [SerializeField] private XNode.Node _node; [SerializeField] private string _name; public NodePortReference(XNode.NodePort nodePort) { _node = nodePort.node; _name = nodePort.fieldName; } public XNode.NodePort GetNodePort() { if (_node == null) { return null; } return _node.GetPort(_name); } } public static NodeGraphWindow current; public XNode.NodeGraph target; /// Are we currently renaming a node? protected bool isRenaming; /// Called when opened by NodeEditorWindow public virtual void OnOpen() { } public virtual void OnOverlayGUI() { } public virtual Texture2D GetGridTexture() { return NodeEditorPreferences.GetSettings().gridTexture; } public virtual Texture2D GetSecondaryGridTexture() { return NodeEditorPreferences.GetSettings().crossTexture; } /// Return default settings for this graph type. This is the settings the user will load if no previous settings have been saved. public virtual NodeEditorPreferences.Settings GetDefaultPreferences() { return new NodeEditorPreferences.Settings(); } /// Returns context node menu path. Null or empty strings for hidden nodes. public virtual string GetNodeMenuName(Type type) { //Check if type has the CreateNodeMenuAttribute XNode.Node.CreateNodeMenuAttribute attrib; if (NodeEditorUtilities.GetAttrib(type, out attrib)) // Return custom path return attrib.menuName; else // Return generated path return ObjectNames.NicifyVariableName(type.ToString().Replace('.', '/')); } /// Add items for the context menu when right-clicking this node. Override to add custom menu items. public virtual void AddContextMenuItems(GenericMenu menu) { Vector2 pos = WindowToGridPosition(Event.current.mousePosition); for (int i = 0; i < NodeEditorReflection.nodeTypes.Length; i++) { Type type = NodeEditorReflection.nodeTypes[i]; //Get node context menu path string path = GetNodeMenuName(type); if (string.IsNullOrEmpty(path)) continue; menu.AddItem(new GUIContent(path), false, () => { CreateNode(type, pos); }); } menu.AddSeparator(""); if (NodeGraphWindow.copyBuffer != null && NodeGraphWindow.copyBuffer.Length > 0) menu.AddItem(new GUIContent("Paste"), false, () => PasteNodes(pos)); else menu.AddDisabledItem(new GUIContent("Paste")); menu.AddItem(new GUIContent("Preferences"), false, () => NodeEditorReflection.OpenPreferences()); NodeEditorReflection.AddCustomContextMenuItems(menu, target); } public virtual Color GetPortColor(XNode.NodePort port) { return GetTypeColor(port.ValueType); } public virtual Color GetTypeColor(Type type) { return NodeEditorPreferences.GetTypeColor(type); } public virtual string GetPortTooltip(XNode.NodePort port) { Type portType = port.ValueType; string tooltip = ""; tooltip = portType.PrettyName(); if (port.IsOutput) { object obj = port.node.GetValue(port); tooltip += " = " + (obj != null ? obj.ToString() : "null"); } return tooltip; } /// Deal with objects dropped into the graph through DragAndDrop public virtual void OnDropObjects(UnityEngine.Object[] objects) { Debug.Log("No OnDropItems override defined for " + GetType()); } /// Create a node and save it in the graph asset public virtual void CreateNode(Type type, Vector2 position) { XNode.Node node = target.AddNode(type); node.position = position; if (node.name == null || node.name.Trim() == "") node.name = NodeEditorUtilities.NodeDefaultName(type); AssetDatabase.AddObjectToAsset(node, target); if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); NodeGraphWindow.RepaintAll(); } /// Creates a copy of the original node in the graph public XNode.Node CopyNode(XNode.Node original) { XNode.Node node = target.CopyNode(original); node.name = original.name; AssetDatabase.AddObjectToAsset(node, target); if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); return node; } /// Safely remove a node and all its connections. public virtual void RemoveNode(XNode.Node node) { target.RemoveNode(node); UnityEngine.Object.DestroyImmediate(node, true); if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); } [AttributeUsage(AttributeTargets.Class)] public class CustomNodeGraphWindowAttribute : Attribute, XNodeEditor.Internal.INodeEditorAttrib { private Type inspectedType; public string editorPrefsKey; /// Tells a NodeGraphEditor which Graph type it is an editor for /// Type that this editor can edit /// Define unique key for unique layout settings instance public CustomNodeGraphWindowAttribute(Type inspectedType, string editorPrefsKey = "xNode.Settings") { this.inspectedType = inspectedType; this.editorPrefsKey = editorPrefsKey; } public Type GetInspectedType() { return inspectedType; } } private void OnDisable() { // Cache portConnectionPoints before serialization starts int count = portConnectionPoints.Count; _references = new NodePortReference[count]; _rects = new Rect[count]; int index = 0; foreach (var portConnectionPoint in portConnectionPoints) { _references[index] = new NodePortReference(portConnectionPoint.Key); _rects[index] = portConnectionPoint.Value; index++; } } private void OnEnable() { // Reload portConnectionPoints if there are any int length = _references.Length; if (length == _rects.Length) { for (int i = 0; i < length; i++) { XNode.NodePort nodePort = _references[i].GetNodePort(); if (nodePort != null) _portConnectionPoints.Add(nodePort, _rects[i]); } } } public Dictionary nodeSizes { get { return _nodeSizes; } } private Dictionary _nodeSizes = new Dictionary(); public XNode.NodeGraph graph; public Vector2 panOffset { get { return _panOffset; } set { _panOffset = value; Repaint(); } } private Vector2 _panOffset; public float zoom { get { return _zoom; } set { _zoom = Mathf.Clamp(value, NodeEditorPreferences.GetSettings().minZoom, NodeEditorPreferences.GetSettings().maxZoom); Repaint(); } } private float _zoom = 1; void OnFocus() { current = this; if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); } [InitializeOnLoadMethod] private static void OnLoad() { Selection.selectionChanged -= OnSelectionChanged; Selection.selectionChanged += OnSelectionChanged; } /// Handle Selection Change events private static void OnSelectionChanged() { XNode.NodeGraph nodeGraph = Selection.activeObject as XNode.NodeGraph; if (nodeGraph && !AssetDatabase.Contains(nodeGraph)) { Open(nodeGraph); } } /// Create editor window public static NodeGraphWindow Init() { NodeGraphWindow w = CreateInstance(); w.titleContent = new GUIContent("xNode"); w.wantsMouseMove = true; w.Show(); return w; } public void Save() { if (AssetDatabase.Contains(graph)) { EditorUtility.SetDirty(graph); if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); } else SaveAs(); } public void SaveAs() { string path = EditorUtility.SaveFilePanelInProject("Save NodeGraph", "NewNodeGraph", "asset", ""); if (string.IsNullOrEmpty(path)) return; else { XNode.NodeGraph existingGraph = AssetDatabase.LoadAssetAtPath(path); if (existingGraph != null) AssetDatabase.DeleteAsset(path); AssetDatabase.CreateAsset(graph, path); EditorUtility.SetDirty(graph); if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); } } private void DraggableWindow(int windowID) { GUI.DragWindow(); } public Vector2 WindowToGridPosition(Vector2 windowPosition) { return (windowPosition - (position.size * 0.5f) - (panOffset / zoom)) * zoom; } public Vector2 GridToWindowPosition(Vector2 gridPosition) { return (position.size * 0.5f) + (panOffset / zoom) + (gridPosition / zoom); } public Rect GridToWindowRectNoClipped(Rect gridRect) { gridRect.position = GridToWindowPositionNoClipped(gridRect.position); return gridRect; } public Rect GridToWindowRect(Rect gridRect) { gridRect.position = GridToWindowPosition(gridRect.position); gridRect.size /= zoom; return gridRect; } public Vector2 GridToWindowPositionNoClipped(Vector2 gridPosition) { Vector2 center = position.size * 0.5f; // UI Sharpness complete fix - Round final offset not panOffset float xOffset = Mathf.Round(center.x * zoom + (panOffset.x + gridPosition.x)); float yOffset = Mathf.Round(center.y * zoom + (panOffset.y + gridPosition.y)); return new Vector2(xOffset, yOffset); } public void SelectNode(XNode.Node node, bool add) { if (add) { List selection = new List(Selection.objects); selection.Add(node); Selection.objects = selection.ToArray(); } else Selection.objects = new Object[] { node }; } public void DeselectNode(XNode.Node node) { List selection = new List(Selection.objects); selection.Remove(node); Selection.objects = selection.ToArray(); } [OnOpenAsset(0)] public static bool OnOpen(int instanceID, int line) { XNode.NodeGraph nodeGraph = EditorUtility.InstanceIDToObject(instanceID) as XNode.NodeGraph; if (nodeGraph != null) { Open(nodeGraph); return true; } return false; } /// Open the provided graph in the NodeEditor public static void Open(XNode.NodeGraph graph) { if (!graph) return; NodeGraphWindow w = GetWindow(typeof(NodeGraphWindow), false, "xNode", true) as NodeGraphWindow; w.wantsMouseMove = true; w.graph = graph; } /// Repaint all open NodeEditorWindows. public static void RepaintAll() { NodeGraphWindow[] windows = Resources.FindObjectsOfTypeAll(); for (int i = 0; i < windows.Length; i++) { windows[i].Repaint(); } } } }