mirror of
https://github.com/Siccity/xNode.git
synced 2025-12-20 01:06:01 +08:00
Merge branch 'master' into examples
This commit is contained in:
commit
8b18006d53
23
README.md
23
README.md
@ -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
|
||||
* [Examples branch](https://github.com/Siccity/xNode/tree/examples) - look at other small projects
|
||||
|
||||
### Installation
|
||||
<details><summary>Instructions</summary>
|
||||
|
||||
### Installing with Unity Package Manager
|
||||
***Via Git URL***
|
||||
*(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
|
||||
```
|
||||
|
||||
### 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:
|
||||
```csharp
|
||||
// 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.
|
||||
Feel free to also leave suggestions/requests in the [issues](https://github.com/Siccity/xNode/issues "Go to Issues") page.
|
||||
|
||||
213
Scripts/Editor/AdvancedGenericMenu.cs
Normal file
213
Scripts/Editor/AdvancedGenericMenu.cs
Normal 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
|
||||
11
Scripts/Editor/AdvancedGenericMenu.cs.meta
Normal file
11
Scripts/Editor/AdvancedGenericMenu.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ddde711109af02e42bfe8eb006577081
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -8,14 +8,15 @@ using Sirenix.OdinInspector.Editor;
|
||||
using Sirenix.Utilities;
|
||||
using Sirenix.Utilities.Editor;
|
||||
#endif
|
||||
#if UNITY_2019_1_OR_NEWER && USE_ADVANCED_GENERIC_MENU
|
||||
using GenericMenu = XNodeEditor.AdvancedGenericMenu;
|
||||
#endif
|
||||
|
||||
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>
|
||||
[CustomNodeEditor(typeof(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>
|
||||
public static Action<XNode.Node> onUpdateNode;
|
||||
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" };
|
||||
|
||||
#if ODIN_INSPECTOR
|
||||
try
|
||||
{
|
||||
#if ODIN_INSPECTOR_3
|
||||
objectTree.BeginDraw( true );
|
||||
#else
|
||||
InspectorUtilities.BeginDrawPropertyTree(objectTree, true);
|
||||
GUIHelper.PushLabelWidth(84);
|
||||
objectTree.Draw(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);
|
||||
#endif
|
||||
GUIHelper.PopLabelWidth();
|
||||
#else
|
||||
|
||||
@ -93,7 +116,7 @@ namespace XNodeEditor {
|
||||
Color color;
|
||||
if (type.TryGetAttributeTint(out color)) return color;
|
||||
// Return default color (grey)
|
||||
else return DEFAULTCOLOR;
|
||||
else return NodeEditorPreferences.GetSettings().tintColor;
|
||||
}
|
||||
|
||||
public virtual GUIStyle GetBodyStyle() {
|
||||
@ -104,6 +127,11 @@ namespace XNodeEditor {
|
||||
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>
|
||||
public virtual void AddContextMenuItems(GenericMenu menu) {
|
||||
bool canRemove = true;
|
||||
|
||||
@ -4,6 +4,9 @@ using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using XNodeEditor.Internal;
|
||||
#if UNITY_2019_1_OR_NEWER && USE_ADVANCED_GENERIC_MENU
|
||||
using GenericMenu = XNodeEditor.AdvancedGenericMenu;
|
||||
#endif
|
||||
|
||||
namespace XNodeEditor {
|
||||
public partial class NodeEditorWindow {
|
||||
@ -14,16 +17,25 @@ namespace XNodeEditor {
|
||||
|
||||
public static XNode.Node[] copyBuffer = null;
|
||||
|
||||
private bool IsDraggingPort { get { return draggedOutput != null; } }
|
||||
private bool IsHoveringPort { get { return hoveredPort != null; } }
|
||||
private bool IsHoveringNode { get { return hoveredNode != null; } }
|
||||
private bool IsHoveringReroute { get { return hoveredReroute.port != null; } }
|
||||
public bool IsDraggingPort { get { return draggedOutput != null; } }
|
||||
public bool IsHoveringPort { get { return hoveredPort != null; } }
|
||||
public bool IsHoveringNode { get { return hoveredNode != 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;
|
||||
[NonSerialized] public XNode.NodePort hoveredPort = null;
|
||||
[NonSerialized] private XNode.NodePort draggedOutput = null;
|
||||
[NonSerialized] private XNode.NodePort draggedOutputTarget = null;
|
||||
[NonSerialized] private XNode.NodePort autoConnectOutput = null;
|
||||
[NonSerialized] private List<Vector2> draggedOutputReroutes = new List<Vector2>();
|
||||
|
||||
private RerouteReference hoveredReroute = new RerouteReference();
|
||||
public List<RerouteReference> selectedReroutes = new List<RerouteReference>();
|
||||
private Vector2 dragBoxStart;
|
||||
@ -224,7 +236,7 @@ namespace XNodeEditor {
|
||||
// Open context menu for auto-connection if there is no target node
|
||||
else if (draggedOutputTarget == null && NodeEditorPreferences.GetSettings().dragToCreate && autoConnectOutput != null) {
|
||||
GenericMenu menu = new GenericMenu();
|
||||
graphEditor.AddContextMenuItems(menu);
|
||||
graphEditor.AddContextMenuItems(menu, draggedOutput.ValueType);
|
||||
menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero));
|
||||
}
|
||||
//Release dragged connection
|
||||
@ -296,7 +308,7 @@ namespace XNodeEditor {
|
||||
isDoubleClick = false;
|
||||
break;
|
||||
case EventType.KeyDown:
|
||||
if (EditorGUIUtility.editingTextField) break;
|
||||
if (EditorGUIUtility.editingTextField || GUIUtility.keyboardControl != 0) break;
|
||||
else if (e.keyCode == KeyCode.F) Home();
|
||||
if (NodeEditorUtilities.IsMac()) {
|
||||
if (e.keyCode == KeyCode.Return) RenameSelectedNode();
|
||||
@ -328,12 +340,16 @@ namespace XNodeEditor {
|
||||
if (e.type == EventType.ExecuteCommand) DuplicateSelectedNodes();
|
||||
e.Use();
|
||||
} else if (e.commandName == "Copy") {
|
||||
if (!EditorGUIUtility.editingTextField) {
|
||||
if (e.type == EventType.ExecuteCommand) CopySelectedNodes();
|
||||
e.Use();
|
||||
}
|
||||
} else if (e.commandName == "Paste") {
|
||||
if (!EditorGUIUtility.editingTextField) {
|
||||
if (e.type == EventType.ExecuteCommand) PasteNodes(WindowToGridPosition(lastMousePosition));
|
||||
e.Use();
|
||||
}
|
||||
}
|
||||
Repaint();
|
||||
break;
|
||||
case EventType.Ignore:
|
||||
@ -478,6 +494,7 @@ namespace XNodeEditor {
|
||||
}
|
||||
}
|
||||
}
|
||||
EditorUtility.SetDirty(graph);
|
||||
// Select the new nodes
|
||||
Selection.objects = newNodes;
|
||||
}
|
||||
@ -502,6 +519,7 @@ namespace XNodeEditor {
|
||||
|
||||
DrawNoodle(gradient, path, stroke, thickness, gridPoints);
|
||||
|
||||
GUIStyle portStyle = NodeEditorWindow.current.graphEditor.GetPortStyle(draggedOutput);
|
||||
Color bgcol = Color.black;
|
||||
Color frcol = gradient.colorKeys[0].color;
|
||||
bgcol.a = 0.6f;
|
||||
@ -514,7 +532,7 @@ namespace XNodeEditor {
|
||||
rect.position = new Vector2(rect.position.x - 8, rect.position.y - 8);
|
||||
rect = GridToWindowRect(rect);
|
||||
|
||||
NodeEditorGUILayout.DrawPortHandle(rect, bgcol, frcol);
|
||||
NodeEditorGUILayout.DrawPortHandle(rect, bgcol, frcol, portStyle.normal.background, portStyle.active.background);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,7 +24,7 @@ namespace XNodeEditor.Internal {
|
||||
private PropertyTree _objectTree;
|
||||
public PropertyTree objectTree {
|
||||
get {
|
||||
if (this._objectTree == null) {
|
||||
if (this._objectTree == null){
|
||||
try {
|
||||
bool wasInEditor = NodeEditor.inNodeEditor;
|
||||
NodeEditor.inNodeEditor = true;
|
||||
@ -58,6 +58,16 @@ namespace XNodeEditor.Internal {
|
||||
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) {
|
||||
if (type == null) return null;
|
||||
if (editorTypes == null) CacheCustomEditors();
|
||||
|
||||
@ -4,6 +4,9 @@ using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using XNodeEditor.Internal;
|
||||
#if UNITY_2019_1_OR_NEWER && USE_ADVANCED_GENERIC_MENU
|
||||
using GenericMenu = XNodeEditor.AdvancedGenericMenu;
|
||||
#endif
|
||||
|
||||
namespace XNodeEditor {
|
||||
/// <summary> Contains GUI methods </summary>
|
||||
@ -112,7 +115,21 @@ namespace XNodeEditor {
|
||||
/// <summary> Show right-click context menu for hovered port </summary>
|
||||
void ShowPortContextMenu(XNode.NodePort hoveredPort) {
|
||||
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());
|
||||
//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));
|
||||
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
|
||||
}
|
||||
@ -329,6 +346,8 @@ namespace XNodeEditor {
|
||||
if (!_portConnectionPoints.TryGetValue(output, out fromRect)) continue;
|
||||
|
||||
Color portColor = graphEditor.GetPortColor(output);
|
||||
GUIStyle portStyle = graphEditor.GetPortStyle(output);
|
||||
|
||||
for (int k = 0; k < output.ConnectionCount; k++) {
|
||||
XNode.NodePort input = output.GetConnection(k);
|
||||
|
||||
@ -362,11 +381,11 @@ namespace XNodeEditor {
|
||||
// Draw selected reroute points with an outline
|
||||
if (selectedReroutes.Contains(rerouteRef)) {
|
||||
GUI.color = NodeEditorPreferences.GetSettings().highlightColor;
|
||||
GUI.DrawTexture(rect, NodeEditorResources.dotOuter);
|
||||
GUI.DrawTexture(rect, portStyle.normal.background);
|
||||
}
|
||||
|
||||
GUI.color = portColor;
|
||||
GUI.DrawTexture(rect, NodeEditorResources.dot);
|
||||
GUI.DrawTexture(rect, portStyle.active.background);
|
||||
if (rect.Overlaps(selectionBox)) selection.Add(rerouteRef);
|
||||
if (rect.Contains(mousePos)) hoveredReroute = rerouteRef;
|
||||
|
||||
@ -551,8 +570,14 @@ namespace XNodeEditor {
|
||||
}
|
||||
|
||||
private void DrawTooltip() {
|
||||
if (hoveredPort != null && NodeEditorPreferences.GetSettings().portTooltips && graphEditor != null) {
|
||||
string tooltip = graphEditor.GetPortTooltip(hoveredPort);
|
||||
if (!NodeEditorPreferences.GetSettings().portTooltips || graphEditor == null)
|
||||
return;
|
||||
string tooltip = null;
|
||||
if (hoveredPort != null) {
|
||||
tooltip = graphEditor.GetPortTooltip(hoveredPort);
|
||||
} else if (hoveredNode != null && IsHoveringNode && IsHoveringTitle(hoveredNode)) {
|
||||
tooltip = NodeEditor.GetEditor(hoveredNode, this).GetHeaderTooltip();
|
||||
}
|
||||
if (string.IsNullOrEmpty(tooltip)) return;
|
||||
GUIContent content = new GUIContent(tooltip);
|
||||
Vector2 size = NodeEditorResources.styles.tooltip.CalcSize(content);
|
||||
@ -562,5 +587,4 @@ namespace XNodeEditor {
|
||||
Repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@ namespace XNodeEditor {
|
||||
|
||||
/// <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) {
|
||||
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>
|
||||
@ -59,6 +59,7 @@ namespace XNodeEditor {
|
||||
(showBacking == XNode.Node.ShowBackingValue.Unconnected && port.IsConnected);
|
||||
|
||||
float spacePadding = 0;
|
||||
string tooltip = null;
|
||||
foreach (var attr in propertyAttributes) {
|
||||
if (attr is SpaceAttribute) {
|
||||
if (usePropertyAttributes) GUILayout.Space((attr as SpaceAttribute).height);
|
||||
@ -71,6 +72,8 @@ namespace XNodeEditor {
|
||||
position = EditorGUI.IndentedRect(position);
|
||||
GUI.Label(position, (attr as HeaderAttribute).header, EditorStyles.boldLabel);
|
||||
} else spacePadding += EditorGUIUtility.singleLineHeight * 1.5f;
|
||||
} else if (attr is TooltipAttribute) {
|
||||
tooltip = (attr as TooltipAttribute).tooltip;
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,13 +86,13 @@ namespace XNodeEditor {
|
||||
switch (showBacking) {
|
||||
case XNode.Node.ShowBackingValue.Unconnected:
|
||||
// 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
|
||||
else EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30));
|
||||
break;
|
||||
case XNode.Node.ShowBackingValue.Never:
|
||||
// Display a label
|
||||
EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName));
|
||||
EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName, tooltip));
|
||||
break;
|
||||
case XNode.Node.ShowBackingValue.Always:
|
||||
// Display an editable property field
|
||||
@ -98,7 +101,8 @@ namespace XNodeEditor {
|
||||
}
|
||||
|
||||
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
|
||||
} else if (port.direction == XNode.NodePort.IO.Output) {
|
||||
// Get data from [Output] attribute
|
||||
@ -115,6 +119,7 @@ namespace XNodeEditor {
|
||||
(showBacking == XNode.Node.ShowBackingValue.Unconnected && port.IsConnected);
|
||||
|
||||
float spacePadding = 0;
|
||||
string tooltip = null;
|
||||
foreach (var attr in propertyAttributes) {
|
||||
if (attr is SpaceAttribute) {
|
||||
if (usePropertyAttributes) GUILayout.Space((attr as SpaceAttribute).height);
|
||||
@ -127,6 +132,8 @@ namespace XNodeEditor {
|
||||
position = EditorGUI.IndentedRect(position);
|
||||
GUI.Label(position, (attr as HeaderAttribute).header, EditorStyles.boldLabel);
|
||||
} else spacePadding += EditorGUIUtility.singleLineHeight * 1.5f;
|
||||
} else if (attr is TooltipAttribute) {
|
||||
tooltip = (attr as TooltipAttribute).tooltip;
|
||||
}
|
||||
}
|
||||
|
||||
@ -139,13 +146,13 @@ namespace XNodeEditor {
|
||||
switch (showBacking) {
|
||||
case XNode.Node.ShowBackingValue.Unconnected:
|
||||
// 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
|
||||
else EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30));
|
||||
break;
|
||||
case XNode.Node.ShowBackingValue.Never:
|
||||
// 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;
|
||||
case XNode.Node.ShowBackingValue.Always:
|
||||
// Display an editable property field
|
||||
@ -154,15 +161,16 @@ namespace XNodeEditor {
|
||||
}
|
||||
|
||||
rect = GUILayoutUtility.GetLastRect();
|
||||
rect.width += NodeEditorWindow.current.graphEditor.GetPortStyle(port).padding.right;
|
||||
rect.position = rect.position + new Vector2(rect.width, spacePadding);
|
||||
}
|
||||
|
||||
rect.size = new Vector2(16, 16);
|
||||
|
||||
NodeEditor editor = NodeEditor.GetEditor(port.node, NodeEditorWindow.current);
|
||||
Color backgroundColor = editor.GetTint();
|
||||
Color backgroundColor = NodeEditorWindow.current.graphEditor.GetPortBackgroundColor(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
|
||||
Vector2 portPos = rect.center;
|
||||
@ -194,7 +202,8 @@ namespace XNodeEditor {
|
||||
EditorGUILayout.LabelField(content, options);
|
||||
|
||||
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
|
||||
else if (port.direction == XNode.NodePort.IO.Output) {
|
||||
@ -202,6 +211,7 @@ namespace XNodeEditor {
|
||||
EditorGUILayout.LabelField(content, NodeEditorResources.OutputPort, options);
|
||||
|
||||
Rect rect = GUILayoutUtility.GetLastRect();
|
||||
rect.width += NodeEditorWindow.current.graphEditor.GetPortStyle(port).padding.right;
|
||||
position = rect.position + new Vector2(rect.width, 0);
|
||||
}
|
||||
PortField(position, port);
|
||||
@ -213,10 +223,11 @@ namespace XNodeEditor {
|
||||
|
||||
Rect rect = new Rect(position, new Vector2(16, 16));
|
||||
|
||||
NodeEditor editor = NodeEditor.GetEditor(port.node, NodeEditorWindow.current);
|
||||
Color backgroundColor = editor.GetTint();
|
||||
Color backgroundColor = NodeEditorWindow.current.graphEditor.GetPortBackgroundColor(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
|
||||
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 (port.direction == XNode.NodePort.IO.Input) {
|
||||
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
|
||||
} else if (port.direction == XNode.NodePort.IO.Output) {
|
||||
rect = GUILayoutUtility.GetLastRect();
|
||||
rect.width += NodeEditorWindow.current.graphEditor.GetPortStyle(port).padding.right;
|
||||
rect.position = rect.position + new Vector2(rect.width, 0);
|
||||
}
|
||||
|
||||
rect.size = new Vector2(16, 16);
|
||||
|
||||
NodeEditor editor = NodeEditor.GetEditor(port.node, NodeEditorWindow.current);
|
||||
Color backgroundColor = editor.GetTint();
|
||||
Color backgroundColor = NodeEditorWindow.current.graphEditor.GetPortBackgroundColor(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
|
||||
Vector2 portPos = rect.center;
|
||||
@ -258,16 +272,25 @@ namespace XNodeEditor {
|
||||
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;
|
||||
GUI.color = backgroundColor;
|
||||
GUI.DrawTexture(rect, NodeEditorResources.dotOuter);
|
||||
GUI.DrawTexture(rect, border);
|
||||
GUI.color = typeColor;
|
||||
GUI.DrawTexture(rect, NodeEditorResources.dot);
|
||||
GUI.DrawTexture(rect, dot);
|
||||
GUI.color = col;
|
||||
}
|
||||
|
||||
#region Obsolete
|
||||
|
||||
#region Obsolete
|
||||
[Obsolete("Use IsDynamicPortListPort instead")]
|
||||
public static bool IsInstancePortListPort(XNode.NodePort 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) {
|
||||
DynamicPortList(fieldName, type, serializedObject, io, connectionType, typeConstraint, onCreation);
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
/// <summary> Is this port part of a DynamicPortList? </summary>
|
||||
public static bool IsDynamicPortListPort(XNode.NodePort port) {
|
||||
@ -308,7 +331,7 @@ namespace XNodeEditor {
|
||||
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);
|
||||
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);
|
||||
} else EditorGUI.LabelField(rect, port != null ? port.fieldName : "");
|
||||
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);
|
||||
}
|
||||
};
|
||||
@ -371,6 +394,7 @@ namespace XNodeEditor {
|
||||
};
|
||||
list.onReorderCallback =
|
||||
(ReorderableList rl) => {
|
||||
serializedObject.Update();
|
||||
bool hasRect = false;
|
||||
bool hasNewRect = false;
|
||||
Rect rect = Rect.zero;
|
||||
@ -385,8 +409,8 @@ namespace XNodeEditor {
|
||||
// Swap cached positions to mitigate twitching
|
||||
hasRect = NodeEditorWindow.current.portConnectionPoints.TryGetValue(port, out rect);
|
||||
hasNewRect = NodeEditorWindow.current.portConnectionPoints.TryGetValue(nextPort, out newRect);
|
||||
NodeEditorWindow.current.portConnectionPoints[port] = hasNewRect?newRect:rect;
|
||||
NodeEditorWindow.current.portConnectionPoints[nextPort] = hasRect?rect:newRect;
|
||||
NodeEditorWindow.current.portConnectionPoints[port] = hasNewRect ? newRect : rect;
|
||||
NodeEditorWindow.current.portConnectionPoints[nextPort] = hasRect ? rect : newRect;
|
||||
}
|
||||
}
|
||||
// Move down
|
||||
@ -399,8 +423,8 @@ namespace XNodeEditor {
|
||||
// Swap cached positions to mitigate twitching
|
||||
hasRect = NodeEditorWindow.current.portConnectionPoints.TryGetValue(port, out rect);
|
||||
hasNewRect = NodeEditorWindow.current.portConnectionPoints.TryGetValue(nextPort, out newRect);
|
||||
NodeEditorWindow.current.portConnectionPoints[port] = hasNewRect?newRect:rect;
|
||||
NodeEditorWindow.current.portConnectionPoints[nextPort] = hasRect?rect:newRect;
|
||||
NodeEditorWindow.current.portConnectionPoints[port] = hasNewRect ? newRect : rect;
|
||||
NodeEditorWindow.current.portConnectionPoints[nextPort] = hasRect ? rect : newRect;
|
||||
}
|
||||
}
|
||||
// Apply changes
|
||||
@ -445,7 +469,7 @@ namespace XNodeEditor {
|
||||
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);
|
||||
dynamicPorts = indexedPorts.OrderBy(x => x.index).Select(x => x.port).ToList();
|
||||
|
||||
|
||||
@ -20,10 +20,10 @@ namespace XNodeEditor {
|
||||
|
||||
[System.Serializable]
|
||||
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; } }
|
||||
|
||||
[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; } }
|
||||
|
||||
[Obsolete("Use maxZoom instead")]
|
||||
@ -32,15 +32,20 @@ namespace XNodeEditor {
|
||||
[UnityEngine.Serialization.FormerlySerializedAs("zoomOutLimit")]
|
||||
public float maxZoom = 5f;
|
||||
public float minZoom = 1f;
|
||||
public Color32 tintColor = new Color32(90, 97, 105, 255);
|
||||
public Color32 highlightColor = new Color32(255, 255, 255, 255);
|
||||
public bool gridSnap = true;
|
||||
public bool autoSave = true;
|
||||
public bool openOnCreate = true;
|
||||
public bool dragToCreate = true;
|
||||
public bool createFilter = true;
|
||||
public bool zoomToMouse = true;
|
||||
public bool portTooltips = true;
|
||||
[SerializeField] private string typeColorsData = "";
|
||||
[NonSerialized] public Dictionary<string, Color> typeColors = new Dictionary<string, Color>();
|
||||
[FormerlySerializedAs("noodleType")] public NoodlePath noodlePath = NoodlePath.Curvy;
|
||||
public float noodleThickness = 2f;
|
||||
|
||||
public NoodleStroke noodleStroke = NoodleStroke.Full;
|
||||
|
||||
private Texture2D _gridTexture;
|
||||
@ -149,6 +154,7 @@ namespace XNodeEditor {
|
||||
//Label
|
||||
EditorGUILayout.LabelField("System", EditorStyles.boldLabel);
|
||||
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);
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
@ -156,11 +162,16 @@ namespace XNodeEditor {
|
||||
private static void NodeSettingsGUI(string key, Settings settings) {
|
||||
//Label
|
||||
EditorGUILayout.LabelField("Node", EditorStyles.boldLabel);
|
||||
settings.tintColor = EditorGUILayout.ColorField("Tint", settings.tintColor);
|
||||
settings.highlightColor = EditorGUILayout.ColorField("Selection", settings.highlightColor);
|
||||
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.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.createFilter = EditorGUILayout.Toggle(new GUIContent("Create Filter", "Only show nodes that are compatible with the selected port"), settings.createFilter);
|
||||
|
||||
//END
|
||||
if (GUI.changed) {
|
||||
SavePrefs(key, settings);
|
||||
NodeEditorWindow.RepaintAll();
|
||||
|
||||
@ -5,6 +5,9 @@ using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
#if UNITY_2019_1_OR_NEWER && USE_ADVANCED_GENERIC_MENU
|
||||
using GenericMenu = XNodeEditor.AdvancedGenericMenu;
|
||||
#endif
|
||||
|
||||
namespace XNodeEditor {
|
||||
/// <summary> Contains reflection-related extensions built for xNode </summary>
|
||||
|
||||
@ -18,7 +18,7 @@ namespace XNodeEditor {
|
||||
public static Styles _styles = null;
|
||||
public static GUIStyle OutputPort { get { return new GUIStyle(EditorStyles.label) { alignment = TextAnchor.UpperRight }; } }
|
||||
public class Styles {
|
||||
public GUIStyle inputPort, nodeHeader, nodeBody, tooltip, nodeHighlight;
|
||||
public GUIStyle inputPort, outputPort, nodeHeader, nodeBody, tooltip, nodeHighlight;
|
||||
|
||||
public Styles() {
|
||||
GUIStyle baseStyle = new GUIStyle("Label");
|
||||
@ -26,7 +26,15 @@ namespace XNodeEditor {
|
||||
|
||||
inputPort = new GUIStyle(baseStyle);
|
||||
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.alignment = TextAnchor.MiddleCenter;
|
||||
|
||||
@ -127,6 +127,57 @@ namespace XNodeEditor {
|
||||
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>
|
||||
public static string PrettyName(this Type type) {
|
||||
if (type == null) return "null";
|
||||
|
||||
@ -99,7 +99,7 @@ namespace XNodeEditor {
|
||||
private static void OnSelectionChanged() {
|
||||
XNode.NodeGraph nodeGraph = Selection.activeObject as XNode.NodeGraph;
|
||||
if (nodeGraph && !AssetDatabase.Contains(nodeGraph)) {
|
||||
Open(nodeGraph);
|
||||
if (NodeEditorPreferences.GetSettings().openOnCreate) Open(nodeGraph);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
#if UNITY_2019_1_OR_NEWER && USE_ADVANCED_GENERIC_MENU
|
||||
using GenericMenu = XNodeEditor.AdvancedGenericMenu;
|
||||
#endif
|
||||
|
||||
namespace XNodeEditor {
|
||||
/// <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;
|
||||
}
|
||||
|
||||
/// <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) {
|
||||
/// <summary>
|
||||
/// 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);
|
||||
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++) {
|
||||
Type type = nodeTypes[i];
|
||||
|
||||
@ -125,7 +141,7 @@ namespace XNodeEditor {
|
||||
/// <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>
|
||||
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) {
|
||||
@ -141,6 +157,29 @@ namespace XNodeEditor {
|
||||
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>
|
||||
public virtual Color GetTypeColor(Type type) {
|
||||
return NodeEditorPreferences.GetTypeColor(type);
|
||||
|
||||
@ -53,6 +53,7 @@ namespace XNodeEditor {
|
||||
GUI.color = Color.white;
|
||||
}
|
||||
}
|
||||
DrawDefaultInspector();
|
||||
}
|
||||
|
||||
private void OnEnable() {
|
||||
|
||||
@ -51,6 +51,8 @@ namespace XNode {
|
||||
Strict,
|
||||
/// <summary> Allow connections where output value type is assignable from input value type (eg. Object --> ScriptableObject)</summary>
|
||||
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
|
||||
|
||||
@ -7,6 +7,7 @@ namespace XNode {
|
||||
/// <summary> Precaches reflection data in editor so we won't have to do it runtime </summary>
|
||||
public static class NodeDataCache {
|
||||
private static PortDataCache portDataCache;
|
||||
private static Dictionary<System.Type, Dictionary<string, string>> formerlySerializedAsCache;
|
||||
private static bool Initialized { get { return portDataCache != null; } }
|
||||
|
||||
/// <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>>();
|
||||
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> typePortCache;
|
||||
@ -43,6 +47,11 @@ namespace XNode {
|
||||
}
|
||||
// If port doesn't exist anymore, remove it
|
||||
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();
|
||||
ports.Remove(port.fieldName);
|
||||
}
|
||||
@ -177,6 +186,7 @@ namespace XNode {
|
||||
object[] attribs = fieldInfo[i].GetCustomAttributes(true);
|
||||
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;
|
||||
UnityEngine.Serialization.FormerlySerializedAsAttribute formerlySerializedAsAttribute = attribs.FirstOrDefault(x => x is UnityEngine.Serialization.FormerlySerializedAsAttribute) as UnityEngine.Serialization.FormerlySerializedAsAttribute;
|
||||
|
||||
if (inputAttrib == null && outputAttrib == null) continue;
|
||||
|
||||
@ -185,6 +195,14 @@ namespace XNode {
|
||||
if (!portDataCache.ContainsKey(nodeType)) portDataCache.Add(nodeType, new List<NodePort>());
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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.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.InheritedAny && !input.ValueType.IsAssignableFrom(output.ValueType) && !output.ValueType.IsAssignableFrom(input.ValueType)) return false;
|
||||
// Check output type constraints
|
||||
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.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
|
||||
return true;
|
||||
}
|
||||
@ -294,12 +296,13 @@ namespace XNode {
|
||||
for (int i = 0; i < port.connections.Count; i++) {
|
||||
if (port.connections[i].Port == this) {
|
||||
port.connections.RemoveAt(i);
|
||||
// Trigger OnRemoveConnection from this side port
|
||||
port.node.OnRemoveConnection(port);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Trigger OnRemoveConnection
|
||||
node.OnRemoveConnection(this);
|
||||
if (port != null) port.node.OnRemoveConnection(port);
|
||||
}
|
||||
|
||||
/// <summary> Disconnect this port from another port </summary>
|
||||
@ -307,11 +310,7 @@ namespace XNode {
|
||||
// Remove the other ports connection to this port
|
||||
NodePort otherPort = connections[i].Port;
|
||||
if (otherPort != null) {
|
||||
for (int k = 0; k < otherPort.connections.Count; k++) {
|
||||
if (otherPort.connections[k].Port == this) {
|
||||
otherPort.connections.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
otherPort.connections.RemoveAll(it => { return it.Port == this; });
|
||||
}
|
||||
// Remove this ports connection to the other
|
||||
connections.RemoveAt(i);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user