1
0
mirror of https://github.com/Siccity/xNode.git synced 2025-12-20 09:16:01 +08:00

Merge branch 'master' into examples

This commit is contained in:
Thor Brigsted 2022-07-20 11:06:22 +02:00
commit 8b18006d53
18 changed files with 566 additions and 83 deletions

View File

@ -33,6 +33,9 @@ With a minimal footprint, it is ideal as a base for custom state machines, dialo
* [Getting started](https://github.com/Siccity/xNode/wiki/Getting%20Started) - create your very first node node and graph * [Getting started](https://github.com/Siccity/xNode/wiki/Getting%20Started) - create your very first node node and graph
* [Examples branch](https://github.com/Siccity/xNode/tree/examples) - look at other small projects * [Examples branch](https://github.com/Siccity/xNode/tree/examples) - look at other small projects
### Installation
<details><summary>Instructions</summary>
### Installing with Unity Package Manager ### Installing with Unity Package Manager
***Via Git URL*** ***Via Git URL***
*(Requires Unity version 2018.3.0b7 or above)* *(Requires Unity version 2018.3.0b7 or above)*
@ -56,6 +59,21 @@ The package is available on the [openupm registry](https://openupm.com). It's re
openupm add com.github.siccity.xnode openupm add com.github.siccity.xnode
``` ```
### Installing with git
***Via Git Submodule***
To add xNode as a [submodule](https://git-scm.com/book/en/v2/Git-Tools-Submodules) in your existing git project,
run the following git command from your project root:
```
git submodule add git@github.com:Siccity/xNode.git Assets/Submodules/xNode
```
### Installing 'the old way'
If no source control or package manager is available to you, you can simply copy/paste the source files into your assets folder.
</details>
### Node example: ### Node example:
```csharp ```csharp
// public classes deriving from Node are registered as nodes for use within a graph // public classes deriving from Node are registered as nodes for use within a graph
@ -92,5 +110,10 @@ public class MathNode : Node {
} }
``` ```
### Plugins
Plugins are repositories that add functionality to xNode
* [xNodeGroups](https://github.com/Siccity/xNodeGroups): adds resizable groups
### Community
Join the [Discord](https://discord.gg/qgPrHv4 "Join Discord server") server to leave feedback or get support. Join the [Discord](https://discord.gg/qgPrHv4 "Join Discord server") server to leave feedback or get support.
Feel free to also leave suggestions/requests in the [issues](https://github.com/Siccity/xNode/issues "Go to Issues") page. Feel free to also leave suggestions/requests in the [issues](https://github.com/Siccity/xNode/issues "Go to Issues") page.

View File

@ -0,0 +1,213 @@
#if UNITY_2019_1_OR_NEWER
using System.Collections.Generic;
using System.Linq;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
using static UnityEditor.GenericMenu;
namespace XNodeEditor
{
public class AdvancedGenericMenu : AdvancedDropdown
{
public static float? DefaultMinWidth = 200f;
public static float? DefaultMaxWidth = 300f;
private class AdvancedGenericMenuItem : AdvancedDropdownItem
{
private MenuFunction func;
private MenuFunction2 func2;
private object userData;
public AdvancedGenericMenuItem( string name ) : base( name )
{
}
public AdvancedGenericMenuItem( string name, bool enabled, Texture2D icon, MenuFunction func ) : base( name )
{
Set( enabled, icon, func );
}
public AdvancedGenericMenuItem( string name, bool enabled, Texture2D icon, MenuFunction2 func, object userData ) : base( name )
{
Set( enabled, icon, func, userData );
}
public void Set( bool enabled, Texture2D icon, MenuFunction func )
{
this.enabled = enabled;
this.icon = icon;
this.func = func;
}
public void Set( bool enabled, Texture2D icon, MenuFunction2 func, object userData )
{
this.enabled = enabled;
this.icon = icon;
this.func2 = func;
this.userData = userData;
}
public void Run()
{
if ( func2 != null )
func2( userData );
else if ( func != null )
func();
}
}
private List<AdvancedGenericMenuItem> items = new List<AdvancedGenericMenuItem>();
private AdvancedGenericMenuItem FindOrCreateItem( string name, AdvancedGenericMenuItem currentRoot = null )
{
if ( string.IsNullOrWhiteSpace( name ) )
return null;
AdvancedGenericMenuItem item = null;
string[] paths = name.Split( '/' );
if ( currentRoot == null )
{
item = items.FirstOrDefault( x => x != null && x.name == paths[0] );
if ( item == null )
items.Add( item = new AdvancedGenericMenuItem( paths[0] ) );
}
else
{
item = currentRoot.children.OfType<AdvancedGenericMenuItem>().FirstOrDefault( x => x.name == paths[0] );
if ( item == null )
currentRoot.AddChild( item = new AdvancedGenericMenuItem( paths[0] ) );
}
if ( paths.Length > 1 )
return FindOrCreateItem( string.Join( "/", paths, 1, paths.Length - 1 ), item );
return item;
}
private AdvancedGenericMenuItem FindParent( string name )
{
string[] paths = name.Split( '/' );
return FindOrCreateItem( string.Join( "/", paths, 0, paths.Length - 1 ) );
}
private string Name { get; set; }
public AdvancedGenericMenu() : base( new AdvancedDropdownState() )
{
Name = "";
}
public AdvancedGenericMenu( string name, AdvancedDropdownState state ) : base( state )
{
Name = name;
}
//
// Summary:
// Add a disabled item to the menu.
//
// Parameters:
// content:
// The GUIContent to display as a disabled menu item.
public void AddDisabledItem( GUIContent content )
{
//var parent = FindParent( content.text );
var item = FindOrCreateItem( content.text );
item.Set( false, null, null );
}
//
// Summary:
// Add a disabled item to the menu.
//
// Parameters:
// content:
// The GUIContent to display as a disabled menu item.
//
// on:
// Specifies whether to show that the item is currently activated (i.e. a tick next
// to the item in the menu).
public void AddDisabledItem( GUIContent content, bool on )
{
}
public void AddItem( string name, bool on, MenuFunction func )
{
AddItem( new GUIContent( name ), on, func );
}
public void AddItem( GUIContent content, bool on, MenuFunction func )
{
//var parent = FindParent( content.text );
var item = FindOrCreateItem( content.text );
item.Set( true/*on*/, null, func );
}
public void AddItem( string name, bool on, MenuFunction2 func, object userData )
{
AddItem( new GUIContent( name ), on, func, userData );
}
public void AddItem( GUIContent content, bool on, MenuFunction2 func, object userData )
{
//var parent = FindParent( content.text );
var item = FindOrCreateItem( content.text );
item.Set( true/*on*/, null, func, userData );
}
//
// Summary:
// Add a seperator item to the menu.
//
// Parameters:
// path:
// The path to the submenu, if adding a separator to a submenu. When adding a separator
// to the top level of a menu, use an empty string as the path.
public void AddSeparator( string path = null )
{
var parent = string.IsNullOrWhiteSpace( path ) ? null : FindParent( path );
if ( parent == null )
items.Add( null );
else
parent.AddSeparator();
}
//
// Summary:
// Show the menu at the given screen rect.
//
// Parameters:
// position:
// The position at which to show the menu.
public void DropDown( Rect position )
{
position.width = Mathf.Clamp( position.width, DefaultMinWidth.HasValue ? DefaultMinWidth.Value : 1f, DefaultMaxWidth.HasValue ? DefaultMaxWidth.Value : Screen.width );
Show( position );
}
protected override AdvancedDropdownItem BuildRoot()
{
var root = new AdvancedDropdownItem( Name );
foreach ( var m in items )
{
if ( m == null )
root.AddSeparator();
else
root.AddChild( m );
}
return root;
}
protected override void ItemSelected( AdvancedDropdownItem item )
{
if ( item is AdvancedGenericMenuItem gmItem )
gmItem.Run();
}
}
}
#endif

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ddde711109af02e42bfe8eb006577081
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -8,14 +8,15 @@ using Sirenix.OdinInspector.Editor;
using Sirenix.Utilities; using Sirenix.Utilities;
using Sirenix.Utilities.Editor; using Sirenix.Utilities.Editor;
#endif #endif
#if UNITY_2019_1_OR_NEWER && USE_ADVANCED_GENERIC_MENU
using GenericMenu = XNodeEditor.AdvancedGenericMenu;
#endif
namespace XNodeEditor { namespace XNodeEditor {
/// <summary> Base class to derive custom Node editors from. Use this to create your own custom inspectors and editors for your nodes. </summary> /// <summary> Base class to derive custom Node editors from. Use this to create your own custom inspectors and editors for your nodes. </summary>
[CustomNodeEditor(typeof(XNode.Node))] [CustomNodeEditor(typeof(XNode.Node))]
public class NodeEditor : XNodeEditor.Internal.NodeEditorBase<NodeEditor, NodeEditor.CustomNodeEditorAttribute, XNode.Node> { public class NodeEditor : XNodeEditor.Internal.NodeEditorBase<NodeEditor, NodeEditor.CustomNodeEditorAttribute, XNode.Node> {
private readonly Color DEFAULTCOLOR = new Color32(90, 97, 105, 255);
/// <summary> Fires every whenever a node was modified through the editor </summary> /// <summary> Fires every whenever a node was modified through the editor </summary>
public static Action<XNode.Node> onUpdateNode; public static Action<XNode.Node> onUpdateNode;
public readonly static Dictionary<XNode.NodePort, Vector2> portPositions = new Dictionary<XNode.NodePort, Vector2>(); public readonly static Dictionary<XNode.NodePort, Vector2> portPositions = new Dictionary<XNode.NodePort, Vector2>();
@ -41,10 +42,32 @@ namespace XNodeEditor {
string[] excludes = { "m_Script", "graph", "position", "ports" }; string[] excludes = { "m_Script", "graph", "position", "ports" };
#if ODIN_INSPECTOR #if ODIN_INSPECTOR
InspectorUtilities.BeginDrawPropertyTree(objectTree, true); try
GUIHelper.PushLabelWidth(84); {
objectTree.Draw(true); #if ODIN_INSPECTOR_3
objectTree.BeginDraw( true );
#else
InspectorUtilities.BeginDrawPropertyTree(objectTree, true);
#endif
}
catch ( ArgumentNullException )
{
#if ODIN_INSPECTOR_3
objectTree.EndDraw();
#else
InspectorUtilities.EndDrawPropertyTree(objectTree);
#endif
NodeEditor.DestroyEditor(this.target);
return;
}
GUIHelper.PushLabelWidth( 84 );
objectTree.Draw( true );
#if ODIN_INSPECTOR_3
objectTree.EndDraw();
#else
InspectorUtilities.EndDrawPropertyTree(objectTree); InspectorUtilities.EndDrawPropertyTree(objectTree);
#endif
GUIHelper.PopLabelWidth(); GUIHelper.PopLabelWidth();
#else #else
@ -93,7 +116,7 @@ namespace XNodeEditor {
Color color; Color color;
if (type.TryGetAttributeTint(out color)) return color; if (type.TryGetAttributeTint(out color)) return color;
// Return default color (grey) // Return default color (grey)
else return DEFAULTCOLOR; else return NodeEditorPreferences.GetSettings().tintColor;
} }
public virtual GUIStyle GetBodyStyle() { public virtual GUIStyle GetBodyStyle() {
@ -104,6 +127,11 @@ namespace XNodeEditor {
return NodeEditorResources.styles.nodeHighlight; return NodeEditorResources.styles.nodeHighlight;
} }
/// <summary> Override to display custom node header tooltips </summary>
public virtual string GetHeaderTooltip() {
return null;
}
/// <summary> Add items for the context menu when right-clicking this node. Override to add custom menu items. </summary> /// <summary> Add items for the context menu when right-clicking this node. Override to add custom menu items. </summary>
public virtual void AddContextMenuItems(GenericMenu menu) { public virtual void AddContextMenuItems(GenericMenu menu) {
bool canRemove = true; bool canRemove = true;

View File

@ -4,6 +4,9 @@ using System.Linq;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
using XNodeEditor.Internal; using XNodeEditor.Internal;
#if UNITY_2019_1_OR_NEWER && USE_ADVANCED_GENERIC_MENU
using GenericMenu = XNodeEditor.AdvancedGenericMenu;
#endif
namespace XNodeEditor { namespace XNodeEditor {
public partial class NodeEditorWindow { public partial class NodeEditorWindow {
@ -14,16 +17,25 @@ namespace XNodeEditor {
public static XNode.Node[] copyBuffer = null; public static XNode.Node[] copyBuffer = null;
private bool IsDraggingPort { get { return draggedOutput != null; } } public bool IsDraggingPort { get { return draggedOutput != null; } }
private bool IsHoveringPort { get { return hoveredPort != null; } } public bool IsHoveringPort { get { return hoveredPort != null; } }
private bool IsHoveringNode { get { return hoveredNode != null; } } public bool IsHoveringNode { get { return hoveredNode != null; } }
private bool IsHoveringReroute { get { return hoveredReroute.port != null; } } public bool IsHoveringReroute { get { return hoveredReroute.port != null; } }
/// <summary> Return the dragged port or null if not exist </summary>
public XNode.NodePort DraggedOutputPort { get { XNode.NodePort result = draggedOutput; return result; } }
/// <summary> Return the Hovered port or null if not exist </summary>
public XNode.NodePort HoveredPort { get { XNode.NodePort result = hoveredPort; return result; } }
/// <summary> Return the Hovered node or null if not exist </summary>
public XNode.Node HoveredNode { get { XNode.Node result = hoveredNode; return result; } }
private XNode.Node hoveredNode = null; private XNode.Node hoveredNode = null;
[NonSerialized] public XNode.NodePort hoveredPort = null; [NonSerialized] public XNode.NodePort hoveredPort = null;
[NonSerialized] private XNode.NodePort draggedOutput = null; [NonSerialized] private XNode.NodePort draggedOutput = null;
[NonSerialized] private XNode.NodePort draggedOutputTarget = null; [NonSerialized] private XNode.NodePort draggedOutputTarget = null;
[NonSerialized] private XNode.NodePort autoConnectOutput = null; [NonSerialized] private XNode.NodePort autoConnectOutput = null;
[NonSerialized] private List<Vector2> draggedOutputReroutes = new List<Vector2>(); [NonSerialized] private List<Vector2> draggedOutputReroutes = new List<Vector2>();
private RerouteReference hoveredReroute = new RerouteReference(); private RerouteReference hoveredReroute = new RerouteReference();
public List<RerouteReference> selectedReroutes = new List<RerouteReference>(); public List<RerouteReference> selectedReroutes = new List<RerouteReference>();
private Vector2 dragBoxStart; private Vector2 dragBoxStart;
@ -224,7 +236,7 @@ namespace XNodeEditor {
// Open context menu for auto-connection if there is no target node // Open context menu for auto-connection if there is no target node
else if (draggedOutputTarget == null && NodeEditorPreferences.GetSettings().dragToCreate && autoConnectOutput != null) { else if (draggedOutputTarget == null && NodeEditorPreferences.GetSettings().dragToCreate && autoConnectOutput != null) {
GenericMenu menu = new GenericMenu(); GenericMenu menu = new GenericMenu();
graphEditor.AddContextMenuItems(menu); graphEditor.AddContextMenuItems(menu, draggedOutput.ValueType);
menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero)); menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero));
} }
//Release dragged connection //Release dragged connection
@ -296,7 +308,7 @@ namespace XNodeEditor {
isDoubleClick = false; isDoubleClick = false;
break; break;
case EventType.KeyDown: case EventType.KeyDown:
if (EditorGUIUtility.editingTextField) break; if (EditorGUIUtility.editingTextField || GUIUtility.keyboardControl != 0) break;
else if (e.keyCode == KeyCode.F) Home(); else if (e.keyCode == KeyCode.F) Home();
if (NodeEditorUtilities.IsMac()) { if (NodeEditorUtilities.IsMac()) {
if (e.keyCode == KeyCode.Return) RenameSelectedNode(); if (e.keyCode == KeyCode.Return) RenameSelectedNode();
@ -328,11 +340,15 @@ namespace XNodeEditor {
if (e.type == EventType.ExecuteCommand) DuplicateSelectedNodes(); if (e.type == EventType.ExecuteCommand) DuplicateSelectedNodes();
e.Use(); e.Use();
} else if (e.commandName == "Copy") { } else if (e.commandName == "Copy") {
if (e.type == EventType.ExecuteCommand) CopySelectedNodes(); if (!EditorGUIUtility.editingTextField) {
e.Use(); if (e.type == EventType.ExecuteCommand) CopySelectedNodes();
e.Use();
}
} else if (e.commandName == "Paste") { } else if (e.commandName == "Paste") {
if (e.type == EventType.ExecuteCommand) PasteNodes(WindowToGridPosition(lastMousePosition)); if (!EditorGUIUtility.editingTextField) {
e.Use(); if (e.type == EventType.ExecuteCommand) PasteNodes(WindowToGridPosition(lastMousePosition));
e.Use();
}
} }
Repaint(); Repaint();
break; break;
@ -478,6 +494,7 @@ namespace XNodeEditor {
} }
} }
} }
EditorUtility.SetDirty(graph);
// Select the new nodes // Select the new nodes
Selection.objects = newNodes; Selection.objects = newNodes;
} }
@ -502,6 +519,7 @@ namespace XNodeEditor {
DrawNoodle(gradient, path, stroke, thickness, gridPoints); DrawNoodle(gradient, path, stroke, thickness, gridPoints);
GUIStyle portStyle = NodeEditorWindow.current.graphEditor.GetPortStyle(draggedOutput);
Color bgcol = Color.black; Color bgcol = Color.black;
Color frcol = gradient.colorKeys[0].color; Color frcol = gradient.colorKeys[0].color;
bgcol.a = 0.6f; bgcol.a = 0.6f;
@ -514,7 +532,7 @@ namespace XNodeEditor {
rect.position = new Vector2(rect.position.x - 8, rect.position.y - 8); rect.position = new Vector2(rect.position.x - 8, rect.position.y - 8);
rect = GridToWindowRect(rect); rect = GridToWindowRect(rect);
NodeEditorGUILayout.DrawPortHandle(rect, bgcol, frcol); NodeEditorGUILayout.DrawPortHandle(rect, bgcol, frcol, portStyle.normal.background, portStyle.active.background);
} }
} }
} }

View File

@ -24,7 +24,7 @@ namespace XNodeEditor.Internal {
private PropertyTree _objectTree; private PropertyTree _objectTree;
public PropertyTree objectTree { public PropertyTree objectTree {
get { get {
if (this._objectTree == null) { if (this._objectTree == null){
try { try {
bool wasInEditor = NodeEditor.inNodeEditor; bool wasInEditor = NodeEditor.inNodeEditor;
NodeEditor.inNodeEditor = true; NodeEditor.inNodeEditor = true;
@ -58,6 +58,16 @@ namespace XNodeEditor.Internal {
return editor; return editor;
} }
public static void DestroyEditor( K target )
{
if ( target == null ) return;
T editor;
if ( editors.TryGetValue( target, out editor ) )
{
editors.Remove( target );
}
}
private static Type GetEditorType(Type type) { private static Type GetEditorType(Type type) {
if (type == null) return null; if (type == null) return null;
if (editorTypes == null) CacheCustomEditors(); if (editorTypes == null) CacheCustomEditors();

View File

@ -4,6 +4,9 @@ using System.Linq;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
using XNodeEditor.Internal; using XNodeEditor.Internal;
#if UNITY_2019_1_OR_NEWER && USE_ADVANCED_GENERIC_MENU
using GenericMenu = XNodeEditor.AdvancedGenericMenu;
#endif
namespace XNodeEditor { namespace XNodeEditor {
/// <summary> Contains GUI methods </summary> /// <summary> Contains GUI methods </summary>
@ -112,7 +115,21 @@ namespace XNodeEditor {
/// <summary> Show right-click context menu for hovered port </summary> /// <summary> Show right-click context menu for hovered port </summary>
void ShowPortContextMenu(XNode.NodePort hoveredPort) { void ShowPortContextMenu(XNode.NodePort hoveredPort) {
GenericMenu contextMenu = new GenericMenu(); GenericMenu contextMenu = new GenericMenu();
foreach (var port in hoveredPort.GetConnections()) {
var name = port.node.name;
var index = hoveredPort.GetConnectionIndex(port);
contextMenu.AddItem(new GUIContent(string.Format("Disconnect({0})", name)), false, () => hoveredPort.Disconnect(index));
}
contextMenu.AddItem(new GUIContent("Clear Connections"), false, () => hoveredPort.ClearConnections()); contextMenu.AddItem(new GUIContent("Clear Connections"), false, () => hoveredPort.ClearConnections());
//Get compatible nodes with this port
if (NodeEditorPreferences.GetSettings().createFilter) {
contextMenu.AddSeparator("");
if (hoveredPort.direction == XNode.NodePort.IO.Input)
graphEditor.AddContextMenuItems(contextMenu, hoveredPort.ValueType, XNode.NodePort.IO.Output);
else
graphEditor.AddContextMenuItems(contextMenu, hoveredPort.ValueType, XNode.NodePort.IO.Input);
}
contextMenu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero)); contextMenu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero));
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
} }
@ -329,6 +346,8 @@ namespace XNodeEditor {
if (!_portConnectionPoints.TryGetValue(output, out fromRect)) continue; if (!_portConnectionPoints.TryGetValue(output, out fromRect)) continue;
Color portColor = graphEditor.GetPortColor(output); Color portColor = graphEditor.GetPortColor(output);
GUIStyle portStyle = graphEditor.GetPortStyle(output);
for (int k = 0; k < output.ConnectionCount; k++) { for (int k = 0; k < output.ConnectionCount; k++) {
XNode.NodePort input = output.GetConnection(k); XNode.NodePort input = output.GetConnection(k);
@ -362,11 +381,11 @@ namespace XNodeEditor {
// Draw selected reroute points with an outline // Draw selected reroute points with an outline
if (selectedReroutes.Contains(rerouteRef)) { if (selectedReroutes.Contains(rerouteRef)) {
GUI.color = NodeEditorPreferences.GetSettings().highlightColor; GUI.color = NodeEditorPreferences.GetSettings().highlightColor;
GUI.DrawTexture(rect, NodeEditorResources.dotOuter); GUI.DrawTexture(rect, portStyle.normal.background);
} }
GUI.color = portColor; GUI.color = portColor;
GUI.DrawTexture(rect, NodeEditorResources.dot); GUI.DrawTexture(rect, portStyle.active.background);
if (rect.Overlaps(selectionBox)) selection.Add(rerouteRef); if (rect.Overlaps(selectionBox)) selection.Add(rerouteRef);
if (rect.Contains(mousePos)) hoveredReroute = rerouteRef; if (rect.Contains(mousePos)) hoveredReroute = rerouteRef;
@ -551,16 +570,21 @@ namespace XNodeEditor {
} }
private void DrawTooltip() { private void DrawTooltip() {
if (hoveredPort != null && NodeEditorPreferences.GetSettings().portTooltips && graphEditor != null) { if (!NodeEditorPreferences.GetSettings().portTooltips || graphEditor == null)
string tooltip = graphEditor.GetPortTooltip(hoveredPort); return;
if (string.IsNullOrEmpty(tooltip)) return; string tooltip = null;
GUIContent content = new GUIContent(tooltip); if (hoveredPort != null) {
Vector2 size = NodeEditorResources.styles.tooltip.CalcSize(content); tooltip = graphEditor.GetPortTooltip(hoveredPort);
size.x += 8; } else if (hoveredNode != null && IsHoveringNode && IsHoveringTitle(hoveredNode)) {
Rect rect = new Rect(Event.current.mousePosition - (size), size); tooltip = NodeEditor.GetEditor(hoveredNode, this).GetHeaderTooltip();
EditorGUI.LabelField(rect, content, NodeEditorResources.styles.tooltip);
Repaint();
} }
if (string.IsNullOrEmpty(tooltip)) return;
GUIContent content = new GUIContent(tooltip);
Vector2 size = NodeEditorResources.styles.tooltip.CalcSize(content);
size.x += 8;
Rect rect = new Rect(Event.current.mousePosition - (size), size);
EditorGUI.LabelField(rect, content, NodeEditorResources.styles.tooltip);
Repaint();
} }
} }
} }

View File

@ -16,7 +16,7 @@ namespace XNodeEditor {
/// <summary> Make a field for a serialized property. Automatically displays relevant node port. </summary> /// <summary> Make a field for a serialized property. Automatically displays relevant node port. </summary>
public static void PropertyField(SerializedProperty property, bool includeChildren = true, params GUILayoutOption[] options) { public static void PropertyField(SerializedProperty property, bool includeChildren = true, params GUILayoutOption[] options) {
PropertyField(property, (GUIContent) null, includeChildren, options); PropertyField(property, (GUIContent)null, includeChildren, options);
} }
/// <summary> Make a field for a serialized property. Automatically displays relevant node port. </summary> /// <summary> Make a field for a serialized property. Automatically displays relevant node port. </summary>
@ -59,6 +59,7 @@ namespace XNodeEditor {
(showBacking == XNode.Node.ShowBackingValue.Unconnected && port.IsConnected); (showBacking == XNode.Node.ShowBackingValue.Unconnected && port.IsConnected);
float spacePadding = 0; float spacePadding = 0;
string tooltip = null;
foreach (var attr in propertyAttributes) { foreach (var attr in propertyAttributes) {
if (attr is SpaceAttribute) { if (attr is SpaceAttribute) {
if (usePropertyAttributes) GUILayout.Space((attr as SpaceAttribute).height); if (usePropertyAttributes) GUILayout.Space((attr as SpaceAttribute).height);
@ -71,6 +72,8 @@ namespace XNodeEditor {
position = EditorGUI.IndentedRect(position); position = EditorGUI.IndentedRect(position);
GUI.Label(position, (attr as HeaderAttribute).header, EditorStyles.boldLabel); GUI.Label(position, (attr as HeaderAttribute).header, EditorStyles.boldLabel);
} else spacePadding += EditorGUIUtility.singleLineHeight * 1.5f; } else spacePadding += EditorGUIUtility.singleLineHeight * 1.5f;
} else if (attr is TooltipAttribute) {
tooltip = (attr as TooltipAttribute).tooltip;
} }
} }
@ -83,13 +86,13 @@ namespace XNodeEditor {
switch (showBacking) { switch (showBacking) {
case XNode.Node.ShowBackingValue.Unconnected: case XNode.Node.ShowBackingValue.Unconnected:
// Display a label if port is connected // Display a label if port is connected
if (port.IsConnected) EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName)); if (port.IsConnected) EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName, tooltip));
// Display an editable property field if port is not connected // Display an editable property field if port is not connected
else EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30)); else EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30));
break; break;
case XNode.Node.ShowBackingValue.Never: case XNode.Node.ShowBackingValue.Never:
// Display a label // Display a label
EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName)); EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName, tooltip));
break; break;
case XNode.Node.ShowBackingValue.Always: case XNode.Node.ShowBackingValue.Always:
// Display an editable property field // Display an editable property field
@ -98,7 +101,8 @@ namespace XNodeEditor {
} }
rect = GUILayoutUtility.GetLastRect(); rect = GUILayoutUtility.GetLastRect();
rect.position = rect.position - new Vector2(16, -spacePadding); float paddingLeft = NodeEditorWindow.current.graphEditor.GetPortStyle(port).padding.left;
rect.position = rect.position - new Vector2(16 + paddingLeft, -spacePadding);
// If property is an output, display a text label and put a port handle on the right side // 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) { } else if (port.direction == XNode.NodePort.IO.Output) {
// Get data from [Output] attribute // Get data from [Output] attribute
@ -115,6 +119,7 @@ namespace XNodeEditor {
(showBacking == XNode.Node.ShowBackingValue.Unconnected && port.IsConnected); (showBacking == XNode.Node.ShowBackingValue.Unconnected && port.IsConnected);
float spacePadding = 0; float spacePadding = 0;
string tooltip = null;
foreach (var attr in propertyAttributes) { foreach (var attr in propertyAttributes) {
if (attr is SpaceAttribute) { if (attr is SpaceAttribute) {
if (usePropertyAttributes) GUILayout.Space((attr as SpaceAttribute).height); if (usePropertyAttributes) GUILayout.Space((attr as SpaceAttribute).height);
@ -127,6 +132,8 @@ namespace XNodeEditor {
position = EditorGUI.IndentedRect(position); position = EditorGUI.IndentedRect(position);
GUI.Label(position, (attr as HeaderAttribute).header, EditorStyles.boldLabel); GUI.Label(position, (attr as HeaderAttribute).header, EditorStyles.boldLabel);
} else spacePadding += EditorGUIUtility.singleLineHeight * 1.5f; } else spacePadding += EditorGUIUtility.singleLineHeight * 1.5f;
} else if (attr is TooltipAttribute) {
tooltip = (attr as TooltipAttribute).tooltip;
} }
} }
@ -139,13 +146,13 @@ namespace XNodeEditor {
switch (showBacking) { switch (showBacking) {
case XNode.Node.ShowBackingValue.Unconnected: case XNode.Node.ShowBackingValue.Unconnected:
// Display a label if port is connected // Display a label if port is connected
if (port.IsConnected) EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName), NodeEditorResources.OutputPort, GUILayout.MinWidth(30)); if (port.IsConnected) EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName, tooltip), NodeEditorResources.OutputPort, GUILayout.MinWidth(30));
// Display an editable property field if port is not connected // Display an editable property field if port is not connected
else EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30)); else EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30));
break; break;
case XNode.Node.ShowBackingValue.Never: case XNode.Node.ShowBackingValue.Never:
// Display a label // Display a label
EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName), NodeEditorResources.OutputPort, GUILayout.MinWidth(30)); EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName, tooltip), NodeEditorResources.OutputPort, GUILayout.MinWidth(30));
break; break;
case XNode.Node.ShowBackingValue.Always: case XNode.Node.ShowBackingValue.Always:
// Display an editable property field // Display an editable property field
@ -154,15 +161,16 @@ namespace XNodeEditor {
} }
rect = GUILayoutUtility.GetLastRect(); rect = GUILayoutUtility.GetLastRect();
rect.width += NodeEditorWindow.current.graphEditor.GetPortStyle(port).padding.right;
rect.position = rect.position + new Vector2(rect.width, spacePadding); rect.position = rect.position + new Vector2(rect.width, spacePadding);
} }
rect.size = new Vector2(16, 16); rect.size = new Vector2(16, 16);
NodeEditor editor = NodeEditor.GetEditor(port.node, NodeEditorWindow.current); Color backgroundColor = NodeEditorWindow.current.graphEditor.GetPortBackgroundColor(port);
Color backgroundColor = editor.GetTint();
Color col = NodeEditorWindow.current.graphEditor.GetPortColor(port); Color col = NodeEditorWindow.current.graphEditor.GetPortColor(port);
DrawPortHandle(rect, backgroundColor, col); GUIStyle portStyle = NodeEditorWindow.current.graphEditor.GetPortStyle(port);
DrawPortHandle(rect, backgroundColor, col, portStyle.normal.background, portStyle.active.background);
// Register the handle position // Register the handle position
Vector2 portPos = rect.center; Vector2 portPos = rect.center;
@ -194,7 +202,8 @@ namespace XNodeEditor {
EditorGUILayout.LabelField(content, options); EditorGUILayout.LabelField(content, options);
Rect rect = GUILayoutUtility.GetLastRect(); Rect rect = GUILayoutUtility.GetLastRect();
position = rect.position - new Vector2(16, 0); float paddingLeft = NodeEditorWindow.current.graphEditor.GetPortStyle(port).padding.left;
position = rect.position - new Vector2(16 + paddingLeft, 0);
} }
// If property is an output, display a text label and put a port handle on the right side // 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) { else if (port.direction == XNode.NodePort.IO.Output) {
@ -202,6 +211,7 @@ namespace XNodeEditor {
EditorGUILayout.LabelField(content, NodeEditorResources.OutputPort, options); EditorGUILayout.LabelField(content, NodeEditorResources.OutputPort, options);
Rect rect = GUILayoutUtility.GetLastRect(); Rect rect = GUILayoutUtility.GetLastRect();
rect.width += NodeEditorWindow.current.graphEditor.GetPortStyle(port).padding.right;
position = rect.position + new Vector2(rect.width, 0); position = rect.position + new Vector2(rect.width, 0);
} }
PortField(position, port); PortField(position, port);
@ -213,10 +223,11 @@ namespace XNodeEditor {
Rect rect = new Rect(position, new Vector2(16, 16)); Rect rect = new Rect(position, new Vector2(16, 16));
NodeEditor editor = NodeEditor.GetEditor(port.node, NodeEditorWindow.current); Color backgroundColor = NodeEditorWindow.current.graphEditor.GetPortBackgroundColor(port);
Color backgroundColor = editor.GetTint();
Color col = NodeEditorWindow.current.graphEditor.GetPortColor(port); Color col = NodeEditorWindow.current.graphEditor.GetPortColor(port);
DrawPortHandle(rect, backgroundColor, col); GUIStyle portStyle = NodeEditorWindow.current.graphEditor.GetPortStyle(port);
DrawPortHandle(rect, backgroundColor, col, portStyle.normal.background, portStyle.active.background);
// Register the handle position // Register the handle position
Vector2 portPos = rect.center; Vector2 portPos = rect.center;
@ -231,19 +242,22 @@ namespace XNodeEditor {
// If property is an input, display a regular property field and put a port handle on the left side // 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) { if (port.direction == XNode.NodePort.IO.Input) {
rect = GUILayoutUtility.GetLastRect(); rect = GUILayoutUtility.GetLastRect();
rect.position = rect.position - new Vector2(16, 0); float paddingLeft = NodeEditorWindow.current.graphEditor.GetPortStyle(port).padding.left;
rect.position = rect.position - new Vector2(16 + paddingLeft, 0);
// If property is an output, display a text label and put a port handle on the right side // 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) { } else if (port.direction == XNode.NodePort.IO.Output) {
rect = GUILayoutUtility.GetLastRect(); rect = GUILayoutUtility.GetLastRect();
rect.width += NodeEditorWindow.current.graphEditor.GetPortStyle(port).padding.right;
rect.position = rect.position + new Vector2(rect.width, 0); rect.position = rect.position + new Vector2(rect.width, 0);
} }
rect.size = new Vector2(16, 16); rect.size = new Vector2(16, 16);
NodeEditor editor = NodeEditor.GetEditor(port.node, NodeEditorWindow.current); Color backgroundColor = NodeEditorWindow.current.graphEditor.GetPortBackgroundColor(port);
Color backgroundColor = editor.GetTint();
Color col = NodeEditorWindow.current.graphEditor.GetPortColor(port); Color col = NodeEditorWindow.current.graphEditor.GetPortColor(port);
DrawPortHandle(rect, backgroundColor, col); GUIStyle portStyle = NodeEditorWindow.current.graphEditor.GetPortStyle(port);
DrawPortHandle(rect, backgroundColor, col, portStyle.normal.background, portStyle.active.background);
// Register the handle position // Register the handle position
Vector2 portPos = rect.center; Vector2 portPos = rect.center;
@ -258,16 +272,25 @@ namespace XNodeEditor {
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
} }
public static void DrawPortHandle(Rect rect, Color backgroundColor, Color typeColor) { /// <summary>
/// Draw the port
/// </summary>
/// <param name="rect">position and size</param>
/// <param name="backgroundColor">color for background texture of the port. Normaly used to Border</param>
/// <param name="typeColor"></param>
/// <param name="border">texture for border of the dot port</param>
/// <param name="dot">texture for the dot port</param>
public static void DrawPortHandle(Rect rect, Color backgroundColor, Color typeColor, Texture2D border, Texture2D dot) {
Color col = GUI.color; Color col = GUI.color;
GUI.color = backgroundColor; GUI.color = backgroundColor;
GUI.DrawTexture(rect, NodeEditorResources.dotOuter); GUI.DrawTexture(rect, border);
GUI.color = typeColor; GUI.color = typeColor;
GUI.DrawTexture(rect, NodeEditorResources.dot); GUI.DrawTexture(rect, dot);
GUI.color = col; GUI.color = col;
} }
#region Obsolete
#region Obsolete
[Obsolete("Use IsDynamicPortListPort instead")] [Obsolete("Use IsDynamicPortListPort instead")]
public static bool IsInstancePortListPort(XNode.NodePort port) { public static bool IsInstancePortListPort(XNode.NodePort port) {
return IsDynamicPortListPort(port); return IsDynamicPortListPort(port);
@ -277,7 +300,7 @@ namespace XNodeEditor {
public static void InstancePortList(string fieldName, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType = XNode.Node.ConnectionType.Multiple, XNode.Node.TypeConstraint typeConstraint = XNode.Node.TypeConstraint.None, Action<ReorderableList> onCreation = null) { public static void InstancePortList(string fieldName, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType = XNode.Node.ConnectionType.Multiple, XNode.Node.TypeConstraint typeConstraint = XNode.Node.TypeConstraint.None, Action<ReorderableList> onCreation = null) {
DynamicPortList(fieldName, type, serializedObject, io, connectionType, typeConstraint, onCreation); DynamicPortList(fieldName, type, serializedObject, io, connectionType, typeConstraint, onCreation);
} }
#endregion #endregion
/// <summary> Is this port part of a DynamicPortList? </summary> /// <summary> Is this port part of a DynamicPortList? </summary>
public static bool IsDynamicPortListPort(XNode.NodePort port) { public static bool IsDynamicPortListPort(XNode.NodePort port) {
@ -308,7 +331,7 @@ namespace XNodeEditor {
return new { index = i, port = x }; return new { index = i, port = x };
} }
} }
return new { index = -1, port = (XNode.NodePort) null }; return new { index = -1, port = (XNode.NodePort)null };
}).Where(x => x.port != null); }).Where(x => x.port != null);
List<XNode.NodePort> dynamicPorts = indexedPorts.OrderBy(x => x.index).Select(x => x.port).ToList(); List<XNode.NodePort> dynamicPorts = indexedPorts.OrderBy(x => x.index).Select(x => x.port).ToList();
@ -349,7 +372,7 @@ namespace XNodeEditor {
EditorGUI.PropertyField(rect, itemData, true); EditorGUI.PropertyField(rect, itemData, true);
} else EditorGUI.LabelField(rect, port != null ? port.fieldName : ""); } else EditorGUI.LabelField(rect, port != null ? port.fieldName : "");
if (port != null) { if (port != null) {
Vector2 pos = rect.position + (port.IsOutput?new Vector2(rect.width + 6, 0) : new Vector2(-36, 0)); Vector2 pos = rect.position + (port.IsOutput ? new Vector2(rect.width + 6, 0) : new Vector2(-36, 0));
NodeEditorGUILayout.PortField(pos, port); NodeEditorGUILayout.PortField(pos, port);
} }
}; };
@ -371,6 +394,7 @@ namespace XNodeEditor {
}; };
list.onReorderCallback = list.onReorderCallback =
(ReorderableList rl) => { (ReorderableList rl) => {
serializedObject.Update();
bool hasRect = false; bool hasRect = false;
bool hasNewRect = false; bool hasNewRect = false;
Rect rect = Rect.zero; Rect rect = Rect.zero;
@ -385,8 +409,8 @@ namespace XNodeEditor {
// Swap cached positions to mitigate twitching // Swap cached positions to mitigate twitching
hasRect = NodeEditorWindow.current.portConnectionPoints.TryGetValue(port, out rect); hasRect = NodeEditorWindow.current.portConnectionPoints.TryGetValue(port, out rect);
hasNewRect = NodeEditorWindow.current.portConnectionPoints.TryGetValue(nextPort, out newRect); hasNewRect = NodeEditorWindow.current.portConnectionPoints.TryGetValue(nextPort, out newRect);
NodeEditorWindow.current.portConnectionPoints[port] = hasNewRect?newRect:rect; NodeEditorWindow.current.portConnectionPoints[port] = hasNewRect ? newRect : rect;
NodeEditorWindow.current.portConnectionPoints[nextPort] = hasRect?rect:newRect; NodeEditorWindow.current.portConnectionPoints[nextPort] = hasRect ? rect : newRect;
} }
} }
// Move down // Move down
@ -399,8 +423,8 @@ namespace XNodeEditor {
// Swap cached positions to mitigate twitching // Swap cached positions to mitigate twitching
hasRect = NodeEditorWindow.current.portConnectionPoints.TryGetValue(port, out rect); hasRect = NodeEditorWindow.current.portConnectionPoints.TryGetValue(port, out rect);
hasNewRect = NodeEditorWindow.current.portConnectionPoints.TryGetValue(nextPort, out newRect); hasNewRect = NodeEditorWindow.current.portConnectionPoints.TryGetValue(nextPort, out newRect);
NodeEditorWindow.current.portConnectionPoints[port] = hasNewRect?newRect:rect; NodeEditorWindow.current.portConnectionPoints[port] = hasNewRect ? newRect : rect;
NodeEditorWindow.current.portConnectionPoints[nextPort] = hasRect?rect:newRect; NodeEditorWindow.current.portConnectionPoints[nextPort] = hasRect ? rect : newRect;
} }
} }
// Apply changes // Apply changes
@ -445,7 +469,7 @@ namespace XNodeEditor {
return new { index = i, port = x }; return new { index = i, port = x };
} }
} }
return new { index = -1, port = (XNode.NodePort) null }; return new { index = -1, port = (XNode.NodePort)null };
}).Where(x => x.port != null); }).Where(x => x.port != null);
dynamicPorts = indexedPorts.OrderBy(x => x.index).Select(x => x.port).ToList(); dynamicPorts = indexedPorts.OrderBy(x => x.index).Select(x => x.port).ToList();

View File

@ -20,10 +20,10 @@ namespace XNodeEditor {
[System.Serializable] [System.Serializable]
public class Settings : ISerializationCallbackReceiver { public class Settings : ISerializationCallbackReceiver {
[SerializeField] private Color32 _gridLineColor = new Color(0.45f, 0.45f, 0.45f); [SerializeField] private Color32 _gridLineColor = new Color(.23f, .23f, .23f);
public Color32 gridLineColor { get { return _gridLineColor; } set { _gridLineColor = value; _gridTexture = null; _crossTexture = null; } } public Color32 gridLineColor { get { return _gridLineColor; } set { _gridLineColor = value; _gridTexture = null; _crossTexture = null; } }
[SerializeField] private Color32 _gridBgColor = new Color(0.18f, 0.18f, 0.18f); [SerializeField] private Color32 _gridBgColor = new Color(.19f, .19f, .19f);
public Color32 gridBgColor { get { return _gridBgColor; } set { _gridBgColor = value; _gridTexture = null; } } public Color32 gridBgColor { get { return _gridBgColor; } set { _gridBgColor = value; _gridTexture = null; } }
[Obsolete("Use maxZoom instead")] [Obsolete("Use maxZoom instead")]
@ -32,15 +32,20 @@ namespace XNodeEditor {
[UnityEngine.Serialization.FormerlySerializedAs("zoomOutLimit")] [UnityEngine.Serialization.FormerlySerializedAs("zoomOutLimit")]
public float maxZoom = 5f; public float maxZoom = 5f;
public float minZoom = 1f; public float minZoom = 1f;
public Color32 tintColor = new Color32(90, 97, 105, 255);
public Color32 highlightColor = new Color32(255, 255, 255, 255); public Color32 highlightColor = new Color32(255, 255, 255, 255);
public bool gridSnap = true; public bool gridSnap = true;
public bool autoSave = true; public bool autoSave = true;
public bool openOnCreate = true;
public bool dragToCreate = true; public bool dragToCreate = true;
public bool createFilter = true;
public bool zoomToMouse = true; public bool zoomToMouse = true;
public bool portTooltips = true; public bool portTooltips = true;
[SerializeField] private string typeColorsData = ""; [SerializeField] private string typeColorsData = "";
[NonSerialized] public Dictionary<string, Color> typeColors = new Dictionary<string, Color>(); [NonSerialized] public Dictionary<string, Color> typeColors = new Dictionary<string, Color>();
[FormerlySerializedAs("noodleType")] public NoodlePath noodlePath = NoodlePath.Curvy; [FormerlySerializedAs("noodleType")] public NoodlePath noodlePath = NoodlePath.Curvy;
public float noodleThickness = 2f;
public NoodleStroke noodleStroke = NoodleStroke.Full; public NoodleStroke noodleStroke = NoodleStroke.Full;
private Texture2D _gridTexture; private Texture2D _gridTexture;
@ -149,6 +154,7 @@ namespace XNodeEditor {
//Label //Label
EditorGUILayout.LabelField("System", EditorStyles.boldLabel); EditorGUILayout.LabelField("System", EditorStyles.boldLabel);
settings.autoSave = EditorGUILayout.Toggle(new GUIContent("Autosave", "Disable for better editor performance"), settings.autoSave); settings.autoSave = EditorGUILayout.Toggle(new GUIContent("Autosave", "Disable for better editor performance"), settings.autoSave);
settings.openOnCreate = EditorGUILayout.Toggle(new GUIContent("Open Editor on Create", "Disable to prevent openening the editor when creating a new graph"), settings.openOnCreate);
if (GUI.changed) SavePrefs(key, settings); if (GUI.changed) SavePrefs(key, settings);
EditorGUILayout.Space(); EditorGUILayout.Space();
} }
@ -156,11 +162,16 @@ namespace XNodeEditor {
private static void NodeSettingsGUI(string key, Settings settings) { private static void NodeSettingsGUI(string key, Settings settings) {
//Label //Label
EditorGUILayout.LabelField("Node", EditorStyles.boldLabel); EditorGUILayout.LabelField("Node", EditorStyles.boldLabel);
settings.tintColor = EditorGUILayout.ColorField("Tint", settings.tintColor);
settings.highlightColor = EditorGUILayout.ColorField("Selection", settings.highlightColor); settings.highlightColor = EditorGUILayout.ColorField("Selection", settings.highlightColor);
settings.noodlePath = (NoodlePath) EditorGUILayout.EnumPopup("Noodle path", (Enum) settings.noodlePath); settings.noodlePath = (NoodlePath) EditorGUILayout.EnumPopup("Noodle path", (Enum) settings.noodlePath);
settings.noodleThickness = EditorGUILayout.FloatField(new GUIContent("Noodle thickness", "Noodle Thickness of the node connections"), settings.noodleThickness);
settings.noodleStroke = (NoodleStroke) EditorGUILayout.EnumPopup("Noodle stroke", (Enum) settings.noodleStroke); settings.noodleStroke = (NoodleStroke) EditorGUILayout.EnumPopup("Noodle stroke", (Enum) settings.noodleStroke);
settings.portTooltips = EditorGUILayout.Toggle("Port Tooltips", settings.portTooltips); settings.portTooltips = EditorGUILayout.Toggle("Port Tooltips", settings.portTooltips);
settings.dragToCreate = EditorGUILayout.Toggle(new GUIContent("Drag to Create", "Drag a port connection anywhere on the grid to create and connect a node"), settings.dragToCreate); settings.dragToCreate = EditorGUILayout.Toggle(new GUIContent("Drag to Create", "Drag a port connection anywhere on the grid to create and connect a node"), settings.dragToCreate);
settings.createFilter = EditorGUILayout.Toggle(new GUIContent("Create Filter", "Only show nodes that are compatible with the selected port"), settings.createFilter);
//END
if (GUI.changed) { if (GUI.changed) {
SavePrefs(key, settings); SavePrefs(key, settings);
NodeEditorWindow.RepaintAll(); NodeEditorWindow.RepaintAll();

View File

@ -5,6 +5,9 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
#if UNITY_2019_1_OR_NEWER && USE_ADVANCED_GENERIC_MENU
using GenericMenu = XNodeEditor.AdvancedGenericMenu;
#endif
namespace XNodeEditor { namespace XNodeEditor {
/// <summary> Contains reflection-related extensions built for xNode </summary> /// <summary> Contains reflection-related extensions built for xNode </summary>

View File

@ -18,7 +18,7 @@ namespace XNodeEditor {
public static Styles _styles = null; public static Styles _styles = null;
public static GUIStyle OutputPort { get { return new GUIStyle(EditorStyles.label) { alignment = TextAnchor.UpperRight }; } } public static GUIStyle OutputPort { get { return new GUIStyle(EditorStyles.label) { alignment = TextAnchor.UpperRight }; } }
public class Styles { public class Styles {
public GUIStyle inputPort, nodeHeader, nodeBody, tooltip, nodeHighlight; public GUIStyle inputPort, outputPort, nodeHeader, nodeBody, tooltip, nodeHighlight;
public Styles() { public Styles() {
GUIStyle baseStyle = new GUIStyle("Label"); GUIStyle baseStyle = new GUIStyle("Label");
@ -26,7 +26,15 @@ namespace XNodeEditor {
inputPort = new GUIStyle(baseStyle); inputPort = new GUIStyle(baseStyle);
inputPort.alignment = TextAnchor.UpperLeft; inputPort.alignment = TextAnchor.UpperLeft;
inputPort.padding.left = 10; inputPort.padding.left = 0;
inputPort.active.background = dot;
inputPort.normal.background = dotOuter;
outputPort = new GUIStyle(baseStyle);
outputPort.alignment = TextAnchor.UpperRight;
outputPort.padding.right = 0;
outputPort.active.background = dot;
outputPort.normal.background = dotOuter;
nodeHeader = new GUIStyle(); nodeHeader = new GUIStyle();
nodeHeader.alignment = TextAnchor.MiddleCenter; nodeHeader.alignment = TextAnchor.MiddleCenter;

View File

@ -127,6 +127,57 @@ namespace XNodeEditor {
return methods.Count() > 0; return methods.Count() > 0;
} }
/// <summary>
/// Looking for ports with value Type compatible with a given type.
/// </summary>
/// <param name="nodeType">Node to search</param>
/// <param name="compatibleType">Type to find compatiblities</param>
/// <param name="direction"></param>
/// <returns>True if NodeType has some port with value type compatible</returns>
public static bool HasCompatiblePortType(Type nodeType, Type compatibleType, XNode.NodePort.IO direction = XNode.NodePort.IO.Input) {
Type findType = typeof(XNode.Node.InputAttribute);
if (direction == XNode.NodePort.IO.Output)
findType = typeof(XNode.Node.OutputAttribute);
//Get All fields from node type and we go filter only field with portAttribute.
//This way is possible to know the values of the all ports and if have some with compatible value tue
foreach (FieldInfo f in XNode.NodeDataCache.GetNodeFields(nodeType)) {
var portAttribute = f.GetCustomAttributes(findType, false).FirstOrDefault();
if (portAttribute != null) {
if (IsCastableTo(f.FieldType, compatibleType)) {
return true;
}
}
}
return false;
}
/// <summary>
/// Filter only node types that contains some port value type compatible with an given type
/// </summary>
/// <param name="nodeTypes">List with all nodes type to filter</param>
/// <param name="compatibleType">Compatible Type to Filter</param>
/// <returns>Return Only Node Types with ports compatible, or an empty list</returns>
public static List<Type> GetCompatibleNodesTypes(Type[] nodeTypes, Type compatibleType, XNode.NodePort.IO direction = XNode.NodePort.IO.Input) {
//Result List
List<Type> filteredTypes = new List<Type>();
//Return empty list
if (nodeTypes == null) { return filteredTypes; }
if (compatibleType == null) { return filteredTypes; }
//Find compatiblity
foreach (Type findType in nodeTypes) {
if (HasCompatiblePortType(findType, compatibleType, direction)) {
filteredTypes.Add(findType);
}
}
return filteredTypes;
}
/// <summary> Return a prettiefied type name. </summary> /// <summary> Return a prettiefied type name. </summary>
public static string PrettyName(this Type type) { public static string PrettyName(this Type type) {
if (type == null) return "null"; if (type == null) return "null";

View File

@ -99,7 +99,7 @@ namespace XNodeEditor {
private static void OnSelectionChanged() { private static void OnSelectionChanged() {
XNode.NodeGraph nodeGraph = Selection.activeObject as XNode.NodeGraph; XNode.NodeGraph nodeGraph = Selection.activeObject as XNode.NodeGraph;
if (nodeGraph && !AssetDatabase.Contains(nodeGraph)) { if (nodeGraph && !AssetDatabase.Contains(nodeGraph)) {
Open(nodeGraph); if (NodeEditorPreferences.GetSettings().openOnCreate) Open(nodeGraph);
} }
} }

View File

@ -1,8 +1,10 @@
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
#if UNITY_2019_1_OR_NEWER && USE_ADVANCED_GENERIC_MENU
using GenericMenu = XNodeEditor.AdvancedGenericMenu;
#endif
namespace XNodeEditor { namespace XNodeEditor {
/// <summary> Base class to derive custom Node Graph editors from. Use this to override how graphs are drawn in the editor. </summary> /// <summary> Base class to derive custom Node Graph editors from. Use this to override how graphs are drawn in the editor. </summary>
@ -57,10 +59,24 @@ namespace XNodeEditor {
return 0; return 0;
} }
/// <summary> Add items for the context menu when right-clicking this node. Override to add custom menu items. </summary> /// <summary>
public virtual void AddContextMenuItems(GenericMenu menu) { /// Add items for the context menu when right-clicking this node.
/// Override to add custom menu items.
/// </summary>
/// <param name="menu"></param>
/// <param name="compatibleType">Use it to filter only nodes with ports value type, compatible with this type</param>
/// <param name="direction">Direction of the compatiblity</param>
public virtual void AddContextMenuItems(GenericMenu menu, Type compatibleType = null, XNode.NodePort.IO direction = XNode.NodePort.IO.Input) {
Vector2 pos = NodeEditorWindow.current.WindowToGridPosition(Event.current.mousePosition); Vector2 pos = NodeEditorWindow.current.WindowToGridPosition(Event.current.mousePosition);
var nodeTypes = NodeEditorReflection.nodeTypes.OrderBy(type => GetNodeMenuOrder(type)).ToArray();
Type[] nodeTypes;
if (compatibleType != null && NodeEditorPreferences.GetSettings().createFilter) {
nodeTypes = NodeEditorUtilities.GetCompatibleNodesTypes(NodeEditorReflection.nodeTypes, compatibleType, direction).OrderBy(GetNodeMenuOrder).ToArray();
} else {
nodeTypes = NodeEditorReflection.nodeTypes.OrderBy(GetNodeMenuOrder).ToArray();
}
for (int i = 0; i < nodeTypes.Length; i++) { for (int i = 0; i < nodeTypes.Length; i++) {
Type type = nodeTypes[i]; Type type = nodeTypes[i];
@ -125,7 +141,7 @@ namespace XNodeEditor {
/// <param name="output"> The output this noodle comes from. Never null. </param> /// <param name="output"> The output this noodle comes from. Never null. </param>
/// <param name="input"> The output this noodle comes from. Can be null if we are dragging the noodle. </param> /// <param name="input"> The output this noodle comes from. Can be null if we are dragging the noodle. </param>
public virtual float GetNoodleThickness(XNode.NodePort output, XNode.NodePort input) { public virtual float GetNoodleThickness(XNode.NodePort output, XNode.NodePort input) {
return 5f; return NodeEditorPreferences.GetSettings().noodleThickness;
} }
public virtual NoodlePath GetNoodlePath(XNode.NodePort output, XNode.NodePort input) { public virtual NoodlePath GetNoodlePath(XNode.NodePort output, XNode.NodePort input) {
@ -141,6 +157,29 @@ namespace XNodeEditor {
return GetTypeColor(port.ValueType); return GetTypeColor(port.ValueType);
} }
/// <summary>
/// The returned Style is used to configure the paddings and icon texture of the ports.
/// Use these properties to customize your port style.
///
/// The properties used is:
/// <see cref="GUIStyle.padding"/>[Left and Right], <see cref="GUIStyle.normal"/> [Background] = border texture,
/// and <seealso cref="GUIStyle.active"/> [Background] = dot texture;
/// </summary>
/// <param name="port">the owner of the style</param>
/// <returns></returns>
public virtual GUIStyle GetPortStyle(XNode.NodePort port) {
if (port.direction == XNode.NodePort.IO.Input)
return NodeEditorResources.styles.inputPort;
return NodeEditorResources.styles.outputPort;
}
/// <summary> The returned color is used to color the background of the door.
/// Usually used for outer edge effect </summary>
public virtual Color GetPortBackgroundColor(XNode.NodePort port) {
return Color.gray;
}
/// <summary> Returns generated color for a type. This color is editable in preferences </summary> /// <summary> Returns generated color for a type. This color is editable in preferences </summary>
public virtual Color GetTypeColor(Type type) { public virtual Color GetTypeColor(Type type) {
return NodeEditorPreferences.GetTypeColor(type); return NodeEditorPreferences.GetTypeColor(type);

View File

@ -53,6 +53,7 @@ namespace XNodeEditor {
GUI.color = Color.white; GUI.color = Color.white;
} }
} }
DrawDefaultInspector();
} }
private void OnEnable() { private void OnEnable() {

View File

@ -51,6 +51,8 @@ namespace XNode {
Strict, Strict,
/// <summary> Allow connections where output value type is assignable from input value type (eg. Object --> ScriptableObject)</summary> /// <summary> Allow connections where output value type is assignable from input value type (eg. Object --> ScriptableObject)</summary>
InheritedInverse, InheritedInverse,
/// <summary> Allow connections where output value type is assignable from input value or input value type is assignable from output value type</summary>
InheritedAny
} }
#region Obsolete #region Obsolete

View File

@ -7,6 +7,7 @@ namespace XNode {
/// <summary> Precaches reflection data in editor so we won't have to do it runtime </summary> /// <summary> Precaches reflection data in editor so we won't have to do it runtime </summary>
public static class NodeDataCache { public static class NodeDataCache {
private static PortDataCache portDataCache; private static PortDataCache portDataCache;
private static Dictionary<System.Type, Dictionary<string, string>> formerlySerializedAsCache;
private static bool Initialized { get { return portDataCache != null; } } private static bool Initialized { get { return portDataCache != null; } }
/// <summary> Update static ports and dynamic ports managed by DynamicPortLists to reflect class fields. </summary> /// <summary> Update static ports and dynamic ports managed by DynamicPortLists to reflect class fields. </summary>
@ -17,6 +18,9 @@ namespace XNode {
Dictionary<string, List<NodePort>> removedPorts = new Dictionary<string, List<NodePort>>(); Dictionary<string, List<NodePort>> removedPorts = new Dictionary<string, List<NodePort>>();
System.Type nodeType = node.GetType(); System.Type nodeType = node.GetType();
Dictionary<string, string> formerlySerializedAs = null;
if (formerlySerializedAsCache != null) formerlySerializedAsCache.TryGetValue(nodeType, out formerlySerializedAs);
List<NodePort> dynamicListPorts = new List<NodePort>(); List<NodePort> dynamicListPorts = new List<NodePort>();
List<NodePort> typePortCache; List<NodePort> typePortCache;
@ -43,6 +47,11 @@ namespace XNode {
} }
// If port doesn't exist anymore, remove it // If port doesn't exist anymore, remove it
else if (port.IsStatic) { else if (port.IsStatic) {
//See if the field is tagged with FormerlySerializedAs, if so add the port with its new field name to removedPorts
// so it can be reconnected in missing ports stage.
string newName = null;
if (formerlySerializedAs != null && formerlySerializedAs.TryGetValue(port.fieldName, out newName)) removedPorts.Add(newName, port.GetConnections());
port.ClearConnections(); port.ClearConnections();
ports.Remove(port.fieldName); ports.Remove(port.fieldName);
} }
@ -177,6 +186,7 @@ namespace XNode {
object[] attribs = fieldInfo[i].GetCustomAttributes(true); object[] attribs = fieldInfo[i].GetCustomAttributes(true);
Node.InputAttribute inputAttrib = attribs.FirstOrDefault(x => x is Node.InputAttribute) as Node.InputAttribute; Node.InputAttribute inputAttrib = attribs.FirstOrDefault(x => x is Node.InputAttribute) as Node.InputAttribute;
Node.OutputAttribute outputAttrib = attribs.FirstOrDefault(x => x is Node.OutputAttribute) as Node.OutputAttribute; Node.OutputAttribute outputAttrib = attribs.FirstOrDefault(x => x is Node.OutputAttribute) as Node.OutputAttribute;
UnityEngine.Serialization.FormerlySerializedAsAttribute formerlySerializedAsAttribute = attribs.FirstOrDefault(x => x is UnityEngine.Serialization.FormerlySerializedAsAttribute) as UnityEngine.Serialization.FormerlySerializedAsAttribute;
if (inputAttrib == null && outputAttrib == null) continue; if (inputAttrib == null && outputAttrib == null) continue;
@ -185,6 +195,14 @@ namespace XNode {
if (!portDataCache.ContainsKey(nodeType)) portDataCache.Add(nodeType, new List<NodePort>()); if (!portDataCache.ContainsKey(nodeType)) portDataCache.Add(nodeType, new List<NodePort>());
portDataCache[nodeType].Add(new NodePort(fieldInfo[i])); portDataCache[nodeType].Add(new NodePort(fieldInfo[i]));
} }
if(formerlySerializedAsAttribute != null) {
if (formerlySerializedAsCache == null) formerlySerializedAsCache = new Dictionary<System.Type, Dictionary<string, string>>();
if (!formerlySerializedAsCache.ContainsKey(nodeType)) formerlySerializedAsCache.Add(nodeType, new Dictionary<string, string>());
if (formerlySerializedAsCache[nodeType].ContainsKey(formerlySerializedAsAttribute.oldName)) Debug.LogError("Another FormerlySerializedAs with value '" + formerlySerializedAsAttribute.oldName + "' already exist on this node.");
else formerlySerializedAsCache[nodeType].Add(formerlySerializedAsAttribute.oldName, fieldInfo[i].Name);
}
} }
} }

View File

@ -273,10 +273,12 @@ namespace XNode {
if (input.typeConstraint == XNode.Node.TypeConstraint.Inherited && !input.ValueType.IsAssignableFrom(output.ValueType)) return false; if (input.typeConstraint == XNode.Node.TypeConstraint.Inherited && !input.ValueType.IsAssignableFrom(output.ValueType)) return false;
if (input.typeConstraint == XNode.Node.TypeConstraint.Strict && input.ValueType != output.ValueType) return false; if (input.typeConstraint == XNode.Node.TypeConstraint.Strict && input.ValueType != output.ValueType) return false;
if (input.typeConstraint == XNode.Node.TypeConstraint.InheritedInverse && !output.ValueType.IsAssignableFrom(input.ValueType)) return false; if (input.typeConstraint == XNode.Node.TypeConstraint.InheritedInverse && !output.ValueType.IsAssignableFrom(input.ValueType)) return false;
if (input.typeConstraint == XNode.Node.TypeConstraint.InheritedAny && !input.ValueType.IsAssignableFrom(output.ValueType) && !output.ValueType.IsAssignableFrom(input.ValueType)) return false;
// Check output type constraints // Check output type constraints
if (output.typeConstraint == XNode.Node.TypeConstraint.Inherited && !input.ValueType.IsAssignableFrom(output.ValueType)) return false; if (output.typeConstraint == XNode.Node.TypeConstraint.Inherited && !input.ValueType.IsAssignableFrom(output.ValueType)) return false;
if (output.typeConstraint == XNode.Node.TypeConstraint.Strict && input.ValueType != output.ValueType) return false; if (output.typeConstraint == XNode.Node.TypeConstraint.Strict && input.ValueType != output.ValueType) return false;
if (output.typeConstraint == XNode.Node.TypeConstraint.InheritedInverse && !output.ValueType.IsAssignableFrom(input.ValueType)) return false; if (output.typeConstraint == XNode.Node.TypeConstraint.InheritedInverse && !output.ValueType.IsAssignableFrom(input.ValueType)) return false;
if (output.typeConstraint == XNode.Node.TypeConstraint.InheritedAny && !input.ValueType.IsAssignableFrom(output.ValueType) && !output.ValueType.IsAssignableFrom(input.ValueType)) return false;
// Success // Success
return true; return true;
} }
@ -294,12 +296,13 @@ namespace XNode {
for (int i = 0; i < port.connections.Count; i++) { for (int i = 0; i < port.connections.Count; i++) {
if (port.connections[i].Port == this) { if (port.connections[i].Port == this) {
port.connections.RemoveAt(i); port.connections.RemoveAt(i);
// Trigger OnRemoveConnection from this side port
port.node.OnRemoveConnection(port);
} }
} }
} }
// Trigger OnRemoveConnection // Trigger OnRemoveConnection
node.OnRemoveConnection(this); node.OnRemoveConnection(this);
if (port != null) port.node.OnRemoveConnection(port);
} }
/// <summary> Disconnect this port from another port </summary> /// <summary> Disconnect this port from another port </summary>
@ -307,11 +310,7 @@ namespace XNode {
// Remove the other ports connection to this port // Remove the other ports connection to this port
NodePort otherPort = connections[i].Port; NodePort otherPort = connections[i].Port;
if (otherPort != null) { if (otherPort != null) {
for (int k = 0; k < otherPort.connections.Count; k++) { otherPort.connections.RemoveAll(it => { return it.Port == this; });
if (otherPort.connections[k].Port == this) {
otherPort.connections.RemoveAt(i);
}
}
} }
// Remove this ports connection to the other // Remove this ports connection to the other
connections.RemoveAt(i); connections.RemoveAt(i);