1
0
mirror of https://github.com/Siccity/xNode.git synced 2026-03-26 22:49:02 +08:00

Merge e49753c58503b1a20a515660397b07d3b6fa595b into 60a8e89cdbf728519c8e615fcf261624a02de7c7

This commit is contained in:
Icarus 2020-08-07 00:18:59 +03:00 committed by GitHub
commit af6012ba0a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
60 changed files with 911 additions and 1233 deletions

1
.gitignore vendored
View File

@ -25,6 +25,7 @@ sysinfo.txt
.git.meta
.gitignore.meta
.gitattributes.meta
*.meta
# OS X only:
.DS_Store

View File

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: bc1db8b29c76d44648c9c86c2dfade6d
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 77523c356ccf04f56b53e6527c6b12fd
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 243efae3a6b7941ad8f8e54dcf38ce8c
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,9 +0,0 @@
fileFormatVersion: 2
guid: 657b15cb3ec32a24ca80faebf094d0f4
folderAsset: yes
timeCreated: 1505418321
licenseType: Free
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,10 +0,0 @@
fileFormatVersion: 2
guid: 5644dfc7eed151045af664a9d4fd1906
folderAsset: yes
timeCreated: 1541633926
licenseType: Free
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,13 +0,0 @@
fileFormatVersion: 2
guid: 10a8338f6c985854697b35459181af0a
timeCreated: 1541633942
licenseType: Free
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,9 +0,0 @@
fileFormatVersion: 2
guid: 94d4fd78d9120634ebe0e8717610c412
folderAsset: yes
timeCreated: 1505418345
licenseType: Free
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,10 +0,0 @@
fileFormatVersion: 2
guid: 7adf21edfb51f514fa991d7556ecd0ef
folderAsset: yes
timeCreated: 1541971984
licenseType: Free
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,13 +0,0 @@
fileFormatVersion: 2
guid: 83db81f92abadca439507e25d517cabe
timeCreated: 1541633798
licenseType: Free
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 327994a52f523b641898a39ff7500a02
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

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

View File

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

View File

@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: a6a1bbc054e282346a02e7bbddde3206
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@ -0,0 +1,101 @@
using System;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
namespace XNodeEditor
{
/// <summary>
/// Menu Popup Window
/// </summary>
public class MenuPopupWindow : PopupWindowContent
{
public Vector2 openBeforeMousePos;
private SearchField search;
private MenuTreeView menuTree;
public Action onCloseAction;
public MenuPopupWindow()
{
search = new SearchField();
menuTree = new MenuTreeView();
}
private bool isInit;
/// <summary>
/// Add Item
/// </summary>
/// <param name="menuPath">Item Path</param>
/// <param name="onClick"></param>
/// <param name="symbol">Path separator</param>
/// <param name="autoClose">Automatically close window after selecting an item</param>
public void AddItem(string menuPath, Action onClick, char symbol = '/',bool autoClose = true)
{
menuTree.AddItem(menuPath, () =>
{
onClick?.Invoke();
if (autoClose)
{
editorWindow.Close();
}
},symbol);
}
/// <summary>
/// Init or Reload Tree
/// </summary>
public void Init()
{
menuTree.Reload();
isInit = true;
}
public override void OnOpen()
{
search.SetFocus();
}
public override void OnClose()
{
onCloseAction?.Invoke();
}
private string _str;
public override void OnGUI(Rect rect)
{
if (!isInit)
{
Init();
}
EventAction();
EditorGUI.BeginChangeCheck();
{
_str = search.OnGUI(new Rect(rect.position, new Vector2(rect.width, 20)),_str);
}
if (EditorGUI.EndChangeCheck())
{
menuTree.searchString = _str;
}
menuTree.OnGUI(new Rect(new Vector2(0,25),rect.size - new Vector2(0,20)));
}
private void EventAction()
{
Event e = Event.current;
switch (e.type)
{
case EventType.KeyDown:
if (e.keyCode == KeyCode.DownArrow && !menuTree.HasFocus())
{
menuTree.SetFocusAndEnsureSelectedItem();
e.Use();
}
break;
}
}
}
}

View File

@ -0,0 +1,169 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
namespace XNodeEditor
{
public class MenuTreeView:TreeView
{
class MenuItem:TreeViewItem
{
public readonly Action OnClick;
public MenuItem(int id, int depth, string displayName, Action onClick) : base(id, depth, displayName)
{
OnClick = onClick;
}
}
public TreeViewItem Root { get; }
public MenuTreeView():this(new TreeViewState())
{
}
public MenuTreeView(TreeViewState state, MultiColumnHeader multiColumnHeader = null) : base(state, multiColumnHeader)
{
Root = new TreeViewItem(id++,-1,nameof(Root));
}
private int id = -1;
private Dictionary<int, List<string>> _menuCache = new Dictionary<int, List<string>>();
/// <summary>
///
/// </summary>
/// <param name="menuPath"></param>
/// <param name="onClick"></param>
/// <param name="symbol"></param>
public void AddItem(string menuPath,Action onClick,char symbol = '/')
{
var paths = menuPath.Split(symbol);
int depth = 0;
TreeViewItem last = Root;
if (paths.Length > 1)
{
for (var i = 0; i < paths.Length - 1; i++)
{
var path = paths[i];
if (!_menuCache.TryGetValue(depth, out var caches))
{
caches = new List<string>();
_menuCache.Add(depth, caches);
}
while (true)
{
if (last.hasChildren)
{
foreach (var item in last.children)
{
if (item.displayName == path)
{
last = item;
depth++;
goto end;
}
}
}
break;
}
if (last.hasChildren)
{
foreach (var child in last.children)
{
if (child.displayName == path)
{
return;
}
}
}
TreeViewItem temp = new TreeViewItem(id++,depth++,path);
last.AddChild(temp);
last = temp;
end: ;
}
}
last.AddChild(new MenuItem(id++,depth,paths.Last(),onClick));
}
protected override bool DoesItemMatchSearch(TreeViewItem item, string search)
{
if (item.parent != null && item.parent.displayName.IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 0)
{
return true;
}
return base.DoesItemMatchSearch(item, search);
}
List<int> ids = new List<int>();
protected override void DoubleClickedItem(int id)
{
TreeViewItem item = FindItem(id,Root);
if (item.hasChildren)
{
if (hasSearch)
{
searchString = "";
ids.Clear();
while (item != null)
{
ids.Add(item.id);
item = item.parent;
}
SetExpanded(ids);
}
else
{
SetExpanded(id, !IsExpanded(id));
}
}
else
{
if (item is MenuItem menuItem)
{
menuItem.OnClick?.Invoke();
}
}
}
protected override TreeViewItem BuildRoot()
{
return Root;
}
protected override void KeyEvent()
{
Event e = Event.current;
switch (e.type)
{
case EventType.KeyDown:
if (e.keyCode == KeyCode.Return || e.keyCode == KeyCode.KeypadEnter)
{
DoubleClickedItem(GetSelection()[0]);
e.Use();
}
break;
}
}
}
}

View File

