diff --git a/.gitignore b/.gitignore index c75a01e..a833bf4 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ sysinfo.txt .git.meta .gitignore.meta .gitattributes.meta +*.meta # OS X only: .DS_Store \ No newline at end of file diff --git a/CONTRIBUTING.md.meta b/CONTRIBUTING.md.meta deleted file mode 100644 index 5d7c128..0000000 --- a/CONTRIBUTING.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: bc1db8b29c76d44648c9c86c2dfade6d -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/LICENSE.md.meta b/LICENSE.md.meta deleted file mode 100644 index 5f0a7c7..0000000 --- a/LICENSE.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 77523c356ccf04f56b53e6527c6b12fd -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/README.md.meta b/README.md.meta deleted file mode 100644 index dd3ed6f..0000000 --- a/README.md.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 243efae3a6b7941ad8f8e54dcf38ce8c -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Scripts.meta b/Scripts.meta deleted file mode 100644 index ab712b6..0000000 --- a/Scripts.meta +++ /dev/null @@ -1,9 +0,0 @@ -fileFormatVersion: 2 -guid: 657b15cb3ec32a24ca80faebf094d0f4 -folderAsset: yes -timeCreated: 1505418321 -licenseType: Free -DefaultImporter: - userData: - assetBundleName: - assetBundleVariant: diff --git a/Scripts/Attributes.meta b/Scripts/Attributes.meta deleted file mode 100644 index c0be849..0000000 --- a/Scripts/Attributes.meta +++ /dev/null @@ -1,10 +0,0 @@ -fileFormatVersion: 2 -guid: 5644dfc7eed151045af664a9d4fd1906 -folderAsset: yes -timeCreated: 1541633926 -licenseType: Free -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Scripts/Attributes/NodeEnum.cs.meta b/Scripts/Attributes/NodeEnum.cs.meta deleted file mode 100644 index 813a80b..0000000 --- a/Scripts/Attributes/NodeEnum.cs.meta +++ /dev/null @@ -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: diff --git a/Scripts/Editor.meta b/Scripts/Editor.meta deleted file mode 100644 index b0ba142..0000000 --- a/Scripts/Editor.meta +++ /dev/null @@ -1,9 +0,0 @@ -fileFormatVersion: 2 -guid: 94d4fd78d9120634ebe0e8717610c412 -folderAsset: yes -timeCreated: 1505418345 -licenseType: Free -DefaultImporter: - userData: - assetBundleName: - assetBundleVariant: diff --git a/Scripts/Editor/Drawers.meta b/Scripts/Editor/Drawers.meta deleted file mode 100644 index b69e0ac..0000000 --- a/Scripts/Editor/Drawers.meta +++ /dev/null @@ -1,10 +0,0 @@ -fileFormatVersion: 2 -guid: 7adf21edfb51f514fa991d7556ecd0ef -folderAsset: yes -timeCreated: 1541971984 -licenseType: Free -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Scripts/Editor/Drawers/NodeEnumDrawer.cs.meta b/Scripts/Editor/Drawers/NodeEnumDrawer.cs.meta deleted file mode 100644 index beacf6b..0000000 --- a/Scripts/Editor/Drawers/NodeEnumDrawer.cs.meta +++ /dev/null @@ -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: diff --git a/Scripts/Editor/Drawers/Odin.meta b/Scripts/Editor/Drawers/Odin.meta deleted file mode 100644 index c2b0ac9..0000000 --- a/Scripts/Editor/Drawers/Odin.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 327994a52f523b641898a39ff7500a02 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Scripts/Editor/Drawers/Odin/InNodeEditorAttributeProcessor.cs.meta b/Scripts/Editor/Drawers/Odin/InNodeEditorAttributeProcessor.cs.meta deleted file mode 100644 index 15f6990..0000000 --- a/Scripts/Editor/Drawers/Odin/InNodeEditorAttributeProcessor.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 3cf2561fbfea9a041ac81efbbb5b3e0d -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Scripts/Editor/Drawers/Odin/InputAttributeDrawer.cs.meta b/Scripts/Editor/Drawers/Odin/InputAttributeDrawer.cs.meta deleted file mode 100644 index 12b7615..0000000 --- a/Scripts/Editor/Drawers/Odin/InputAttributeDrawer.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 2fd590b2e9ea0bd49b6986a2ca9010ab -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Scripts/Editor/Drawers/Odin/OutputAttributeDrawer.cs.meta b/Scripts/Editor/Drawers/Odin/OutputAttributeDrawer.cs.meta deleted file mode 100644 index aa22218..0000000 --- a/Scripts/Editor/Drawers/Odin/OutputAttributeDrawer.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: e7ebd8f2b42e2384aa109551dc46af88 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Scripts/Editor/Internal.meta b/Scripts/Editor/Internal.meta deleted file mode 100644 index 600ad29..0000000 --- a/Scripts/Editor/Internal.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: a6a1bbc054e282346a02e7bbddde3206 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Scripts/Editor/Internal/RerouteReference.cs.meta b/Scripts/Editor/Internal/RerouteReference.cs.meta deleted file mode 100644 index 9a2f9cb..0000000 --- a/Scripts/Editor/Internal/RerouteReference.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 399f3c5fb717b2c458c3e9746f8959a3 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Scripts/Editor/MenuPopup/MenuPopupWindow.cs b/Scripts/Editor/MenuPopup/MenuPopupWindow.cs new file mode 100644 index 0000000..62ea6ef --- /dev/null +++ b/Scripts/Editor/MenuPopup/MenuPopupWindow.cs @@ -0,0 +1,101 @@ +using System; +using UnityEditor; +using UnityEditor.IMGUI.Controls; +using UnityEngine; + +namespace XNodeEditor +{ + /// + /// Menu Popup Window + /// + 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; + + /// + /// Add Item + /// + /// Item Path + /// + /// Path separator + /// Automatically close window after selecting an item + public void AddItem(string menuPath, Action onClick, char symbol = '/',bool autoClose = true) + { + menuTree.AddItem(menuPath, () => + { + onClick?.Invoke(); + if (autoClose) + { + editorWindow.Close(); + } + },symbol); + } + + /// + /// Init or Reload Tree + /// + 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; + } + } + } +} \ No newline at end of file diff --git a/Scripts/Editor/MenuPopup/MenuTreeView.cs b/Scripts/Editor/MenuPopup/MenuTreeView.cs new file mode 100644 index 0000000..aa81fed --- /dev/null +++ b/Scripts/Editor/MenuPopup/MenuTreeView.cs @@ -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> _menuCache = new Dictionary>(); + + /// + /// + /// + /// + /// + /// + 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(); + _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 ids = new List(); + 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; + } + } + } +} \ No newline at end of file diff --git a/Scripts/Editor/NodeEditor.cs b/Scripts/Editor/NodeEditor.cs index 36c3a6e..4262231 100644 --- a/Scripts/Editor/NodeEditor.cs +++ b/Scripts/Editor/NodeEditor.cs @@ -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 { - /// Base class to derive custom Node editors from. Use this to create your own custom inspectors and editors for your nodes. - [CustomNodeEditor(typeof(XNode.Node))] - public class NodeEditor : XNodeEditor.Internal.NodeEditorBase { - - private readonly Color DEFAULTCOLOR = new Color32(90, 97, 105, 255); - - /// Fires every whenever a node was modified through the editor - public static Action onUpdateNode; - public readonly static Dictionary portPositions = new Dictionary(); - -#if ODIN_INSPECTOR - protected internal static bool inNodeEditor = false; -#endif - - public virtual void OnHeaderGUI() { - GUILayout.Label(target.name, NodeEditorResources.styles.nodeHeader, GUILayout.Height(30)); - } - - /// Draws standard field editors for all public fields - 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; - } - - /// Returns color for target node - 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; - } - - /// Add items for the context menu when right-clicking this node. Override to add custom menu items. - 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); - } - } - - /// Rename the node asset. This will trigger a reimport of the node. - public void Rename(string newName) { - if (newName == null || newName.Trim() == "") newName = NodeEditorUtilities.NodeDefaultName(target.GetType()); - target.name = newName; - OnRename(); - AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target)); - } - - /// Called after this node's name has changed. - public virtual void OnRename() { } - - [AttributeUsage(AttributeTargets.Class)] - public class CustomNodeEditorAttribute : Attribute, - XNodeEditor.Internal.NodeEditorBase.INodeEditorAttrib { - private Type inspectedType; - /// Tells a NodeEditor which Node type it is an editor for - /// Type that this editor can edit - public CustomNodeEditorAttribute(Type inspectedType) { - this.inspectedType = inspectedType; - } - - public Type GetInspectedType() { - return inspectedType; - } - } - } -} \ No newline at end of file +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 { + /// Base class to derive custom Node editors from. Use this to create your own custom inspectors and editors for your nodes. + [CustomNodeEditor(typeof(XNode.Node))] + public class NodeEditor : XNodeEditor.Internal.NodeEditorBase { + + private readonly Color DEFAULTCOLOR = new Color32(90, 97, 105, 255); + + /// Fires every whenever a node was modified through the editor + public static Action onUpdateNode; + public readonly static Dictionary portPositions = new Dictionary(); + +#if ODIN_INSPECTOR + internal static bool inNodeEditor = false; +#endif + private List excludesField; + private List portNames = new List(); + + public override void OnCreate() + { + excludesField = new List { "m_Script", "graph", "position", "ports" }; + + IEnumerable 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 GetExcludesField() + { + return null; + } + + /// Draws standard field editors for all public fields + 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; + } + + /// Returns color for target node + 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; + } + + /// Add items for the context menu when right-clicking this node. Override to add custom menu items. + 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); + } + } + + /// Rename the node asset. This will trigger a reimport of the node. + public void Rename(string newName) { + if (newName == null || newName.Trim() == "") newName = NodeEditorUtilities.NodeDefaultName(target.GetType()); + target.name = newName; + AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target)); + } + + /// Called after this node's name has changed. + public virtual void OnRename() { } + + [AttributeUsage(AttributeTargets.Class)] + public class CustomNodeEditorAttribute : Attribute, + XNodeEditor.Internal.NodeEditorBase.INodeEditorAttrib { + private Type inspectedType; + /// Tells a NodeEditor which Node type it is an editor for + /// Type that this editor can edit + public CustomNodeEditorAttribute(Type inspectedType) { + this.inspectedType = inspectedType; + } + + public Type GetInspectedType() { + return inspectedType; + } + } + } +} diff --git a/Scripts/Editor/NodeEditor.cs.meta b/Scripts/Editor/NodeEditor.cs.meta deleted file mode 100644 index db8651d..0000000 --- a/Scripts/Editor/NodeEditor.cs.meta +++ /dev/null @@ -1,12 +0,0 @@ -fileFormatVersion: 2 -guid: 712c3fc5d9eeb4c45b1e23918df6018f -timeCreated: 1505462176 -licenseType: Free -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Scripts/Editor/NodeEditorAction.cs b/Scripts/Editor/NodeEditorAction.cs index b112732..ac58583 100644 --- a/Scripts/Editor/NodeEditorAction.cs +++ b/Scripts/Editor/NodeEditorAction.cs @@ -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 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 diff --git a/Scripts/Editor/NodeEditorAction.cs.meta b/Scripts/Editor/NodeEditorAction.cs.meta deleted file mode 100644 index a081bf7..0000000 --- a/Scripts/Editor/NodeEditorAction.cs.meta +++ /dev/null @@ -1,12 +0,0 @@ -fileFormatVersion: 2 -guid: aa7d4286bf0ad2e4086252f2893d2cf5 -timeCreated: 1505426655 -licenseType: Free -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Scripts/Editor/NodeEditorAssetModProcessor.cs b/Scripts/Editor/NodeEditorAssetModProcessor.cs index f4b14a2..acc8380 100644 --- a/Scripts/Editor/NodeEditorAssetModProcessor.cs +++ b/Scripts/Editor/NodeEditorAssetModProcessor.cs @@ -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++) { diff --git a/Scripts/Editor/NodeEditorAssetModProcessor.cs.meta b/Scripts/Editor/NodeEditorAssetModProcessor.cs.meta deleted file mode 100644 index 057198b..0000000 --- a/Scripts/Editor/NodeEditorAssetModProcessor.cs.meta +++ /dev/null @@ -1,12 +0,0 @@ -fileFormatVersion: 2 -guid: e515e86efe8160243a68b7c06d730c9c -timeCreated: 1507982232 -licenseType: Free -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Scripts/Editor/NodeEditorBase.cs b/Scripts/Editor/NodeEditorBase.cs index 1fc28c7..8133d66 100644 --- a/Scripts/Editor/NodeEditorBase.cs +++ b/Scripts/Editor/NodeEditorBase.cs @@ -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 { /// Called on creation, after references have been set public virtual void OnCreate() { } + /// + /// node editor window OnFocus call + /// + public virtual void OnInit() { } + public interface INodeEditorAttrib { Type GetInspectedType(); } diff --git a/Scripts/Editor/NodeEditorBase.cs.meta b/Scripts/Editor/NodeEditorBase.cs.meta deleted file mode 100644 index 4ded02a..0000000 --- a/Scripts/Editor/NodeEditorBase.cs.meta +++ /dev/null @@ -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: diff --git a/Scripts/Editor/NodeEditorGUI.cs b/Scripts/Editor/NodeEditorGUI.cs index 99cdecf..b682bbf 100755 --- a/Scripts/Editor/NodeEditorGUI.cs +++ b/Scripts/Editor/NodeEditorGUI.cs @@ -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 { /// Show right-click context menu for hovered port 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); } diff --git a/Scripts/Editor/NodeEditorGUI.cs.meta b/Scripts/Editor/NodeEditorGUI.cs.meta deleted file mode 100644 index 543878b..0000000 --- a/Scripts/Editor/NodeEditorGUI.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 756276bfe9a0c2f4da3930ba1964f58d -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Scripts/Editor/NodeEditorGUILayout.cs b/Scripts/Editor/NodeEditorGUILayout.cs index 3574ace..542ea03 100644 --- a/Scripts/Editor/NodeEditorGUILayout.cs +++ b/Scripts/Editor/NodeEditorGUILayout.cs @@ -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 { /// xNode-specific version of @@ -35,10 +36,25 @@ namespace XNodeEditor { /// Make a field for a serialized property. Manual node port override. 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 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; } - /// Draw an editable list of dynamic ports. Port names are named as "[fieldName] [index]" + /// Draw an editable list of dynamic ports. /// Supply a list for editable values /// Value type of added dynamic ports /// The serializedObject of the node /// Connection type of added dynamic ports /// Called on the list on creation. Use this if you want to customize the created ReorderableList - 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 onCreation = null) { + /// Return port name after adding port + 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 onCreation = null,Action 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 dynamicPorts = indexedPorts.OrderBy(x => x.index).Select(x => x.port).ToList(); - - node.UpdatePorts(); + var indexedPorts = io == NodePort.IO.Input ? node.DynamicInputs : node.DynamicOutputs; + List dynamicPorts = indexedPorts.ToList(); + ReorderableList list = null; Dictionary 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() { { fieldName, list } }); } list.list = dynamicPorts; list.DoLayoutList(); - } - private static ReorderableList CreateReorderableList(string fieldName, List dynamicPorts, SerializedProperty arrayData, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType, XNode.Node.TypeConstraint typeConstraint, Action onCreation) { + private static ReorderableList CreateReorderableList(string fieldName, List dynamicPorts, SerializedProperty arrayData, Type type, + SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType, XNode.Node.TypeConstraint typeConstraint,Action 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; } } diff --git a/Scripts/Editor/NodeEditorGUILayout.cs.meta b/Scripts/Editor/NodeEditorGUILayout.cs.meta deleted file mode 100644 index 89596e2..0000000 --- a/Scripts/Editor/NodeEditorGUILayout.cs.meta +++ /dev/null @@ -1,12 +0,0 @@ -fileFormatVersion: 2 -guid: 1d6c2d118d1c77948a23f2f4a34d1f64 -timeCreated: 1507966608 -licenseType: Free -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Scripts/Editor/NodeEditorPreferences.cs b/Scripts/Editor/NodeEditorPreferences.cs index 72eb8aa..9afa556 100644 --- a/Scripts/Editor/NodeEditorPreferences.cs +++ b/Scripts/Editor/NodeEditorPreferences.cs @@ -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); diff --git a/Scripts/Editor/NodeEditorPreferences.cs.meta b/Scripts/Editor/NodeEditorPreferences.cs.meta deleted file mode 100644 index 156543b..0000000 --- a/Scripts/Editor/NodeEditorPreferences.cs.meta +++ /dev/null @@ -1,12 +0,0 @@ -fileFormatVersion: 2 -guid: 6b1f47e387a6f714c9f2ff82a6888c85 -timeCreated: 1507920216 -licenseType: Free -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Scripts/Editor/NodeEditorReflection.cs b/Scripts/Editor/NodeEditorReflection.cs index 0a0a36a..2ef5ac1 100644 --- a/Scripts/Editor/NodeEditorReflection.cs +++ b/Scripts/Editor/NodeEditorReflection.cs @@ -74,6 +74,23 @@ namespace XNodeEditor { } return types.ToArray(); } + + /// Find methods marked with the [ContextMenu] attribute and add them to the menu popup window + public static void AddCustomContextMenuItems(this MenuPopupWindow contextMenu, object obj) { + KeyValuePair[] items = GetContextMenuMethods(obj); + if (items.Length != 0) { + List invalidatedEntries = new List(); + foreach (KeyValuePair 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 kvp = items[i]; + contextMenu.AddItem(kvp.Key.menuItem, () => kvp.Value.Invoke(obj, null)); + } + } + } /// Find methods marked with the [ContextMenu] attribute and add them to the context menu public static void AddCustomContextMenuItems(this GenericMenu contextMenu, object obj) { diff --git a/Scripts/Editor/NodeEditorReflection.cs.meta b/Scripts/Editor/NodeEditorReflection.cs.meta deleted file mode 100644 index fe4ba9b..0000000 --- a/Scripts/Editor/NodeEditorReflection.cs.meta +++ /dev/null @@ -1,12 +0,0 @@ -fileFormatVersion: 2 -guid: c78a0fa4a13abcd408ebe73006b7b1bb -timeCreated: 1505419458 -licenseType: Free -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Scripts/Editor/NodeEditorResources.cs.meta b/Scripts/Editor/NodeEditorResources.cs.meta deleted file mode 100644 index 5e85895..0000000 --- a/Scripts/Editor/NodeEditorResources.cs.meta +++ /dev/null @@ -1,12 +0,0 @@ -fileFormatVersion: 2 -guid: 69f55d341299026489b29443c3dd13d1 -timeCreated: 1505418919 -licenseType: Free -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Scripts/Editor/NodeEditorUtilities.cs.meta b/Scripts/Editor/NodeEditorUtilities.cs.meta deleted file mode 100644 index a8988ef..0000000 --- a/Scripts/Editor/NodeEditorUtilities.cs.meta +++ /dev/null @@ -1,12 +0,0 @@ -fileFormatVersion: 2 -guid: 120960fe5b50aba418a8e8ad3c4c4bc8 -timeCreated: 1506073499 -licenseType: Free -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Scripts/Editor/NodeEditorWindow.cs b/Scripts/Editor/NodeEditorWindow.cs index 4f0a102..5fa0fe8 100644 --- a/Scripts/Editor/NodeEditorWindow.cs +++ b/Scripts/Editor/NodeEditorWindow.cs @@ -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; } + + /// + /// Magic method which Unity detects automatically. + /// + /// Position of button. + 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(); + + foreach (var window in windows) + { + if (window) + { + try + { + window.Close(); + } + catch (Exception e) + { + DestroyImmediate(window); + } + } + } + } + public static NodeEditorWindow current; /// Stores node positions for all nodePorts. @@ -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 w = null; + foreach (var window in windows) + { + if (window.Lock) + { + if (window.graph == graph) + { + w = window; + } + } + else + { + w = window; + } + } + + if (!w) + { + w = EditorWindow.CreateInstance(); + 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; diff --git a/Scripts/Editor/NodeEditorWindow.cs.meta b/Scripts/Editor/NodeEditorWindow.cs.meta deleted file mode 100644 index 541b5c7..0000000 --- a/Scripts/Editor/NodeEditorWindow.cs.meta +++ /dev/null @@ -1,12 +0,0 @@ -fileFormatVersion: 2 -guid: 5ce2bf59ec7a25c4ba691cad7819bf38 -timeCreated: 1505418450 -licenseType: Free -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Scripts/Editor/NodeGraphEditor.cs b/Scripts/Editor/NodeGraphEditor.cs index 01de70e..b1968aa 100644 --- a/Scripts/Editor/NodeGraphEditor.cs +++ b/Scripts/Editor/NodeGraphEditor.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using UnityEditor; using UnityEngine; @@ -17,7 +16,7 @@ namespace XNodeEditor { /// Called when opened by NodeEditorWindow public virtual void OnOpen() { } - + /// Called when NodeEditorWindow gains focus public virtual void OnWindowFocus() { } @@ -58,7 +57,7 @@ namespace XNodeEditor { } /// Add items for the context menu when right-clicking this node. Override to add custom menu items. - 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); } diff --git a/Scripts/Editor/NodeGraphEditor.cs.meta b/Scripts/Editor/NodeGraphEditor.cs.meta deleted file mode 100644 index bc1c153..0000000 --- a/Scripts/Editor/NodeGraphEditor.cs.meta +++ /dev/null @@ -1,12 +0,0 @@ -fileFormatVersion: 2 -guid: ddcbb5432255d3247a0718b15a9c193c -timeCreated: 1505462176 -licenseType: Free -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Scripts/Editor/RenamePopup.cs.meta b/Scripts/Editor/RenamePopup.cs.meta deleted file mode 100644 index 5c40a02..0000000 --- a/Scripts/Editor/RenamePopup.cs.meta +++ /dev/null @@ -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: diff --git a/Scripts/Editor/Resources.meta b/Scripts/Editor/Resources.meta deleted file mode 100644 index 786ef41..0000000 --- a/Scripts/Editor/Resources.meta +++ /dev/null @@ -1,9 +0,0 @@ -fileFormatVersion: 2 -guid: 964fc201163fe884ca6a20094b6f3b49 -folderAsset: yes -timeCreated: 1506110871 -licenseType: Free -DefaultImporter: - userData: - assetBundleName: - assetBundleVariant: diff --git a/Scripts/Editor/Resources/ScriptTemplates.meta b/Scripts/Editor/Resources/ScriptTemplates.meta deleted file mode 100644 index b2435e8..0000000 --- a/Scripts/Editor/Resources/ScriptTemplates.meta +++ /dev/null @@ -1,10 +0,0 @@ -fileFormatVersion: 2 -guid: 86b677955452bb5449f9f4dd47b6ddfe -folderAsset: yes -timeCreated: 1519049391 -licenseType: Free -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Scripts/Editor/Resources/ScriptTemplates/xNode_NodeGraphTemplate.cs.txt.meta b/Scripts/Editor/Resources/ScriptTemplates/xNode_NodeGraphTemplate.cs.txt.meta deleted file mode 100644 index b55bd75..0000000 --- a/Scripts/Editor/Resources/ScriptTemplates/xNode_NodeGraphTemplate.cs.txt.meta +++ /dev/null @@ -1,9 +0,0 @@ -fileFormatVersion: 2 -guid: 8165767f64da7d94e925f61a38da668c -timeCreated: 1519049802 -licenseType: Free -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Scripts/Editor/Resources/ScriptTemplates/xNode_NodeTemplate.cs.txt.meta b/Scripts/Editor/Resources/ScriptTemplates/xNode_NodeTemplate.cs.txt.meta deleted file mode 100644 index 455420a..0000000 --- a/Scripts/Editor/Resources/ScriptTemplates/xNode_NodeTemplate.cs.txt.meta +++ /dev/null @@ -1,9 +0,0 @@ -fileFormatVersion: 2 -guid: 85f6f570600a1a44d8e734cb111a8b89 -timeCreated: 1519049802 -licenseType: Free -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Scripts/Editor/Resources/xnode_dot.png.meta b/Scripts/Editor/Resources/xnode_dot.png.meta deleted file mode 100644 index 00c23bc..0000000 --- a/Scripts/Editor/Resources/xnode_dot.png.meta +++ /dev/null @@ -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: diff --git a/Scripts/Editor/Resources/xnode_dot_outer.png.meta b/Scripts/Editor/Resources/xnode_dot_outer.png.meta deleted file mode 100644 index 0781a52..0000000 --- a/Scripts/Editor/Resources/xnode_dot_outer.png.meta +++ /dev/null @@ -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: diff --git a/Scripts/Editor/Resources/xnode_node.png.meta b/Scripts/Editor/Resources/xnode_node.png.meta deleted file mode 100644 index 979e6f9..0000000 --- a/Scripts/Editor/Resources/xnode_node.png.meta +++ /dev/null @@ -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: diff --git a/Scripts/Editor/Resources/xnode_node_highlight.png.meta b/Scripts/Editor/Resources/xnode_node_highlight.png.meta deleted file mode 100644 index 21b6034..0000000 --- a/Scripts/Editor/Resources/xnode_node_highlight.png.meta +++ /dev/null @@ -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: diff --git a/Scripts/Editor/Resources/xnode_node_workfile.psd.meta b/Scripts/Editor/Resources/xnode_node_workfile.psd.meta deleted file mode 100644 index 3ca0437..0000000 --- a/Scripts/Editor/Resources/xnode_node_workfile.psd.meta +++ /dev/null @@ -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: diff --git a/Scripts/Editor/XNodeEditor.asmdef.meta b/Scripts/Editor/XNodeEditor.asmdef.meta deleted file mode 100644 index 7bff074..0000000 --- a/Scripts/Editor/XNodeEditor.asmdef.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 002c1bbed08fa44d282ef34fd5edb138 -AssemblyDefinitionImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Scripts/Node.cs b/Scripts/Node.cs index 6744cc5..eba5604 100644 --- a/Scripts/Node.cs +++ b/Scripts/Node.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using UnityEngine; namespace XNode { @@ -113,6 +114,12 @@ namespace XNode { /// It is recommended not to modify these at hand. Instead, see and [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 + /// Used during node instantiation to fix null/misconfigured graph during OnEnable/Init. Set it before instantiating a node. Will automatically be unset during OnEnable public static NodeGraph graphHotfix; @@ -148,7 +155,7 @@ namespace XNode { /// /// 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); } /// Add a dynamic, serialized port to this node. @@ -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; } /// Mark a serializable field as an input port. You can access this through + /// 指定更准确的父类类型,只有当参数为才可用 /// Should we display the backing value for this port as an editor field? /// Should we allow multiple connections? /// Constrains which input connections can be made to this port /// If true, will display a reorderable list of inputs instead of a single port. Will automatically add and display values for lists and arrays - 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; } } + + /// Custom Port Label + [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, ISerializationCallbackReceiver { [SerializeField] private List keys = new List(); [SerializeField] private List values = new List(); +#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 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); } diff --git a/Scripts/Node.cs.meta b/Scripts/Node.cs.meta deleted file mode 100644 index a267e40..0000000 --- a/Scripts/Node.cs.meta +++ /dev/null @@ -1,12 +0,0 @@ -fileFormatVersion: 2 -guid: f26231e5ab9368746948d0ea49e8178a -timeCreated: 1505419984 -licenseType: Free -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Scripts/NodeDataCache.cs b/Scripts/NodeDataCache.cs index ba52e1b..e8b83ac 100644 --- a/Scripts/NodeDataCache.cs +++ b/Scripts/NodeDataCache.cs @@ -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 { /// /// 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. /// 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; }); } - + /// Cache node types 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) { diff --git a/Scripts/NodeDataCache.cs.meta b/Scripts/NodeDataCache.cs.meta deleted file mode 100644 index 34482f2..0000000 --- a/Scripts/NodeDataCache.cs.meta +++ /dev/null @@ -1,12 +0,0 @@ -fileFormatVersion: 2 -guid: 64ea6af1e195d024d8df0ead1921e517 -timeCreated: 1507566823 -licenseType: Free -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Scripts/NodeGraph.cs.meta b/Scripts/NodeGraph.cs.meta deleted file mode 100644 index b2e1264..0000000 --- a/Scripts/NodeGraph.cs.meta +++ /dev/null @@ -1,12 +0,0 @@ -fileFormatVersion: 2 -guid: 093f68ef2455d544fa2d14b80c811322 -timeCreated: 1505461376 -licenseType: Free -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Scripts/NodePort.cs b/Scripts/NodePort.cs index b2f1ad1..fea85c8 100644 --- a/Scripts/NodePort.cs +++ b/Scripts/NodePort.cs @@ -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(); 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 diff --git a/Scripts/NodePort.cs.meta b/Scripts/NodePort.cs.meta deleted file mode 100644 index 3863705..0000000 --- a/Scripts/NodePort.cs.meta +++ /dev/null @@ -1,12 +0,0 @@ -fileFormatVersion: 2 -guid: 7dd2f76ac25c6f44c9426dff3e7491a3 -timeCreated: 1505734054 -licenseType: Free -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Scripts/XNode.asmdef.meta b/Scripts/XNode.asmdef.meta deleted file mode 100644 index 8479d75..0000000 --- a/Scripts/XNode.asmdef.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: b8e24fd1eb19b4226afebb2810e3c19b -AssemblyDefinitionImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/package.json.meta b/package.json.meta deleted file mode 100644 index c8f1dc4..0000000 --- a/package.json.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: e9869d68f06b74538a01e9b8e406159e -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: