From 941189a4d67f77c1fdb840a4148fe676e6c8b7f9 Mon Sep 17 00:00:00 2001 From: Thor Brigsted Date: Sat, 31 Mar 2018 19:01:36 +0200 Subject: [PATCH] Started work on Reroute nodes --- Scripts/Editor/NodeEditorAction.cs | 138 +++++++++++++++++++++----- Scripts/Editor/NodeEditorGUI.cs | 46 ++++++++- Scripts/Editor/NodeEditorGUILayout.cs | 2 +- Scripts/Editor/NodeEditorWindow.cs | 8 +- Scripts/NodeGraph.cs | 5 + 5 files changed, 167 insertions(+), 32 deletions(-) diff --git a/Scripts/Editor/NodeEditorAction.cs b/Scripts/Editor/NodeEditorAction.cs index c1f7a40..df0af09 100644 --- a/Scripts/Editor/NodeEditorAction.cs +++ b/Scripts/Editor/NodeEditorAction.cs @@ -5,7 +5,7 @@ using UnityEngine; namespace XNodeEditor { public partial class NodeEditorWindow { - public enum NodeActivity { Idle, HoldHeader, DragHeader, HoldGrid, DragGrid } + public enum NodeActivity { Idle, HoldNode, DragNode, HoldGrid, DragGrid } public static NodeActivity currentActivity = NodeActivity.Idle; public static bool isPanning { get; private set; } public static Vector2[] dragOffset; @@ -13,13 +13,17 @@ namespace XNodeEditor { private bool IsDraggingPort { get { return draggedOutput != null; } } private bool IsHoveringPort { get { return hoveredPort != null; } } private bool IsHoveringNode { get { return hoveredNode != null; } } + private bool IsHoveringReroute { get { return hoveredReroute >= 0; } } private XNode.Node hoveredNode = null; [NonSerialized] private XNode.NodePort hoveredPort = null; [NonSerialized] private XNode.NodePort draggedOutput = null; [NonSerialized] private XNode.NodePort draggedOutputTarget = null; + private int hoveredReroute = -1; + private List selectedReroutes = new List(); private Rect nodeRects; private Vector2 dragBoxStart; private UnityEngine.Object[] preBoxSelection; + private int[] preBoxSelectionReroute; public void Controls() { wantsMouseMove = true; @@ -42,32 +46,47 @@ namespace XNodeEditor { draggedOutputTarget = null; } Repaint(); - } else if (currentActivity == NodeActivity.HoldHeader || currentActivity == NodeActivity.DragHeader) { + } else if (currentActivity == NodeActivity.HoldNode) { + RecalculateDragOffsets(e); + currentActivity = NodeActivity.DragNode; + Repaint(); + } + if (currentActivity == NodeActivity.DragNode) { + // Holding ctrl inverts grid snap + bool gridSnap = NodeEditorPreferences.GetSettings().gridSnap; + if (e.control) gridSnap = !gridSnap; + + Vector2 mousePos = WindowToGridPosition(e.mousePosition); + // Move selected nodes with offset for (int i = 0; i < Selection.objects.Length; i++) { if (Selection.objects[i] is XNode.Node) { XNode.Node node = Selection.objects[i] as XNode.Node; - node.position = WindowToGridPosition(e.mousePosition) + dragOffset[i]; - bool gridSnap = NodeEditorPreferences.GetSettings().gridSnap; - if (e.control) { - gridSnap = !gridSnap; - } + node.position = mousePos + dragOffset[i]; if (gridSnap) { node.position.x = (Mathf.Round((node.position.x + 8) / 16) * 16) - 8; node.position.y = (Mathf.Round((node.position.y + 8) / 16) * 16) - 8; } } } - currentActivity = NodeActivity.DragHeader; + // Move selected reroutes with offset + for (int i = 0; i < selectedReroutes.Count; i++) { + Vector2 pos = mousePos + dragOffset[Selection.objects.Length + i]; + pos.x -= 8; + pos.y -= 8; + if (gridSnap) { + pos.x = (Mathf.Round((pos.x + 8) / 16) * 16); + pos.y = (Mathf.Round((pos.y + 8) / 16) * 16); + } + SetReroute(selectedReroutes[i], pos); + } Repaint(); } else if (currentActivity == NodeActivity.HoldGrid) { currentActivity = NodeActivity.DragGrid; preBoxSelection = Selection.objects; + preBoxSelectionReroute = selectedReroutes.ToArray(); dragBoxStart = WindowToGridPosition(e.mousePosition); Repaint(); } else if (currentActivity == NodeActivity.DragGrid) { - foreach (XNode.Node node in graph.nodes) { - - } Repaint(); } } else if (e.button == 1 || e.button == 2) { @@ -83,7 +102,6 @@ namespace XNodeEditor { case EventType.MouseDown: Repaint(); if (e.button == 0) { - if (IsHoveringPort) { if (hoveredPort.IsOutput) { draggedOutput = hoveredPort; @@ -100,22 +118,36 @@ namespace XNodeEditor { } } else if (IsHoveringNode && IsHoveringTitle(hoveredNode)) { // If mousedown on node header, select or deselect - if (!Selection.Contains(hoveredNode)) SelectNode(hoveredNode, e.control || e.shift); - else if (e.control || e.shift) DeselectNode(hoveredNode); + if (!Selection.Contains(hoveredNode)) { + SelectNode(hoveredNode, e.control || e.shift); + if (!e.control && !e.shift) selectedReroutes.Clear(); + } else if (e.control || e.shift) DeselectNode(hoveredNode); e.Use(); - currentActivity = NodeActivity.HoldHeader; - dragOffset = new Vector2[Selection.objects.Length]; - for (int i = 0; i < dragOffset.Length; i++) { - if (Selection.objects[i] is XNode.Node) { - XNode.Node node = Selection.objects[i] as XNode.Node; - dragOffset[i] = node.position - WindowToGridPosition(e.mousePosition); + currentActivity = NodeActivity.HoldNode; + } else if (IsHoveringReroute) { + // If reroute isn't selected + if (!selectedReroutes.Contains(hoveredReroute)) { + // Add it + if (e.control || e.shift) selectedReroutes.Add(hoveredReroute); + // Select it + else { + selectedReroutes = new List() { hoveredReroute }; + Selection.activeObject = null; } + } + // Deselect + else if (e.control || e.shift) selectedReroutes.RemoveAt(hoveredReroute); + e.Use(); + currentActivity = NodeActivity.HoldNode; } // If mousedown on grid background, deselect all else if (!IsHoveringNode) { currentActivity = NodeActivity.HoldGrid; - if (!e.control && !e.shift) Selection.activeObject = null; + if (!e.control && !e.shift) { + selectedReroutes.Clear(); + Selection.activeObject = null; + } } } break; @@ -135,7 +167,7 @@ namespace XNodeEditor { draggedOutputTarget = null; EditorUtility.SetDirty(graph); AssetDatabase.SaveAssets(); - } else if (currentActivity == NodeActivity.DragHeader) { + } else if (currentActivity == NodeActivity.DragNode) { AssetDatabase.SaveAssets(); } else if (!IsHoveringNode) { // If click outside node, release field focus @@ -150,18 +182,27 @@ namespace XNodeEditor { AssetDatabase.SaveAssets(); } - // If click node header, select single node. - if (currentActivity == NodeActivity.HoldHeader && !(e.control || e.shift)) { + // If click node header, select it. + if (currentActivity == NodeActivity.HoldNode && !(e.control || e.shift)) { + selectedReroutes.Clear(); SelectNode(hoveredNode, false); } + // If click reroute, select it. + if (IsHoveringReroute && !(e.control || e.shift)) { + selectedReroutes = new List() { hoveredReroute }; + Selection.activeObject = null; + } + Repaint(); currentActivity = NodeActivity.Idle; } else if (e.button == 1) { if (!isPanning) { - if (IsHoveringPort) + if (IsHoveringReroute) { + ShowRerouteContextMenu(hoveredReroute); + } else if (IsHoveringPort) { ShowPortContextMenu(hoveredPort); - if (IsHoveringNode && IsHoveringTitle(hoveredNode)) { + } else if (IsHoveringNode && IsHoveringTitle(hoveredNode)) { if (!Selection.Contains(hoveredNode)) SelectNode(hoveredNode, false); ShowNodeContextMenu(); } else if (!IsHoveringNode) { @@ -190,6 +231,22 @@ namespace XNodeEditor { } } + private void RecalculateDragOffsets(Event current) { + dragOffset = new Vector2[Selection.objects.Length + selectedReroutes.Count]; + // Selected nodes + for (int i = 0; i < Selection.objects.Length; i++) { + if (Selection.objects[i] is XNode.Node) { + XNode.Node node = Selection.objects[i] as XNode.Node; + dragOffset[i] = node.position - WindowToGridPosition(current.mousePosition); + } + } + + // Selected reroutes + for (int i = 0; i < selectedReroutes.Count; i++) { + dragOffset[Selection.objects.Length + i] = GetReroutePos(selectedReroutes[i]) - WindowToGridPosition(current.mousePosition); + } + } + /// Puts all nodes in focus. If no nodes are present, resets view to public void Home() { zoom = 2; @@ -253,6 +310,35 @@ namespace XNodeEditor { Selection.objects = newNodes; } + /// Add a reroute node to a graph and return index + public int AddReroute(Vector2 position) { + SerializedProperty reroutes = graphEditor.serializedObject.FindProperty("reroutes"); + reroutes.arraySize++; + reroutes.GetArrayElementAtIndex(reroutes.arraySize - 1).vector2Value = position; + graphEditor.serializedObject.ApplyModifiedProperties(); + return reroutes.arraySize - 1; + } + + /// Set the position of a reroute node + public void SetReroute(int index, Vector2 position) { + SerializedProperty reroutes = graphEditor.serializedObject.FindProperty("reroutes"); + reroutes.GetArrayElementAtIndex(index).vector2Value = position; + graphEditor.serializedObject.ApplyModifiedProperties(); + } + + /// Get the position of a reroute node + public Vector2 GetReroutePos(int index) { + SerializedProperty reroutes = graphEditor.serializedObject.FindProperty("reroutes"); + return reroutes.GetArrayElementAtIndex(index).vector2Value; + } + + /// Remove a reroute node + public void RemoveReroute(int index) { + SerializedProperty reroutes = graphEditor.serializedObject.FindProperty("reroutes"); + reroutes.DeleteArrayElementAtIndex(index); + graphEditor.serializedObject.ApplyModifiedProperties(); + } + /// Draw a connection as we are dragging it public void DrawDraggedConnection() { if (IsDraggingPort) { diff --git a/Scripts/Editor/NodeEditorGUI.cs b/Scripts/Editor/NodeEditorGUI.cs index f5fd919..9cc4b1c 100644 --- a/Scripts/Editor/NodeEditorGUI.cs +++ b/Scripts/Editor/NodeEditorGUI.cs @@ -20,8 +20,9 @@ namespace XNodeEditor { DrawGrid(position, zoom, panOffset); DrawConnections(); DrawDraggedConnection(); + DrawReroutes(); DrawNodes(); - DrawBox(); + DrawSelectionBox(); DrawTooltip(); GUI.matrix = m; @@ -72,7 +73,7 @@ namespace XNodeEditor { GUI.DrawTextureWithTexCoords(rect, crossTex, new Rect(tileOffset + new Vector2(0.5f, 0.5f), tileAmount)); } - public void DrawBox() { + public void DrawSelectionBox() { if (currentActivity == NodeActivity.DragGrid) { Vector2 curPos = WindowToGridPosition(Event.current.mousePosition); Vector2 size = curPos - dragBoxStart; @@ -87,6 +88,14 @@ namespace XNodeEditor { return GUILayout.Button(name, EditorStyles.toolbarDropDown, GUILayout.Width(width)); } + /// Show right-click context menu for hovered reroute + void ShowRerouteContextMenu(int reroute) { + GenericMenu contextMenu = new GenericMenu(); + contextMenu.AddItem(new GUIContent("Remove"), false, () => RemoveReroute(reroute)); + contextMenu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero)); + AssetDatabase.SaveAssets(); + } + /// Show right-click context menu for hovered port void ShowPortContextMenu(XNode.NodePort hoveredPort) { GenericMenu contextMenu = new GenericMenu(); @@ -138,6 +147,7 @@ namespace XNodeEditor { }); } contextMenu.AddSeparator(""); + contextMenu.AddItem(new GUIContent("Reroute"), false, () => AddReroute(pos)); contextMenu.AddItem(new GUIContent("Preferences"), false, () => OpenPreferences()); AddCustomContextMenuItems(contextMenu, graph); contextMenu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero)); @@ -229,6 +239,34 @@ namespace XNodeEditor { } } + /// Draws all connections + public void DrawReroutes() { + Vector2 mousePos = Event.current.mousePosition; + SerializedProperty reroutes = graphEditor.serializedObject.FindProperty("reroutes"); + + // Box selection support + List selection = preBoxSelectionReroute != null ? new List(preBoxSelectionReroute) : new List(); + Vector2 boxStartPos = GridToWindowPosition(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 boxRect = new Rect(boxStartPos, boxSize); + + hoveredReroute = -1; + for (int i = 0; i < reroutes.arraySize; i++) { + Rect rect = new Rect(reroutes.GetArrayElementAtIndex(i).vector2Value, new Vector2(16, 16)); + rect.position = new Vector2(rect.position.x - 8, rect.position.y - 8); + rect = GridToWindowRect(rect); + Color bgcol = Color.black; + if (selectedReroutes.Contains(i)) bgcol = Color.yellow; + NodeEditorGUILayout.DrawPortHandle(rect, bgcol, Color.white); + + if (rect.Overlaps(boxRect)) selection.Add(i); + if (rect.Contains(mousePos)) hoveredReroute = i; + } + if (Event.current.type != EventType.Layout && currentActivity == NodeActivity.DragGrid) selectedReroutes = new List(selection); + } + private void DrawNodes() { Event e = Event.current; if (e.type == EventType.Layout) { @@ -340,14 +378,14 @@ namespace XNodeEditor { foreach (XNode.NodePort input in node.Inputs) { //Check if port rect is available if (!portConnectionPoints.ContainsKey(input)) continue; - Rect r = GridToWindowRect(portConnectionPoints[input]); + 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 = GridToWindowRect(portConnectionPoints[output]); + Rect r = GridToWindowRectNoClipped(portConnectionPoints[output]); if (r.Contains(mousePos)) hoveredPort = output; } } diff --git a/Scripts/Editor/NodeEditorGUILayout.cs b/Scripts/Editor/NodeEditorGUILayout.cs index d173abf..8a629e8 100644 --- a/Scripts/Editor/NodeEditorGUILayout.cs +++ b/Scripts/Editor/NodeEditorGUILayout.cs @@ -128,7 +128,7 @@ namespace XNodeEditor { else NodeEditor.portPositions.Add(port, portPos); } - private static void DrawPortHandle(Rect rect, Color backgroundColor, Color typeColor) { + public static void DrawPortHandle(Rect rect, Color backgroundColor, Color typeColor) { Color col = GUI.color; GUI.color = backgroundColor; GUI.DrawTexture(rect, NodeEditorResources.dotOuter); diff --git a/Scripts/Editor/NodeEditorWindow.cs b/Scripts/Editor/NodeEditorWindow.cs index cf35616..67d7e73 100644 --- a/Scripts/Editor/NodeEditorWindow.cs +++ b/Scripts/Editor/NodeEditorWindow.cs @@ -65,11 +65,17 @@ namespace XNodeEditor { return (position.size * 0.5f) + (panOffset / zoom) + (gridPosition / zoom); } - public Rect GridToWindowRect(Rect gridRect) { + 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; float xOffset = (center.x * zoom + (panOffset.x + gridPosition.x)); diff --git a/Scripts/NodeGraph.cs b/Scripts/NodeGraph.cs index c2092e0..399ea74 100644 --- a/Scripts/NodeGraph.cs +++ b/Scripts/NodeGraph.cs @@ -11,6 +11,11 @@ namespace XNode { /// See: [SerializeField] public List nodes = new List(); +#if UNITY_EDITOR + /// Nodes used primarily for organization (Editor only) + public List reroutes = new List(); +#endif + /// Add a node to the graph by type public T AddNode() where T : Node { return AddNode(typeof(T)) as T;