@ -1,159 +1,212 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
#if ODIN_INSPECTOR
using Sirenix.OdinInspector.Editor;
using Sirenix.Utilities;
using Sirenix.Utilities.Editor;
#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>();
#if ODIN_INSPECTOR
protected internal static bool inNodeEditor = false;
#endif
public virtual void OnHeaderGUI() {
GUILayout.Label(target.name, NodeEditorResources.styles.nodeHeader, GUILayout.Height(30));
}
/// <summary> Draws standard field editors for all public fields </summary>
public virtual void OnBodyGUI() {
#if ODIN_INSPECTOR
inNodeEditor = true;
#endif
// Unity specifically requires this to save/update any serial object.
// serializedObject.Update(); must go at the start of an inspector gui, and
// serializedObject.ApplyModifiedProperties(); goes at the end.
serializedObject.Update();
string[] excludes = { "m_Script", "graph", "position", "ports" };
#if ODIN_INSPECTOR
InspectorUtilities.BeginDrawPropertyTree(objectTree, true);
GUIHelper.PushLabelWidth(84);
objectTree.Draw(true);
InspectorUtilities.EndDrawPropertyTree(objectTree);
GUIHelper.PopLabelWidth();
#else
// Iterate through serialized properties and draw them like the Inspector (But with ports)
SerializedProperty iterator = serializedObject.GetIterator();
bool enterChildren = true;
while (iterator.NextVisible(enterChildren)) {
enterChildren = false;
if (excludes.Contains(iterator.name)) continue;
NodeEditorGUILayout.PropertyField(iterator, true);
}
#endif
// Iterate through dynamic ports and draw them in the order in which they are serialized
foreach (XNode.NodePort dynamicPort in target.DynamicPorts) {
if (NodeEditorGUILayout.IsDynamicPortListPort(dynamicPort)) continue;
NodeEditorGUILayout.PortField(dynamicPort);
}
serializedObject.ApplyModifiedProperties();
#if ODIN_INSPECTOR
// Call repaint so that the graph window elements respond properly to layout changes coming from Odin
if (GUIHelper.RepaintRequested) {
GUIHelper.ClearRepaintRequest();
window.Repaint();
}
#endif
#if ODIN_INSPECTOR
inNodeEditor = false;
#endif
}
public virtual int GetWidth() {
Type type = target.GetType();
int width;
if (type.TryGetAttributeWidth(out width)) return width;
else return 208;
}
/// <summary> Returns color for target node </summary>
public virtual Color GetTint() {
// Try get color from [NodeTint] attribute
Type type = target.GetType();
Color color;
if (type.TryGetAttributeTint(out color)) return color;
// Return default color (grey)
else return DEFAULTCOLOR;
}
public virtual GUIStyle GetBodyStyle() {
return NodeEditorResources.styles.nodeBody;
}
public virtual GUIStyle GetBodyHighlightStyle() {
return NodeEditorResources.styles.nodeHighlight;
}
/// <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;
// Actions if only one node is selected
if (Selection.objects.Length == 1 && Selection.activeObject is XNode.Node) {
XNode.Node node = Selection.activeObject as XNode.Node;
menu.AddItem(new GUIContent("Move To Top"), false, () => NodeEditorWindow.current.MoveNodeToTop(node));
menu.AddItem(new GUIContent("Rename"), false, NodeEditorWindow.current.RenameSelectedNode);
canRemove = NodeGraphEditor.GetEditor(node.graph, NodeEditorWindow.current).CanRemove(node);
}
// Add actions to any number of selected nodes
menu.AddItem(new GUIContent("Copy"), false, NodeEditorWindow.current.CopySelectedNodes);
menu.AddItem(new GUIContent("Duplicate"), false, NodeEditorWindow.current.DuplicateSelectedNodes);
if (canRemove) menu.AddItem(new GUIContent("Remove"), false, NodeEditorWindow.current.RemoveSelectedNodes);
else menu.AddItem(new GUIContent("Remove"), false, null);
// Custom sctions if only one node is selected
if (Selection.objects.Length == 1 && Selection.activeObject is XNode.Node) {
XNode.Node node = Selection.activeObject as XNode.Node;
menu.AddCustomContextMenuItems(node);
}
}
/// <summary> Rename the node asset. This will trigger a reimport of the node. </summary>
public void Rename(string newName) {
if (newName == null || newName.Trim() == "") newName = NodeEditorUtilities.NodeDefaultName(target.GetType());
target.name = newName;
OnRename();
AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target));
}
/// <summary> Called after this node's name has changed. </summary>
public virtual void OnRename() { }
[AttributeUsage(AttributeTargets.Class)]
public class CustomNodeEditorAttribute : Attribute,
XNodeEditor.Internal.NodeEditorBase<NodeEditor, NodeEditor.CustomNodeEditorAttribute, XNode.Node>.INodeEditorAttrib {
private Type inspectedType;
/// <summary> Tells a NodeEditor which Node type it is an editor for </summary>
/// <param name="inspectedType">Type that this editor can edit</param>
public CustomNodeEditorAttribute(Type inspectedType) {
this.inspectedType = inspectedType;
}
public Type GetInspectedType() {
return inspectedType;
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
using XNode;
#if ODIN_INSPECTOR
using Sirenix.OdinInspector.Editor;
using Sirenix.Utilities;
using Sirenix.Utilities.Editor;
#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>();
#if ODIN_INSPECTOR
internal static bool inNodeEditor = false;
#endif
private List<string> excludesField;
private List<string> portNames = new List<string>();
public override void OnCreate()
{
excludesField = new List<string> { "m_Script", "graph", "position", "ports" };
IEnumerable<string> fields = GetExcludesField();
if (fields != null)
{
excludesField.AddRange(fields);
}
}
public virtual void OnHeaderGUI() {
GUILayout.Label(target.name, NodeEditorResources.styles.nodeHeader, GUILayout.Height(30));
}
protected virtual IEnumerable<string> GetExcludesField()
{
return null;
}
/// <summary> Draws standard field editors for all public fields </summary>
public virtual void OnBodyGUI() {
#if ODIN_INSPECTOR
inNodeEditor = true;
#endif
// Unity specifically requires this to save/update any serial object.
// serializedObject.Update(); must go at the start of an inspector gui, and
// serializedObject.ApplyModifiedProperties(); goes at the end.
serializedObject.Update();
#if ODIN_INSPECTOR
InspectorUtilities.BeginDrawPropertyTree(objectTree, true);
GUIHelper.PushLabelWidth(84);
objectTree.Draw(true);
InspectorUtilities.EndDrawPropertyTree(objectTree);
GUIHelper.PopLabelWidth();
#else
// Iterate through serialized properties and draw them like the Inspector (But with ports)
SerializedProperty iterator = serializedObject.GetIterator();
bool enterChildren = true;
portNames.Clear();
while (iterator.NextVisible(enterChildren)) {
enterChildren = false;
if (excludesField.Contains(iterator.name)) continue;
NodeEditorGUILayout.PropertyField(iterator, true);
portNames.Add(iterator.name);
}
_drawPort(NodePort.IO.Input);
#endif
// Iterate through dynamic ports and draw them in the order in which they are serialized
foreach (XNode.NodePort dynamicPort in target.DynamicPorts) {
if (NodeEditorGUILayout.IsDynamicPortListPort(dynamicPort)) continue;
NodeEditorGUILayout.PortField(dynamicPort);
}
#if !ODIN_INSPECTOR
_drawPort(NodePort.IO.Output);
#endif
serializedObject.ApplyModifiedProperties();
#if ODIN_INSPECTOR
// Call repaint so that the graph window elements respond properly to layout changes coming from Odin
if (GUIHelper.RepaintRequested) {
GUIHelper.ClearRepaintRequest();
window.Repaint();
}
#endif
#if ODIN_INSPECTOR
inNodeEditor = false;
#endif
}
private void _drawPort(NodePort.IO io)
{
//Deal with ports that are not drawn
foreach (var port in io == NodePort.IO.Input ? target.Inputs : target.Outputs)
{
//Dynamic skip
if (port.IsDynamic)
{
continue;
}
//Not supported by unity serialization, but marked as input or output
if (!portNames.Contains(port.fieldName))
{
NodeEditorGUILayout.PortField(port);
}
}
}
public virtual int GetWidth() {
Type type = target.GetType();
int width;
if (type.TryGetAttributeWidth(out width)) return width;
else return 208;
}
public Vector2 GetCurrentMousePosition(float yOffset = 10)
{
var mouseGridPos = Event.current.mousePosition;//* window.zoom;
var nodeWindowPos = window.GridToWindowPosition(target.position + mouseGridPos);
var position = nodeWindowPos;//window.GridToWindowPositionNoClipped(nodeWindowPos);
// position += mouseGridPos;
// position.x = Event.current.mousePosition.x;
// position.y += yOffset;
return position;
}
/// <summary> Returns color for target node </summary>
public virtual Color GetTint() {
// Try get color from [NodeTint] attribute
Type type = target.GetType();
Color color;
if (type.TryGetAttributeTint(out color)) return color;
// Return default color (grey)
else return DEFAULTCOLOR;
}
public virtual GUIStyle GetBodyStyle() {
return NodeEditorResources.styles.nodeBody;
}
public virtual GUIStyle GetBodyHighlightStyle() {
return NodeEditorResources.styles.nodeHighlight;
}
/// <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) {
// Actions if only one node is selected
if (Selection.objects.Length == 1 && Selection.activeObject is XNode.Node) {
XNode.Node node = Selection.activeObject as XNode.Node;
menu.AddItem(new GUIContent("Move To Top"), false, () => NodeEditorWindow.current.MoveNodeToTop(node));
menu.AddItem(new GUIContent("Rename"), false, NodeEditorWindow.current.RenameSelectedNode);
}
// Add actions to any number of selected nodes
menu.AddItem(new GUIContent("Copy"), false, NodeEditorWindow.current.CopySelectedNodes);
menu.AddItem(new GUIContent("Duplicate"), false, NodeEditorWindow.current.DuplicateSelectedNodes);
menu.AddItem(new GUIContent("Remove"), false, NodeEditorWindow.current.RemoveSelectedNodes);
// Custom sctions if only one node is selected
if (Selection.objects.Length == 1 && Selection.activeObject is XNode.Node) {
XNode.Node node = Selection.activeObject as XNode.Node;
menu.AddCustomContextMenuItems(node);
}
}
/// <summary> Rename the node asset. This will trigger a reimport of the node. </summary>
public void Rename(string newName) {
if (newName == null || newName.Trim() == "") newName = NodeEditorUtilities.NodeDefaultName(target.GetType());
target.name = newName;
AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target));
}
/// <summary> Called after this node's name has changed. </summary>
public virtual void OnRename() { }
[AttributeUsage(AttributeTargets.Class)]
public class CustomNodeEditorAttribute : Attribute,
XNodeEditor.Internal.NodeEditorBase<NodeEditor, NodeEditor.CustomNodeEditorAttribute, XNode.Node>.INodeEditorAttrib {
private Type inspectedType;
/// <summary> Tells a NodeEditor which Node type it is an editor for </summary>
/// <param name="inspectedType">Type that this editor can edit</param>
public CustomNodeEditorAttribute(Type inspectedType) {
this.inspectedType = inspectedType;
}
public Type GetInspectedType() {
return inspectedType;
}
}
}
}

