diff --git a/Scripts/Editor/NodeEditorAction.cs b/Scripts/Editor/NodeEditorAction.cs index 3a70848..41bb196 100644 --- a/Scripts/Editor/NodeEditorAction.cs +++ b/Scripts/Editor/NodeEditorAction.cs @@ -4,25 +4,21 @@ using UnityEngine; namespace XNodeEditor { public partial class NodeEditorWindow { - + public enum NodeActivity { Idle, HoldHeader, DragHeader, HoldGrid, DragGrid } + public static NodeActivity currentActivity = NodeActivity.Idle; public static bool isPanning { get; private set; } - public static Vector2 dragOffset; + public static Vector2[] dragOffset; - private bool IsDraggingNode { get { return draggedNode != null; } } private bool IsDraggingPort { get { return draggedOutput != null; } } private bool IsHoveringPort { get { return hoveredPort != null; } } private bool IsHoveringNode { get { return hoveredNode != null; } } - private bool HasSelectedNode { get { return selectedNode != null; } } - private XNode.Node hoveredNode = null; - - [NonSerialized] private XNode.Node selectedNode = null; - [NonSerialized] private XNode.Node draggedNode = null; [NonSerialized] private XNode.NodePort hoveredPort = null; [NonSerialized] private XNode.NodePort draggedOutput = null; [NonSerialized] private XNode.NodePort draggedOutputTarget = null; - private Rect nodeRects; + private Vector2 dragBoxStart; + private UnityEngine.Object[] preBoxSelection; public void Controls() { wantsMouseMove = true; @@ -45,23 +41,44 @@ namespace XNodeEditor { draggedOutputTarget = null; } Repaint(); - } else if (IsDraggingNode) { - draggedNode.position = WindowToGridPosition(e.mousePosition) + dragOffset; - if (NodeEditorPreferences.gridSnap) { - draggedNode.position.x = (Mathf.Round((draggedNode.position.x + 8) / 16) * 16) - 8; - draggedNode.position.y = (Mathf.Round((draggedNode.position.y + 8) / 16) * 16) - 8; + } else if (currentActivity == NodeActivity.HoldHeader || currentActivity == NodeActivity.DragHeader) { + 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]; + if (NodeEditorPreferences.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; + Repaint(); + } else if (currentActivity == NodeActivity.HoldGrid) { + currentActivity = NodeActivity.DragGrid; + preBoxSelection = Selection.objects; + 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) { - panOffset += e.delta * zoom; + Vector2 tempOffset = panOffset; + tempOffset += e.delta * zoom; + // Round value to increase crispyness of UI text + tempOffset.x = Mathf.Round(tempOffset.x); + tempOffset.y = Mathf.Round(tempOffset.y); + panOffset = tempOffset; isPanning = true; } break; case EventType.MouseDown: Repaint(); if (e.button == 0) { - SelectNode(hoveredNode); + if (IsHoveringPort) { if (hoveredPort.IsOutput) { draggedOutput = hoveredPort; @@ -77,8 +94,23 @@ namespace XNodeEditor { } } } else if (IsHoveringNode && IsHoveringTitle(hoveredNode)) { - draggedNode = hoveredNode; - dragOffset = hoveredNode.position - WindowToGridPosition(e.mousePosition); + // 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); + 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); + } + } + } + // If mousedown on grid background, deselect all + else if (!IsHoveringNode) { + currentActivity = NodeActivity.HoldGrid; + if (!e.control && !e.shift) Selection.activeObject = null; } } break; @@ -97,18 +129,30 @@ namespace XNodeEditor { draggedOutput = null; draggedOutputTarget = null; EditorUtility.SetDirty(graph); - Repaint(); AssetDatabase.SaveAssets(); - } else if (IsDraggingNode) { - draggedNode = null; + } else if (currentActivity == NodeActivity.DragHeader) { AssetDatabase.SaveAssets(); - } else if (GUIUtility.hotControl != 0) { + } else if (!IsHoveringNode) { + // If click outside node, release field focus + if (!isPanning) { + GUIUtility.hotControl = 0; + GUIUtility.keyboardControl = 0; + } AssetDatabase.SaveAssets(); } + + // If click node header, select single node. + if (currentActivity == NodeActivity.HoldHeader && !(e.control || e.shift)) { + SelectNode(hoveredNode, false); + } + + Repaint(); + currentActivity = NodeActivity.Idle; } else if (e.button == 1) { if (!isPanning) { if (IsHoveringNode && IsHoveringTitle(hoveredNode)) { - ShowNodeContextMenu(hoveredNode); + if (!Selection.Contains(hoveredNode)) SelectNode(hoveredNode, false); + ShowNodeContextMenu(); } else if (!IsHoveringNode) { ShowGraphContextMenu(); } @@ -116,6 +160,18 @@ namespace XNodeEditor { isPanning = false; } break; + case EventType.KeyDown: + if (e.keyCode == KeyCode.Delete) RemoveSelectedNodes(); + else if (e.keyCode == KeyCode.D && e.control) DublicateSelectedNodes(); + Repaint(); + break; + case EventType.Ignore: + // If release mouse outside window + if (e.rawType == EventType.MouseUp && currentActivity == NodeActivity.DragGrid) { + Repaint(); + currentActivity = NodeActivity.Idle; + } + break; } } @@ -131,6 +187,31 @@ namespace XNodeEditor { Repaint(); } + /// Remove nodes in the graph in Selection.objects + public void RemoveSelectedNodes() { + foreach (UnityEngine.Object item in Selection.objects) { + if (item is XNode.Node) { + XNode.Node node = item as XNode.Node; + graph.RemoveNode(node); + } + } + } + + // Dublicate selected nodes and select the dublicates + public void DublicateSelectedNodes() { + UnityEngine.Object[] newNodes = new UnityEngine.Object[Selection.objects.Length]; + 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; + if (node.graph != graph) continue; // ignore nodes selected in another graph + XNode.Node n = graph.CopyNode(node); + n.position = node.position + new Vector2(30, 30); + newNodes[i] = n; + } + } + Selection.objects = newNodes; + } + /// 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 f8ab6d5..e3f8c4c 100644 --- a/Scripts/Editor/NodeEditorGUI.cs +++ b/Scripts/Editor/NodeEditorGUI.cs @@ -6,7 +6,8 @@ using UnityEngine; namespace XNodeEditor { /// Contains GUI methods public partial class NodeEditorWindow { - NodeGraphEditor currentGraphEditor; + private NodeGraphEditor currentGraphEditor; + private List selectionCache; private void OnGUI() { Event e = Event.current; @@ -20,6 +21,7 @@ namespace XNodeEditor { DrawConnections(); DrawDraggedConnection(); DrawNodes(); + DrawBox(); DrawTooltip(); GUI.matrix = m; @@ -70,27 +72,45 @@ namespace XNodeEditor { GUI.DrawTextureWithTexCoords(rect, crossTex, new Rect(tileOffset + new Vector2(0.5f, 0.5f), tileAmount)); } + public void DrawBox() { + if (currentActivity == NodeActivity.DragGrid) { + Vector2 curPos = WindowToGridPosition(Event.current.mousePosition); + Vector2 size = curPos - dragBoxStart; + Rect r = new Rect(dragBoxStart, size); + r.position = GridToWindowPosition(r.position); + r.size /= zoom; + Handles.DrawSolidRectangleWithOutline(r, new Color(0, 0, 0, 0.1f), new Color(1, 1, 1, 0.6f)); + } + } + public static bool DropdownButton(string name, float width) { return GUILayout.Button(name, EditorStyles.toolbarDropDown, GUILayout.Width(width)); } - /// Show right-click context menu for a node - public void ShowNodeContextMenu(XNode.Node node) { + /// Show right-click context menu for selected nodes + public void ShowNodeContextMenu() { GenericMenu contextMenu = new GenericMenu(); - contextMenu.AddItem(new GUIContent("Move To Top"), false, () => { - int index; - while ((index = graph.nodes.IndexOf(node)) != graph.nodes.Count - 1) { - graph.nodes[index] = graph.nodes[index + 1]; - graph.nodes[index + 1] = node; - } - }); - contextMenu.AddItem(new GUIContent("Duplicate"), false, () => { - XNode.Node n = graph.CopyNode(node); - n.position = node.position + new Vector2(30, 30); - }); - contextMenu.AddItem(new GUIContent("Remove"), false, () => graph.RemoveNode(node)); + // If only one node is selected + if (Selection.objects.Length == 1 && Selection.activeObject is XNode.Node) { + XNode.Node node = Selection.activeObject as XNode.Node; + contextMenu.AddItem(new GUIContent("Move To Top"), false, () => { + int index; + while ((index = graph.nodes.IndexOf(node)) != graph.nodes.Count - 1) { + graph.nodes[index] = graph.nodes[index + 1]; + graph.nodes[index + 1] = node; + } + }); + } + + contextMenu.AddItem(new GUIContent("Duplicate"), false, DublicateSelectedNodes); + contextMenu.AddItem(new GUIContent("Remove"), false, RemoveSelectedNodes); + + // If only one node is selected + if (Selection.objects.Length == 1 && Selection.activeObject is XNode.Node) { + XNode.Node node = Selection.activeObject as XNode.Node; + AddCustomContextMenuItems(contextMenu, node); + } - AddCustomContextMenuItems(contextMenu, node); contextMenu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero)); } @@ -168,17 +188,20 @@ namespace XNodeEditor { private void DrawNodes() { Event e = Event.current; + if (e.type == EventType.Layout) { + selectionCache = new List(Selection.objects); + } if (e.type == EventType.Repaint) { portConnectionPoints.Clear(); nodeWidths.Clear(); } - //Selected node is hashed before and after node GUI to detect changes + //Active node is hashed before and after node GUI to detect changes int nodeHash = 0; System.Reflection.MethodInfo onValidate = null; - if (selectedNode != null) { - onValidate = selectedNode.GetType().GetMethod("OnValidate"); - if (onValidate != null) nodeHash = selectedNode.GetHashCode(); + if (Selection.activeObject != null && Selection.activeObject is XNode.Node) { + onValidate = Selection.activeObject.GetType().GetMethod("OnValidate"); + if (onValidate != null) nodeHash = Selection.activeObject.GetHashCode(); } BeginZoomed(position, zoom); @@ -190,6 +213,8 @@ namespace XNodeEditor { hoveredPort = null; } + List preSelection = new List(preBoxSelection); + //Save guiColor so we can revert it Color guiColor = GUI.color; for (int n = 0; n < graph.nodes.Count; n++) { @@ -206,9 +231,23 @@ namespace XNodeEditor { GUILayout.BeginArea(new Rect(nodePos, new Vector2(nodeEditor.GetWidth(), 4000))); - GUIStyle style = NodeEditorResources.styles.nodeBody; - GUI.color = nodeEditor.GetTint(); - GUILayout.BeginVertical(new GUIStyle(style)); + bool selected = selectionCache.Contains(graph.nodes[n]); + + if (selected) { + GUIStyle style = new GUIStyle(NodeEditorResources.styles.nodeBody); + GUIStyle highlightStyle = new GUIStyle(NodeEditorResources.styles.nodeHighlight); + highlightStyle.padding = style.padding; + style.padding = new RectOffset(); + GUI.color = nodeEditor.GetTint(); + GUILayout.BeginVertical(new GUIStyle(style)); + GUI.color = NodeEditorPreferences.HighlightColor; + GUILayout.BeginVertical(new GUIStyle(highlightStyle)); + } else { + GUIStyle style = NodeEditorResources.styles.nodeBody; + GUI.color = nodeEditor.GetTint(); + GUILayout.BeginVertical(new GUIStyle(style)); + } + GUI.color = guiColor; EditorGUI.BeginChangeCheck(); @@ -235,6 +274,7 @@ namespace XNodeEditor { } GUILayout.EndVertical(); + if (selected) GUILayout.EndVertical(); if (e.type != EventType.Layout) { //Check if we are hovering this node @@ -242,6 +282,16 @@ namespace XNodeEditor { 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) { + Vector2 startPos = GridToWindowPositionNoClipped(dragBoxStart); + Vector2 size = mousePos - startPos; + if (size.x < 0) { startPos.x += size.x; size.x = Mathf.Abs(size.x); } + if (size.y < 0) { startPos.y += size.y; size.y = Mathf.Abs(size.y); } + Rect r = new Rect(startPos, size); + if (windowRect.Overlaps(r)) preSelection.Add(node); + } + //Check if we are hovering any of this nodes ports //Check input ports foreach (XNode.NodePort input in node.Inputs) { @@ -262,13 +312,14 @@ namespace XNodeEditor { GUILayout.EndArea(); } + if (e.type != EventType.Layout && currentActivity == NodeActivity.DragGrid) Selection.objects = preSelection.ToArray(); EndZoomed(position, zoom); //If a change in hash 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 (selectedNode != null) { - if (onValidate != null && nodeHash != selectedNode.GetHashCode()) onValidate.Invoke(selectedNode, null); + if (nodeHash != 0) { + if (onValidate != null && nodeHash != Selection.activeObject.GetHashCode()) onValidate.Invoke(Selection.activeObject, null); } } diff --git a/Scripts/Editor/NodeEditorPreferences.cs b/Scripts/Editor/NodeEditorPreferences.cs index 910d426..0df1ad2 100644 --- a/Scripts/Editor/NodeEditorPreferences.cs +++ b/Scripts/Editor/NodeEditorPreferences.cs @@ -9,7 +9,7 @@ namespace XNodeEditor { public static Texture2D gridTexture { get { VerifyLoaded(); - if (_gridTexture == null) _gridTexture = NodeEditorResources.GenerateGridTexture(_gridLineColor, _gridBgColor); + if (_gridTexture == null) _gridTexture = NodeEditorResources.GenerateGridTexture(settings.gridLineColor, settings.gridBgColor); return _gridTexture; } } @@ -17,27 +17,53 @@ namespace XNodeEditor { public static Texture2D crossTexture { get { VerifyLoaded(); - if (_crossTexture == null) _crossTexture = NodeEditorResources.GenerateCrossTexture(_gridLineColor); + if (_crossTexture == null) _crossTexture = NodeEditorResources.GenerateCrossTexture(settings.gridLineColor); return _crossTexture; } } private static Texture2D _crossTexture; - /// Have we loaded the prefs yet - private static bool prefsLoaded = false; + public static bool GridSnap { get { VerifyLoaded(); return settings.gridSnap; } } + public static Color HighlightColor { get { VerifyLoaded(); return settings.highlightColor; } } + + private static Dictionary typeColors = new Dictionary(); + private static Settings settings; + + [System.Serializable] + private class Settings : ISerializationCallbackReceiver { + public Color32 gridLineColor = new Color(0.45f, 0.45f, 0.45f); + public Color32 gridBgColor = new Color(0.18f, 0.18f, 0.18f); + public Color32 highlightColor = new Color32(255, 223, 255, 255); + public bool gridSnap = true; + public string typeColorsData = ""; + public Dictionary typeColors = new Dictionary(); + + public void OnAfterDeserialize() { + // Deserialize typeColorsData + typeColors = new Dictionary(); + string[] data = typeColorsData.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + for (int i = 0; i < data.Length; i += 2) { + Color col; + if (ColorUtility.TryParseHtmlString("#" + data[i + 1], out col)) { + typeColors.Add(data[i], col); + } + } + } + + public void OnBeforeSerialize() { + // Serialize typeColors + typeColorsData = ""; + foreach (var item in typeColors) { + typeColorsData += item.Key + "," + ColorUtility.ToHtmlStringRGB(item.Value) + ","; + } + } + } - private static Dictionary typeColors; - private static Dictionary generatedTypeColors; - public static bool gridSnap { get { VerifyLoaded(); return _gridSnap; } } - private static bool _gridSnap = true; - public static Color gridLineColor { get { VerifyLoaded(); return _gridLineColor; } } - private static Color _gridLineColor; - public static Color gridBgColor { get { VerifyLoaded(); return _gridBgColor; } } - private static Color _gridBgColor; [PreferenceItem("Node Editor")] private static void PreferencesGUI() { VerifyLoaded(); + NodeSettingsGUI(); GridSettingsGUI(); TypeColorsGUI(); if (GUILayout.Button(new GUIContent("Set Default", "Reset all values to default"), GUILayout.Width(120))) { @@ -48,15 +74,25 @@ namespace XNodeEditor { private static void GridSettingsGUI() { //Label EditorGUILayout.LabelField("Grid", EditorStyles.boldLabel); - _gridSnap = EditorGUILayout.Toggle("Snap", _gridSnap); + settings.gridSnap = EditorGUILayout.Toggle("Snap", settings.gridSnap); - //EditorGUIUtility.labelWidth = 30; - _gridLineColor = EditorGUILayout.ColorField("Color", _gridLineColor); - _gridBgColor = EditorGUILayout.ColorField(" ", _gridBgColor); + settings.gridLineColor = EditorGUILayout.ColorField("Color", settings.gridLineColor); + settings.gridBgColor = EditorGUILayout.ColorField(" ", settings.gridBgColor); + if (GUI.changed) { + SavePrefs(); + _gridTexture = NodeEditorResources.GenerateGridTexture(settings.gridLineColor, settings.gridBgColor); + _crossTexture = NodeEditorResources.GenerateCrossTexture(settings.gridLineColor); + NodeEditorWindow.RepaintAll(); + } + EditorGUILayout.Space(); + } + + private static void NodeSettingsGUI() { + //Label + EditorGUILayout.LabelField("Node", EditorStyles.boldLabel); + settings.highlightColor = EditorGUILayout.ColorField("Selection", settings.highlightColor); if (GUI.changed) { SavePrefs(); - _gridTexture = NodeEditorResources.GenerateGridTexture(_gridLineColor, _gridBgColor); - _crossTexture = NodeEditorResources.GenerateCrossTexture(_gridLineColor); NodeEditorWindow.RepaintAll(); } EditorGUILayout.Space(); @@ -64,100 +100,54 @@ namespace XNodeEditor { private static void TypeColorsGUI() { //Label - EditorGUILayout.LabelField("Type colors", EditorStyles.boldLabel); + EditorGUILayout.LabelField("Types", EditorStyles.boldLabel); - //Get saved type keys - string[] typeKeys = new string[typeColors.Count]; - typeColors.Keys.CopyTo(typeKeys, 0); - //Display saved type colors - foreach (var key in typeKeys) { - EditorGUILayout.BeginHorizontal(); - if (!EditorGUILayout.Toggle(new GUIContent(key, key), true)) { - typeColors.Remove(key); - SavePrefs(); - EditorGUILayout.EndHorizontal(); - continue; - } + //Display type colors. Save them if they are edited by the user + List keys = new List(typeColors.Keys); + foreach (string key in keys) { Color col = typeColors[key]; - col = EditorGUILayout.ColorField(col); - typeColors[key] = col; - EditorGUILayout.EndHorizontal(); - } - if (GUI.changed) { - SavePrefs(); - NodeEditorWindow.RepaintAll(); - } - - //Get generated type keys - string[] generatedTypeKeys = new string[generatedTypeColors.Count]; - generatedTypeColors.Keys.CopyTo(generatedTypeKeys, 0); - //Display generated type colors - foreach (var key in generatedTypeKeys) { + EditorGUI.BeginChangeCheck(); EditorGUILayout.BeginHorizontal(); - if (EditorGUILayout.Toggle(new GUIContent(key, key), false)) { - typeColors.Add(key, generatedTypeColors[key]); - generatedTypeColors.Remove(key); - SavePrefs(); - EditorGUILayout.EndHorizontal(); - continue; - } - Color col = generatedTypeColors[key]; - EditorGUI.BeginDisabledGroup(true); - col = EditorGUILayout.ColorField(col); - EditorGUI.EndDisabledGroup(); - + col = EditorGUILayout.ColorField(key, col); EditorGUILayout.EndHorizontal(); + if (EditorGUI.EndChangeCheck()) { + typeColors[key] = col; + if (settings.typeColors.ContainsKey(key)) settings.typeColors[key] = col; + else settings.typeColors.Add(key, col); + SavePrefs(); + NodeEditorWindow.RepaintAll(); + } } } - private static void LoadPrefs() { - //Load type colors - generatedTypeColors = new Dictionary(); + private static Settings LoadPrefs() { + // Remove obsolete editorprefs + if (EditorPrefs.HasKey("xnode_typecolors")) EditorPrefs.DeleteKey("xnode_typecolors"); + if (EditorPrefs.HasKey("xnode_gridcolor0")) EditorPrefs.DeleteKey("xnode_gridcolor0"); + if (EditorPrefs.HasKey("xnode_gridcolor1")) EditorPrefs.DeleteKey("xnode_gridcolor1"); + if (EditorPrefs.HasKey("xnode_gridsnap")) EditorPrefs.DeleteKey("xnode_gridcolor1"); - if (!EditorPrefs.HasKey("xnode_typecolors")) EditorPrefs.SetString("xnode_typecolors", ""); - string[] data = EditorPrefs.GetString("xnode_typecolors").Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - typeColors = new Dictionary(); - for (int i = 0; i < data.Length; i += 2) { - Color col; - if (ColorUtility.TryParseHtmlString("#" + data[i + 1], out col)) { - typeColors.Add(data[i], col); - } - } - - //Load grid colors - if (!EditorPrefs.HasKey("xnode_gridcolor0")) EditorPrefs.SetString("xnode_gridcolor0", ColorUtility.ToHtmlStringRGB(new Color(0.45f, 0.45f, 0.45f))); - ColorUtility.TryParseHtmlString("#" + EditorPrefs.GetString("xnode_gridcolor0"), out _gridLineColor); - if (!EditorPrefs.HasKey("xnode_gridcolor1")) EditorPrefs.SetString("xnode_gridcolor1", ColorUtility.ToHtmlStringRGB(new Color(0.18f, 0.18f, 0.18f))); - ColorUtility.TryParseHtmlString("#" + EditorPrefs.GetString("xnode_gridcolor1"), out _gridBgColor); - - //Load snap option - if (EditorPrefs.HasKey("xnode_gridsnap")) _gridSnap = EditorPrefs.GetBool("xnode_gridsnap"); - - NodeEditorWindow.RepaintAll(); - prefsLoaded = true; + if (!EditorPrefs.HasKey("xNode.Settings")) EditorPrefs.SetString("xNode.Settings", JsonUtility.ToJson(new Settings())); + return JsonUtility.FromJson(EditorPrefs.GetString("xNode.Settings")); } /// Delete all prefs public static void ResetPrefs() { - if (EditorPrefs.HasKey("xnode_typecolors")) EditorPrefs.DeleteKey("xnode_typecolors"); - if (EditorPrefs.HasKey("xnode_gridcolor0")) EditorPrefs.DeleteKey("xnode_gridcolor0"); - if (EditorPrefs.HasKey("xnode_gridcolor1")) EditorPrefs.DeleteKey("xnode_gridcolor1"); - LoadPrefs(); + if (EditorPrefs.HasKey("xNode.Settings")) EditorPrefs.DeleteKey("xNode.Settings"); + + settings = LoadPrefs(); + typeColors = new Dictionary(); + _gridTexture = NodeEditorResources.GenerateGridTexture(settings.gridLineColor, settings.gridBgColor); + _crossTexture = NodeEditorResources.GenerateCrossTexture(settings.gridLineColor); + NodeEditorWindow.RepaintAll(); } private static void SavePrefs() { - string s = ""; - foreach (var item in typeColors) { - s += item.Key + "," + ColorUtility.ToHtmlStringRGB(item.Value) + ","; - } - EditorPrefs.SetString("xnode_typecolors", s); - EditorPrefs.SetString("xnode_gridcolor0", ColorUtility.ToHtmlStringRGB(_gridLineColor)); - EditorPrefs.SetString("xnode_gridcolor1", ColorUtility.ToHtmlStringRGB(_gridBgColor)); - EditorPrefs.SetBool("xnode_gridsnap", _gridSnap); + EditorPrefs.SetString("xNode.Settings", JsonUtility.ToJson(settings)); } private static void VerifyLoaded() { - if (!prefsLoaded) LoadPrefs(); + if (settings == null) settings = LoadPrefs(); } /// Return color based on type @@ -165,15 +155,18 @@ namespace XNodeEditor { VerifyLoaded(); if (type == null) return Color.gray; string typeName = type.PrettyName(); - if (typeColors.ContainsKey(typeName)) return typeColors[typeName]; - if (generatedTypeColors.ContainsKey(typeName)) return generatedTypeColors[typeName]; - #if UNITY_5_4_OR_NEWER - UnityEngine.Random.InitState(typeName.GetHashCode()); - #else - UnityEngine.Random.seed = typeName.GetHashCode(); - #endif - generatedTypeColors.Add(typeName, new Color(UnityEngine.Random.value, UnityEngine.Random.value, UnityEngine.Random.value)); - return generatedTypeColors[typeName]; + if (!typeColors.ContainsKey(typeName)) { + if (settings.typeColors.ContainsKey(typeName)) typeColors.Add(typeName, settings.typeColors[typeName]); + else { +#if UNITY_5_4_OR_NEWER + UnityEngine.Random.InitState(typeName.GetHashCode()); +#else + UnityEngine.Random.seed = typeName.GetHashCode(); +#endif + typeColors.Add(typeName, new Color(UnityEngine.Random.value, UnityEngine.Random.value, UnityEngine.Random.value)); + } + } + return typeColors[typeName]; } } } \ No newline at end of file diff --git a/Scripts/Editor/NodeEditorResources.cs b/Scripts/Editor/NodeEditorResources.cs index 490743b..9b55c8f 100644 --- a/Scripts/Editor/NodeEditorResources.cs +++ b/Scripts/Editor/NodeEditorResources.cs @@ -9,13 +9,15 @@ namespace XNodeEditor { private static Texture2D _dotOuter; public static Texture2D nodeBody { get { return _nodeBody != null ? _nodeBody : _nodeBody = Resources.Load("xnode_node"); } } private static Texture2D _nodeBody; + public static Texture2D nodeHighlight { get { return _nodeHighlight != null ? _nodeHighlight : _nodeHighlight = Resources.Load("xnode_node_highlight"); } } + private static Texture2D _nodeHighlight; // Styles public static Styles styles { get { return _styles != null ? _styles : _styles = new Styles(); } } public static Styles _styles = null; public class Styles { - public GUIStyle inputPort, outputPort, nodeHeader, nodeBody, tooltip; + public GUIStyle inputPort, outputPort, nodeHeader, nodeBody, tooltip, nodeHighlight; public Styles() { GUIStyle baseStyle = new GUIStyle("Label"); @@ -39,6 +41,10 @@ namespace XNodeEditor { nodeBody.border = new RectOffset(32, 32, 32, 32); nodeBody.padding = new RectOffset(16, 16, 4, 16); + nodeHighlight = new GUIStyle(); + nodeHighlight.normal.background = NodeEditorResources.nodeHighlight; + nodeHighlight.border = new RectOffset(32, 32, 32, 32); + tooltip = new GUIStyle("helpBox"); tooltip.alignment = TextAnchor.MiddleCenter; } @@ -50,8 +56,8 @@ namespace XNodeEditor { for (int y = 0; y < 64; y++) { for (int x = 0; x < 64; x++) { Color col = bg; - if (y % 16 == 0 || x % 16 == 0) col = Color.Lerp(line,bg,0.65f); - if (y == 63 || x == 63) col = Color.Lerp(line,bg,0.35f); + if (y % 16 == 0 || x % 16 == 0) col = Color.Lerp(line, bg, 0.65f); + if (y == 63 || x == 63) col = Color.Lerp(line, bg, 0.35f); cols[(y * 64) + x] = col; } } diff --git a/Scripts/Editor/NodeEditorWindow.cs b/Scripts/Editor/NodeEditorWindow.cs index 3c79e71..cf35616 100644 --- a/Scripts/Editor/NodeEditorWindow.cs +++ b/Scripts/Editor/NodeEditorWindow.cs @@ -77,8 +77,18 @@ namespace XNodeEditor { return new Vector2(xOffset, yOffset); } - public void SelectNode(XNode.Node node) { - selectedNode = node; + 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)] diff --git a/Scripts/Editor/Resources/xnode_node_highlight.png b/Scripts/Editor/Resources/xnode_node_highlight.png new file mode 100644 index 0000000..f1bb27f Binary files /dev/null and b/Scripts/Editor/Resources/xnode_node_highlight.png differ diff --git a/Scripts/Editor/Resources/xnode_node_highlight.png.meta b/Scripts/Editor/Resources/xnode_node_highlight.png.meta new file mode 100644 index 0000000..21b6034 --- /dev/null +++ b/Scripts/Editor/Resources/xnode_node_highlight.png.meta @@ -0,0 +1,87 @@ +fileFormatVersion: 2 +guid: 2ab2b92d7e1771b47bba0a46a6f0f6d5 +timeCreated: 1516610730 +licenseType: Free +TextureImporter: + fileIDToRecycleName: {} + externalObjects: {} + serializedVersion: 4 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 0 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: -1 + aniso: 1 + mipBias: -1 + wrapU: 1 + wrapV: 1 + wrapW: -1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spritePixelsToUnits: 100 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + platformSettings: + - buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + - buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + spritePackingTag: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/Node.cs b/Scripts/Node.cs index 2c2ff9b..c1883d3 100644 --- a/Scripts/Node.cs +++ b/Scripts/Node.cs @@ -35,9 +35,9 @@ namespace XNode { } public enum ConnectionType { - /// Allow multiple connections + /// Allow multiple connections Multiple, - /// always override the current connection + /// always override the current connection Override, } @@ -200,13 +200,13 @@ namespace XNode { return JsonUtility.ToJson(this).GetHashCode(); } - /// Mark a serializable field as an input port. You can access this through + /// Mark a serializable field as an input port. You can access this through [AttributeUsage(AttributeTargets.Field, AllowMultiple = true)] public class InputAttribute : Attribute { public ShowBackingValue backingValue; public ConnectionType connectionType; - /// Mark a serializable field as an input port. You can access this through + /// Mark a serializable field as an input port. You can access this through /// Should we display the backing value for this port as an editor field? /// Should we allow multiple connections? public InputAttribute(ShowBackingValue backingValue = ShowBackingValue.Unconnected, ConnectionType connectionType = ConnectionType.Multiple) { @@ -215,13 +215,13 @@ namespace XNode { } } - /// Mark a serializable field as an output port. You can access this through + /// Mark a serializable field as an output port. You can access this through [AttributeUsage(AttributeTargets.Field, AllowMultiple = true)] public class OutputAttribute : Attribute { public ShowBackingValue backingValue; public ConnectionType connectionType; - /// Mark a serializable field as an output port. You can access this through + /// Mark a serializable field as an output port. You can access this through /// Should we display the backing value for this port as an editor field? /// Should we allow multiple connections? public OutputAttribute(ShowBackingValue backingValue = ShowBackingValue.Never, ConnectionType connectionType = ConnectionType.Multiple) { diff --git a/Scripts/NodeDataCache.cs b/Scripts/NodeDataCache.cs index d37f31f..acffe69 100644 --- a/Scripts/NodeDataCache.cs +++ b/Scripts/NodeDataCache.cs @@ -23,12 +23,17 @@ namespace XNode { } // 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 if (staticPorts.ContainsKey(port.fieldName)) { NodePort staticPort = staticPorts[port.fieldName]; - if (port.IsDynamic || port.direction != staticPort.direction) ports.Remove(port.fieldName); + // If port exists but with wrong settings, remove it. Re-add it later. + if (port.connectionType != staticPort.connectionType || port.IsDynamic || port.direction != staticPort.direction) ports.Remove(port.fieldName); else port.ValueType = staticPort.ValueType; - } else if (port.IsStatic) ports.Remove(port.fieldName); + } + // 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) { @@ -63,7 +68,7 @@ namespace XNode { if (inputAttrib == null && outputAttrib == null) continue; - if (inputAttrib != null && outputAttrib != null) Debug.LogError("Field " + fieldInfo + " cannot be both input and output."); + if (inputAttrib != null && outputAttrib != null) Debug.LogError("Field " + fieldInfo[i].Name + " of type " + nodeType.FullName + " cannot be both input and output."); else { if (!portDataCache.ContainsKey(nodeType)) portDataCache.Add(nodeType, new List()); portDataCache[nodeType].Add(new NodePort(fieldInfo[i])); @@ -98,4 +103,4 @@ namespace XNode { } } } -} \ No newline at end of file +}