diff --git a/Scripts/Editor/Drawers/Odin.meta b/Scripts/Editor/Drawers/Odin.meta new file mode 100644 index 0000000..c2b0ac9 --- /dev/null +++ b/Scripts/Editor/Drawers/Odin.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 327994a52f523b641898a39ff7500a02 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/Editor/Drawers/Odin/InNodeEditorAttributeProcessor.cs b/Scripts/Editor/Drawers/Odin/InNodeEditorAttributeProcessor.cs new file mode 100644 index 0000000..84c6d8e --- /dev/null +++ b/Scripts/Editor/Drawers/Odin/InNodeEditorAttributeProcessor.cs @@ -0,0 +1,48 @@ +#if UNITY_EDITOR && ODIN_INSPECTOR +using System; +using System.Collections.Generic; +using System.Reflection; +using Sirenix.OdinInspector.Editor; +using UnityEngine; +using XNode; + +namespace XNodeEditor { + internal class OdinNodeInGraphAttributeProcessor : OdinAttributeProcessor where T : Node { + public override bool CanProcessSelfAttributes(InspectorProperty property) { + return false; + } + + public override bool CanProcessChildMemberAttributes(InspectorProperty parentProperty, MemberInfo member) { + if (!NodeEditor.inNodeEditor) + return false; + + if (member.MemberType == MemberTypes.Field) { + switch (member.Name) { + case "graph": + case "position": + case "ports": + return true; + + default: + break; + } + } + + return false; + } + + public override void ProcessChildMemberAttributes(InspectorProperty parentProperty, MemberInfo member, List attributes) { + switch (member.Name) { + case "graph": + case "position": + case "ports": + attributes.Add(new HideInInspector()); + break; + + default: + break; + } + } + } +} +#endif \ No newline at end of file diff --git a/Scripts/Editor/Drawers/Odin/InNodeEditorAttributeProcessor.cs.meta b/Scripts/Editor/Drawers/Odin/InNodeEditorAttributeProcessor.cs.meta new file mode 100644 index 0000000..15f6990 --- /dev/null +++ b/Scripts/Editor/Drawers/Odin/InNodeEditorAttributeProcessor.cs.meta @@ -0,0 +1,11 @@ +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 b/Scripts/Editor/Drawers/Odin/InputAttributeDrawer.cs new file mode 100644 index 0000000..a384bdc --- /dev/null +++ b/Scripts/Editor/Drawers/Odin/InputAttributeDrawer.cs @@ -0,0 +1,49 @@ +#if UNITY_EDITOR && ODIN_INSPECTOR +using Sirenix.OdinInspector; +using Sirenix.OdinInspector.Editor; +using Sirenix.Utilities.Editor; +using UnityEngine; +using XNode; + +namespace XNodeEditor { + public class InputAttributeDrawer : OdinAttributeDrawer { + protected override bool CanDrawAttributeProperty(InspectorProperty property) { + Node node = property.Tree.WeakTargets[0] as Node; + return node != null; + } + + protected override void DrawPropertyLayout(GUIContent label) { + Node node = Property.Tree.WeakTargets[0] as Node; + NodePort port = node.GetInputPort(Property.Name); + + if (!NodeEditor.inNodeEditor) { + if (Attribute.backingValue == XNode.Node.ShowBackingValue.Always || Attribute.backingValue == XNode.Node.ShowBackingValue.Unconnected && !port.IsConnected) + CallNextDrawer(label); + return; + } + + if (Property.Tree.WeakTargets.Count > 1) { + SirenixEditorGUI.WarningMessageBox("Cannot draw ports with multiple nodes selected"); + return; + } + + if (port != null) { + var portPropoerty = Property.Tree.GetUnityPropertyForPath(Property.UnityPropertyPath); + if (portPropoerty == null) { + SirenixEditorGUI.ErrorMessageBox("Port property missing at: " + Property.UnityPropertyPath); + return; + } else { + var labelWidth = Property.GetAttribute(); + if (labelWidth != null) + GUIHelper.PushLabelWidth(labelWidth.Width); + + NodeEditorGUILayout.PropertyField(portPropoerty, label == null ? GUIContent.none : label, true, GUILayout.MinWidth(30)); + + if (labelWidth != null) + GUIHelper.PopLabelWidth(); + } + } + } + } +} +#endif \ No newline at end of file diff --git a/Scripts/Editor/Drawers/Odin/InputAttributeDrawer.cs.meta b/Scripts/Editor/Drawers/Odin/InputAttributeDrawer.cs.meta new file mode 100644 index 0000000..12b7615 --- /dev/null +++ b/Scripts/Editor/Drawers/Odin/InputAttributeDrawer.cs.meta @@ -0,0 +1,11 @@ +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 b/Scripts/Editor/Drawers/Odin/OutputAttributeDrawer.cs new file mode 100644 index 0000000..ff59615 --- /dev/null +++ b/Scripts/Editor/Drawers/Odin/OutputAttributeDrawer.cs @@ -0,0 +1,49 @@ +#if UNITY_EDITOR && ODIN_INSPECTOR +using Sirenix.OdinInspector; +using Sirenix.OdinInspector.Editor; +using Sirenix.Utilities.Editor; +using UnityEngine; +using XNode; + +namespace XNodeEditor { + public class OutputAttributeDrawer : OdinAttributeDrawer { + protected override bool CanDrawAttributeProperty(InspectorProperty property) { + Node node = property.Tree.WeakTargets[0] as Node; + return node != null; + } + + protected override void DrawPropertyLayout(GUIContent label) { + Node node = Property.Tree.WeakTargets[0] as Node; + NodePort port = node.GetOutputPort(Property.Name); + + if (!NodeEditor.inNodeEditor) { + if (Attribute.backingValue == XNode.Node.ShowBackingValue.Always || Attribute.backingValue == XNode.Node.ShowBackingValue.Unconnected && !port.IsConnected) + CallNextDrawer(label); + return; + } + + if (Property.Tree.WeakTargets.Count > 1) { + SirenixEditorGUI.WarningMessageBox("Cannot draw ports with multiple nodes selected"); + return; + } + + if (port != null) { + var portPropoerty = Property.Tree.GetUnityPropertyForPath(Property.UnityPropertyPath); + if (portPropoerty == null) { + SirenixEditorGUI.ErrorMessageBox("Port property missing at: " + Property.UnityPropertyPath); + return; + } else { + var labelWidth = Property.GetAttribute(); + if (labelWidth != null) + GUIHelper.PushLabelWidth(labelWidth.Width); + + NodeEditorGUILayout.PropertyField(portPropoerty, label == null ? GUIContent.none : label, true, GUILayout.MinWidth(30)); + + if (labelWidth != null) + GUIHelper.PopLabelWidth(); + } + } + } + } +} +#endif \ No newline at end of file diff --git a/Scripts/Editor/Drawers/Odin/OutputAttributeDrawer.cs.meta b/Scripts/Editor/Drawers/Odin/OutputAttributeDrawer.cs.meta new file mode 100644 index 0000000..aa22218 --- /dev/null +++ b/Scripts/Editor/Drawers/Odin/OutputAttributeDrawer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e7ebd8f2b42e2384aa109551dc46af88 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/Editor/NodeEditor.cs b/Scripts/Editor/NodeEditor.cs index 631d8e5..8d293ab 100644 --- a/Scripts/Editor/NodeEditor.cs +++ b/Scripts/Editor/NodeEditor.cs @@ -3,6 +3,11 @@ 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. @@ -10,23 +15,39 @@ namespace XNodeEditor { 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 + 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; @@ -36,6 +57,7 @@ namespace XNodeEditor { 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) { @@ -44,6 +66,20 @@ namespace XNodeEditor { } 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(); + } +#else + window.Repaint(); +#endif + +#if ODIN_INSPECTOR + inNodeEditor = false; +#endif } public virtual int GetWidth() { diff --git a/Scripts/Editor/NodeEditorBase.cs b/Scripts/Editor/NodeEditorBase.cs index 261a284..1fc28c7 100644 --- a/Scripts/Editor/NodeEditorBase.cs +++ b/Scripts/Editor/NodeEditorBase.cs @@ -4,6 +4,9 @@ using System.Collections.Generic; using System.Reflection; using UnityEditor; using UnityEngine; +#if ODIN_INSPECTOR +using Sirenix.OdinInspector.Editor; +#endif namespace XNodeEditor.Internal { /// Handles caching of custom editor classes and their target types. Accessible with GetEditor(Type type) @@ -17,6 +20,24 @@ namespace XNodeEditor.Internal { public NodeEditorWindow window; public K target; public SerializedObject serializedObject; +#if ODIN_INSPECTOR + private PropertyTree _objectTree; + public PropertyTree objectTree { + get { + if (this._objectTree == null) { + try { + bool wasInEditor = NodeEditor.inNodeEditor; + NodeEditor.inNodeEditor = true; + this._objectTree = PropertyTree.Create(this.serializedObject); + NodeEditor.inNodeEditor = wasInEditor; + } catch (ArgumentException ex) { + Debug.Log(ex); + } + } + return this._objectTree; + } + } +#endif public static T GetEditor(K target, NodeEditorWindow window) { if (target == null) return null;