View File

@ -1,12 +0,0 @@
fileFormatVersion: 2
guid: 712c3fc5d9eeb4c45b1e23918df6018f
timeCreated: 1505462176
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -32,7 +32,7 @@ namespace XNodeEditor {
private Rect selectionBox;
private bool isDoubleClick = false;
private Vector2 lastMousePosition;
private float dragThreshold = 1f;
private float dragThreshold = 1f;
public void Controls() {
wantsMouseMove = true;
@ -223,15 +223,15 @@ 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);
menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero));
var menuPopupWindow = new MenuPopupWindow();
graphEditor.AddContextMenuItems(menuPopupWindow);
menuPopupWindow.onCloseAction = ReleaseDraggedConnection;
menuPopupWindow.openBeforeMousePos = e.mousePosition;
PopupWindow.Show(new Rect(Event.current.mousePosition, Vector2.zero),menuPopupWindow);
}
//Release dragged connection
draggedOutput = null;
draggedOutputTarget = null;
EditorUtility.SetDirty(graph);
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
ReleaseDraggedConnection();
} else if (currentActivity == NodeActivity.DragNode) {
IEnumerable<XNode.Node> nodes = Selection.objects.Where(x => x is XNode.Node).Select(x => x as XNode.Node);
foreach (XNode.Node node in nodes) EditorUtility.SetDirty(node);
@ -285,9 +285,10 @@ namespace XNodeEditor {
e.Use(); // Fixes copy/paste context menu appearing in Unity 5.6.6f2 - doesn't occur in 2018.3.2f1 Probably needs to be used in other places.
} else if (!IsHoveringNode) {
autoConnectOutput = null;
GenericMenu menu = new GenericMenu();
graphEditor.AddContextMenuItems(menu);
menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero));
var menuPopupWindow = new MenuPopupWindow();
graphEditor.AddContextMenuItems(menuPopupWindow);
menuPopupWindow.openBeforeMousePos = e.mousePosition;
PopupWindow.Show(new Rect(Event.current.mousePosition, Vector2.zero),menuPopupWindow);
}
}
isPanning = false;
@ -346,6 +347,15 @@ namespace XNodeEditor {
}
}
private void ReleaseDraggedConnection()
{
//Release dragged connection
draggedOutput = null;
draggedOutputTarget = null;
EditorUtility.SetDirty(graph);
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
}
private void RecalculateDragOffsets(Event current) {
dragOffset = new Vector2[Selection.objects.Length + selectedReroutes.Count];
// Selected nodes

View File

@ -1,12 +0,0 @@
fileFormatVersion: 2
guid: aa7d4286bf0ad2e4086252f2893d2cf5
timeCreated: 1505426655
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -52,7 +52,11 @@ namespace XNodeEditor {
for (int i = 0; i < guids.Length; i++) {
string assetpath = AssetDatabase.GUIDToAssetPath (guids[i]);
XNode.NodeGraph graph = AssetDatabase.LoadAssetAtPath (assetpath, typeof (XNode.NodeGraph)) as XNode.NodeGraph;
graph.nodes.RemoveAll(x => x == null); //Remove null items
if (!graph)
{
return;
}
graph.nodes.RemoveAll(x => !x); //Remove null items
Object[] objs = AssetDatabase.LoadAllAssetRepresentationsAtPath (assetpath);
// Ensure that all sub node assets are present in the graph node list
for (int u = 0; u < objs.Length; u++) {

View File

@ -1,12 +0,0 @@
fileFormatVersion: 2
guid: e515e86efe8160243a68b7c06d730c9c
timeCreated: 1507982232
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -50,6 +50,7 @@ namespace XNodeEditor.Internal {
editor.serializedObject = new SerializedObject(target);
editor.window = window;
editor.OnCreate();
editor.OnInit();
editors.Add(target, editor);
}
if (editor.target == null) editor.target = target;
@ -84,6 +85,11 @@ namespace XNodeEditor.Internal {
/// <summary> Called on creation, after references have been set </summary>
public virtual void OnCreate() { }
/// <summary>
/// node editor window OnFocus call
/// </summary>
public virtual void OnInit() { }
public interface INodeEditorAttrib {
Type GetInspectedType();
}

View File

@ -1,13 +0,0 @@
fileFormatVersion: 2
guid: e85122ded59aceb4eb4b1bd9d9202642
timeCreated: 1511353946
licenseType: Free
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -25,12 +25,13 @@ namespace XNodeEditor {
Controls();
DrawGrid(position, zoom, panOffset);
DrawConnections();
DrawDraggedConnection();
DrawConnections();
DrawNodes();
DrawSelectionBox();
DrawTooltip();
graphEditor.OnGUI();
DrawTooltip();
DrawGroupName();
// Run and reset onLateGUI
if (onLateGUI != null) {
@ -41,6 +42,19 @@ namespace XNodeEditor {
GUI.matrix = m;
}
private void DrawGroupName()
{
GUIContent guiContent = new GUIContent(this.graph.name);
Color col = EditorStyles.label.normal.textColor;
var fontSize = EditorStyles.label.fontSize;
EditorStyles.label.fontSize = 48;
Rect size = GUILayoutUtility.GetRect(guiContent, EditorStyles.label);
EditorStyles.label.normal.textColor = new Color(175 / 255f, 185 / 255f, 185 / 255f);
EditorGUI.LabelField(new Rect(new Vector2(5, 15), size.size), guiContent);
EditorStyles.label.fontSize = fontSize;
EditorStyles.label.normal.textColor = col;
}
public static void BeginZoomed(Rect rect, float zoom, float topPadding) {
GUI.EndClip();
@ -112,7 +126,11 @@ namespace XNodeEditor {
/// <summary> Show right-click context menu for hovered port </summary>
void ShowPortContextMenu(XNode.NodePort hoveredPort) {
GenericMenu contextMenu = new GenericMenu();
contextMenu.AddItem(new GUIContent("Clear Connections"), false, () => hoveredPort.ClearConnections());
contextMenu.AddItem(new GUIContent("Clear Connections"), false, hoveredPort.ClearConnections);
if (hoveredPort.IsDynamic)
{
contextMenu.AddItem(new GUIContent("Remove"), false, () => hoveredPort.node.RemoveDynamicPort(hoveredPort));
}
contextMenu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero));
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
}
@ -531,8 +549,8 @@ namespace XNodeEditor {
if (e.type != EventType.Layout && currentActivity == NodeActivity.DragGrid) Selection.objects = preSelection.ToArray();
EndZoomed(position, zoom, topPadding);
//If a change in is detected in the selected node, call OnValidate method.
//This is done through reflection because OnValidate is only relevant in editor,
//If a change in is detected in the selected node, call OnValidate method.
//This is done through reflection because OnValidate is only relevant in editor,
//and thus, the code should not be included in build.
if (onValidate != null && EditorGUI.EndChangeCheck()) onValidate.Invoke(Selection.activeObject, null);
}

View File

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

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
@ -6,6 +6,7 @@ using System.Reflection;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
using XNode;
namespace XNodeEditor {
/// <summary> xNode-specific version of <see cref="EditorGUILayout"/> </summary>
@ -35,10 +36,25 @@ namespace XNodeEditor {
/// <summary> Make a field for a serialized property. Manual node port override. </summary>
public static void PropertyField(SerializedProperty property, GUIContent label, XNode.NodePort port, bool includeChildren = true, params GUILayoutOption[] options) {
if (property == null) throw new NullReferenceException();
// If property is not a port, display a regular property field
if (port == null) EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30));
else {
Node.LabelAttribute labelAttribute;
if (NodeEditorUtilities.GetCachedAttrib(port.node.GetType(), property.name, out labelAttribute))
{
if (label != null)
{
label.text = labelAttribute.Label;
}
else
{
label = new GUIContent(labelAttribute.Label);
}
}
Rect rect = new Rect();
List<PropertyAttribute> propertyAttributes = NodeEditorUtilities.GetCachedPropertyAttribs(port.node.GetType(), property.name);
@ -217,7 +233,31 @@ namespace XNodeEditor {
Color backgroundColor = editor.GetTint();
Color col = NodeEditorWindow.current.graphEditor.GetPortColor(port);
DrawPortHandle(rect, backgroundColor, col);
//Show all input point indexes when node is selected
if (port.direction == XNode.NodePort.IO.Output)
{
if (port.Connection != null && port.Connection.ConnectionCount > 1)
{
if (port.Connection.node == Selection.activeObject)
{
var dCol = GUI.color;
var fontStyle = EditorStyles.label.fontStyle;
var textCol = Color.white - col;
textCol.a = 1;
var index = port.Connection.GetConnectionIndex(port);
EditorStyles.label.fontStyle = FontStyle.Bold;
GUI.contentColor = textCol;
{
EditorGUI.LabelField(new Rect( rect.position + new Vector2(rect.size.x / 4 - 0.5f,0), rect.size),
(index + 1).ToString());
}
GUI.contentColor = dCol;
EditorStyles.label.fontStyle = fontStyle;
}
}
}
// Register the handle position
Vector2 portPos = rect.center;
NodeEditor.portPositions[port] = portPos;
@ -291,63 +331,65 @@ namespace XNodeEditor {
return false;
}
/// <summary> Draw an editable list of dynamic ports. Port names are named as "[fieldName] [index]" </summary>
/// <summary> Draw an editable list of dynamic ports.</summary>
/// <param name="fieldName">Supply a list for editable values</param>
/// <param name="type">Value type of added dynamic ports</param>
/// <param name="serializedObject">The serializedObject of the node</param>
/// <param name="connectionType">Connection type of added dynamic ports</param>
/// <param name="onCreation">Called on the list on creation. Use this if you want to customize the created ReorderableList</param>
public static void DynamicPortList(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) {
/// <param name="onAdd">Return port name after adding port</param>
public static void DynamicPortList(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,Action<string> onAdd = null) {
XNode.Node node = serializedObject.targetObject as XNode.Node;
var indexedPorts = node.DynamicPorts.Select(x => {
string[] split = x.fieldName.Split(' ');
if (split != null && split.Length == 2 && split[0] == fieldName) {
int i = -1;
if (int.TryParse(split[1], out i)) {
return new { index = i, port = x };
}
}
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();
node.UpdatePorts();
var indexedPorts = io == NodePort.IO.Input ? node.DynamicInputs : node.DynamicOutputs;
List<XNode.NodePort> dynamicPorts = indexedPorts.ToList();
ReorderableList list = null;
Dictionary<string, ReorderableList> rlc;
//todo rename no update cache -- 2019年11月28日03点00分
if (reorderableListCache.TryGetValue(serializedObject.targetObject, out rlc)) {
if (!rlc.TryGetValue(fieldName, out list)) list = null;
}
// If a ReorderableList isn't cached for this array, do so.
if (list == null) {
SerializedProperty arrayData = serializedObject.FindProperty(fieldName);
list = CreateReorderableList(fieldName, dynamicPorts, arrayData, type, serializedObject, io, connectionType, typeConstraint, onCreation);
list = CreateReorderableList(fieldName, dynamicPorts, arrayData, type, serializedObject,
io, connectionType, typeConstraint, onAdd);
onCreation?.Invoke(list);
if (reorderableListCache.TryGetValue(serializedObject.targetObject, out rlc)) rlc.Add(fieldName, list);
else reorderableListCache.Add(serializedObject.targetObject, new Dictionary<string, ReorderableList>() { { fieldName, list } });
}
list.list = dynamicPorts;
list.DoLayoutList();
}
private static ReorderableList CreateReorderableList(string fieldName, List<XNode.NodePort> dynamicPorts, SerializedProperty arrayData, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType, XNode.Node.TypeConstraint typeConstraint, Action<ReorderableList> onCreation) {
private static ReorderableList CreateReorderableList(string fieldName, List<XNode.NodePort> dynamicPorts, SerializedProperty arrayData, Type type,
SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType, XNode.Node.TypeConstraint typeConstraint,Action<string> onAdd) {
bool hasArrayData = arrayData != null && arrayData.isArray;
XNode.Node node = serializedObject.targetObject as XNode.Node;
ReorderableList list = new ReorderableList(dynamicPorts, null, true, true, true, true);
string label = arrayData != null ? arrayData.displayName : ObjectNames.NicifyVariableName(fieldName);
list.drawElementCallback =
(Rect rect, int index, bool isActive, bool isFocused) => {
XNode.NodePort port = node.GetPort(fieldName + " " + index);
if (hasArrayData && arrayData.propertyType != SerializedPropertyType.String) {
(Rect rect, int index, bool isActive, bool isFocused) =>
{
XNode.NodePort port = (NodePort) list.list[index];
if (hasArrayData)
{
if (arrayData.arraySize <= index) {
EditorGUI.LabelField(rect, "Array[" + index + "] data out of range");
return;
}
SerializedProperty itemData = arrayData.GetArrayElementAtIndex(index);
EditorGUI.PropertyField(rect, itemData, true);
} else EditorGUI.LabelField(rect, port != null ? port.fieldName : "");
}
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));
NodeEditorGUILayout.PortField(pos, port);
@ -361,63 +403,59 @@ namespace XNodeEditor {
return EditorGUI.GetPropertyHeight(itemData);
} else return EditorGUIUtility.singleLineHeight;
};
list.drawHeaderCallback =
(Rect rect) => {
EditorGUI.LabelField(rect, label);
};
list.onSelectCallback =
(ReorderableList rl) => {
reorderableListIndex = rl.index;
};
list.onReorderCallback =
(ReorderableList rl) => {
bool hasRect = false;
bool hasNewRect = false;
Rect rect = Rect.zero;
Rect newRect = Rect.zero;
// Move up
if (rl.index > reorderableListIndex) {
for (int i = reorderableListIndex; i < rl.index; ++i) {
XNode.NodePort port = node.GetPort(fieldName + " " + i);
XNode.NodePort nextPort = node.GetPort(fieldName + " " + (i + 1));
port.SwapConnections(nextPort);
// 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;
list.onReorderCallbackWithDetails = (reorderableList, index, newIndex) =>
{
var portSer = serializedObject.FindProperty(Node.PortFieldName);
SerializedProperty keys = portSer.FindPropertyRelative(Node.KeysFieldName);
SerializedProperty values = portSer.FindPropertyRelative(Node.ValuesFieldName);
var baseIndex = 0;
if (io == NodePort.IO.Output)
{
foreach (var nodePort in node.Ports)
{
if (nodePort.direction == NodePort.IO.Input)
{
baseIndex++;
continue;
}
}
// Move down
else {
for (int i = reorderableListIndex; i > rl.index; --i) {
XNode.NodePort port = node.GetPort(fieldName + " " + i);
XNode.NodePort nextPort = node.GetPort(fieldName + " " + (i - 1));
port.SwapConnections(nextPort);
// 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;
}
}
// Apply changes
serializedObject.ApplyModifiedProperties();
serializedObject.Update();
// Move array data if there is any
if (hasArrayData) {
arrayData.MoveArrayElement(reorderableListIndex, rl.index);
break;
}
// Apply changes
serializedObject.ApplyModifiedProperties();
serializedObject.Update();
NodeEditorWindow.current.Repaint();
EditorApplication.delayCall += NodeEditorWindow.current.Repaint;
};
baseIndex++;
}
index += baseIndex;
newIndex += baseIndex;
keys.MoveArrayElement(index, newIndex);
values.MoveArrayElement(index, newIndex);
serializedObject.ApplyModifiedProperties();
EditorUtility.SetDirty(node);
serializedObject.Update();
foreach (NodePort port in reorderableList.list)
{
port.RefreshValueType();
}
NodeEditorWindow.current.Repaint();
EditorApplication.delayCall += NodeEditorWindow.current.Repaint;
};
list.onAddCallback =
(ReorderableList rl) => {
// Add dynamic port postfixed with an index number
@ -433,63 +471,19 @@ namespace XNodeEditor {
arrayData.InsertArrayElementAtIndex(arrayData.arraySize);
}
serializedObject.ApplyModifiedProperties();
onAdd?.Invoke(newName);
};
list.onRemoveCallback =
(ReorderableList rl) => {
var indexedPorts = node.DynamicPorts.Select(x => {
string[] split = x.fieldName.Split(' ');
if (split != null && split.Length == 2 && split[0] == fieldName) {
int i = -1;
if (int.TryParse(split[1], out i)) {
return new { index = i, port = x };
}
}
return new { index = -1, port = (XNode.NodePort) null };
}).Where(x => x.port != null);
dynamicPorts = indexedPorts.OrderBy(x => x.index).Select(x => x.port).ToList();
(ReorderableList rl) =>
{
int index = rl.index;
if (dynamicPorts[index] == null) {
Debug.LogWarning("No port found at index " + index + " - Skipped");
} else if (dynamicPorts.Count <= index) {
Debug.LogWarning("DynamicPorts[" + index + "] out of range. Length was " + dynamicPorts.Count + " - Skipped");
} else {
// Clear the removed ports connections
dynamicPorts[index].ClearConnections();
// Move following connections one step up to replace the missing connection
for (int k = index + 1; k < dynamicPorts.Count(); k++) {
for (int j = 0; j < dynamicPorts[k].ConnectionCount; j++) {
XNode.NodePort other = dynamicPorts[k].GetConnection(j);
dynamicPorts[k].Disconnect(other);
dynamicPorts[k - 1].Connect(other);
}
}
// Remove the last dynamic port, to avoid messing up the indexing
node.RemoveDynamicPort(dynamicPorts[dynamicPorts.Count() - 1].fieldName);
serializedObject.Update();
EditorUtility.SetDirty(node);
}
if (hasArrayData && arrayData.propertyType != SerializedPropertyType.String) {
if (arrayData.arraySize <= index) {
Debug.LogWarning("Attempted to remove array index " + index + " where only " + arrayData.arraySize + " exist - Skipped");
Debug.Log(rl.list[0]);
return;
}
arrayData.DeleteArrayElementAtIndex(index);
// Error handling. If the following happens too often, file a bug report at https://github.com/Siccity/xNode/issues
if (dynamicPorts.Count <= arrayData.arraySize) {
while (dynamicPorts.Count <= arrayData.arraySize) {
arrayData.DeleteArrayElementAtIndex(arrayData.arraySize - 1);
}
UnityEngine.Debug.LogWarning("Array size exceeded dynamic ports size. Excess items removed.");
}
serializedObject.ApplyModifiedProperties();
serializedObject.Update();
}
NodePort o = (NodePort) rl.list[index];
node.RemoveDynamicPort(o);
EditorUtility.SetDirty(node);
};
if (hasArrayData) {
@ -499,7 +493,7 @@ namespace XNodeEditor {
string newName = arrayData.name + " 0";
int i = 0;
while (node.HasPort(newName)) newName = arrayData.name + " " + (++i);
if (io == XNode.NodePort.IO.Output) node.AddDynamicOutput(type, connectionType, typeConstraint, newName);
if (io == XNode.NodePort.IO.Output) node.AddDynamicOutput(type, connectionType, typeConstraint,newName);
else node.AddDynamicInput(type, connectionType, typeConstraint, newName);
EditorUtility.SetDirty(node);
dynamicPortCount++;
@ -510,7 +504,7 @@ namespace XNodeEditor {
serializedObject.ApplyModifiedProperties();
serializedObject.Update();
}
if (onCreation != null) onCreation(list);
return list;
}
}

View File

@ -1,12 +0,0 @@
fileFormatVersion: 2
guid: 1d6c2d118d1c77948a23f2f4a34d1f64
timeCreated: 1507966608
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -113,7 +113,19 @@ namespace XNodeEditor {
VerifyLoaded();
Settings settings = NodeEditorPreferences.settings[lastKey];
if (GUILayout.Button(new GUIContent("Documentation", "https://github.com/Siccity/xNode/wiki"), GUILayout.Width(100))) Application.OpenURL("https://github.com/Siccity/xNode/wiki");
EditorGUILayout.BeginHorizontal();
{
EditorGUILayout.Space();
{
if (GUILayout.Button(new GUIContent("XNode Documentation", "https://github.com/Siccity/xNode/wiki"),
GUILayout.Width(180)))
Application.OpenURL("https://github.com/Siccity/xNode/wiki");
}
EditorGUILayout.Space();
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space();
NodeSettingsGUI(lastKey, settings);

View File

@ -1,12 +0,0 @@
fileFormatVersion: 2
guid: 6b1f47e387a6f714c9f2ff82a6888c85
timeCreated: 1507920216
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -74,6 +74,23 @@ namespace XNodeEditor {
}
return types.ToArray();
}
/// <summary> Find methods marked with the [ContextMenu] attribute and add them to the menu popup window</summary>
public static void AddCustomContextMenuItems(this MenuPopupWindow contextMenu, object obj) {
KeyValuePair<ContextMenu, MethodInfo>[] items = GetContextMenuMethods(obj);
if (items.Length != 0) {
List<string> invalidatedEntries = new List<string>();
foreach (KeyValuePair<ContextMenu, MethodInfo> checkValidate in items) {
if (checkValidate.Key.validate && !(bool) checkValidate.Value.Invoke(obj, null)) {
invalidatedEntries.Add(checkValidate.Key.menuItem);
}
}
for (int i = 0; i < items.Length; i++) {
KeyValuePair<ContextMenu, MethodInfo> kvp = items[i];
contextMenu.AddItem(kvp.Key.menuItem, () => kvp.Value.Invoke(obj, null));
}
}
}
/// <summary> Find methods marked with the [ContextMenu] attribute and add them to the context menu </summary>
public static void AddCustomContextMenuItems(this GenericMenu contextMenu, object obj) {

View File

@ -1,12 +0,0 @@
fileFormatVersion: 2
guid: c78a0fa4a13abcd408ebe73006b7b1bb
timeCreated: 1505419458
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,12 +0,0 @@
fileFormatVersion: 2
guid: 69f55d341299026489b29443c3dd13d1
timeCreated: 1505418919
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,12 +0,0 @@
fileFormatVersion: 2
guid: 120960fe5b50aba418a8e8ad3c4c4bc8
timeCreated: 1506073499
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -7,7 +7,46 @@ using Object = UnityEngine.Object;
namespace XNodeEditor {
[InitializeOnLoad]
public partial class NodeEditorWindow : EditorWindow {
public partial class NodeEditorWindow : EditorWindow,IHasCustomMenu {
public bool Lock { get; set; }
/// <summary>
/// Magic method which Unity detects automatically.
/// </summary>
/// <param name="position">Position of button.</param>
void ShowButton(Rect position) {
Lock = GUI.Toggle(position, Lock, GUIContent.none, "IN LockButton");
}
public void AddItemsToMenu(GenericMenu menu)
{
menu.AddItem(new GUIContent("Lock"), Lock, () => {
Lock = !Lock;
});
}
[MenuItem("xNode/Close All Editor Window")]
static void CloseAllNodeEditorWindow()
{
NodeEditorWindow[] windows = Resources.FindObjectsOfTypeAll<NodeEditorWindow>();
foreach (var window in windows)
{
if (window)
{
try
{
window.Close();
}
catch (Exception e)
{
DestroyImmediate(window);
}
}
}
}
public static NodeEditorWindow current;
/// <summary> Stores node positions for all nodePorts. </summary>
@ -74,17 +113,18 @@ namespace XNodeEditor {
public float zoom { get { return _zoom; } set { _zoom = Mathf.Clamp(value, NodeEditorPreferences.GetSettings().minZoom, NodeEditorPreferences.GetSettings().maxZoom); Repaint(); } }
private float _zoom = 1;
void OnFocus() {
void OnFocus()
{
current = this;
ValidateGraphEditor();
if (graphEditor != null) {
graphEditor.OnWindowFocus();
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets();
}
dragThreshold = Math.Max(1f, Screen.width / 1000f);
}
void OnLostFocus() {
if (graphEditor != null) graphEditor.OnWindowFocusLost();
}
@ -109,6 +149,7 @@ namespace XNodeEditor {
if (this.graphEditor != graphEditor && graphEditor != null) {
this.graphEditor = graphEditor;
graphEditor.OnOpen();
this.graphEditor.window.minSize = new Vector2(300,300);
}
}
@ -199,7 +240,43 @@ namespace XNodeEditor {
public static NodeEditorWindow Open(XNode.NodeGraph graph) {
if (!graph) return null;
NodeEditorWindow w = GetWindow(typeof(NodeEditorWindow), false, "xNode", true) as NodeEditorWindow;
var windows = Resources.FindObjectsOfTypeAll<NodeEditorWindow>();
NodeEditorWindow w = null;
foreach (var window in windows)
{
if (window.Lock)
{
if (window.graph == graph)
{
w = window;
}
}
else
{
w = window;
}
}
if (!w)
{
w = EditorWindow.CreateInstance<NodeEditorWindow>();
w.titleContent = new GUIContent("xNode");
}
w.Show(true);
w.Focus();
if (w.graphEditor == null)
{
NodeGraphEditor graphEditor = NodeGraphEditor.GetEditor(graph, w);
w.graphEditor = graphEditor;
}
else
{
//refresh target
w.graphEditor.target = graph;
}
w.wantsMouseMove = true;
w.graph = graph;
return w;

View File

@ -1,12 +0,0 @@
fileFormatVersion: 2
guid: 5ce2bf59ec7a25c4ba691cad7819bf38
timeCreated: 1505418450
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
@ -17,7 +16,7 @@ namespace XNodeEditor {
/// <summary> Called when opened by NodeEditorWindow </summary>
public virtual void OnOpen() { }
/// <summary> Called when NodeEditorWindow gains focus </summary>
public virtual void OnWindowFocus() { }
@ -58,7 +57,7 @@ namespace XNodeEditor {
}
/// <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(MenuPopupWindow menu) {
Vector2 pos = NodeEditorWindow.current.WindowToGridPosition(Event.current.mousePosition);
var nodeTypes = NodeEditorReflection.nodeTypes.OrderBy(type => GetNodeMenuOrder(type)).ToArray();
for (int i = 0; i < nodeTypes.Length; i++) {
@ -68,7 +67,8 @@ namespace XNodeEditor {
string path = GetNodeMenuName(type);
if (string.IsNullOrEmpty(path)) continue;
// Check if user is allowed to add more of given node type
// Check if user is allowed to add more of given node type
XNode.Node.DisallowMultipleNodesAttribute disallowAttrib;
bool disallowed = false;
if (NodeEditorUtilities.GetAttrib(type, out disallowAttrib)) {
@ -76,17 +76,49 @@ namespace XNodeEditor {
if (typeCount >= disallowAttrib.max) disallowed = true;
}
// Add node entry to context menu
if (disallowed) menu.AddItem(new GUIContent(path), false, null);
else menu.AddItem(new GUIContent(path), false, () => {
if (!disallowed)
{
menu.AddItem(path, () => {
pos = NodeEditorWindow.current.WindowToGridPosition(menu.openBeforeMousePos);
XNode.Node node = CreateNode(type, pos);
NodeEditorWindow.current.AutoConnect(node);
});
}
}
if (NodeEditorWindow.copyBuffer != null && NodeEditorWindow.copyBuffer.Length > 0)
menu.AddItem("Paste", () =>
{
pos = NodeEditorWindow.current.WindowToGridPosition(menu.openBeforeMousePos);
NodeEditorWindow.current.PasteNodes(pos);
});
menu.AddItem("Preferences", () => NodeEditorReflection.OpenPreferences());
menu.AddItem("Create All Node ---> Test use", () =>
{
if (!EditorUtility.DisplayDialog("warning","Are you sure you want to create all the nodes?","ok","no"))
{
return;
}
pos = NodeEditorWindow.current.WindowToGridPosition(menu.openBeforeMousePos);
for (int i = 0; i < NodeEditorReflection.nodeTypes.Length; i++)
{
Type type = NodeEditorReflection.nodeTypes[i];
//Get node context menu path
string path = GetNodeMenuName(type);
// skip empty path
if (string.IsNullOrEmpty(path))
{
continue;
}
XNode.Node node = CreateNode(type, pos);
NodeEditorWindow.current.AutoConnect(node);
});
}
menu.AddSeparator("");
if (NodeEditorWindow.copyBuffer != null && NodeEditorWindow.copyBuffer.Length > 0) menu.AddItem(new GUIContent("Paste"), false, () => NodeEditorWindow.current.PasteNodes(pos));
else menu.AddDisabledItem(new GUIContent("Paste"));
menu.AddItem(new GUIContent("Preferences"), false, () => NodeEditorReflection.OpenPreferences());
}
});
menu.AddCustomContextMenuItems(target);
}

View File

@ -1,12 +0,0 @@
fileFormatVersion: 2
guid: ddcbb5432255d3247a0718b15a9c193c
timeCreated: 1505462176
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,13 +0,0 @@
fileFormatVersion: 2
guid: 4ef3ddc25518318469bce838980c64be
timeCreated: 1552067957
licenseType: Free
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,9 +0,0 @@
fileFormatVersion: 2
guid: 964fc201163fe884ca6a20094b6f3b49
folderAsset: yes
timeCreated: 1506110871
licenseType: Free
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,10 +0,0 @@
fileFormatVersion: 2
guid: 86b677955452bb5449f9f4dd47b6ddfe
folderAsset: yes
timeCreated: 1519049391
licenseType: Free
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,9 +0,0 @@
fileFormatVersion: 2
guid: 8165767f64da7d94e925f61a38da668c
timeCreated: 1519049802
licenseType: Free
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,9 +0,0 @@
fileFormatVersion: 2
guid: 85f6f570600a1a44d8e734cb111a8b89
timeCreated: 1519049802
licenseType: Free
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,98 +0,0 @@
fileFormatVersion: 2
guid: 75a1fe0b102226a418486ed823c9a7fb
timeCreated: 1506110357
licenseType: Free
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 4
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 0
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: 1
mipBias: -1
wrapU: 1
wrapV: -1
wrapW: -1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: Standalone
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: Android
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: WebGL
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,98 +0,0 @@
fileFormatVersion: 2
guid: 434ca8b4bdfa5574abb0002bbc9b65ad
timeCreated: 1506110357
licenseType: Free
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 4
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 0
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: 1
mipBias: -1
wrapU: 1
wrapV: -1
wrapW: -1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: Standalone
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: Android
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: WebGL
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,98 +0,0 @@
fileFormatVersion: 2
guid: 2fea1dcb24935ef4ca514d534eb6aa3d
timeCreated: 1507454532
licenseType: Free
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 4
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 0
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: 1
mipBias: -1
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: Standalone
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: Android
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: WebGL
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,87 +0,0 @@
fileFormatVersion: 2
guid: 2ab2b92d7e1771b47bba0a46a6f0f6d5
timeCreated: 1516610730
licenseType: Free
TextureImporter:
fileIDToRecycleName: {}
externalObjects: {}
serializedVersion: 4
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 0
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: 1
mipBias: -1
wrapU: 1
wrapV: 1
wrapW: -1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
- buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,98 +0,0 @@
fileFormatVersion: 2
guid: 2267efa6e1e349348ae0b28fb659a6e2
timeCreated: 1507454532
licenseType: Free
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 4
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 0
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: 1
mipBias: -1
wrapU: 1
wrapV: -1
wrapW: -1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: Standalone
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: Android
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: WebGL
maxTextureSize: 2048
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 002c1bbed08fa44d282ef34fd5edb138
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace XNode {
@ -113,6 +114,12 @@ namespace XNode {
/// <summary> It is recommended not to modify these at hand. Instead, see <see cref="InputAttribute"/> and <see cref="OutputAttribute"/> </summary>
[SerializeField] private NodePortDictionary ports = new NodePortDictionary();
#if UNITY_EDITOR
public const string PortFieldName = nameof(ports);
public const string KeysFieldName = NodePortDictionary.KeyFieldName;
public const string ValuesFieldName = NodePortDictionary.ValueFieldName;
#endif
/// <summary> Used during node instantiation to fix null/misconfigured graph during OnEnable/Init. Set it before instantiating a node. Will automatically be unset during OnEnable </summary>
public static NodeGraph graphHotfix;
@ -148,7 +155,7 @@ namespace XNode {
/// <seealso cref="AddInstancePort"/>
/// <seealso cref="AddInstanceInput"/>
public NodePort AddDynamicOutput(Type type, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) {
return AddDynamicPort(type, NodePort.IO.Output, connectionType, typeConstraint, fieldName);
return AddDynamicPort(type, NodePort.IO.Output, connectionType, typeConstraint,fieldName);
}
/// <summary> Add a dynamic, serialized port to this node. </summary>
@ -164,7 +171,9 @@ namespace XNode {
return ports[fieldName];
}
NodePort port = new NodePort(fieldName, type, direction, connectionType, typeConstraint, this);
ports.Add(fieldName, port);
return port;
}
@ -270,17 +279,20 @@ namespace XNode {
public bool instancePortList { get { return dynamicPortList; } set { dynamicPortList = value; } }
public bool dynamicPortList;
public TypeConstraint typeConstraint;
public Type BaseType { get; }
/// <summary> Mark a serializable field as an input port. You can access this through <see cref="GetInputPort(string)"/> </summary>
/// <param name="baseType">指定更准确的父类类型,只有当<see cref="typeConstraint"/>参数为<seealso cref="TypeConstraint.Inherited"/>才可用</param>
/// <param name="backingValue">Should we display the backing value for this port as an editor field? </param>
/// <param name="connectionType">Should we allow multiple connections? </param>
/// <param name="typeConstraint">Constrains which input connections can be made to this port </param>
/// <param name="dynamicPortList">If true, will display a reorderable list of inputs instead of a single port. Will automatically add and display values for lists and arrays </param>
public InputAttribute(ShowBackingValue backingValue = ShowBackingValue.Unconnected, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None, bool dynamicPortList = false) {
public InputAttribute(ShowBackingValue backingValue = ShowBackingValue.Unconnected, ConnectionType connectionType = ConnectionType.Multiple, TypeConstraint typeConstraint = TypeConstraint.None,Type baseType = null, bool dynamicPortList = false) {
this.backingValue = backingValue;
this.connectionType = connectionType;
this.dynamicPortList = dynamicPortList;
this.typeConstraint = typeConstraint;
BaseType = baseType;
}
}
@ -385,16 +397,48 @@ namespace XNode {
this.width = width;
}
}
/// <summary> Custom Port Label </summary>
[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
public class LabelAttribute : Attribute
{
public string Label { get; }
public LabelAttribute(string label)
{
Label = label;
}
}
#endregion
[Serializable] private class NodePortDictionary : Dictionary<string, NodePort>, ISerializationCallbackReceiver {
[SerializeField] private List<string> keys = new List<string>();
[SerializeField] private List<NodePort> values = new List<NodePort>();
#if UNITY_EDITOR
public const string KeyFieldName = nameof(keys);
public const string ValueFieldName = nameof(values);
#endif
public void OnBeforeSerialize() {
keys.Clear();
values.Clear();
foreach (KeyValuePair<string, NodePort> pair in this) {
//Sorting, output port is always after input port
if (pair.Value.direction == NodePort.IO.Input)
{
var firstOutIndex = values.FindIndex(x => x.direction == NodePort.IO.Output);
if (firstOutIndex > -1)
{
keys.Insert(firstOutIndex,pair.Key);
values.Insert(firstOutIndex,pair.Value);
continue;
}
}
keys.Add(pair.Key);
values.Add(pair.Value);
}

View File

@ -1,12 +0,0 @@
fileFormatVersion: 2
guid: f26231e5ab9368746948d0ea49e8178a
timeCreated: 1505419984
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -39,7 +39,11 @@ namespace XNode {
if (!port.IsDynamic && port.direction == staticPort.direction) removedPorts.Add(port.fieldName, port.GetConnections());
port.ClearConnections();
ports.Remove(port.fieldName);
} else port.ValueType = staticPort.ValueType;
}
else
{
port.ValueType = staticPort.ValueType;
}
}
// If port doesn't exist anymore, remove it
else if (port.IsStatic) {
@ -67,14 +71,14 @@ namespace XNode {
ports.Add(staticPort.fieldName, port);
}
}
// Finally, make sure dynamic list port settings correspond to the settings of their "backing port"
foreach (NodePort listPort in dynamicListPorts) {
// At this point we know that ports here are dynamic list ports
// which have passed name/"backing port" checks, ergo we can proceed more safely.
string backingPortName = listPort.fieldName.Split(' ')[0];
NodePort backingPort = staticPorts[backingPortName];
// Update port constraints. Creating a new port instead will break the editor, mandating the need for setters.
listPort.ValueType = GetBackingValueType(backingPort.ValueType);
listPort.direction = backingPort.direction;
@ -86,7 +90,7 @@ namespace XNode {
/// <summary>
/// Extracts the underlying types from arrays and lists, the only collections for dynamic port lists
/// currently supported. If the given type is not applicable (i.e. if the dynamic list port was not
/// defined as an array or a list), returns the given type itself.
/// defined as an array or a list), returns the given type itself.
/// </summary>
private static System.Type GetBackingValueType(System.Type portValType) {
if (portValType.HasElementType) {
@ -105,10 +109,10 @@ namespace XNode {
// Thus, we need to check for attributes... (but at least we don't need to look at all fields this time)
string[] fieldNameParts = port.fieldName.Split(' ');
if (fieldNameParts.Length != 2) return false;
FieldInfo backingPortInfo = port.node.GetType().GetField(fieldNameParts[0]);
if (backingPortInfo == null) return false;
object[] attribs = backingPortInfo.GetCustomAttributes(true);
return attribs.Any(x => {
Node.InputAttribute inputAttribute = x as Node.InputAttribute;
@ -117,7 +121,7 @@ namespace XNode {
outputAttribute != null && outputAttribute.dynamicPortList;
});
}
/// <summary> Cache node types </summary>
private static void BuildCache() {
portDataCache = new PortDataCache();
@ -155,17 +159,20 @@ namespace XNode {
// GetFields doesnt return inherited private fields, so walk through base types and pick those up
System.Type tempType = nodeType;
while ((tempType = tempType.BaseType) != typeof(XNode.Node)) {
FieldInfo[] parentFields = tempType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
for (int i = 0; i < parentFields.Length; i++) {
// Ensure that we do not already have a member with this type and name
FieldInfo parentField = parentFields[i];
if (fieldInfo.TrueForAll(x => x.Name != parentField.Name)) {
fieldInfo.Add(parentField);
}
}
}
return fieldInfo;
while ((tempType = tempType.BaseType) != typeof(XNode.Node))
{
// Only return private, protected, etc.
var fieldInfos = tempType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance).Where(x=>x.IsPrivate).ToArray();
for (int i = 0; i < fieldInfos.Length; i++) {
// Ensure that we do not already have a member with this type and name
FieldInfo parentField = fieldInfos[i];
if (fieldInfo.TrueForAll(x => x.Name != parentField.Name)) {
fieldInfo.Add(parentField);
}
}
}
return fieldInfo;
}
private static void CachePorts(System.Type nodeType) {

View File

@ -1,12 +0,0 @@
fileFormatVersion: 2
guid: 64ea6af1e195d024d8df0ead1921e517
timeCreated: 1507566823
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,12 +0,0 @@
fileFormatVersion: 2
guid: 093f68ef2455d544fa2d14b80c811322
timeCreated: 1505461376
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -19,7 +19,7 @@ namespace XNode {
}
}
public IO direction {
public IO direction {
get { return _direction; }
internal set { _direction = value; }
}
@ -41,6 +41,14 @@ namespace XNode {
public Node node { get { return _node; } }
public bool IsDynamic { get { return _dynamic; } }
public bool IsStatic { get { return !_dynamic; } }
#if UNITY_EDITOR
public void RefreshValueType()
{
valueType = null;
}
#endif
public Type ValueType {
get {
if (valueType == null && !string.IsNullOrEmpty(_typeQualifiedName)) valueType = Type.GetType(_typeQualifiedName, false);
@ -51,8 +59,12 @@ namespace XNode {
if (value != null) _typeQualifiedName = value.AssemblyQualifiedName;
}
}
private Type valueType;
private Type valueType;
#if UNITY_EDITOR
public const string FIELDNAMEEDITOR = nameof(_fieldName);
public const string ConnectionsEditor = nameof(connections);
#endif
[SerializeField] private string _fieldName;
[SerializeField] private Node _node;
[SerializeField] private string _typeQualifiedName;
@ -213,7 +225,17 @@ namespace XNode {
UnityEditor.Undo.RecordObject(port.node, "Connect Port");
#endif
if (port.connectionType == Node.ConnectionType.Override && port.ConnectionCount != 0) { port.ClearConnections(); }
if (connectionType == Node.ConnectionType.Override && ConnectionCount != 0) { ClearConnections(); }
if (connectionType == Node.ConnectionType.Override && ConnectionCount != 0)
{
var conPort = GetConnection(0);
//Same connection, not disconnect
if(conPort.node != port.node || conPort != port)
{
ClearConnections();
}
return;
}
connections.Add(new PortConnection(port));
if (port.connections == null) port.connections = new List<PortConnection>();
if (!port.IsConnectedTo(this)) port.connections.Add(new PortConnection(this));
@ -269,8 +291,6 @@ namespace XNode {
else output = port;
// If there isn't one of each, they can't connect
if (input == null || output == null) return false;
// Check input type constraints
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;
// Check output type constraints

View File

@ -1,12 +0,0 @@
fileFormatVersion: 2
guid: 7dd2f76ac25c6f44c9426dff3e7491a3
timeCreated: 1505734054
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: b8e24fd1eb19b4226afebb2810e3c19b
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: e9869d68f06b74538a01e9b8e406159e
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: