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:
commit
af6012ba0a
1
.gitignore
vendored
1
.gitignore
vendored
@ -25,6 +25,7 @@ sysinfo.txt
|
||||
.git.meta
|
||||
.gitignore.meta
|
||||
.gitattributes.meta
|
||||
*.meta
|
||||
|
||||
# OS X only:
|
||||
.DS_Store
|
||||
@ -1,7 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bc1db8b29c76d44648c9c86c2dfade6d
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -1,7 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 77523c356ccf04f56b53e6527c6b12fd
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -1,7 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 243efae3a6b7941ad8f8e54dcf38ce8c
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -1,9 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 657b15cb3ec32a24ca80faebf094d0f4
|
||||
folderAsset: yes
|
||||
timeCreated: 1505418321
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -1,10 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5644dfc7eed151045af664a9d4fd1906
|
||||
folderAsset: yes
|
||||
timeCreated: 1541633926
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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:
|
||||
@ -1,9 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 94d4fd78d9120634ebe0e8717610c412
|
||||
folderAsset: yes
|
||||
timeCreated: 1505418345
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -1,10 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7adf21edfb51f514fa991d7556ecd0ef
|
||||
folderAsset: yes
|
||||
timeCreated: 1541971984
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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:
|
||||
@ -1,8 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 327994a52f523b641898a39ff7500a02
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3cf2561fbfea9a041ac81efbbb5b3e0d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2fd590b2e9ea0bd49b6986a2ca9010ab
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e7ebd8f2b42e2384aa109551dc46af88
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -1,8 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a6a1bbc054e282346a02e7bbddde3206
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 399f3c5fb717b2c458c3e9746f8959a3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
101
Scripts/Editor/MenuPopup/MenuPopupWindow.cs
Normal file
101
Scripts/Editor/MenuPopup/MenuPopupWindow.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
169
Scripts/Editor/MenuPopup/MenuTreeView.cs
Normal file
169
Scripts/Editor/MenuPopup/MenuTreeView.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 712c3fc5d9eeb4c45b1e23918df6018f
|
||||
timeCreated: 1505462176
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aa7d4286bf0ad2e4086252f2893d2cf5
|
||||
timeCreated: 1505426655
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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++) {
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e515e86efe8160243a68b7c06d730c9c
|
||||
timeCreated: 1507982232
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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:
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 756276bfe9a0c2f4da3930ba1964f58d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1d6c2d118d1c77948a23f2f4a34d1f64
|
||||
timeCreated: 1507966608
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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);
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6b1f47e387a6f714c9f2ff82a6888c85
|
||||
timeCreated: 1507920216
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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) {
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c78a0fa4a13abcd408ebe73006b7b1bb
|
||||
timeCreated: 1505419458
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -1,12 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 69f55d341299026489b29443c3dd13d1
|
||||
timeCreated: 1505418919
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -1,12 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 120960fe5b50aba418a8e8ad3c4c4bc8
|
||||
timeCreated: 1506073499
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5ce2bf59ec7a25c4ba691cad7819bf38
|
||||
timeCreated: 1505418450
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ddcbb5432255d3247a0718b15a9c193c
|
||||
timeCreated: 1505462176
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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:
|
||||
@ -1,9 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 964fc201163fe884ca6a20094b6f3b49
|
||||
folderAsset: yes
|
||||
timeCreated: 1506110871
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -1,10 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 86b677955452bb5449f9f4dd47b6ddfe
|
||||
folderAsset: yes
|
||||
timeCreated: 1519049391
|
||||
licenseType: Free
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -1,9 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8165767f64da7d94e925f61a38da668c
|
||||
timeCreated: 1519049802
|
||||
licenseType: Free
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -1,9 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 85f6f570600a1a44d8e734cb111a8b89
|
||||
timeCreated: 1519049802
|
||||
licenseType: Free
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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:
|
||||
@ -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:
|
||||
@ -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:
|
||||
@ -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:
|
||||
@ -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:
|
||||
@ -1,7 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 002c1bbed08fa44d282ef34fd5edb138
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f26231e5ab9368746948d0ea49e8178a
|
||||
timeCreated: 1505419984
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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) {
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 64ea6af1e195d024d8df0ead1921e517
|
||||
timeCreated: 1507566823
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -1,12 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 093f68ef2455d544fa2d14b80c811322
|
||||
timeCreated: 1505461376
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7dd2f76ac25c6f44c9426dff3e7491a3
|
||||
timeCreated: 1505734054
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -1,7 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b8e24fd1eb19b4226afebb2810e3c19b
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -1,7 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e9869d68f06b74538a01e9b8e406159e
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Loading…
x
Reference in New Issue
Block a user