diff --git a/Scripts/Editor/NodeEditorAction.cs b/Scripts/Editor/NodeEditorAction.cs index 2496749..b6c6fb7 100644 --- a/Scripts/Editor/NodeEditorAction.cs +++ b/Scripts/Editor/NodeEditorAction.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using UnityEditor; using UnityEngine; +using XNodeEditor.Internal; namespace XNodeEditor { public partial class NodeEditorWindow { @@ -11,6 +12,8 @@ namespace XNodeEditor { public static bool isPanning { get; private set; } public static Vector2[] dragOffset; + public static XNode.Node[] copyBuffer = null; + private bool IsDraggingPort { get { return draggedOutput != null; } } private bool IsHoveringPort { get { return hoveredPort != null; } } private bool IsHoveringNode { get { return hoveredNode != null; } } @@ -27,10 +30,10 @@ namespace XNodeEditor { private RerouteReference[] preBoxSelectionReroute; private Rect selectionBox; private bool isDoubleClick = false; + private bool stoppedDraggingPort = true; private XNode.NodePort draggedPort; - private struct RerouteReference { public XNode.NodePort port; public int connectionIndex; @@ -48,11 +51,23 @@ namespace XNodeEditor { public Vector2 GetPoint() { return port.GetReroutePoints(connectionIndex) [pointIndex]; } } + private Vector2 lastMousePosition; + public void Controls() { wantsMouseMove = true; Event e = Event.current; switch (e.type) { + case EventType.DragUpdated: + case EventType.DragPerform: + DragAndDrop.visualMode = DragAndDropVisualMode.Generic; + if (e.type == EventType.DragPerform) { + DragAndDrop.AcceptDrag(); + graphEditor.OnDropObjects(DragAndDrop.objectReferences); + } + break; case EventType.MouseMove: + //Keyboard commands will not get correct mouse position from Event + lastMousePosition = e.mousePosition; break; case EventType.ScrollWheel: float oldZoom = zoom; @@ -289,7 +304,7 @@ namespace XNodeEditor { case EventType.KeyDown: if (EditorGUIUtility.editingTextField) break; else if (e.keyCode == KeyCode.F) Home(); - if (IsMac()) { + if (NodeEditorUtilities.IsMac()) { if (e.keyCode == KeyCode.Return) RenameSelectedNode(); } else { if (e.keyCode == KeyCode.F2) RenameSelectedNode(); @@ -300,12 +315,18 @@ namespace XNodeEditor { if (e.commandName == "SoftDelete") { if (e.type == EventType.ExecuteCommand) RemoveSelectedNodes(); e.Use(); - } else if (IsMac() && e.commandName == "Delete") { + } else if (NodeEditorUtilities.IsMac() && e.commandName == "Delete") { if (e.type == EventType.ExecuteCommand) RemoveSelectedNodes(); e.Use(); } else if (e.commandName == "Duplicate") { if (e.type == EventType.ExecuteCommand) DuplicateSelectedNodes(); e.Use(); + } else if (e.commandName == "Copy") { + if (e.type == EventType.ExecuteCommand) CopySelectedNodes(); + e.Use(); + } else if (e.commandName == "Paste") { + if (e.type == EventType.ExecuteCommand) PasteNodes(WindowToGridPosition(lastMousePosition)); + e.Use(); } Repaint(); break; @@ -319,14 +340,6 @@ 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) { dragOffset = new Vector2[Selection.objects.Length + selectedReroutes.Count]; // Selected nodes @@ -343,10 +356,17 @@ namespace XNodeEditor { } } - /// Puts all nodes in focus. If no nodes are present, resets view to + /// Puts all selected nodes in focus. If no nodes are present, resets view and zoom to to origin public void Home() { - zoom = 2; - panOffset = Vector2.zero; + var nodes = Selection.objects.Where(o => o is XNode.Node).Cast().ToList(); + if (nodes.Count > 0) { + Vector2 minPos = nodes.Select(x => x.position).Aggregate((x, y) => new Vector2(Mathf.Min(x.x, y.x), Mathf.Min(x.y, y.y))); + Vector2 maxPos = nodes.Select(x => x.position + (nodeSizes.ContainsKey(x) ? nodeSizes[x] : Vector2.zero)).Aggregate((x, y) => new Vector2(Mathf.Max(x.x, y.x), Mathf.Max(x.y, y.y))); + panOffset = -(minPos + (maxPos - minPos) / 2f); + } else { + zoom = 2; + panOffset = Vector2.zero; + } } /// Remove nodes in the graph in Selection.objects @@ -389,41 +409,60 @@ namespace XNodeEditor { /// Duplicate selected nodes and select the duplicates public void DuplicateSelectedNodes() { - UnityEngine.Object[] newNodes = new UnityEngine.Object[Selection.objects.Length]; + // Get selected nodes which are part of this graph + XNode.Node[] selectedNodes = Selection.objects.Select(x => x as XNode.Node).Where(x => x != null && x.graph == graph).ToArray(); + // Get top left node position + Vector2 topLeftNode = selectedNodes.Select(x => x.position).Aggregate((x, y) => new Vector2(Mathf.Min(x.x, y.x), Mathf.Min(x.y, y.y))); + InsertDuplicateNodes(selectedNodes, topLeftNode + new Vector2(30, 30)); + } + + public void CopySelectedNodes() { + copyBuffer = Selection.objects.Select(x => x as XNode.Node).Where(x => x != null && x.graph == graph).ToArray(); + } + + public void PasteNodes(Vector2 pos) { + InsertDuplicateNodes(copyBuffer, pos); + } + + 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 substitutes = new Dictionary(); - for (int i = 0; i < Selection.objects.Length; i++) { - if (Selection.objects[i] is XNode.Node) { - XNode.Node srcNode = Selection.objects[i] as XNode.Node; - if (srcNode.graph != graph) continue; // ignore nodes selected in another graph - XNode.Node newNode = graphEditor.CopyNode(srcNode); - substitutes.Add(srcNode, newNode); - newNode.position = srcNode.position + new Vector2(30, 30); - newNodes[i] = newNode; - } + for (int i = 0; i < nodes.Length; i++) { + XNode.Node srcNode = nodes[i]; + if (srcNode == null) 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 < Selection.objects.Length; i++) { - if (Selection.objects[i] is XNode.Node) { - XNode.Node srcNode = Selection.objects[i] as XNode.Node; - if (srcNode.graph != graph) continue; // ignore nodes selected in another graph - 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); + 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.UpdateStaticPorts(); - newNodeOut.UpdateStaticPorts(); - inputPort = newNodeIn.GetInputPort(inputPort.fieldName); - outputPort = newNodeOut.GetOutputPort(outputPort.fieldName); - } - if (!inputPort.IsConnectedTo(outputPort)) inputPort.Connect(outputPort); + XNode.Node newNodeIn, newNodeOut; + if (substitutes.TryGetValue(inputPort.node, out newNodeIn) && substitutes.TryGetValue(outputPort.node, out newNodeOut)) { + newNodeIn.UpdateStaticPorts(); + newNodeOut.UpdateStaticPorts(); + inputPort = newNodeIn.GetInputPort(inputPort.fieldName); + outputPort = newNodeOut.GetOutputPort(outputPort.fieldName); } + if (!inputPort.IsConnectedTo(outputPort)) inputPort.Connect(outputPort); } } } + // Select the new nodes Selection.objects = newNodes; } @@ -501,4 +540,4 @@ namespace XNodeEditor { draggedPort.Connect(graph.nodes.Last().Ports.Where(r => r.IsInput == true).ToArray()[0]); } } -} +} \ No newline at end of file diff --git a/Scripts/Editor/NodeGraphEditor.cs b/Scripts/Editor/NodeGraphEditor.cs index bf77d1b..ad65552 100644 --- a/Scripts/Editor/NodeGraphEditor.cs +++ b/Scripts/Editor/NodeGraphEditor.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using UnityEditor; @@ -38,14 +38,14 @@ namespace XNodeEditor { if (NodeEditorUtilities.GetAttrib(type, out attrib)) // Return custom path return attrib.menuName; else // Return generated path - return ObjectNames.NicifyVariableName(type.ToString().Replace('.', '/')); + return NodeEditorUtilities.NodeDefaultPath(type); } /// Add items for the context menu when right-clicking this node. Override to add custom menu items. public virtual void AddContextMenuItems(GenericMenu menu, GenericMenu.MenuFunction2 call = default(GenericMenu.MenuFunction2)) { Vector2 pos = NodeEditorWindow.current.WindowToGridPosition(Event.current.mousePosition); - for (int i = 0; i < NodeEditorWindow.nodeTypes.Length; i++) { - Type type = NodeEditorWindow.nodeTypes[i]; + for (int i = 0; i < NodeEditorReflection.nodeTypes.Length; i++) { + Type type = NodeEditorReflection.nodeTypes[i]; //Get node context menu path string path = GetNodeMenuName(type); @@ -62,8 +62,10 @@ namespace XNodeEditor { }); } menu.AddSeparator(""); - menu.AddItem(new GUIContent("Preferences"), false, () => NodeEditorWindow.OpenPreferences()); - NodeEditorWindow.AddCustomContextMenuItems(menu, target); + if (NodeEditorWindow.copyBuffer != null && NodeEditorWindow.copyBuffer.Length > 0) menu.AddItem(new GUIContent("Paste"), false, () => NodeEditorWindow.current.PasteNodes(pos)); + else menu.AddDisabledItem(new GUIContent("Paste")); + menu.AddItem(new GUIContent("Preferences"), false, () => NodeEditorReflection.OpenPreferences()); + menu.AddCustomContextMenuItems(target); } public virtual Color GetPortColor(XNode.NodePort port) { @@ -74,16 +76,27 @@ namespace XNodeEditor { 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 (string.IsNullOrEmpty(node.name)) { - // Automatically remove redundant 'Node' postfix - string typeName = type.Name; - if (typeName.EndsWith("Node")) typeName = typeName.Substring(0, typeName.LastIndexOf("Node")); - node.name = UnityEditor.ObjectNames.NicifyVariableName(typeName); - } + if (node.name == null || node.name.Trim() == "") node.name = NodeEditorUtilities.NodeDefaultName(type); AssetDatabase.AddObjectToAsset(node, target); if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); NodeEditorWindow.RepaintAll(); @@ -107,7 +120,7 @@ namespace XNodeEditor { [AttributeUsage(AttributeTargets.Class)] public class CustomNodeGraphEditorAttribute : Attribute, - XNodeEditor.Internal.NodeEditorBase.INodeEditorAttrib { + XNodeEditor.Internal.NodeEditorBase.INodeEditorAttrib { private Type inspectedType; public string editorPrefsKey; /// Tells a NodeGraphEditor which Graph type it is an editor for @@ -123,4 +136,4 @@ namespace XNodeEditor { } } } -} +} \ No newline at end of file