diff options
Diffstat (limited to 'Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor')
65 files changed, 4593 insertions, 0 deletions
diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Drawers.meta b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Drawers.meta new file mode 100644 index 00000000..b69e0ac8 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Drawers.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 7adf21edfb51f514fa991d7556ecd0ef +folderAsset: yes +timeCreated: 1541971984 +licenseType: Free +DefaultImporter: +  externalObjects: {} +  userData:  +  assetBundleName:  +  assetBundleVariant:  diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Drawers/NodeEnumDrawer.cs b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Drawers/NodeEnumDrawer.cs new file mode 100644 index 00000000..8aa748c2 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Drawers/NodeEnumDrawer.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; +using XNode; +using XNodeEditor; + +namespace XNodeEditor { +	[CustomPropertyDrawer(typeof(NodeEnumAttribute))] +	public class NodeEnumDrawer : PropertyDrawer { +		public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { +			EditorGUI.BeginProperty(position, label, property); + +			EnumPopup(position, property, label); + +			EditorGUI.EndProperty(); +		} + +		public static void EnumPopup(Rect position, SerializedProperty property, GUIContent label) { +			// Throw error on wrong type +			if (property.propertyType != SerializedPropertyType.Enum) { +				throw new ArgumentException("Parameter selected must be of type System.Enum"); +			} + +			// Add label +			position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label); + +			// Get current enum name +			string enumName = ""; +			if (property.enumValueIndex >= 0 && property.enumValueIndex < property.enumDisplayNames.Length) enumName = property.enumDisplayNames[property.enumValueIndex]; + +#if UNITY_2017_1_OR_NEWER +			// Display dropdown +			if (EditorGUI.DropdownButton(position, new GUIContent(enumName), FocusType.Passive)) { +				// Position is all wrong if we show the dropdown during the node draw phase. +				// Instead, add it to onLateGUI to display it later. +				NodeEditorWindow.current.onLateGUI += () => ShowContextMenuAtMouse(property); +			} +#else +			// Display dropdown +			if (GUI.Button(position, new GUIContent(enumName), "MiniPopup")) { +				// Position is all wrong if we show the dropdown during the node draw phase. +				// Instead, add it to onLateGUI to display it later. +				NodeEditorWindow.current.onLateGUI += () => ShowContextMenuAtMouse(property); +			} +#endif +		} + +		public static void ShowContextMenuAtMouse(SerializedProperty property) { +			// Initialize menu +			GenericMenu menu = new GenericMenu(); + +			// Add all enum display names to menu +			for (int i = 0; i < property.enumDisplayNames.Length; i++) { +				int index = i; +				menu.AddItem(new GUIContent(property.enumDisplayNames[i]), false, () => SetEnum(property, index)); +			} + +			// Display at cursor position +			Rect r = new Rect(Event.current.mousePosition, new Vector2(0, 0)); +			menu.DropDown(r); +		} + +		private static void SetEnum(SerializedProperty property, int index) { +			property.enumValueIndex = index; +			property.serializedObject.ApplyModifiedProperties(); +			property.serializedObject.Update(); +		} +	} +}
\ No newline at end of file diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Drawers/NodeEnumDrawer.cs.meta b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Drawers/NodeEnumDrawer.cs.meta new file mode 100644 index 00000000..beacf6b3 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Drawers/NodeEnumDrawer.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 83db81f92abadca439507e25d517cabe +timeCreated: 1541633798 +licenseType: Free +MonoImporter: +  externalObjects: {} +  serializedVersion: 2 +  defaultReferences: [] +  executionOrder: 0 +  icon: {instanceID: 0} +  userData:  +  assetBundleName:  +  assetBundleVariant:  diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Drawers/Odin.meta b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Drawers/Odin.meta new file mode 100644 index 00000000..c2b0ac98 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Drawers/Odin.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 327994a52f523b641898a39ff7500a02 +folderAsset: yes +DefaultImporter: +  externalObjects: {} +  userData:  +  assetBundleName:  +  assetBundleVariant:  diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Drawers/Odin/InNodeEditorAttributeProcessor.cs b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Drawers/Odin/InNodeEditorAttributeProcessor.cs new file mode 100644 index 00000000..84c6d8e7 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/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<T> : OdinAttributeProcessor<T> 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<Attribute> 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/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Drawers/Odin/InNodeEditorAttributeProcessor.cs.meta b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Drawers/Odin/InNodeEditorAttributeProcessor.cs.meta new file mode 100644 index 00000000..15f69908 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/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/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Drawers/Odin/InputAttributeDrawer.cs b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Drawers/Odin/InputAttributeDrawer.cs new file mode 100644 index 00000000..a384bdcf --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/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<XNode.Node.InputAttribute> { +		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<LabelWidthAttribute>(); +					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/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Drawers/Odin/InputAttributeDrawer.cs.meta b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Drawers/Odin/InputAttributeDrawer.cs.meta new file mode 100644 index 00000000..12b76158 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/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/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Drawers/Odin/OutputAttributeDrawer.cs b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Drawers/Odin/OutputAttributeDrawer.cs new file mode 100644 index 00000000..ff596156 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/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<XNode.Node.OutputAttribute> { +		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<LabelWidthAttribute>(); +					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/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Drawers/Odin/OutputAttributeDrawer.cs.meta b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Drawers/Odin/OutputAttributeDrawer.cs.meta new file mode 100644 index 00000000..aa22218d --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/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/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/GraphAndNodeEditor.cs b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/GraphAndNodeEditor.cs new file mode 100644 index 00000000..6859855c --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/GraphAndNodeEditor.cs @@ -0,0 +1,75 @@ +using UnityEditor; +using UnityEngine; +#if ODIN_INSPECTOR +using Sirenix.OdinInspector.Editor; +using Sirenix.Utilities; +using Sirenix.Utilities.Editor; +#endif + +namespace XNodeEditor { +    /// <summary> Override graph inspector to show an 'Open Graph' button at the top </summary> +    [CustomEditor(typeof(XNode.NodeGraph), true)] +#if ODIN_INSPECTOR +    public class GlobalGraphEditor : OdinEditor { +        public override void OnInspectorGUI() { +            if (GUILayout.Button("Edit graph", GUILayout.Height(40))) { +                NodeEditorWindow.Open(serializedObject.targetObject as XNode.NodeGraph); +            } +            base.OnInspectorGUI(); +        } +    } +#else +    [CanEditMultipleObjects] +    public class GlobalGraphEditor : Editor { +        public override void OnInspectorGUI() { +            serializedObject.Update(); + +            if (GUILayout.Button("Edit graph", GUILayout.Height(40))) { +                NodeEditorWindow.Open(serializedObject.targetObject as XNode.NodeGraph); +            } + +            GUILayout.Space(EditorGUIUtility.singleLineHeight); +            GUILayout.Label("Raw data", "BoldLabel"); + +            DrawDefaultInspector(); + +            serializedObject.ApplyModifiedProperties(); +        } +    } +#endif + +    [CustomEditor(typeof(XNode.Node), true)] +#if ODIN_INSPECTOR +    public class GlobalNodeEditor : OdinEditor { +        public override void OnInspectorGUI() { +            if (GUILayout.Button("Edit graph", GUILayout.Height(40))) { +                SerializedProperty graphProp = serializedObject.FindProperty("graph"); +                NodeEditorWindow w = NodeEditorWindow.Open(graphProp.objectReferenceValue as XNode.NodeGraph); +                w.Home(); // Focus selected node +            } +            base.OnInspectorGUI(); +        } +    } +#else +    [CanEditMultipleObjects] +    public class GlobalNodeEditor : Editor { +        public override void OnInspectorGUI() { +            serializedObject.Update(); + +            if (GUILayout.Button("Edit graph", GUILayout.Height(40))) { +                SerializedProperty graphProp = serializedObject.FindProperty("graph"); +                NodeEditorWindow w = NodeEditorWindow.Open(graphProp.objectReferenceValue as XNode.NodeGraph); +                w.Home(); // Focus selected node +            } + +            GUILayout.Space(EditorGUIUtility.singleLineHeight); +            GUILayout.Label("Raw data", "BoldLabel"); + +            // Now draw the node itself. +            DrawDefaultInspector(); + +            serializedObject.ApplyModifiedProperties(); +        } +    } +#endif +}
\ No newline at end of file diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/GraphAndNodeEditor.cs.meta b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/GraphAndNodeEditor.cs.meta new file mode 100644 index 00000000..5cc60df1 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/GraphAndNodeEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bdd6e443125ccac4dad0665515759637 +MonoImporter: +  externalObjects: {} +  serializedVersion: 2 +  defaultReferences: [] +  executionOrder: 0 +  icon: {instanceID: 0} +  userData:  +  assetBundleName:  +  assetBundleVariant:  diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/GraphRenameFixAssetProcessor.cs b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/GraphRenameFixAssetProcessor.cs new file mode 100644 index 00000000..264e8b12 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/GraphRenameFixAssetProcessor.cs @@ -0,0 +1,35 @@ +using UnityEditor; +using XNode; + +namespace XNodeEditor { +    /// <summary> +    /// This asset processor resolves an issue with the new v2 AssetDatabase system present on 2019.3 and later. When +    /// renaming a <see cref="XNode.NodeGraph"/> asset, it appears that sometimes the v2 AssetDatabase will swap which asset +    /// is the main asset (present at top level) between the <see cref="XNode.NodeGraph"/> and one of its <see cref="XNode.Node"/> +    /// sub-assets. As a workaround until Unity fixes this, this asset processor checks all renamed assets and if it +    /// finds a case where a <see cref="XNode.Node"/> has been made the main asset it will swap it back to being a sub-asset +    /// and rename the node to the default name for that node type. +    /// </summary> +    internal sealed class GraphRenameFixAssetProcessor : AssetPostprocessor { +        private static void OnPostprocessAllAssets( +            string[] importedAssets, +            string[] deletedAssets, +            string[] movedAssets, +            string[] movedFromAssetPaths) { +            for (int i = 0; i < movedAssets.Length; i++) { +                Node nodeAsset = AssetDatabase.LoadMainAssetAtPath(movedAssets[i]) as Node; + +                // If the renamed asset is a node graph, but the v2 AssetDatabase has swapped a sub-asset node to be its +                // main asset, reset the node graph to be the main asset and rename the node asset back to its default +                // name. +                if (nodeAsset != null && AssetDatabase.IsMainAsset(nodeAsset)) { +                    AssetDatabase.SetMainObject(nodeAsset.graph, movedAssets[i]); +                    AssetDatabase.ImportAsset(movedAssets[i]); + +                    nodeAsset.name = NodeEditorUtilities.NodeDefaultName(nodeAsset.GetType()); +                    EditorUtility.SetDirty(nodeAsset); +                } +            } +        } +    } +}
\ No newline at end of file diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/GraphRenameFixAssetProcessor.cs.meta b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/GraphRenameFixAssetProcessor.cs.meta new file mode 100644 index 00000000..77e87eef --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/GraphRenameFixAssetProcessor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 65da1ff1c50a9984a9c95fd18799e8dd +MonoImporter: +  externalObjects: {} +  serializedVersion: 2 +  defaultReferences: [] +  executionOrder: 0 +  icon: {instanceID: 0} +  userData:  +  assetBundleName:  +  assetBundleVariant:  diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Internal.meta b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Internal.meta new file mode 100644 index 00000000..600ad293 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Internal.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a6a1bbc054e282346a02e7bbddde3206 +folderAsset: yes +DefaultImporter: +  externalObjects: {} +  userData:  +  assetBundleName:  +  assetBundleVariant:  diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Internal/RerouteReference.cs b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Internal/RerouteReference.cs new file mode 100644 index 00000000..4e211306 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Internal/RerouteReference.cs @@ -0,0 +1,20 @@ +using UnityEngine; + +namespace XNodeEditor.Internal { +	public struct RerouteReference { +		public XNode.NodePort port; +		public int connectionIndex; +		public int pointIndex; + +		public RerouteReference(XNode.NodePort port, int connectionIndex, int pointIndex) { +			this.port = port; +			this.connectionIndex = connectionIndex; +			this.pointIndex = pointIndex; +		} + +		public void InsertPoint(Vector2 pos) { port.GetReroutePoints(connectionIndex).Insert(pointIndex, pos); } +		public void SetPoint(Vector2 pos) { port.GetReroutePoints(connectionIndex) [pointIndex] = pos; } +		public void RemovePoint() { port.GetReroutePoints(connectionIndex).RemoveAt(pointIndex); } +		public Vector2 GetPoint() { return port.GetReroutePoints(connectionIndex) [pointIndex]; } +	} +}
\ No newline at end of file diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Internal/RerouteReference.cs.meta b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Internal/RerouteReference.cs.meta new file mode 100644 index 00000000..9a2f9cb0 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Internal/RerouteReference.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 399f3c5fb717b2c458c3e9746f8959a3 +MonoImporter: +  externalObjects: {} +  serializedVersion: 2 +  defaultReferences: [] +  executionOrder: 0 +  icon: {instanceID: 0} +  userData:  +  assetBundleName:  +  assetBundleVariant:  diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditor.cs b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditor.cs new file mode 100644 index 00000000..45b96050 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditor.cs @@ -0,0 +1,159 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEngine; +#if ODIN_INSPECTOR +using Sirenix.OdinInspector.Editor; +using Sirenix.Utilities; +using Sirenix.Utilities.Editor; +#endif + +namespace XNodeEditor { +    /// <summary> Base class to derive custom Node editors from. Use this to create your own custom inspectors and editors for your nodes. </summary> +    [CustomNodeEditor(typeof(XNode.Node))] +    public class NodeEditor : XNodeEditor.Internal.NodeEditorBase<NodeEditor, NodeEditor.CustomNodeEditorAttribute, XNode.Node> { + +        private readonly Color DEFAULTCOLOR = new Color32(90, 97, 105, 255); + +        /// <summary> Fires every whenever a node was modified through the editor </summary> +        public static Action<XNode.Node> onUpdateNode; +        public readonly static Dictionary<XNode.NodePort, Vector2> portPositions = new Dictionary<XNode.NodePort, Vector2>(); + +#if ODIN_INSPECTOR +        protected internal static bool inNodeEditor = false; +#endif + +        public virtual void OnHeaderGUI() { +            GUILayout.Label(target.name, NodeEditorResources.styles.nodeHeader, GUILayout.Height(30)); +        } + +        /// <summary> Draws standard field editors for all public fields </summary> +        public virtual void OnBodyGUI() { +#if ODIN_INSPECTOR +            inNodeEditor = true; +#endif + +            // Unity specifically requires this to save/update any serial object. +            // serializedObject.Update(); must go at the start of an inspector gui, and +            // serializedObject.ApplyModifiedProperties(); goes at the end. +            serializedObject.Update(); +            string[] excludes = { "m_Script", "graph", "position", "ports" }; + +#if ODIN_INSPECTOR +            InspectorUtilities.BeginDrawPropertyTree(objectTree, true); +            GUIHelper.PushLabelWidth(84); +            objectTree.Draw(true); +            InspectorUtilities.EndDrawPropertyTree(objectTree); +            GUIHelper.PopLabelWidth(); +#else + +            // Iterate through serialized properties and draw them like the Inspector (But with ports) +            SerializedProperty iterator = serializedObject.GetIterator(); +            bool enterChildren = true; +            while (iterator.NextVisible(enterChildren)) { +                enterChildren = false; +                if (excludes.Contains(iterator.name)) continue; +                NodeEditorGUILayout.PropertyField(iterator, true); +            } +#endif + +            // Iterate through dynamic ports and draw them in the order in which they are serialized +            foreach (XNode.NodePort dynamicPort in target.DynamicPorts) { +                if (NodeEditorGUILayout.IsDynamicPortListPort(dynamicPort)) continue; +                NodeEditorGUILayout.PortField(dynamicPort); +            } + +            serializedObject.ApplyModifiedProperties(); + +#if ODIN_INSPECTOR +            // Call repaint so that the graph window elements respond properly to layout changes coming from Odin +            if (GUIHelper.RepaintRequested) { +                GUIHelper.ClearRepaintRequest(); +                window.Repaint(); +            } +#endif + +#if ODIN_INSPECTOR +            inNodeEditor = false; +#endif +        } + +        public virtual int GetWidth() { +            Type type = target.GetType(); +            int width; +            if (type.TryGetAttributeWidth(out width)) return width; +            else return 208; +        } + +        /// <summary> Returns color for target node </summary> +        public virtual Color GetTint() { +            // Try get color from [NodeTint] attribute +            Type type = target.GetType(); +            Color color; +            if (type.TryGetAttributeTint(out color)) return color; +            // Return default color (grey) +            else return DEFAULTCOLOR; +        } + +        public virtual GUIStyle GetBodyStyle() { +            return NodeEditorResources.styles.nodeBody; +        } + +        public virtual GUIStyle GetBodyHighlightStyle() { +            return NodeEditorResources.styles.nodeHighlight; +        } + +        /// <summary> Add items for the context menu when right-clicking this node. Override to add custom menu items. </summary> +        public virtual void AddContextMenuItems(GenericMenu menu) { +            bool canRemove = true; +            // Actions if only one node is selected +            if (Selection.objects.Length == 1 && Selection.activeObject is XNode.Node) { +                XNode.Node node = Selection.activeObject as XNode.Node; +                menu.AddItem(new GUIContent("Move To Top"), false, () => NodeEditorWindow.current.MoveNodeToTop(node)); +                menu.AddItem(new GUIContent("Rename"), false, NodeEditorWindow.current.RenameSelectedNode); + +                canRemove = NodeGraphEditor.GetEditor(node.graph, NodeEditorWindow.current).CanRemove(node); +            } + +            // Add actions to any number of selected nodes +            menu.AddItem(new GUIContent("Copy"), false, NodeEditorWindow.current.CopySelectedNodes); +            menu.AddItem(new GUIContent("Duplicate"), false, NodeEditorWindow.current.DuplicateSelectedNodes); + +            if (canRemove) menu.AddItem(new GUIContent("Remove"), false, NodeEditorWindow.current.RemoveSelectedNodes); +            else menu.AddItem(new GUIContent("Remove"), false, null); + +            // Custom sctions if only one node is selected +            if (Selection.objects.Length == 1 && Selection.activeObject is XNode.Node) { +                XNode.Node node = Selection.activeObject as XNode.Node; +                menu.AddCustomContextMenuItems(node); +            } +        } + +        /// <summary> Rename the node asset. This will trigger a reimport of the node. </summary> +        public void Rename(string newName) { +            if (newName == null || newName.Trim() == "") newName = NodeEditorUtilities.NodeDefaultName(target.GetType()); +            target.name = newName; +            OnRename(); +            AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target)); +        } + +        /// <summary> Called after this node's name has changed. </summary> +        public virtual void OnRename() { } + +        [AttributeUsage(AttributeTargets.Class)] +        public class CustomNodeEditorAttribute : Attribute, +        XNodeEditor.Internal.NodeEditorBase<NodeEditor, NodeEditor.CustomNodeEditorAttribute, XNode.Node>.INodeEditorAttrib { +            private Type inspectedType; +            /// <summary> Tells a NodeEditor which Node type it is an editor for </summary> +            /// <param name="inspectedType">Type that this editor can edit</param> +            public CustomNodeEditorAttribute(Type inspectedType) { +                this.inspectedType = inspectedType; +            } + +            public Type GetInspectedType() { +                return inspectedType; +            } +        } +    } +}
\ No newline at end of file diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditor.cs.meta b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditor.cs.meta new file mode 100644 index 00000000..db8651d5 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditor.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 712c3fc5d9eeb4c45b1e23918df6018f +timeCreated: 1505462176 +licenseType: Free +MonoImporter: +  serializedVersion: 2 +  defaultReferences: [] +  executionOrder: 0 +  icon: {instanceID: 0} +  userData:  +  assetBundleName:  +  assetBundleVariant:  diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorAction.cs b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorAction.cs new file mode 100644 index 00000000..8c151698 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorAction.cs @@ -0,0 +1,551 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEngine; +using XNodeEditor.Internal; + +namespace XNodeEditor { +    public partial class NodeEditorWindow { +        public enum NodeActivity { Idle, HoldNode, DragNode, HoldGrid, DragGrid } +        public static NodeActivity currentActivity = NodeActivity.Idle; +        public static bool isPanning { get; private set; } +        public static Vector2[] dragOffset; + +        public static XNode.Node[] copyBuffer = null; + +        private bool IsDraggingPort { get { return draggedOutput != null; } } +        private bool IsHoveringPort { get { return hoveredPort != null; } } +        private bool IsHoveringNode { get { return hoveredNode != null; } } +        private bool IsHoveringReroute { get { return hoveredReroute.port != null; } } +        private XNode.Node hoveredNode = null; +        [NonSerialized] public XNode.NodePort hoveredPort = null; +        [NonSerialized] private XNode.NodePort draggedOutput = null; +        [NonSerialized] private XNode.NodePort draggedOutputTarget = null; +        [NonSerialized] private XNode.NodePort autoConnectOutput = null; +        [NonSerialized] private List<Vector2> draggedOutputReroutes = new List<Vector2>(); +        private RerouteReference hoveredReroute = new RerouteReference(); +        public List<RerouteReference> selectedReroutes = new List<RerouteReference>(); +        private Vector2 dragBoxStart; +        private UnityEngine.Object[] preBoxSelection; +        private RerouteReference[] preBoxSelectionReroute; +        private Rect selectionBox; +        private bool isDoubleClick = false; +        private Vector2 lastMousePosition; +        private float dragThreshold = 1f; + +        public void Controls() { +            wantsMouseMove = true; +            Event e = Event.current; +            switch (e.type) { +                case EventType.DragUpdated: +                case EventType.DragPerform: +                    DragAndDrop.visualMode = DragAndDropVisualMode.Generic; +                    if (e.type == EventType.DragPerform) { +                        DragAndDrop.AcceptDrag(); +                        graphEditor.OnDropObjects(DragAndDrop.objectReferences); +                    } +                    break; +                case EventType.MouseMove: +                    //Keyboard commands will not get correct mouse position from Event +                    lastMousePosition = e.mousePosition; +                    break; +                case EventType.ScrollWheel: +                    float oldZoom = zoom; +                    if (e.delta.y > 0) zoom += 0.1f * zoom; +                    else zoom -= 0.1f * zoom; +                    if (NodeEditorPreferences.GetSettings().zoomToMouse) panOffset += (1 - oldZoom / zoom) * (WindowToGridPosition(e.mousePosition) + panOffset); +                    break; +                case EventType.MouseDrag: +                    if (e.button == 0) { +                        if (IsDraggingPort) { +                            // Set target even if we can't connect, so as to prevent auto-conn menu from opening erroneously +                            if (IsHoveringPort && hoveredPort.IsInput && !draggedOutput.IsConnectedTo(hoveredPort)) { +                                draggedOutputTarget = hoveredPort; +                            } else { +                                draggedOutputTarget = null; +                            } +                            Repaint(); +                        } else if (currentActivity == NodeActivity.HoldNode) { +                            RecalculateDragOffsets(e); +                            currentActivity = NodeActivity.DragNode; +                            Repaint(); +                        } +                        if (currentActivity == NodeActivity.DragNode) { +                            // Holding ctrl inverts grid snap +                            bool gridSnap = NodeEditorPreferences.GetSettings().gridSnap; +                            if (e.control) gridSnap = !gridSnap; + +                            Vector2 mousePos = WindowToGridPosition(e.mousePosition); +                            // Move selected nodes with offset +                            for (int i = 0; i < Selection.objects.Length; i++) { +                                if (Selection.objects[i] is XNode.Node) { +                                    XNode.Node node = Selection.objects[i] as XNode.Node; +                                    Undo.RecordObject(node, "Moved Node"); +                                    Vector2 initial = node.position; +                                    node.position = mousePos + dragOffset[i]; +                                    if (gridSnap) { +                                        node.position.x = (Mathf.Round((node.position.x + 8) / 16) * 16) - 8; +                                        node.position.y = (Mathf.Round((node.position.y + 8) / 16) * 16) - 8; +                                    } + +                                    // Offset portConnectionPoints instantly if a node is dragged so they aren't delayed by a frame. +                                    Vector2 offset = node.position - initial; +                                    if (offset.sqrMagnitude > 0) { +                                        foreach (XNode.NodePort output in node.Outputs) { +                                            Rect rect; +                                            if (portConnectionPoints.TryGetValue(output, out rect)) { +                                                rect.position += offset; +                                                portConnectionPoints[output] = rect; +                                            } +                                        } + +                                        foreach (XNode.NodePort input in node.Inputs) { +                                            Rect rect; +                                            if (portConnectionPoints.TryGetValue(input, out rect)) { +                                                rect.position += offset; +                                                portConnectionPoints[input] = rect; +                                            } +                                        } +                                    } +                                } +                            } +                            // Move selected reroutes with offset +                            for (int i = 0; i < selectedReroutes.Count; i++) { +                                Vector2 pos = mousePos + dragOffset[Selection.objects.Length + i]; +                                if (gridSnap) { +                                    pos.x = (Mathf.Round(pos.x / 16) * 16); +                                    pos.y = (Mathf.Round(pos.y / 16) * 16); +                                } +                                selectedReroutes[i].SetPoint(pos); +                            } +                            Repaint(); +                        } else if (currentActivity == NodeActivity.HoldGrid) { +                            currentActivity = NodeActivity.DragGrid; +                            preBoxSelection = Selection.objects; +                            preBoxSelectionReroute = selectedReroutes.ToArray(); +                            dragBoxStart = WindowToGridPosition(e.mousePosition); +                            Repaint(); +                        } else if (currentActivity == NodeActivity.DragGrid) { +                            Vector2 boxStartPos = GridToWindowPosition(dragBoxStart); +                            Vector2 boxSize = e.mousePosition - boxStartPos; +                            if (boxSize.x < 0) { boxStartPos.x += boxSize.x; boxSize.x = Mathf.Abs(boxSize.x); } +                            if (boxSize.y < 0) { boxStartPos.y += boxSize.y; boxSize.y = Mathf.Abs(boxSize.y); } +                            selectionBox = new Rect(boxStartPos, boxSize); +                            Repaint(); +                        } +                    } else if (e.button == 1 || e.button == 2) { +                        //check drag threshold for larger screens +                        if (e.delta.magnitude > dragThreshold) { +                            panOffset += e.delta * zoom; +                            isPanning = true; +                        } +                    } +                    break; +                case EventType.MouseDown: +                    Repaint(); +                    if (e.button == 0) { +                        draggedOutputReroutes.Clear(); + +                        if (IsHoveringPort) { +                            if (hoveredPort.IsOutput) { +                                draggedOutput = hoveredPort; +                                autoConnectOutput = hoveredPort; +                            } else { +                                hoveredPort.VerifyConnections(); +                                autoConnectOutput = null; +                                if (hoveredPort.IsConnected) { +                                    XNode.Node node = hoveredPort.node; +                                    XNode.NodePort output = hoveredPort.Connection; +                                    int outputConnectionIndex = output.GetConnectionIndex(hoveredPort); +                                    draggedOutputReroutes = output.GetReroutePoints(outputConnectionIndex); +                                    hoveredPort.Disconnect(output); +                                    draggedOutput = output; +                                    draggedOutputTarget = hoveredPort; +                                    if (NodeEditor.onUpdateNode != null) NodeEditor.onUpdateNode(node); +                                } +                            } +                        } else if (IsHoveringNode && IsHoveringTitle(hoveredNode)) { +                            // If mousedown on node header, select or deselect +                            if (!Selection.Contains(hoveredNode)) { +                                SelectNode(hoveredNode, e.control || e.shift); +                                if (!e.control && !e.shift) selectedReroutes.Clear(); +                            } else if (e.control || e.shift) DeselectNode(hoveredNode); + +                            // Cache double click state, but only act on it in MouseUp - Except ClickCount only works in mouseDown. +                            isDoubleClick = (e.clickCount == 2); + +                            e.Use(); +                            currentActivity = NodeActivity.HoldNode; +                        } else if (IsHoveringReroute) { +                            // If reroute isn't selected +                            if (!selectedReroutes.Contains(hoveredReroute)) { +                                // Add it +                                if (e.control || e.shift) selectedReroutes.Add(hoveredReroute); +                                // Select it +                                else { +                                    selectedReroutes = new List<RerouteReference>() { hoveredReroute }; +                                    Selection.activeObject = null; +                                } + +                            } +                            // Deselect +                            else if (e.control || e.shift) selectedReroutes.Remove(hoveredReroute); +                            e.Use(); +                            currentActivity = NodeActivity.HoldNode; +                        } +                        // If mousedown on grid background, deselect all +                        else if (!IsHoveringNode) { +                            currentActivity = NodeActivity.HoldGrid; +                            if (!e.control && !e.shift) { +                                selectedReroutes.Clear(); +                                Selection.activeObject = null; +                            } +                        } +                    } +                    break; +                case EventType.MouseUp: +                    if (e.button == 0) { +                        //Port drag release +                        if (IsDraggingPort) { +                            // If connection is valid, save it +                            if (draggedOutputTarget != null && draggedOutput.CanConnectTo(draggedOutputTarget)) { +                                XNode.Node node = draggedOutputTarget.node; +                                if (graph.nodes.Count != 0) draggedOutput.Connect(draggedOutputTarget); + +                                // ConnectionIndex can be -1 if the connection is removed instantly after creation +                                int connectionIndex = draggedOutput.GetConnectionIndex(draggedOutputTarget); +                                if (connectionIndex != -1) { +                                    draggedOutput.GetReroutePoints(connectionIndex).AddRange(draggedOutputReroutes); +                                    if (NodeEditor.onUpdateNode != null) NodeEditor.onUpdateNode(node); +                                    EditorUtility.SetDirty(graph); +                                } +                            } +                            // 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)); +                            } +                            //Release dragged connection +                            draggedOutput = null; +                            draggedOutputTarget = null; +                            EditorUtility.SetDirty(graph); +                            if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); +                        } else if (currentActivity == NodeActivity.DragNode) { +                            IEnumerable<XNode.Node> nodes = Selection.objects.Where(x => x is XNode.Node).Select(x => x as XNode.Node); +                            foreach (XNode.Node node in nodes) EditorUtility.SetDirty(node); +                            if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); +                        } else if (!IsHoveringNode) { +                            // If click outside node, release field focus +                            if (!isPanning) { +                                EditorGUI.FocusTextInControl(null); +                                EditorGUIUtility.editingTextField = false; +                            } +                            if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); +                        } + +                        // If click node header, select it. +                        if (currentActivity == NodeActivity.HoldNode && !(e.control || e.shift)) { +                            selectedReroutes.Clear(); +                            SelectNode(hoveredNode, false); + +                            // Double click to center node +                            if (isDoubleClick) { +                                Vector2 nodeDimension = nodeSizes.ContainsKey(hoveredNode) ? nodeSizes[hoveredNode] / 2 : Vector2.zero; +                                panOffset = -hoveredNode.position - nodeDimension; +                            } +                        } + +                        // If click reroute, select it. +                        if (IsHoveringReroute && !(e.control || e.shift)) { +                            selectedReroutes = new List<RerouteReference>() { hoveredReroute }; +                            Selection.activeObject = null; +                        } + +                        Repaint(); +                        currentActivity = NodeActivity.Idle; +                    } else if (e.button == 1 || e.button == 2) { +                        if (!isPanning) { +                            if (IsDraggingPort) { +                                draggedOutputReroutes.Add(WindowToGridPosition(e.mousePosition)); +                            } else if (currentActivity == NodeActivity.DragNode && Selection.activeObject == null && selectedReroutes.Count == 1) { +                                selectedReroutes[0].InsertPoint(selectedReroutes[0].GetPoint()); +                                selectedReroutes[0] = new RerouteReference(selectedReroutes[0].port, selectedReroutes[0].connectionIndex, selectedReroutes[0].pointIndex + 1); +                            } else if (IsHoveringReroute) { +                                ShowRerouteContextMenu(hoveredReroute); +                            } else if (IsHoveringPort) { +                                ShowPortContextMenu(hoveredPort); +                            } else if (IsHoveringNode && IsHoveringTitle(hoveredNode)) { +                                if (!Selection.Contains(hoveredNode)) SelectNode(hoveredNode, false); +                                autoConnectOutput = null; +                                GenericMenu menu = new GenericMenu(); +                                NodeEditor.GetEditor(hoveredNode, this).AddContextMenuItems(menu); +                                menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero)); +                                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)); +                            } +                        } +                        isPanning = false; +                    } +                    // Reset DoubleClick +                    isDoubleClick = false; +                    break; +                case EventType.KeyDown: +                    if (EditorGUIUtility.editingTextField) break; +                    else if (e.keyCode == KeyCode.F) Home(); +                    if (NodeEditorUtilities.IsMac()) { +                        if (e.keyCode == KeyCode.Return) RenameSelectedNode(); +                    } else { +                        if (e.keyCode == KeyCode.F2) RenameSelectedNode(); +                    } +                    if (e.keyCode == KeyCode.A) { +                        if (Selection.objects.Any(x => graph.nodes.Contains(x as XNode.Node))) { +                            foreach (XNode.Node node in graph.nodes) { +                                DeselectNode(node); +                            } +                        } else { +                            foreach (XNode.Node node in graph.nodes) { +                                SelectNode(node, true); +                            } +                        } +                        Repaint(); +                    } +                    break; +                case EventType.ValidateCommand: +                case EventType.ExecuteCommand: +                    if (e.commandName == "SoftDelete") { +                        if (e.type == EventType.ExecuteCommand) RemoveSelectedNodes(); +                        e.Use(); +                    } else if (NodeEditorUtilities.IsMac() && e.commandName == "Delete") { +                        if (e.type == EventType.ExecuteCommand) RemoveSelectedNodes(); +                        e.Use(); +                    } else if (e.commandName == "Duplicate") { +                        if (e.type == EventType.ExecuteCommand) DuplicateSelectedNodes(); +                        e.Use(); +                    } else if (e.commandName == "Copy") { +                        if (e.type == EventType.ExecuteCommand) CopySelectedNodes(); +                        e.Use(); +                    } else if (e.commandName == "Paste") { +                        if (e.type == EventType.ExecuteCommand) PasteNodes(WindowToGridPosition(lastMousePosition)); +                        e.Use(); +                    } +                    Repaint(); +                    break; +                case EventType.Ignore: +                    // If release mouse outside window +                    if (e.rawType == EventType.MouseUp && currentActivity == NodeActivity.DragGrid) { +                        Repaint(); +                        currentActivity = NodeActivity.Idle; +                    } +                    break; +            } +        } + +        private void RecalculateDragOffsets(Event current) { +            dragOffset = new Vector2[Selection.objects.Length + selectedReroutes.Count]; +            // Selected nodes +            for (int i = 0; i < Selection.objects.Length; i++) { +                if (Selection.objects[i] is XNode.Node) { +                    XNode.Node node = Selection.objects[i] as XNode.Node; +                    dragOffset[i] = node.position - WindowToGridPosition(current.mousePosition); +                } +            } + +            // Selected reroutes +            for (int i = 0; i < selectedReroutes.Count; i++) { +                dragOffset[Selection.objects.Length + i] = selectedReroutes[i].GetPoint() - WindowToGridPosition(current.mousePosition); +            } +        } + +        /// <summary> Puts all selected nodes in focus. If no nodes are present, resets view and zoom to to origin </summary> +        public void Home() { +            var nodes = Selection.objects.Where(o => o is XNode.Node).Cast<XNode.Node>().ToList(); +            if (nodes.Count > 0) { +                Vector2 minPos = nodes.Select(x => x.position).Aggregate((x, y) => new Vector2(Mathf.Min(x.x, y.x), Mathf.Min(x.y, y.y))); +                Vector2 maxPos = nodes.Select(x => x.position + (nodeSizes.ContainsKey(x) ? nodeSizes[x] : Vector2.zero)).Aggregate((x, y) => new Vector2(Mathf.Max(x.x, y.x), Mathf.Max(x.y, y.y))); +                panOffset = -(minPos + (maxPos - minPos) / 2f); +            } else { +                zoom = 2; +                panOffset = Vector2.zero; +            } +        } + +        /// <summary> Remove nodes in the graph in Selection.objects</summary> +        public void RemoveSelectedNodes() { +            // We need to delete reroutes starting at the highest point index to avoid shifting indices +            selectedReroutes = selectedReroutes.OrderByDescending(x => x.pointIndex).ToList(); +            for (int i = 0; i < selectedReroutes.Count; i++) { +                selectedReroutes[i].RemovePoint(); +            } +            selectedReroutes.Clear(); +            foreach (UnityEngine.Object item in Selection.objects) { +                if (item is XNode.Node) { +                    XNode.Node node = item as XNode.Node; +                    graphEditor.RemoveNode(node); +                } +            } +        } + +        /// <summary> Initiate a rename on the currently selected node </summary> +        public void RenameSelectedNode() { +            if (Selection.objects.Length == 1 && Selection.activeObject is XNode.Node) { +                XNode.Node node = Selection.activeObject as XNode.Node; +                Vector2 size; +                if (nodeSizes.TryGetValue(node, out size)) { +                    RenamePopup.Show(Selection.activeObject, size.x); +                } else { +                    RenamePopup.Show(Selection.activeObject); +                } +            } +        } + +        /// <summary> Draw this node on top of other nodes by placing it last in the graph.nodes list </summary> +        public void MoveNodeToTop(XNode.Node node) { +            int index; +            while ((index = graph.nodes.IndexOf(node)) != graph.nodes.Count - 1) { +                graph.nodes[index] = graph.nodes[index + 1]; +                graph.nodes[index + 1] = node; +            } +        } + +        /// <summary> Duplicate selected nodes and select the duplicates </summary> +        public void DuplicateSelectedNodes() { +            // Get selected nodes which are part of this graph +            XNode.Node[] selectedNodes = Selection.objects.Select(x => x as XNode.Node).Where(x => x != null && x.graph == graph).ToArray(); +            if (selectedNodes == null || selectedNodes.Length == 0) return; +            // Get top left node position +            Vector2 topLeftNode = selectedNodes.Select(x => x.position).Aggregate((x, y) => new Vector2(Mathf.Min(x.x, y.x), Mathf.Min(x.y, y.y))); +            InsertDuplicateNodes(selectedNodes, topLeftNode + new Vector2(30, 30)); +        } + +        public void CopySelectedNodes() { +            copyBuffer = Selection.objects.Select(x => x as XNode.Node).Where(x => x != null && x.graph == graph).ToArray(); +        } + +        public void PasteNodes(Vector2 pos) { +            InsertDuplicateNodes(copyBuffer, pos); +        } + +        private void InsertDuplicateNodes(XNode.Node[] nodes, Vector2 topLeft) { +            if (nodes == null || nodes.Length == 0) return; + +            // Get top-left node +            Vector2 topLeftNode = nodes.Select(x => x.position).Aggregate((x, y) => new Vector2(Mathf.Min(x.x, y.x), Mathf.Min(x.y, y.y))); +            Vector2 offset = topLeft - topLeftNode; + +            UnityEngine.Object[] newNodes = new UnityEngine.Object[nodes.Length]; +            Dictionary<XNode.Node, XNode.Node> substitutes = new Dictionary<XNode.Node, XNode.Node>(); +            for (int i = 0; i < nodes.Length; i++) { +                XNode.Node srcNode = nodes[i]; +                if (srcNode == null) continue; + +                // Check if user is allowed to add more of given node type +                XNode.Node.DisallowMultipleNodesAttribute disallowAttrib; +                Type nodeType = srcNode.GetType(); +                if (NodeEditorUtilities.GetAttrib(nodeType, out disallowAttrib)) { +                    int typeCount = graph.nodes.Count(x => x.GetType() == nodeType); +                    if (typeCount >= disallowAttrib.max) continue; +                } + +                XNode.Node newNode = graphEditor.CopyNode(srcNode); +                substitutes.Add(srcNode, newNode); +                newNode.position = srcNode.position + offset; +                newNodes[i] = newNode; +            } + +            // Walk through the selected nodes again, recreate connections, using the new nodes +            for (int i = 0; i < nodes.Length; i++) { +                XNode.Node srcNode = nodes[i]; +                if (srcNode == null) continue; +                foreach (XNode.NodePort port in srcNode.Ports) { +                    for (int c = 0; c < port.ConnectionCount; c++) { +                        XNode.NodePort inputPort = port.direction == XNode.NodePort.IO.Input ? port : port.GetConnection(c); +                        XNode.NodePort outputPort = port.direction == XNode.NodePort.IO.Output ? port : port.GetConnection(c); + +                        XNode.Node newNodeIn, newNodeOut; +                        if (substitutes.TryGetValue(inputPort.node, out newNodeIn) && substitutes.TryGetValue(outputPort.node, out newNodeOut)) { +                            newNodeIn.UpdatePorts(); +                            newNodeOut.UpdatePorts(); +                            inputPort = newNodeIn.GetInputPort(inputPort.fieldName); +                            outputPort = newNodeOut.GetOutputPort(outputPort.fieldName); +                        } +                        if (!inputPort.IsConnectedTo(outputPort)) inputPort.Connect(outputPort); +                    } +                } +            } +            // Select the new nodes +            Selection.objects = newNodes; +        } + +        /// <summary> Draw a connection as we are dragging it </summary> +        public void DrawDraggedConnection() { +            if (IsDraggingPort) { +                Gradient gradient = graphEditor.GetNoodleGradient(draggedOutput, null); +                float thickness = graphEditor.GetNoodleThickness(draggedOutput, null); +                NoodlePath path = graphEditor.GetNoodlePath(draggedOutput, null); +                NoodleStroke stroke = graphEditor.GetNoodleStroke(draggedOutput, null); + +                Rect fromRect; +                if (!_portConnectionPoints.TryGetValue(draggedOutput, out fromRect)) return; +                List<Vector2> gridPoints = new List<Vector2>(); +                gridPoints.Add(fromRect.center); +                for (int i = 0; i < draggedOutputReroutes.Count; i++) { +                    gridPoints.Add(draggedOutputReroutes[i]); +                } +                if (draggedOutputTarget != null) gridPoints.Add(portConnectionPoints[draggedOutputTarget].center); +                else gridPoints.Add(WindowToGridPosition(Event.current.mousePosition)); + +                DrawNoodle(gradient, path, stroke, thickness, gridPoints); + +                Color bgcol = Color.black; +                Color frcol = gradient.colorKeys[0].color; +                bgcol.a = 0.6f; +                frcol.a = 0.6f; + +                // Loop through reroute points again and draw the points +                for (int i = 0; i < draggedOutputReroutes.Count; i++) { +                    // Draw reroute point at position +                    Rect rect = new Rect(draggedOutputReroutes[i], new Vector2(16, 16)); +                    rect.position = new Vector2(rect.position.x - 8, rect.position.y - 8); +                    rect = GridToWindowRect(rect); + +                    NodeEditorGUILayout.DrawPortHandle(rect, bgcol, frcol); +                } +            } +        } + +        bool IsHoveringTitle(XNode.Node node) { +            Vector2 mousePos = Event.current.mousePosition; +            //Get node position +            Vector2 nodePos = GridToWindowPosition(node.position); +            float width; +            Vector2 size; +            if (nodeSizes.TryGetValue(node, out size)) width = size.x; +            else width = 200; +            Rect windowRect = new Rect(nodePos, new Vector2(width / zoom, 30 / zoom)); +            return windowRect.Contains(mousePos); +        } + +        /// <summary> Attempt to connect dragged output to target node </summary> +        public void AutoConnect(XNode.Node node) { +            if (autoConnectOutput == null) return; + +            // Find input port of same type +            XNode.NodePort inputPort = node.Ports.FirstOrDefault(x => x.IsInput && x.ValueType == autoConnectOutput.ValueType); +            // Fallback to input port +            if (inputPort == null) inputPort = node.Ports.FirstOrDefault(x => x.IsInput); +            // Autoconnect if connection is compatible +            if (inputPort != null && inputPort.CanConnectTo(autoConnectOutput)) autoConnectOutput.Connect(inputPort); + +            // Save changes +            EditorUtility.SetDirty(graph); +            if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); +            autoConnectOutput = null; +        } +    } +} diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorAction.cs.meta b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorAction.cs.meta new file mode 100644 index 00000000..a081bf79 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorAction.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: aa7d4286bf0ad2e4086252f2893d2cf5 +timeCreated: 1505426655 +licenseType: Free +MonoImporter: +  serializedVersion: 2 +  defaultReferences: [] +  executionOrder: 0 +  icon: {instanceID: 0} +  userData:  +  assetBundleName:  +  assetBundleVariant:  diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorAssetModProcessor.cs b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorAssetModProcessor.cs new file mode 100644 index 00000000..f4b14a27 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorAssetModProcessor.cs @@ -0,0 +1,66 @@ +using UnityEditor; +using UnityEngine; +using System.IO; + +namespace XNodeEditor { +    /// <summary> Deals with modified assets </summary> +    class NodeEditorAssetModProcessor : UnityEditor.AssetModificationProcessor { + +        /// <summary> Automatically delete Node sub-assets before deleting their script. +        /// This is important to do, because you can't delete null sub assets. +        /// <para/> For another workaround, see: https://gitlab.com/RotaryHeart-UnityShare/subassetmissingscriptdelete </summary>  +        private static AssetDeleteResult OnWillDeleteAsset (string path, RemoveAssetOptions options) { +            // Skip processing anything without the .cs extension +            if (Path.GetExtension(path) != ".cs") return AssetDeleteResult.DidNotDelete; +             +            // Get the object that is requested for deletion +            UnityEngine.Object obj = AssetDatabase.LoadAssetAtPath<UnityEngine.Object> (path); + +            // If we aren't deleting a script, return +            if (!(obj is UnityEditor.MonoScript)) return AssetDeleteResult.DidNotDelete; + +            // Check script type. Return if deleting a non-node script +            UnityEditor.MonoScript script = obj as UnityEditor.MonoScript; +            System.Type scriptType = script.GetClass (); +            if (scriptType == null || (scriptType != typeof (XNode.Node) && !scriptType.IsSubclassOf (typeof (XNode.Node)))) return AssetDeleteResult.DidNotDelete; + +            // Find all ScriptableObjects using this script +            string[] guids = AssetDatabase.FindAssets ("t:" + scriptType); +            for (int i = 0; i < guids.Length; i++) { +                string assetpath = AssetDatabase.GUIDToAssetPath (guids[i]); +                Object[] objs = AssetDatabase.LoadAllAssetRepresentationsAtPath (assetpath); +                for (int k = 0; k < objs.Length; k++) { +                    XNode.Node node = objs[k] as XNode.Node; +                    if (node.GetType () == scriptType) { +                        if (node != null && node.graph != null) { +                            // Delete the node and notify the user +                            Debug.LogWarning (node.name + " of " + node.graph + " depended on deleted script and has been removed automatically.", node.graph); +                            node.graph.RemoveNode (node); +                        } +                    } +                } +            } +            // We didn't actually delete the script. Tell the internal system to carry on with normal deletion procedure +            return AssetDeleteResult.DidNotDelete; +        } + +        /// <summary> Automatically re-add loose node assets to the Graph node list </summary> +        [InitializeOnLoadMethod] +        private static void OnReloadEditor () { +            // Find all NodeGraph assets +            string[] guids = AssetDatabase.FindAssets ("t:" + typeof (XNode.NodeGraph)); +            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 +                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++) { +                    // Ignore null sub assets +                    if (objs[u] == null) continue; +                    if (!graph.nodes.Contains (objs[u] as XNode.Node)) graph.nodes.Add(objs[u] as XNode.Node); +                } +            } +        } +    } +}
\ No newline at end of file diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorAssetModProcessor.cs.meta b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorAssetModProcessor.cs.meta new file mode 100644 index 00000000..057198bc --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorAssetModProcessor.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: e515e86efe8160243a68b7c06d730c9c +timeCreated: 1507982232 +licenseType: Free +MonoImporter: +  serializedVersion: 2 +  defaultReferences: [] +  executionOrder: 0 +  icon: {instanceID: 0} +  userData:  +  assetBundleName:  +  assetBundleVariant:  diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorBase.cs b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorBase.cs new file mode 100644 index 00000000..1fc28c72 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorBase.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using UnityEditor; +using UnityEngine; +#if ODIN_INSPECTOR +using Sirenix.OdinInspector.Editor; +#endif + +namespace XNodeEditor.Internal { +	/// <summary> Handles caching of custom editor classes and their target types. Accessible with GetEditor(Type type) </summary> +	/// <typeparam name="T">Editor Type. Should be the type of the deriving script itself (eg. NodeEditor) </typeparam> +	/// <typeparam name="A">Attribute Type. The attribute used to connect with the runtime type (eg. CustomNodeEditorAttribute) </typeparam> +	/// <typeparam name="K">Runtime Type. The ScriptableObject this can be an editor for (eg. Node) </typeparam> +	public abstract class NodeEditorBase<T, A, K> where A : Attribute, NodeEditorBase<T, A, K>.INodeEditorAttrib where T : NodeEditorBase<T, A, K> where K : ScriptableObject { +		/// <summary> Custom editors defined with [CustomNodeEditor] </summary> +		private static Dictionary<Type, Type> editorTypes; +		private static Dictionary<K, T> editors = new Dictionary<K, T>(); +		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; +			T editor; +			if (!editors.TryGetValue(target, out editor)) { +				Type type = target.GetType(); +				Type editorType = GetEditorType(type); +				editor = Activator.CreateInstance(editorType) as T; +				editor.target = target; +				editor.serializedObject = new SerializedObject(target); +				editor.window = window; +				editor.OnCreate(); +				editors.Add(target, editor); +			} +			if (editor.target == null) editor.target = target; +			if (editor.window != window) editor.window = window; +			if (editor.serializedObject == null) editor.serializedObject = new SerializedObject(target); +			return editor; +		} + +		private static Type GetEditorType(Type type) { +			if (type == null) return null; +			if (editorTypes == null) CacheCustomEditors(); +			Type result; +			if (editorTypes.TryGetValue(type, out result)) return result; +			//If type isn't found, try base type +			return GetEditorType(type.BaseType); +		} + +		private static void CacheCustomEditors() { +			editorTypes = new Dictionary<Type, Type>(); + +			//Get all classes deriving from NodeEditor via reflection +			Type[] nodeEditors = typeof(T).GetDerivedTypes(); +			for (int i = 0; i < nodeEditors.Length; i++) { +				if (nodeEditors[i].IsAbstract) continue; +				var attribs = nodeEditors[i].GetCustomAttributes(typeof(A), false); +				if (attribs == null || attribs.Length == 0) continue; +				A attrib = attribs[0] as A; +				editorTypes.Add(attrib.GetInspectedType(), nodeEditors[i]); +			} +		} + +		/// <summary> Called on creation, after references have been set </summary> +		public virtual void OnCreate() { } + +		public interface INodeEditorAttrib { +			Type GetInspectedType(); +		} +	} +}
\ No newline at end of file diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorBase.cs.meta b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorBase.cs.meta new file mode 100644 index 00000000..4ded02a2 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorBase.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: e85122ded59aceb4eb4b1bd9d9202642 +timeCreated: 1511353946 +licenseType: Free +MonoImporter: +  externalObjects: {} +  serializedVersion: 2 +  defaultReferences: [] +  executionOrder: 0 +  icon: {instanceID: 0} +  userData:  +  assetBundleName:  +  assetBundleVariant:  diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorGUI.cs b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorGUI.cs new file mode 100644 index 00000000..41f1d9ca --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorGUI.cs @@ -0,0 +1,567 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEngine; +using XNodeEditor.Internal; + +namespace XNodeEditor { +    /// <summary> Contains GUI methods </summary> +    public partial class NodeEditorWindow { +        public NodeGraphEditor graphEditor; +        private List<UnityEngine.Object> selectionCache; +        private List<XNode.Node> culledNodes; +        /// <summary> 19 if docked, 22 if not </summary> +        private int topPadding { get { return isDocked() ? 19 : 22; } } +        /// <summary> Executed after all other window GUI. Useful if Zoom is ruining your day. Automatically resets after being run.</summary> +        public event Action onLateGUI; +        private static readonly Vector3[] polyLineTempArray = new Vector3[2]; + +        protected virtual void OnGUI() { +            Event e = Event.current; +            Matrix4x4 m = GUI.matrix; +            if (graph == null) return; +            ValidateGraphEditor(); +            Controls(); + +            DrawGrid(position, zoom, panOffset); +            DrawConnections(); +            DrawDraggedConnection(); +            DrawNodes(); +            DrawSelectionBox(); +            DrawTooltip(); +            graphEditor.OnGUI(); + +            // Run and reset onLateGUI +            if (onLateGUI != null) { +                onLateGUI(); +                onLateGUI = null; +            } + +            GUI.matrix = m; +        } + +        public static void BeginZoomed(Rect rect, float zoom, float topPadding) { +            GUI.EndClip(); + +            GUIUtility.ScaleAroundPivot(Vector2.one / zoom, rect.size * 0.5f); +            Vector4 padding = new Vector4(0, topPadding, 0, 0); +            padding *= zoom; +            GUI.BeginClip(new Rect(-((rect.width * zoom) - rect.width) * 0.5f, -(((rect.height * zoom) - rect.height) * 0.5f) + (topPadding * zoom), +                rect.width * zoom, +                rect.height * zoom)); +        } + +        public static void EndZoomed(Rect rect, float zoom, float topPadding) { +            GUIUtility.ScaleAroundPivot(Vector2.one * zoom, rect.size * 0.5f); +            Vector3 offset = new Vector3( +                (((rect.width * zoom) - rect.width) * 0.5f), +                (((rect.height * zoom) - rect.height) * 0.5f) + (-topPadding * zoom) + topPadding, +                0); +            GUI.matrix = Matrix4x4.TRS(offset, Quaternion.identity, Vector3.one); +        } + +        public void DrawGrid(Rect rect, float zoom, Vector2 panOffset) { + +            rect.position = Vector2.zero; + +            Vector2 center = rect.size / 2f; +            Texture2D gridTex = graphEditor.GetGridTexture(); +            Texture2D crossTex = graphEditor.GetSecondaryGridTexture(); + +            // Offset from origin in tile units +            float xOffset = -(center.x * zoom + panOffset.x) / gridTex.width; +            float yOffset = ((center.y - rect.size.y) * zoom + panOffset.y) / gridTex.height; + +            Vector2 tileOffset = new Vector2(xOffset, yOffset); + +            // Amount of tiles +            float tileAmountX = Mathf.Round(rect.size.x * zoom) / gridTex.width; +            float tileAmountY = Mathf.Round(rect.size.y * zoom) / gridTex.height; + +            Vector2 tileAmount = new Vector2(tileAmountX, tileAmountY); + +            // Draw tiled background +            GUI.DrawTextureWithTexCoords(rect, gridTex, new Rect(tileOffset, tileAmount)); +            GUI.DrawTextureWithTexCoords(rect, crossTex, new Rect(tileOffset + new Vector2(0.5f, 0.5f), tileAmount)); +        } + +        public void DrawSelectionBox() { +            if (currentActivity == NodeActivity.DragGrid) { +                Vector2 curPos = WindowToGridPosition(Event.current.mousePosition); +                Vector2 size = curPos - dragBoxStart; +                Rect r = new Rect(dragBoxStart, size); +                r.position = GridToWindowPosition(r.position); +                r.size /= zoom; +                Handles.DrawSolidRectangleWithOutline(r, new Color(0, 0, 0, 0.1f), new Color(1, 1, 1, 0.6f)); +            } +        } + +        public static bool DropdownButton(string name, float width) { +            return GUILayout.Button(name, EditorStyles.toolbarDropDown, GUILayout.Width(width)); +        } + +        /// <summary> Show right-click context menu for hovered reroute </summary> +        void ShowRerouteContextMenu(RerouteReference reroute) { +            GenericMenu contextMenu = new GenericMenu(); +            contextMenu.AddItem(new GUIContent("Remove"), false, () => reroute.RemovePoint()); +            contextMenu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero)); +            if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); +        } + +        /// <summary> Show right-click context menu for hovered port </summary> +        void ShowPortContextMenu(XNode.NodePort hoveredPort) { +            GenericMenu contextMenu = new GenericMenu(); +            contextMenu.AddItem(new GUIContent("Clear Connections"), false, () => hoveredPort.ClearConnections()); +            contextMenu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero)); +            if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); +        } + +        static Vector2 CalculateBezierPoint(Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float t) { +            float u = 1 - t; +            float tt = t * t, uu = u * u; +            float uuu = uu * u, ttt = tt * t; +            return new Vector2( +                (uuu * p0.x) + (3 * uu * t * p1.x) + (3 * u * tt * p2.x) + (ttt * p3.x), +                (uuu * p0.y) + (3 * uu * t * p1.y) + (3 * u * tt * p2.y) + (ttt * p3.y) +            ); +        } + +        /// <summary> Draws a line segment without allocating temporary arrays </summary> +        static void DrawAAPolyLineNonAlloc(float thickness, Vector2 p0, Vector2 p1) { +            polyLineTempArray[0].x = p0.x; +            polyLineTempArray[0].y = p0.y; +            polyLineTempArray[1].x = p1.x; +            polyLineTempArray[1].y = p1.y; +            Handles.DrawAAPolyLine(thickness, polyLineTempArray); +        } + +        /// <summary> Draw a bezier from output to input in grid coordinates </summary> +        public void DrawNoodle(Gradient gradient, NoodlePath path, NoodleStroke stroke, float thickness, List<Vector2> gridPoints) { +            // convert grid points to window points +            for (int i = 0; i < gridPoints.Count; ++i) +                gridPoints[i] = GridToWindowPosition(gridPoints[i]); + +            Color originalHandlesColor = Handles.color; +            Handles.color = gradient.Evaluate(0f); +            int length = gridPoints.Count; +            switch (path) { +                case NoodlePath.Curvy: +                    Vector2 outputTangent = Vector2.right; +                    for (int i = 0; i < length - 1; i++) { +                        Vector2 inputTangent; +                        // Cached most variables that repeat themselves here to avoid so many indexer calls :p +                        Vector2 point_a = gridPoints[i]; +                        Vector2 point_b = gridPoints[i + 1]; +                        float dist_ab = Vector2.Distance(point_a, point_b); +                        if (i == 0) outputTangent = zoom * dist_ab * 0.01f * Vector2.right; +                        if (i < length - 2) { +                            Vector2 point_c = gridPoints[i + 2]; +                            Vector2 ab = (point_b - point_a).normalized; +                            Vector2 cb = (point_b - point_c).normalized; +                            Vector2 ac = (point_c - point_a).normalized; +                            Vector2 p = (ab + cb) * 0.5f; +                            float tangentLength = (dist_ab + Vector2.Distance(point_b, point_c)) * 0.005f * zoom; +                            float side = ((ac.x * (point_b.y - point_a.y)) - (ac.y * (point_b.x - point_a.x))); + +                            p = tangentLength * Mathf.Sign(side) * new Vector2(-p.y, p.x); +                            inputTangent = p; +                        } else { +                            inputTangent = zoom * dist_ab * 0.01f * Vector2.left; +                        } + +                        // Calculates the tangents for the bezier's curves. +                        float zoomCoef = 50 / zoom; +                        Vector2 tangent_a = point_a + outputTangent * zoomCoef; +                        Vector2 tangent_b = point_b + inputTangent * zoomCoef; +                        // Hover effect. +                        int division = Mathf.RoundToInt(.2f * dist_ab) + 3; +                        // Coloring and bezier drawing. +                        int draw = 0; +                        Vector2 bezierPrevious = point_a; +                        for (int j = 1; j <= division; ++j) { +                            if (stroke == NoodleStroke.Dashed) { +                                draw++; +                                if (draw >= 2) draw = -2; +                                if (draw < 0) continue; +                                if (draw == 0) bezierPrevious = CalculateBezierPoint(point_a, tangent_a, tangent_b, point_b, (j - 1f) / (float) division); +                            } +                            if (i == length - 2) +                                Handles.color = gradient.Evaluate((j + 1f) / division); +                            Vector2 bezierNext = CalculateBezierPoint(point_a, tangent_a, tangent_b, point_b, j / (float) division); +                            DrawAAPolyLineNonAlloc(thickness, bezierPrevious, bezierNext); +                            bezierPrevious = bezierNext; +                        } +                        outputTangent = -inputTangent; +                    } +                    break; +                case NoodlePath.Straight: +                    for (int i = 0; i < length - 1; i++) { +                        Vector2 point_a = gridPoints[i]; +                        Vector2 point_b = gridPoints[i + 1]; +                        // Draws the line with the coloring. +                        Vector2 prev_point = point_a; +                        // Approximately one segment per 5 pixels +                        int segments = (int) Vector2.Distance(point_a, point_b) / 5; +                        segments = Math.Max(segments, 1); + +                        int draw = 0; +                        for (int j = 0; j <= segments; j++) { +                            draw++; +                            float t = j / (float) segments; +                            Vector2 lerp = Vector2.Lerp(point_a, point_b, t); +                            if (draw > 0) { +                                if (i == length - 2) Handles.color = gradient.Evaluate(t); +                                DrawAAPolyLineNonAlloc(thickness, prev_point, lerp); +                            } +                            prev_point = lerp; +                            if (stroke == NoodleStroke.Dashed && draw >= 2) draw = -2; +                        } +                    } +                    break; +                case NoodlePath.Angled: +                    for (int i = 0; i < length - 1; i++) { +                        if (i == length - 1) continue; // Skip last index +                        if (gridPoints[i].x <= gridPoints[i + 1].x - (50 / zoom)) { +                            float midpoint = (gridPoints[i].x + gridPoints[i + 1].x) * 0.5f; +                            Vector2 start_1 = gridPoints[i]; +                            Vector2 end_1 = gridPoints[i + 1]; +                            start_1.x = midpoint; +                            end_1.x = midpoint; +                            if (i == length - 2) { +                                DrawAAPolyLineNonAlloc(thickness, gridPoints[i], start_1); +                                Handles.color = gradient.Evaluate(0.5f); +                                DrawAAPolyLineNonAlloc(thickness, start_1, end_1); +                                Handles.color = gradient.Evaluate(1f); +                                DrawAAPolyLineNonAlloc(thickness, end_1, gridPoints[i + 1]); +                            } else { +                                DrawAAPolyLineNonAlloc(thickness, gridPoints[i], start_1); +                                DrawAAPolyLineNonAlloc(thickness, start_1, end_1); +                                DrawAAPolyLineNonAlloc(thickness, end_1, gridPoints[i + 1]); +                            } +                        } else { +                            float midpoint = (gridPoints[i].y + gridPoints[i + 1].y) * 0.5f; +                            Vector2 start_1 = gridPoints[i]; +                            Vector2 end_1 = gridPoints[i + 1]; +                            start_1.x += 25 / zoom; +                            end_1.x -= 25 / zoom; +                            Vector2 start_2 = start_1; +                            Vector2 end_2 = end_1; +                            start_2.y = midpoint; +                            end_2.y = midpoint; +                            if (i == length - 2) { +                                DrawAAPolyLineNonAlloc(thickness, gridPoints[i], start_1); +                                Handles.color = gradient.Evaluate(0.25f); +                                DrawAAPolyLineNonAlloc(thickness, start_1, start_2); +                                Handles.color = gradient.Evaluate(0.5f); +                                DrawAAPolyLineNonAlloc(thickness, start_2, end_2); +                                Handles.color = gradient.Evaluate(0.75f); +                                DrawAAPolyLineNonAlloc(thickness, end_2, end_1); +                                Handles.color = gradient.Evaluate(1f); +                                DrawAAPolyLineNonAlloc(thickness, end_1, gridPoints[i + 1]); +                            } else { +                                DrawAAPolyLineNonAlloc(thickness, gridPoints[i], start_1); +                                DrawAAPolyLineNonAlloc(thickness, start_1, start_2); +                                DrawAAPolyLineNonAlloc(thickness, start_2, end_2); +                                DrawAAPolyLineNonAlloc(thickness, end_2, end_1); +                                DrawAAPolyLineNonAlloc(thickness, end_1, gridPoints[i + 1]); +                            } +                        } +                    } +                    break; +                case NoodlePath.ShaderLab: +                    Vector2 start = gridPoints[0]; +                    Vector2 end = gridPoints[length - 1]; +                    //Modify first and last point in array so we can loop trough them nicely. +                    gridPoints[0] = gridPoints[0] + Vector2.right * (20 / zoom); +                    gridPoints[length - 1] = gridPoints[length - 1] + Vector2.left * (20 / zoom); +                    //Draw first vertical lines going out from nodes +                    Handles.color = gradient.Evaluate(0f); +                    DrawAAPolyLineNonAlloc(thickness, start, gridPoints[0]); +                    Handles.color = gradient.Evaluate(1f); +                    DrawAAPolyLineNonAlloc(thickness, end, gridPoints[length - 1]); +                    for (int i = 0; i < length - 1; i++) { +                        Vector2 point_a = gridPoints[i]; +                        Vector2 point_b = gridPoints[i + 1]; +                        // Draws the line with the coloring. +                        Vector2 prev_point = point_a; +                        // Approximately one segment per 5 pixels +                        int segments = (int) Vector2.Distance(point_a, point_b) / 5; +                        segments = Math.Max(segments, 1); + +                        int draw = 0; +                        for (int j = 0; j <= segments; j++) { +                            draw++; +                            float t = j / (float) segments; +                            Vector2 lerp = Vector2.Lerp(point_a, point_b, t); +                            if (draw > 0) { +                                if (i == length - 2) Handles.color = gradient.Evaluate(t); +                                DrawAAPolyLineNonAlloc(thickness, prev_point, lerp); +                            } +                            prev_point = lerp; +                            if (stroke == NoodleStroke.Dashed && draw >= 2) draw = -2; +                        } +                    } +                    gridPoints[0] = start; +                    gridPoints[length - 1] = end; +                    break; +            } +            Handles.color = originalHandlesColor; +        } + +        /// <summary> Draws all connections </summary> +        public void DrawConnections() { +            Vector2 mousePos = Event.current.mousePosition; +            List<RerouteReference> selection = preBoxSelectionReroute != null ? new List<RerouteReference>(preBoxSelectionReroute) : new List<RerouteReference>(); +            hoveredReroute = new RerouteReference(); + +            List<Vector2> gridPoints = new List<Vector2>(2); + +            Color col = GUI.color; +            foreach (XNode.Node node in graph.nodes) { +                //If a null node is found, return. This can happen if the nodes associated script is deleted. It is currently not possible in Unity to delete a null asset. +                if (node == null) continue; + +                // Draw full connections and output > reroute +                foreach (XNode.NodePort output in node.Outputs) { +                    //Needs cleanup. Null checks are ugly +                    Rect fromRect; +                    if (!_portConnectionPoints.TryGetValue(output, out fromRect)) continue; + +                    Color portColor = graphEditor.GetPortColor(output); +                    for (int k = 0; k < output.ConnectionCount; k++) { +                        XNode.NodePort input = output.GetConnection(k); + +                        Gradient noodleGradient = graphEditor.GetNoodleGradient(output, input); +                        float noodleThickness = graphEditor.GetNoodleThickness(output, input); +                        NoodlePath noodlePath = graphEditor.GetNoodlePath(output, input); +                        NoodleStroke noodleStroke = graphEditor.GetNoodleStroke(output, input); + +                        // Error handling +                        if (input == null) continue; //If a script has been updated and the port doesn't exist, it is removed and null is returned. If this happens, return. +                        if (!input.IsConnectedTo(output)) input.Connect(output); +                        Rect toRect; +                        if (!_portConnectionPoints.TryGetValue(input, out toRect)) continue; + +                        List<Vector2> reroutePoints = output.GetReroutePoints(k); + +                        gridPoints.Clear(); +                        gridPoints.Add(fromRect.center); +                        gridPoints.AddRange(reroutePoints); +                        gridPoints.Add(toRect.center); +                        DrawNoodle(noodleGradient, noodlePath, noodleStroke, noodleThickness, gridPoints); + +                        // Loop through reroute points again and draw the points +                        for (int i = 0; i < reroutePoints.Count; i++) { +                            RerouteReference rerouteRef = new RerouteReference(output, k, i); +                            // Draw reroute point at position +                            Rect rect = new Rect(reroutePoints[i], new Vector2(12, 12)); +                            rect.position = new Vector2(rect.position.x - 6, rect.position.y - 6); +                            rect = GridToWindowRect(rect); + +                            // Draw selected reroute points with an outline +                            if (selectedReroutes.Contains(rerouteRef)) { +                                GUI.color = NodeEditorPreferences.GetSettings().highlightColor; +                                GUI.DrawTexture(rect, NodeEditorResources.dotOuter); +                            } + +                            GUI.color = portColor; +                            GUI.DrawTexture(rect, NodeEditorResources.dot); +                            if (rect.Overlaps(selectionBox)) selection.Add(rerouteRef); +                            if (rect.Contains(mousePos)) hoveredReroute = rerouteRef; + +                        } +                    } +                } +            } +            GUI.color = col; +            if (Event.current.type != EventType.Layout && currentActivity == NodeActivity.DragGrid) selectedReroutes = selection; +        } + +        private void DrawNodes() { +            Event e = Event.current; +            if (e.type == EventType.Layout) { +                selectionCache = new List<UnityEngine.Object>(Selection.objects); +            } + +            System.Reflection.MethodInfo onValidate = null; +            if (Selection.activeObject != null && Selection.activeObject is XNode.Node) { +                onValidate = Selection.activeObject.GetType().GetMethod("OnValidate"); +                if (onValidate != null) EditorGUI.BeginChangeCheck(); +            } + +            BeginZoomed(position, zoom, topPadding); + +            Vector2 mousePos = Event.current.mousePosition; + +            if (e.type != EventType.Layout) { +                hoveredNode = null; +                hoveredPort = null; +            } + +            List<UnityEngine.Object> preSelection = preBoxSelection != null ? new List<UnityEngine.Object>(preBoxSelection) : new List<UnityEngine.Object>(); + +            // Selection box stuff +            Vector2 boxStartPos = GridToWindowPositionNoClipped(dragBoxStart); +            Vector2 boxSize = mousePos - boxStartPos; +            if (boxSize.x < 0) { boxStartPos.x += boxSize.x; boxSize.x = Mathf.Abs(boxSize.x); } +            if (boxSize.y < 0) { boxStartPos.y += boxSize.y; boxSize.y = Mathf.Abs(boxSize.y); } +            Rect selectionBox = new Rect(boxStartPos, boxSize); + +            //Save guiColor so we can revert it +            Color guiColor = GUI.color; + +            List<XNode.NodePort> removeEntries = new List<XNode.NodePort>(); + +            if (e.type == EventType.Layout) culledNodes = new List<XNode.Node>(); + +            for (int n = 0; n < graph.nodes.Count; n++) { +                // Skip null nodes. The user could be in the process of renaming scripts, so removing them at this point is not advisable. +                if (graph.nodes[n] == null) continue; +                if (n >= graph.nodes.Count) return; +                XNode.Node node = graph.nodes[n]; + +                // Culling +                if (e.type == EventType.Layout) { +                    // Cull unselected nodes outside view +                    if (!Selection.Contains(node) && ShouldBeCulled(node)) { +                        culledNodes.Add(node); +                        continue; +                    } +                } else if (culledNodes.Contains(node)) continue; + +                if (e.type == EventType.Repaint) { +                    removeEntries.Clear(); +                    foreach (var kvp in _portConnectionPoints) +                        if (kvp.Key.node == node) removeEntries.Add(kvp.Key); +                    foreach (var k in removeEntries) _portConnectionPoints.Remove(k); +                } + +                NodeEditor nodeEditor = NodeEditor.GetEditor(node, this); + +                NodeEditor.portPositions.Clear(); + +                // Set default label width. This is potentially overridden in OnBodyGUI +                EditorGUIUtility.labelWidth = 84; + +                //Get node position +                Vector2 nodePos = GridToWindowPositionNoClipped(node.position); + +                GUILayout.BeginArea(new Rect(nodePos, new Vector2(nodeEditor.GetWidth(), 4000))); + +                bool selected = selectionCache.Contains(graph.nodes[n]); + +                if (selected) { +                    GUIStyle style = new GUIStyle(nodeEditor.GetBodyStyle()); +                    GUIStyle highlightStyle = new GUIStyle(nodeEditor.GetBodyHighlightStyle()); +                    highlightStyle.padding = style.padding; +                    style.padding = new RectOffset(); +                    GUI.color = nodeEditor.GetTint(); +                    GUILayout.BeginVertical(style); +                    GUI.color = NodeEditorPreferences.GetSettings().highlightColor; +                    GUILayout.BeginVertical(new GUIStyle(highlightStyle)); +                } else { +                    GUIStyle style = new GUIStyle(nodeEditor.GetBodyStyle()); +                    GUI.color = nodeEditor.GetTint(); +                    GUILayout.BeginVertical(style); +                } + +                GUI.color = guiColor; +                EditorGUI.BeginChangeCheck(); + +                //Draw node contents +                nodeEditor.OnHeaderGUI(); +                nodeEditor.OnBodyGUI(); + +                //If user changed a value, notify other scripts through onUpdateNode +                if (EditorGUI.EndChangeCheck()) { +                    if (NodeEditor.onUpdateNode != null) NodeEditor.onUpdateNode(node); +                    EditorUtility.SetDirty(node); +                    nodeEditor.serializedObject.ApplyModifiedProperties(); +                } + +                GUILayout.EndVertical(); + +                //Cache data about the node for next frame +                if (e.type == EventType.Repaint) { +                    Vector2 size = GUILayoutUtility.GetLastRect().size; +                    if (nodeSizes.ContainsKey(node)) nodeSizes[node] = size; +                    else nodeSizes.Add(node, size); + +                    foreach (var kvp in NodeEditor.portPositions) { +                        Vector2 portHandlePos = kvp.Value; +                        portHandlePos += node.position; +                        Rect rect = new Rect(portHandlePos.x - 8, portHandlePos.y - 8, 16, 16); +                        portConnectionPoints[kvp.Key] = rect; +                    } +                } + +                if (selected) GUILayout.EndVertical(); + +                if (e.type != EventType.Layout) { +                    //Check if we are hovering this node +                    Vector2 nodeSize = GUILayoutUtility.GetLastRect().size; +                    Rect windowRect = new Rect(nodePos, nodeSize); +                    if (windowRect.Contains(mousePos)) hoveredNode = node; + +                    //If dragging a selection box, add nodes inside to selection +                    if (currentActivity == NodeActivity.DragGrid) { +                        if (windowRect.Overlaps(selectionBox)) preSelection.Add(node); +                    } + +                    //Check if we are hovering any of this nodes ports +                    //Check input ports +                    foreach (XNode.NodePort input in node.Inputs) { +                        //Check if port rect is available +                        if (!portConnectionPoints.ContainsKey(input)) continue; +                        Rect r = GridToWindowRectNoClipped(portConnectionPoints[input]); +                        if (r.Contains(mousePos)) hoveredPort = input; +                    } +                    //Check all output ports +                    foreach (XNode.NodePort output in node.Outputs) { +                        //Check if port rect is available +                        if (!portConnectionPoints.ContainsKey(output)) continue; +                        Rect r = GridToWindowRectNoClipped(portConnectionPoints[output]); +                        if (r.Contains(mousePos)) hoveredPort = output; +                    } +                } + +                GUILayout.EndArea(); +            } + +            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,  +            //and thus, the code should not be included in build. +            if (onValidate != null && EditorGUI.EndChangeCheck()) onValidate.Invoke(Selection.activeObject, null); +        } + +        private bool ShouldBeCulled(XNode.Node node) { + +            Vector2 nodePos = GridToWindowPositionNoClipped(node.position); +            if (nodePos.x / _zoom > position.width) return true; // Right +            else if (nodePos.y / _zoom > position.height) return true; // Bottom +            else if (nodeSizes.ContainsKey(node)) { +                Vector2 size = nodeSizes[node]; +                if (nodePos.x + size.x < 0) return true; // Left +                else if (nodePos.y + size.y < 0) return true; // Top +            } +            return false; +        } + +        private void DrawTooltip() { +            if (hoveredPort != null && NodeEditorPreferences.GetSettings().portTooltips && graphEditor != null) { +                string tooltip = graphEditor.GetPortTooltip(hoveredPort); +                if (string.IsNullOrEmpty(tooltip)) return; +                GUIContent content = new GUIContent(tooltip); +                Vector2 size = NodeEditorResources.styles.tooltip.CalcSize(content); +                size.x += 8; +                Rect rect = new Rect(Event.current.mousePosition - (size), size); +                EditorGUI.LabelField(rect, content, NodeEditorResources.styles.tooltip); +                Repaint(); +            } +        } +    } +} diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorGUI.cs.meta b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorGUI.cs.meta new file mode 100644 index 00000000..543878b8 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorGUI.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 756276bfe9a0c2f4da3930ba1964f58d +MonoImporter: +  externalObjects: {} +  serializedVersion: 2 +  defaultReferences: [] +  executionOrder: 0 +  icon: {instanceID: 0} +  userData:  +  assetBundleName:  +  assetBundleVariant:  diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorGUILayout.cs b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorGUILayout.cs new file mode 100644 index 00000000..3574acef --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorGUILayout.cs @@ -0,0 +1,517 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEditor; +using UnityEditorInternal; +using UnityEngine; + +namespace XNodeEditor { +    /// <summary> xNode-specific version of <see cref="EditorGUILayout"/> </summary> +    public static class NodeEditorGUILayout { + +        private static readonly Dictionary<UnityEngine.Object, Dictionary<string, ReorderableList>> reorderableListCache = new Dictionary<UnityEngine.Object, Dictionary<string, ReorderableList>>(); +        private static int reorderableListIndex = -1; + +        /// <summary> Make a field for a serialized property. Automatically displays relevant node port. </summary> +        public static void PropertyField(SerializedProperty property, bool includeChildren = true, params GUILayoutOption[] options) { +            PropertyField(property, (GUIContent) null, includeChildren, options); +        } + +        /// <summary> Make a field for a serialized property. Automatically displays relevant node port. </summary> +        public static void PropertyField(SerializedProperty property, GUIContent label, bool includeChildren = true, params GUILayoutOption[] options) { +            if (property == null) throw new NullReferenceException(); +            XNode.Node node = property.serializedObject.targetObject as XNode.Node; +            XNode.NodePort port = node.GetPort(property.name); +            PropertyField(property, label, port, includeChildren); +        } + +        /// <summary> Make a field for a serialized property. Manual node port override. </summary> +        public static void PropertyField(SerializedProperty property, XNode.NodePort port, bool includeChildren = true, params GUILayoutOption[] options) { +            PropertyField(property, null, port, includeChildren, options); +        } + +        /// <summary> Make a field for a serialized property. Manual node port override. </summary> +        public static void PropertyField(SerializedProperty property, GUIContent label, XNode.NodePort port, bool includeChildren = true, params GUILayoutOption[] options) { +            if (property == null) throw new NullReferenceException(); + +            // If property is not a port, display a regular property field +            if (port == null) EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30)); +            else { +                Rect rect = new Rect(); + +                List<PropertyAttribute> propertyAttributes = NodeEditorUtilities.GetCachedPropertyAttribs(port.node.GetType(), property.name); + +                // If property is an input, display a regular property field and put a port handle on the left side +                if (port.direction == XNode.NodePort.IO.Input) { +                    // Get data from [Input] attribute +                    XNode.Node.ShowBackingValue showBacking = XNode.Node.ShowBackingValue.Unconnected; +                    XNode.Node.InputAttribute inputAttribute; +                    bool dynamicPortList = false; +                    if (NodeEditorUtilities.GetCachedAttrib(port.node.GetType(), property.name, out inputAttribute)) { +                        dynamicPortList = inputAttribute.dynamicPortList; +                        showBacking = inputAttribute.backingValue; +                    } + +                    bool usePropertyAttributes = dynamicPortList || +                        showBacking == XNode.Node.ShowBackingValue.Never || +                        (showBacking == XNode.Node.ShowBackingValue.Unconnected && port.IsConnected); + +                    float spacePadding = 0; +                    foreach (var attr in propertyAttributes) { +                        if (attr is SpaceAttribute) { +                            if (usePropertyAttributes) GUILayout.Space((attr as SpaceAttribute).height); +                            else spacePadding += (attr as SpaceAttribute).height; +                        } else if (attr is HeaderAttribute) { +                            if (usePropertyAttributes) { +                                //GUI Values are from https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/ScriptAttributeGUI/Implementations/DecoratorDrawers.cs +                                Rect position = GUILayoutUtility.GetRect(0, (EditorGUIUtility.singleLineHeight * 1.5f) - EditorGUIUtility.standardVerticalSpacing); //Layout adds standardVerticalSpacing after rect so we subtract it. +                                position.yMin += EditorGUIUtility.singleLineHeight * 0.5f; +                                position = EditorGUI.IndentedRect(position); +                                GUI.Label(position, (attr as HeaderAttribute).header, EditorStyles.boldLabel); +                            } else spacePadding += EditorGUIUtility.singleLineHeight * 1.5f; +                        } +                    } + +                    if (dynamicPortList) { +                        Type type = GetType(property); +                        XNode.Node.ConnectionType connectionType = inputAttribute != null ? inputAttribute.connectionType : XNode.Node.ConnectionType.Multiple; +                        DynamicPortList(property.name, type, property.serializedObject, port.direction, connectionType); +                        return; +                    } +                    switch (showBacking) { +                        case XNode.Node.ShowBackingValue.Unconnected: +                            // Display a label if port is connected +                            if (port.IsConnected) EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName)); +                            // Display an editable property field if port is not connected +                            else EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30)); +                            break; +                        case XNode.Node.ShowBackingValue.Never: +                            // Display a label +                            EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName)); +                            break; +                        case XNode.Node.ShowBackingValue.Always: +                            // Display an editable property field +                            EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30)); +                            break; +                    } + +                    rect = GUILayoutUtility.GetLastRect(); +                    rect.position = rect.position - new Vector2(16, -spacePadding); +                    // If property is an output, display a text label and put a port handle on the right side +                } else if (port.direction == XNode.NodePort.IO.Output) { +                    // Get data from [Output] attribute +                    XNode.Node.ShowBackingValue showBacking = XNode.Node.ShowBackingValue.Unconnected; +                    XNode.Node.OutputAttribute outputAttribute; +                    bool dynamicPortList = false; +                    if (NodeEditorUtilities.GetCachedAttrib(port.node.GetType(), property.name, out outputAttribute)) { +                        dynamicPortList = outputAttribute.dynamicPortList; +                        showBacking = outputAttribute.backingValue; +                    } + +                    bool usePropertyAttributes = dynamicPortList || +                        showBacking == XNode.Node.ShowBackingValue.Never || +                        (showBacking == XNode.Node.ShowBackingValue.Unconnected && port.IsConnected); + +                    float spacePadding = 0; +                    foreach (var attr in propertyAttributes) { +                        if (attr is SpaceAttribute) { +                            if (usePropertyAttributes) GUILayout.Space((attr as SpaceAttribute).height); +                            else spacePadding += (attr as SpaceAttribute).height; +                        } else if (attr is HeaderAttribute) { +                            if (usePropertyAttributes) { +                                //GUI Values are from https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/ScriptAttributeGUI/Implementations/DecoratorDrawers.cs +                                Rect position = GUILayoutUtility.GetRect(0, (EditorGUIUtility.singleLineHeight * 1.5f) - EditorGUIUtility.standardVerticalSpacing); //Layout adds standardVerticalSpacing after rect so we subtract it. +                                position.yMin += EditorGUIUtility.singleLineHeight * 0.5f; +                                position = EditorGUI.IndentedRect(position); +                                GUI.Label(position, (attr as HeaderAttribute).header, EditorStyles.boldLabel); +                            } else spacePadding += EditorGUIUtility.singleLineHeight * 1.5f; +                        } +                    } + +                    if (dynamicPortList) { +                        Type type = GetType(property); +                        XNode.Node.ConnectionType connectionType = outputAttribute != null ? outputAttribute.connectionType : XNode.Node.ConnectionType.Multiple; +                        DynamicPortList(property.name, type, property.serializedObject, port.direction, connectionType); +                        return; +                    } +                    switch (showBacking) { +                        case XNode.Node.ShowBackingValue.Unconnected: +                            // Display a label if port is connected +                            if (port.IsConnected) EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName), NodeEditorResources.OutputPort, GUILayout.MinWidth(30)); +                            // Display an editable property field if port is not connected +                            else EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30)); +                            break; +                        case XNode.Node.ShowBackingValue.Never: +                            // Display a label +                            EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName), NodeEditorResources.OutputPort, GUILayout.MinWidth(30)); +                            break; +                        case XNode.Node.ShowBackingValue.Always: +                            // Display an editable property field +                            EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30)); +                            break; +                    } + +                    rect = GUILayoutUtility.GetLastRect(); +                    rect.position = rect.position + new Vector2(rect.width, spacePadding); +                } + +                rect.size = new Vector2(16, 16); + +                NodeEditor editor = NodeEditor.GetEditor(port.node, NodeEditorWindow.current); +                Color backgroundColor = editor.GetTint(); +                Color col = NodeEditorWindow.current.graphEditor.GetPortColor(port); +                DrawPortHandle(rect, backgroundColor, col); + +                // Register the handle position +                Vector2 portPos = rect.center; +                NodeEditor.portPositions[port] = portPos; +            } +        } + +        private static System.Type GetType(SerializedProperty property) { +            System.Type parentType = property.serializedObject.targetObject.GetType(); +            System.Reflection.FieldInfo fi = parentType.GetFieldInfo(property.name); +            return fi.FieldType; +        } + +        /// <summary> Make a simple port field. </summary> +        public static void PortField(XNode.NodePort port, params GUILayoutOption[] options) { +            PortField(null, port, options); +        } + +        /// <summary> Make a simple port field. </summary> +        public static void PortField(GUIContent label, XNode.NodePort port, params GUILayoutOption[] options) { +            if (port == null) return; +            if (options == null) options = new GUILayoutOption[] { GUILayout.MinWidth(30) }; +            Vector2 position = Vector3.zero; +            GUIContent content = label != null ? label : new GUIContent(ObjectNames.NicifyVariableName(port.fieldName)); + +            // If property is an input, display a regular property field and put a port handle on the left side +            if (port.direction == XNode.NodePort.IO.Input) { +                // Display a label +                EditorGUILayout.LabelField(content, options); + +                Rect rect = GUILayoutUtility.GetLastRect(); +                position = rect.position - new Vector2(16, 0); +            } +            // If property is an output, display a text label and put a port handle on the right side +            else if (port.direction == XNode.NodePort.IO.Output) { +                // Display a label +                EditorGUILayout.LabelField(content, NodeEditorResources.OutputPort, options); + +                Rect rect = GUILayoutUtility.GetLastRect(); +                position = rect.position + new Vector2(rect.width, 0); +            } +            PortField(position, port); +        } + +        /// <summary> Make a simple port field. </summary> +        public static void PortField(Vector2 position, XNode.NodePort port) { +            if (port == null) return; + +            Rect rect = new Rect(position, new Vector2(16, 16)); + +            NodeEditor editor = NodeEditor.GetEditor(port.node, NodeEditorWindow.current); +            Color backgroundColor = editor.GetTint(); +            Color col = NodeEditorWindow.current.graphEditor.GetPortColor(port); +            DrawPortHandle(rect, backgroundColor, col); + +            // Register the handle position +            Vector2 portPos = rect.center; +            NodeEditor.portPositions[port] = portPos; +        } + +        /// <summary> Add a port field to previous layout element. </summary> +        public static void AddPortField(XNode.NodePort port) { +            if (port == null) return; +            Rect rect = new Rect(); + +            // If property is an input, display a regular property field and put a port handle on the left side +            if (port.direction == XNode.NodePort.IO.Input) { +                rect = GUILayoutUtility.GetLastRect(); +                rect.position = rect.position - new Vector2(16, 0); +                // If property is an output, display a text label and put a port handle on the right side +            } else if (port.direction == XNode.NodePort.IO.Output) { +                rect = GUILayoutUtility.GetLastRect(); +                rect.position = rect.position + new Vector2(rect.width, 0); +            } + +            rect.size = new Vector2(16, 16); + +            NodeEditor editor = NodeEditor.GetEditor(port.node, NodeEditorWindow.current); +            Color backgroundColor = editor.GetTint(); +            Color col = NodeEditorWindow.current.graphEditor.GetPortColor(port); +            DrawPortHandle(rect, backgroundColor, col); + +            // Register the handle position +            Vector2 portPos = rect.center; +            NodeEditor.portPositions[port] = portPos; +        } + +        /// <summary> Draws an input and an output port on the same line </summary> +        public static void PortPair(XNode.NodePort input, XNode.NodePort output) { +            GUILayout.BeginHorizontal(); +            NodeEditorGUILayout.PortField(input, GUILayout.MinWidth(0)); +            NodeEditorGUILayout.PortField(output, GUILayout.MinWidth(0)); +            GUILayout.EndHorizontal(); +        } + +        public static void DrawPortHandle(Rect rect, Color backgroundColor, Color typeColor) { +            Color col = GUI.color; +            GUI.color = backgroundColor; +            GUI.DrawTexture(rect, NodeEditorResources.dotOuter); +            GUI.color = typeColor; +            GUI.DrawTexture(rect, NodeEditorResources.dot); +            GUI.color = col; +        } + +#region Obsolete +        [Obsolete("Use IsDynamicPortListPort instead")] +        public static bool IsInstancePortListPort(XNode.NodePort port) { +            return IsDynamicPortListPort(port); +        } + +        [Obsolete("Use DynamicPortList instead")] +        public static void InstancePortList(string fieldName, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType = XNode.Node.ConnectionType.Multiple, XNode.Node.TypeConstraint typeConstraint = XNode.Node.TypeConstraint.None, Action<ReorderableList> onCreation = null) { +            DynamicPortList(fieldName, type, serializedObject, io, connectionType, typeConstraint, onCreation); +        } +#endregion + +        /// <summary> Is this port part of a DynamicPortList? </summary> +        public static bool IsDynamicPortListPort(XNode.NodePort port) { +            string[] parts = port.fieldName.Split(' '); +            if (parts.Length != 2) return false; +            Dictionary<string, ReorderableList> cache; +            if (reorderableListCache.TryGetValue(port.node, out cache)) { +                ReorderableList list; +                if (cache.TryGetValue(parts[0], out list)) return true; +            } +            return false; +        } + +        /// <summary> Draw an editable list of dynamic ports. Port names are named as "[fieldName] [index]" </summary> +        /// <param name="fieldName">Supply a list for editable values</param> +        /// <param name="type">Value type of added dynamic ports</param> +        /// <param name="serializedObject">The serializedObject of the node</param> +        /// <param name="connectionType">Connection type of added dynamic ports</param> +        /// <param name="onCreation">Called on the list on creation. Use this if you want to customize the created ReorderableList</param> +        public static void DynamicPortList(string fieldName, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType = XNode.Node.ConnectionType.Multiple, XNode.Node.TypeConstraint typeConstraint = XNode.Node.TypeConstraint.None, Action<ReorderableList> onCreation = null) { +            XNode.Node node = serializedObject.targetObject as XNode.Node; + +            var indexedPorts = node.DynamicPorts.Select(x => { +                string[] split = x.fieldName.Split(' '); +                if (split != null && split.Length == 2 && split[0] == fieldName) { +                    int i = -1; +                    if (int.TryParse(split[1], out i)) { +                        return new { index = i, port = x }; +                    } +                } +                return new { index = -1, port = (XNode.NodePort) null }; +            }).Where(x => x.port != null); +            List<XNode.NodePort> dynamicPorts = indexedPorts.OrderBy(x => x.index).Select(x => x.port).ToList(); + +            node.UpdatePorts(); +             +            ReorderableList list = null; +            Dictionary<string, ReorderableList> rlc; +            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); +                if (reorderableListCache.TryGetValue(serializedObject.targetObject, out rlc)) rlc.Add(fieldName, list); +                else reorderableListCache.Add(serializedObject.targetObject, new Dictionary<string, ReorderableList>() { { fieldName, list } }); +            } +            list.list = dynamicPorts; +            list.DoLayoutList(); +             +        } + +        private static ReorderableList CreateReorderableList(string fieldName, List<XNode.NodePort> dynamicPorts, SerializedProperty arrayData, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType, XNode.Node.TypeConstraint typeConstraint, Action<ReorderableList> onCreation) { +            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) { +                        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 : ""); +                    if (port != null) { +                        Vector2 pos = rect.position + (port.IsOutput?new Vector2(rect.width + 6, 0) : new Vector2(-36, 0)); +                        NodeEditorGUILayout.PortField(pos, port); +                    } +                }; +            list.elementHeightCallback = +                (int index) => { +                    if (hasArrayData) { +                        if (arrayData.arraySize <= index) return EditorGUIUtility.singleLineHeight; +                        SerializedProperty itemData = arrayData.GetArrayElementAtIndex(index); +                        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; +                        } +                    } +                    // 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); +                    } + +                    // Apply changes +                    serializedObject.ApplyModifiedProperties(); +                    serializedObject.Update(); +                    NodeEditorWindow.current.Repaint(); +                    EditorApplication.delayCall += NodeEditorWindow.current.Repaint; +                }; +            list.onAddCallback = +                (ReorderableList rl) => { +                    // Add dynamic port postfixed with an index number +                    string newName = fieldName + " 0"; +                    int i = 0; +                    while (node.HasPort(newName)) newName = fieldName + " " + (++i); + +                    if (io == XNode.NodePort.IO.Output) node.AddDynamicOutput(type, connectionType, XNode.Node.TypeConstraint.None, newName); +                    else node.AddDynamicInput(type, connectionType, typeConstraint, newName); +                    serializedObject.Update(); +                    EditorUtility.SetDirty(node); +                    if (hasArrayData) { +                        arrayData.InsertArrayElementAtIndex(arrayData.arraySize); +                    } +                    serializedObject.ApplyModifiedProperties(); +                }; +            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(); + +                    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(); +                    } +                }; + +            if (hasArrayData) { +                int dynamicPortCount = dynamicPorts.Count; +                while (dynamicPortCount < arrayData.arraySize) { +                    // Add dynamic port postfixed with an index number +                    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); +                    else node.AddDynamicInput(type, connectionType, typeConstraint, newName); +                    EditorUtility.SetDirty(node); +                    dynamicPortCount++; +                } +                while (arrayData.arraySize < dynamicPortCount) { +                    arrayData.InsertArrayElementAtIndex(arrayData.arraySize); +                } +                serializedObject.ApplyModifiedProperties(); +                serializedObject.Update(); +            } +            if (onCreation != null) onCreation(list); +            return list; +        } +    } +} diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorGUILayout.cs.meta b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorGUILayout.cs.meta new file mode 100644 index 00000000..89596e2b --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorGUILayout.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 1d6c2d118d1c77948a23f2f4a34d1f64 +timeCreated: 1507966608 +licenseType: Free +MonoImporter: +  serializedVersion: 2 +  defaultReferences: [] +  executionOrder: 0 +  icon: {instanceID: 0} +  userData:  +  assetBundleName:  +  assetBundleVariant:  diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorPreferences.cs b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorPreferences.cs new file mode 100644 index 00000000..72eb8aaf --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorPreferences.cs @@ -0,0 +1,253 @@ +using System; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; +using UnityEngine.Serialization; + +namespace XNodeEditor { +    public enum NoodlePath { Curvy, Straight, Angled, ShaderLab } +    public enum NoodleStroke { Full, Dashed } + +    public static class NodeEditorPreferences { + +        /// <summary> The last editor we checked. This should be the one we modify </summary> +        private static XNodeEditor.NodeGraphEditor lastEditor; +        /// <summary> The last key we checked. This should be the one we modify </summary> +        private static string lastKey = "xNode.Settings"; + +        private static Dictionary<Type, Color> typeColors = new Dictionary<Type, Color>(); +        private static Dictionary<string, Settings> settings = new Dictionary<string, Settings>(); + +        [System.Serializable] +        public class Settings : ISerializationCallbackReceiver { +            [SerializeField] private Color32 _gridLineColor = new Color(0.45f, 0.45f, 0.45f); +            public Color32 gridLineColor { get { return _gridLineColor; } set { _gridLineColor = value; _gridTexture = null; _crossTexture = null; } } + +            [SerializeField] private Color32 _gridBgColor = new Color(0.18f, 0.18f, 0.18f); +            public Color32 gridBgColor { get { return _gridBgColor; } set { _gridBgColor = value; _gridTexture = null; } } + +            [Obsolete("Use maxZoom instead")] +            public float zoomOutLimit { get { return maxZoom; } set { maxZoom = value; } } + +            [UnityEngine.Serialization.FormerlySerializedAs("zoomOutLimit")] +            public float maxZoom = 5f; +            public float minZoom = 1f; +            public Color32 highlightColor = new Color32(255, 255, 255, 255); +            public bool gridSnap = true; +            public bool autoSave = true; +            public bool dragToCreate = true; +            public bool zoomToMouse = true; +            public bool portTooltips = true; +            [SerializeField] private string typeColorsData = ""; +            [NonSerialized] public Dictionary<string, Color> typeColors = new Dictionary<string, Color>(); +            [FormerlySerializedAs("noodleType")] public NoodlePath noodlePath = NoodlePath.Curvy; +            public NoodleStroke noodleStroke = NoodleStroke.Full; + +            private Texture2D _gridTexture; +            public Texture2D gridTexture { +                get { +                    if (_gridTexture == null) _gridTexture = NodeEditorResources.GenerateGridTexture(gridLineColor, gridBgColor); +                    return _gridTexture; +                } +            } +            private Texture2D _crossTexture; +            public Texture2D crossTexture { +                get { +                    if (_crossTexture == null) _crossTexture = NodeEditorResources.GenerateCrossTexture(gridLineColor); +                    return _crossTexture; +                } +            } + +            public void OnAfterDeserialize() { +                // Deserialize typeColorsData +                typeColors = new Dictionary<string, Color>(); +                string[] data = typeColorsData.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); +                for (int i = 0; i < data.Length; i += 2) { +                    Color col; +                    if (ColorUtility.TryParseHtmlString("#" + data[i + 1], out col)) { +                        typeColors.Add(data[i], col); +                    } +                } +            } + +            public void OnBeforeSerialize() { +                // Serialize typeColors +                typeColorsData = ""; +                foreach (var item in typeColors) { +                    typeColorsData += item.Key + "," + ColorUtility.ToHtmlStringRGB(item.Value) + ","; +                } +            } +        } + +        /// <summary> Get settings of current active editor </summary> +        public static Settings GetSettings() { +            if (XNodeEditor.NodeEditorWindow.current == null) return new Settings(); + +            if (lastEditor != XNodeEditor.NodeEditorWindow.current.graphEditor) { +                object[] attribs = XNodeEditor.NodeEditorWindow.current.graphEditor.GetType().GetCustomAttributes(typeof(XNodeEditor.NodeGraphEditor.CustomNodeGraphEditorAttribute), true); +                if (attribs.Length == 1) { +                    XNodeEditor.NodeGraphEditor.CustomNodeGraphEditorAttribute attrib = attribs[0] as XNodeEditor.NodeGraphEditor.CustomNodeGraphEditorAttribute; +                    lastEditor = XNodeEditor.NodeEditorWindow.current.graphEditor; +                    lastKey = attrib.editorPrefsKey; +                } else return null; +            } +            if (!settings.ContainsKey(lastKey)) VerifyLoaded(); +            return settings[lastKey]; +        } + +#if UNITY_2019_1_OR_NEWER +        [SettingsProvider] +        public static SettingsProvider CreateXNodeSettingsProvider() { +            SettingsProvider provider = new SettingsProvider("Preferences/Node Editor", SettingsScope.User) { +                guiHandler = (searchContext) => { XNodeEditor.NodeEditorPreferences.PreferencesGUI(); }, +                keywords = new HashSet<string>(new [] { "xNode", "node", "editor", "graph", "connections", "noodles", "ports" }) +            }; +            return provider; +        } +#endif + +#if !UNITY_2019_1_OR_NEWER +        [PreferenceItem("Node Editor")] +#endif +        private static void PreferencesGUI() { +            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.Space(); + +            NodeSettingsGUI(lastKey, settings); +            GridSettingsGUI(lastKey, settings); +            SystemSettingsGUI(lastKey, settings); +            TypeColorsGUI(lastKey, settings); +            if (GUILayout.Button(new GUIContent("Set Default", "Reset all values to default"), GUILayout.Width(120))) { +                ResetPrefs(); +            } +        } + +        private static void GridSettingsGUI(string key, Settings settings) { +            //Label +            EditorGUILayout.LabelField("Grid", EditorStyles.boldLabel); +            settings.gridSnap = EditorGUILayout.Toggle(new GUIContent("Snap", "Hold CTRL in editor to invert"), settings.gridSnap); +            settings.zoomToMouse = EditorGUILayout.Toggle(new GUIContent("Zoom to Mouse", "Zooms towards mouse position"), settings.zoomToMouse); +            EditorGUILayout.LabelField("Zoom"); +            EditorGUI.indentLevel++; +            settings.maxZoom = EditorGUILayout.FloatField(new GUIContent("Max", "Upper limit to zoom"), settings.maxZoom); +            settings.minZoom = EditorGUILayout.FloatField(new GUIContent("Min", "Lower limit to zoom"), settings.minZoom); +            EditorGUI.indentLevel--; +            settings.gridLineColor = EditorGUILayout.ColorField("Color", settings.gridLineColor); +            settings.gridBgColor = EditorGUILayout.ColorField(" ", settings.gridBgColor); +            if (GUI.changed) { +                SavePrefs(key, settings); + +                NodeEditorWindow.RepaintAll(); +            } +            EditorGUILayout.Space(); +        } + +        private static void SystemSettingsGUI(string key, Settings settings) { +            //Label +            EditorGUILayout.LabelField("System", EditorStyles.boldLabel); +            settings.autoSave = EditorGUILayout.Toggle(new GUIContent("Autosave", "Disable for better editor performance"), settings.autoSave); +            if (GUI.changed) SavePrefs(key, settings); +            EditorGUILayout.Space(); +        } + +        private static void NodeSettingsGUI(string key, Settings settings) { +            //Label +            EditorGUILayout.LabelField("Node", EditorStyles.boldLabel); +            settings.highlightColor = EditorGUILayout.ColorField("Selection", settings.highlightColor); +            settings.noodlePath = (NoodlePath) EditorGUILayout.EnumPopup("Noodle path", (Enum) settings.noodlePath); +            settings.noodleStroke = (NoodleStroke) EditorGUILayout.EnumPopup("Noodle stroke", (Enum) settings.noodleStroke); +            settings.portTooltips = EditorGUILayout.Toggle("Port Tooltips", settings.portTooltips); +            settings.dragToCreate = EditorGUILayout.Toggle(new GUIContent("Drag to Create", "Drag a port connection anywhere on the grid to create and connect a node"), settings.dragToCreate); +            if (GUI.changed) { +                SavePrefs(key, settings); +                NodeEditorWindow.RepaintAll(); +            } +            EditorGUILayout.Space(); +        } + +        private static void TypeColorsGUI(string key, Settings settings) { +            //Label +            EditorGUILayout.LabelField("Types", EditorStyles.boldLabel); + +            //Clone keys so we can enumerate the dictionary and make changes. +            var typeColorKeys = new List<Type>(typeColors.Keys); + +            //Display type colors. Save them if they are edited by the user +            foreach (var type in typeColorKeys) { +                string typeColorKey = NodeEditorUtilities.PrettyName(type); +                Color col = typeColors[type]; +                EditorGUI.BeginChangeCheck(); +                EditorGUILayout.BeginHorizontal(); +                col = EditorGUILayout.ColorField(typeColorKey, col); +                EditorGUILayout.EndHorizontal(); +                if (EditorGUI.EndChangeCheck()) { +                    typeColors[type] = col; +                    if (settings.typeColors.ContainsKey(typeColorKey)) settings.typeColors[typeColorKey] = col; +                    else settings.typeColors.Add(typeColorKey, col); +                    SavePrefs(key, settings); +                    NodeEditorWindow.RepaintAll(); +                } +            } +        } + +        /// <summary> Load prefs if they exist. Create if they don't </summary> +        private static Settings LoadPrefs() { +            // Create settings if it doesn't exist +            if (!EditorPrefs.HasKey(lastKey)) { +                if (lastEditor != null) EditorPrefs.SetString(lastKey, JsonUtility.ToJson(lastEditor.GetDefaultPreferences())); +                else EditorPrefs.SetString(lastKey, JsonUtility.ToJson(new Settings())); +            } +            return JsonUtility.FromJson<Settings>(EditorPrefs.GetString(lastKey)); +        } + +        /// <summary> Delete all prefs </summary> +        public static void ResetPrefs() { +            if (EditorPrefs.HasKey(lastKey)) EditorPrefs.DeleteKey(lastKey); +            if (settings.ContainsKey(lastKey)) settings.Remove(lastKey); +            typeColors = new Dictionary<Type, Color>(); +            VerifyLoaded(); +            NodeEditorWindow.RepaintAll(); +        } + +        /// <summary> Save preferences in EditorPrefs </summary> +        private static void SavePrefs(string key, Settings settings) { +            EditorPrefs.SetString(key, JsonUtility.ToJson(settings)); +        } + +        /// <summary> Check if we have loaded settings for given key. If not, load them </summary> +        private static void VerifyLoaded() { +            if (!settings.ContainsKey(lastKey)) settings.Add(lastKey, LoadPrefs()); +        } + +        /// <summary> Return color based on type </summary> +        public static Color GetTypeColor(System.Type type) { +            VerifyLoaded(); +            if (type == null) return Color.gray; +            Color col; +            if (!typeColors.TryGetValue(type, out col)) { +                string typeName = type.PrettyName(); +                if (settings[lastKey].typeColors.ContainsKey(typeName)) typeColors.Add(type, settings[lastKey].typeColors[typeName]); +                else { +#if UNITY_5_4_OR_NEWER +                    UnityEngine.Random.State oldState = UnityEngine.Random.state; +                    UnityEngine.Random.InitState(typeName.GetHashCode()); +#else +                    int oldSeed = UnityEngine.Random.seed; +                    UnityEngine.Random.seed = typeName.GetHashCode(); +#endif +                    col = new Color(UnityEngine.Random.value, UnityEngine.Random.value, UnityEngine.Random.value); +                    typeColors.Add(type, col); +#if UNITY_5_4_OR_NEWER +                    UnityEngine.Random.state = oldState; +#else +                    UnityEngine.Random.seed = oldSeed; +#endif +                } +            } +            return col; +        } +    } +}
\ No newline at end of file diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorPreferences.cs.meta b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorPreferences.cs.meta new file mode 100644 index 00000000..156543b4 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorPreferences.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 6b1f47e387a6f714c9f2ff82a6888c85 +timeCreated: 1507920216 +licenseType: Free +MonoImporter: +  serializedVersion: 2 +  defaultReferences: [] +  executionOrder: 0 +  icon: {instanceID: 0} +  userData:  +  assetBundleName:  +  assetBundleVariant:  diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorReflection.cs b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorReflection.cs new file mode 100644 index 00000000..ff8d417f --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorReflection.cs @@ -0,0 +1,180 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEditor; +using UnityEngine; + +namespace XNodeEditor { +    /// <summary> Contains reflection-related extensions built for xNode </summary> +    public static class NodeEditorReflection { +        [NonSerialized] private static Dictionary<Type, Color> nodeTint; +        [NonSerialized] private static Dictionary<Type, int> nodeWidth; +        /// <summary> All available node types </summary> +        public static Type[] nodeTypes { get { return _nodeTypes != null ? _nodeTypes : _nodeTypes = GetNodeTypes(); } } + +        [NonSerialized] private static Type[] _nodeTypes = null; + +        /// <summary> Return a delegate used to determine whether window is docked or not. It is faster to cache this delegate than run the reflection required each time. </summary> +        public static Func<bool> GetIsDockedDelegate(this EditorWindow window) { +            BindingFlags fullBinding = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static; +            MethodInfo isDockedMethod = typeof(EditorWindow).GetProperty("docked", fullBinding).GetGetMethod(true); +            return (Func<bool>) Delegate.CreateDelegate(typeof(Func<bool>), window, isDockedMethod); +        } + +        public static Type[] GetNodeTypes() { +            //Get all classes deriving from Node via reflection +            return GetDerivedTypes(typeof(XNode.Node)); +        } + +        /// <summary> Custom node tint colors defined with [NodeColor(r, g, b)] </summary> +        public static bool TryGetAttributeTint(this Type nodeType, out Color tint) { +            if (nodeTint == null) { +                CacheAttributes<Color, XNode.Node.NodeTintAttribute>(ref nodeTint, x => x.color); +            } +            return nodeTint.TryGetValue(nodeType, out tint); +        } + +        /// <summary> Get custom node widths defined with [NodeWidth(width)] </summary> +        public static bool TryGetAttributeWidth(this Type nodeType, out int width) { +            if (nodeWidth == null) { +                CacheAttributes<int, XNode.Node.NodeWidthAttribute>(ref nodeWidth, x => x.width); +            } +            return nodeWidth.TryGetValue(nodeType, out width); +        } + +        private static void CacheAttributes<V, A>(ref Dictionary<Type, V> dict, Func<A, V> getter) where A : Attribute { +            dict = new Dictionary<Type, V>(); +            for (int i = 0; i < nodeTypes.Length; i++) { +                object[] attribs = nodeTypes[i].GetCustomAttributes(typeof(A), true); +                if (attribs == null || attribs.Length == 0) continue; +                A attrib = attribs[0] as A; +                dict.Add(nodeTypes[i], getter(attrib)); +            } +        } + +        /// <summary> Get FieldInfo of a field, including those that are private and/or inherited </summary> +        public static FieldInfo GetFieldInfo(this Type type, string fieldName) { +            // If we can't find field in the first run, it's probably a private field in a base class. +            FieldInfo field = type.GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); +            // Search base classes for private fields only. Public fields are found above +            while (field == null && (type = type.BaseType) != typeof(XNode.Node)) field = type.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance); +            return field; +        } + +        /// <summary> Get all classes deriving from baseType via reflection </summary> +        public static Type[] GetDerivedTypes(this Type baseType) { +            List<System.Type> types = new List<System.Type>(); +            System.Reflection.Assembly[] assemblies = System.AppDomain.CurrentDomain.GetAssemblies(); +            foreach (Assembly assembly in assemblies) { +                try { +                    types.AddRange(assembly.GetTypes().Where(t => !t.IsAbstract && baseType.IsAssignableFrom(t)).ToArray()); +                } catch (ReflectionTypeLoadException) { } +            } +            return types.ToArray(); +        } + +        /// <summary> Find methods marked with the [ContextMenu] attribute and add them to the context menu </summary> +        public static void AddCustomContextMenuItems(this GenericMenu contextMenu, object obj) { +            KeyValuePair<ContextMenu, MethodInfo>[] items = GetContextMenuMethods(obj); +            if (items.Length != 0) { +                contextMenu.AddSeparator(""); +                List<string> invalidatedEntries = new List<string>(); +                foreach (KeyValuePair<ContextMenu, MethodInfo> checkValidate in items) { +                    if (checkValidate.Key.validate && !(bool) checkValidate.Value.Invoke(obj, null)) { +                        invalidatedEntries.Add(checkValidate.Key.menuItem); +                    } +                } +                for (int i = 0; i < items.Length; i++) { +                    KeyValuePair<ContextMenu, MethodInfo> kvp = items[i]; +                    if (invalidatedEntries.Contains(kvp.Key.menuItem)) { +                        contextMenu.AddDisabledItem(new GUIContent(kvp.Key.menuItem)); +                    } else { +                        contextMenu.AddItem(new GUIContent(kvp.Key.menuItem), false, () => kvp.Value.Invoke(obj, null)); +                    } +                } +            } +        } + +        /// <summary> Call OnValidate on target </summary> +        public static void TriggerOnValidate(this UnityEngine.Object target) { +            System.Reflection.MethodInfo onValidate = null; +            if (target != null) { +                onValidate = target.GetType().GetMethod("OnValidate", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); +                if (onValidate != null) onValidate.Invoke(target, null); +            } +        } + +        public static KeyValuePair<ContextMenu, MethodInfo>[] GetContextMenuMethods(object obj) { +            Type type = obj.GetType(); +            MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); +            List<KeyValuePair<ContextMenu, MethodInfo>> kvp = new List<KeyValuePair<ContextMenu, MethodInfo>>(); +            for (int i = 0; i < methods.Length; i++) { +                ContextMenu[] attribs = methods[i].GetCustomAttributes(typeof(ContextMenu), true).Select(x => x as ContextMenu).ToArray(); +                if (attribs == null || attribs.Length == 0) continue; +                if (methods[i].GetParameters().Length != 0) { +                    Debug.LogWarning("Method " + methods[i].DeclaringType.Name + "." + methods[i].Name + " has parameters and cannot be used for context menu commands."); +                    continue; +                } +                if (methods[i].IsStatic) { +                    Debug.LogWarning("Method " + methods[i].DeclaringType.Name + "." + methods[i].Name + " is static and cannot be used for context menu commands."); +                    continue; +                } + +                for (int k = 0; k < attribs.Length; k++) { +                    kvp.Add(new KeyValuePair<ContextMenu, MethodInfo>(attribs[k], methods[i])); +                } +            } +#if UNITY_5_5_OR_NEWER +            //Sort menu items +            kvp.Sort((x, y) => x.Key.priority.CompareTo(y.Key.priority)); +#endif +            return kvp.ToArray(); +        } + +        /// <summary> Very crude. Uses a lot of reflection. </summary> +        public static void OpenPreferences() { +            try { +#if UNITY_2018_3_OR_NEWER +                SettingsService.OpenUserPreferences("Preferences/Node Editor"); +#else +                //Open preferences window +                Assembly assembly = Assembly.GetAssembly(typeof(UnityEditor.EditorWindow)); +                Type type = assembly.GetType("UnityEditor.PreferencesWindow"); +                type.GetMethod("ShowPreferencesWindow", BindingFlags.NonPublic | BindingFlags.Static).Invoke(null, null); + +                //Get the window +                EditorWindow window = EditorWindow.GetWindow(type); + +                //Make sure custom sections are added (because waiting for it to happen automatically is too slow) +                FieldInfo refreshField = type.GetField("m_RefreshCustomPreferences", BindingFlags.NonPublic | BindingFlags.Instance); +                if ((bool) refreshField.GetValue(window)) { +                    type.GetMethod("AddCustomSections", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(window, null); +                    refreshField.SetValue(window, false); +                } + +                //Get sections +                FieldInfo sectionsField = type.GetField("m_Sections", BindingFlags.Instance | BindingFlags.NonPublic); +                IList sections = sectionsField.GetValue(window) as IList; + +                //Iterate through sections and check contents +                Type sectionType = sectionsField.FieldType.GetGenericArguments() [0]; +                FieldInfo sectionContentField = sectionType.GetField("content", BindingFlags.Instance | BindingFlags.Public); +                for (int i = 0; i < sections.Count; i++) { +                    GUIContent sectionContent = sectionContentField.GetValue(sections[i]) as GUIContent; +                    if (sectionContent.text == "Node Editor") { +                        //Found contents - Set index +                        FieldInfo sectionIndexField = type.GetField("m_SelectedSectionIndex", BindingFlags.Instance | BindingFlags.NonPublic); +                        sectionIndexField.SetValue(window, i); +                        return; +                    } +                } +#endif +            } catch (Exception e) { +                Debug.LogError(e); +                Debug.LogWarning("Unity has changed around internally. Can't open properties through reflection. Please contact xNode developer and supply unity version number."); +            } +        } +    } +} diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorReflection.cs.meta b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorReflection.cs.meta new file mode 100644 index 00000000..fe4ba9b4 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorReflection.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: c78a0fa4a13abcd408ebe73006b7b1bb +timeCreated: 1505419458 +licenseType: Free +MonoImporter: +  serializedVersion: 2 +  defaultReferences: [] +  executionOrder: 0 +  icon: {instanceID: 0} +  userData:  +  assetBundleName:  +  assetBundleVariant:  diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorResources.cs b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorResources.cs new file mode 100644 index 00000000..b426f324 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorResources.cs @@ -0,0 +1,87 @@ +using UnityEditor; +using UnityEngine; + +namespace XNodeEditor { +    public static class NodeEditorResources { +        // Textures +        public static Texture2D dot { get { return _dot != null ? _dot : _dot = Resources.Load<Texture2D>("xnode_dot"); } } +        private static Texture2D _dot; +        public static Texture2D dotOuter { get { return _dotOuter != null ? _dotOuter : _dotOuter = Resources.Load<Texture2D>("xnode_dot_outer"); } } +        private static Texture2D _dotOuter; +        public static Texture2D nodeBody { get { return _nodeBody != null ? _nodeBody : _nodeBody = Resources.Load<Texture2D>("xnode_node"); } } +        private static Texture2D _nodeBody; +        public static Texture2D nodeHighlight { get { return _nodeHighlight != null ? _nodeHighlight : _nodeHighlight = Resources.Load<Texture2D>("xnode_node_highlight"); } } +        private static Texture2D _nodeHighlight; + +        // Styles +        public static Styles styles { get { return _styles != null ? _styles : _styles = new Styles(); } } +        public static Styles _styles = null; +        public static GUIStyle OutputPort { get { return new GUIStyle(EditorStyles.label) { alignment = TextAnchor.UpperRight }; } } +        public class Styles { +            public GUIStyle inputPort, nodeHeader, nodeBody, tooltip, nodeHighlight; + +            public Styles() { +                GUIStyle baseStyle = new GUIStyle("Label"); +                baseStyle.fixedHeight = 18; + +                inputPort = new GUIStyle(baseStyle); +                inputPort.alignment = TextAnchor.UpperLeft; +                inputPort.padding.left = 10; + +                nodeHeader = new GUIStyle(); +                nodeHeader.alignment = TextAnchor.MiddleCenter; +                nodeHeader.fontStyle = FontStyle.Bold; +                nodeHeader.normal.textColor = Color.white; + +                nodeBody = new GUIStyle(); +                nodeBody.normal.background = NodeEditorResources.nodeBody; +                nodeBody.border = new RectOffset(32, 32, 32, 32); +                nodeBody.padding = new RectOffset(16, 16, 4, 16); + +                nodeHighlight = new GUIStyle(); +                nodeHighlight.normal.background = NodeEditorResources.nodeHighlight; +                nodeHighlight.border = new RectOffset(32, 32, 32, 32); + +                tooltip = new GUIStyle("helpBox"); +                tooltip.alignment = TextAnchor.MiddleCenter; +            } +        } + +        public static Texture2D GenerateGridTexture(Color line, Color bg) { +            Texture2D tex = new Texture2D(64, 64); +            Color[] cols = new Color[64 * 64]; +            for (int y = 0; y < 64; y++) { +                for (int x = 0; x < 64; x++) { +                    Color col = bg; +                    if (y % 16 == 0 || x % 16 == 0) col = Color.Lerp(line, bg, 0.65f); +                    if (y == 63 || x == 63) col = Color.Lerp(line, bg, 0.35f); +                    cols[(y * 64) + x] = col; +                } +            } +            tex.SetPixels(cols); +            tex.wrapMode = TextureWrapMode.Repeat; +            tex.filterMode = FilterMode.Bilinear; +            tex.name = "Grid"; +            tex.Apply(); +            return tex; +        } + +        public static Texture2D GenerateCrossTexture(Color line) { +            Texture2D tex = new Texture2D(64, 64); +            Color[] cols = new Color[64 * 64]; +            for (int y = 0; y < 64; y++) { +                for (int x = 0; x < 64; x++) { +                    Color col = line; +                    if (y != 31 && x != 31) col.a = 0; +                    cols[(y * 64) + x] = col; +                } +            } +            tex.SetPixels(cols); +            tex.wrapMode = TextureWrapMode.Clamp; +            tex.filterMode = FilterMode.Bilinear; +            tex.name = "Grid"; +            tex.Apply(); +            return tex; +        } +    } +}
\ No newline at end of file diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorResources.cs.meta b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorResources.cs.meta new file mode 100644 index 00000000..5e85895b --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorResources.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 69f55d341299026489b29443c3dd13d1 +timeCreated: 1505418919 +licenseType: Free +MonoImporter: +  serializedVersion: 2 +  defaultReferences: [] +  executionOrder: 0 +  icon: {instanceID: 0} +  userData:  +  assetBundleName:  +  assetBundleVariant:  diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorUtilities.cs b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorUtilities.cs new file mode 100644 index 00000000..a6ede849 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorUtilities.cs @@ -0,0 +1,266 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using UnityEditor; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace XNodeEditor { +    /// <summary> A set of editor-only utilities and extensions for xNode </summary> +    public static class NodeEditorUtilities { + +        /// <summary>C#'s Script Icon [The one MonoBhevaiour Scripts have].</summary> +        private static Texture2D scriptIcon = (EditorGUIUtility.IconContent("cs Script Icon").image as Texture2D); + +        /// Saves Attribute from Type+Field for faster lookup. Resets on recompiles. +        private static Dictionary<Type, Dictionary<string, Dictionary<Type, Attribute>>> typeAttributes = new Dictionary<Type, Dictionary<string, Dictionary<Type, Attribute>>>(); + +        /// Saves ordered PropertyAttribute from Type+Field for faster lookup. Resets on recompiles. +        private static Dictionary<Type, Dictionary<string, List<PropertyAttribute>>> typeOrderedPropertyAttributes = new Dictionary<Type, Dictionary<string, List<PropertyAttribute>>>(); + +        public static bool GetAttrib<T>(Type classType, out T attribOut) where T : Attribute { +            object[] attribs = classType.GetCustomAttributes(typeof(T), false); +            return GetAttrib(attribs, out attribOut); +        } + +        public static bool GetAttrib<T>(object[] attribs, out T attribOut) where T : Attribute { +            for (int i = 0; i < attribs.Length; i++) { +                if (attribs[i] is T) { +                    attribOut = attribs[i] as T; +                    return true; +                } +            } +            attribOut = null; +            return false; +        } + +        public static bool GetAttrib<T>(Type classType, string fieldName, out T attribOut) where T : Attribute { +            // If we can't find field in the first run, it's probably a private field in a base class. +            FieldInfo field = classType.GetFieldInfo(fieldName); +            // This shouldn't happen. Ever. +            if (field == null) { +                Debug.LogWarning("Field " + fieldName + " couldnt be found"); +                attribOut = null; +                return false; +            } +            object[] attribs = field.GetCustomAttributes(typeof(T), true); +            return GetAttrib(attribs, out attribOut); +        } + +        public static bool HasAttrib<T>(object[] attribs) where T : Attribute { +            for (int i = 0; i < attribs.Length; i++) { +                if (attribs[i].GetType() == typeof(T)) { +                    return true; +                } +            } +            return false; +        } + +        public static bool GetCachedAttrib<T>(Type classType, string fieldName, out T attribOut) where T : Attribute { +            Dictionary<string, Dictionary<Type, Attribute>> typeFields; +            if (!typeAttributes.TryGetValue(classType, out typeFields)) { +                typeFields = new Dictionary<string, Dictionary<Type, Attribute>>(); +                typeAttributes.Add(classType, typeFields); +            } + +            Dictionary<Type, Attribute> typeTypes; +            if (!typeFields.TryGetValue(fieldName, out typeTypes)) { +                typeTypes = new Dictionary<Type, Attribute>(); +                typeFields.Add(fieldName, typeTypes); +            } + +            Attribute attr; +            if (!typeTypes.TryGetValue(typeof(T), out attr)) { +                if (GetAttrib<T>(classType, fieldName, out attribOut)) { +                    typeTypes.Add(typeof(T), attribOut); +                    return true; +                } else typeTypes.Add(typeof(T), null); +            } + +            if (attr == null) { +                attribOut = null; +                return false; +            } + +            attribOut = attr as T; +            return true; +        } + +        public static List<PropertyAttribute> GetCachedPropertyAttribs(Type classType, string fieldName) { +            Dictionary<string, List<PropertyAttribute>> typeFields; +            if (!typeOrderedPropertyAttributes.TryGetValue(classType, out typeFields)) { +                typeFields = new Dictionary<string, List<PropertyAttribute>>(); +                typeOrderedPropertyAttributes.Add(classType, typeFields); +            } + +            List<PropertyAttribute> typeAttributes; +            if (!typeFields.TryGetValue(fieldName, out typeAttributes)) { +                FieldInfo field = classType.GetFieldInfo(fieldName); +                object[] attribs = field.GetCustomAttributes(typeof(PropertyAttribute), true); +                typeAttributes = attribs.Cast<PropertyAttribute>().Reverse().ToList(); //Unity draws them in reverse +                typeFields.Add(fieldName, typeAttributes); +            } + +            return typeAttributes; +        } + +        public static bool IsMac() { +#if UNITY_2017_1_OR_NEWER +            return SystemInfo.operatingSystemFamily == OperatingSystemFamily.MacOSX; +#else +            return SystemInfo.operatingSystem.StartsWith("Mac"); +#endif +        } + +        /// <summary> Returns true if this can be casted to <see cref="Type"/></summary> +        public static bool IsCastableTo(this Type from, Type to) { +            if (to.IsAssignableFrom(from)) return true; +            var methods = from.GetMethods(BindingFlags.Public | BindingFlags.Static) +                .Where( +                    m => m.ReturnType == to && +                    (m.Name == "op_Implicit" || +                        m.Name == "op_Explicit") +                ); +            return methods.Count() > 0; +        } + +        /// <summary> Return a prettiefied type name. </summary> +        public static string PrettyName(this Type type) { +            if (type == null) return "null"; +            if (type == typeof(System.Object)) return "object"; +            if (type == typeof(float)) return "float"; +            else if (type == typeof(int)) return "int"; +            else if (type == typeof(long)) return "long"; +            else if (type == typeof(double)) return "double"; +            else if (type == typeof(string)) return "string"; +            else if (type == typeof(bool)) return "bool"; +            else if (type.IsGenericType) { +                string s = ""; +                Type genericType = type.GetGenericTypeDefinition(); +                if (genericType == typeof(List<>)) s = "List"; +                else s = type.GetGenericTypeDefinition().ToString(); + +                Type[] types = type.GetGenericArguments(); +                string[] stypes = new string[types.Length]; +                for (int i = 0; i < types.Length; i++) { +                    stypes[i] = types[i].PrettyName(); +                } +                return s + "<" + string.Join(", ", stypes) + ">"; +            } else if (type.IsArray) { +                string rank = ""; +                for (int i = 1; i < type.GetArrayRank(); i++) { +                    rank += ","; +                } +                Type elementType = type.GetElementType(); +                if (!elementType.IsArray) return elementType.PrettyName() + "[" + rank + "]"; +                else { +                    string s = elementType.PrettyName(); +                    int i = s.IndexOf('['); +                    return s.Substring(0, i) + "[" + rank + "]" + s.Substring(i); +                } +            } else return type.ToString(); +        } + +        /// <summary> Returns the default name for the node type. </summary> +        public static string NodeDefaultName(Type type) { +            string typeName = type.Name; +            // Automatically remove redundant 'Node' postfix +            if (typeName.EndsWith("Node")) typeName = typeName.Substring(0, typeName.LastIndexOf("Node")); +            typeName = UnityEditor.ObjectNames.NicifyVariableName(typeName); +            return typeName; +        } + +        /// <summary> Returns the default creation path for the node type. </summary> +        public static string NodeDefaultPath(Type type) { +            string typePath = type.ToString().Replace('.', '/'); +            // Automatically remove redundant 'Node' postfix +            if (typePath.EndsWith("Node")) typePath = typePath.Substring(0, typePath.LastIndexOf("Node")); +            typePath = UnityEditor.ObjectNames.NicifyVariableName(typePath); +            return typePath; +        } + +        /// <summary>Creates a new C# Class.</summary> +        [MenuItem("Assets/Create/xNode/Node C# Script", false, 89)] +        private static void CreateNode() { +            string[] guids = AssetDatabase.FindAssets("xNode_NodeTemplate.cs"); +            if (guids.Length == 0) { +                Debug.LogWarning("xNode_NodeTemplate.cs.txt not found in asset database"); +                return; +            } +            string path = AssetDatabase.GUIDToAssetPath(guids[0]); +            CreateFromTemplate( +                "NewNode.cs", +                path +            ); +        } + +        /// <summary>Creates a new C# Class.</summary> +        [MenuItem("Assets/Create/xNode/NodeGraph C# Script", false, 89)] +        private static void CreateGraph() { +            string[] guids = AssetDatabase.FindAssets("xNode_NodeGraphTemplate.cs"); +            if (guids.Length == 0) { +                Debug.LogWarning("xNode_NodeGraphTemplate.cs.txt not found in asset database"); +                return; +            } +            string path = AssetDatabase.GUIDToAssetPath(guids[0]); +            CreateFromTemplate( +                "NewNodeGraph.cs", +                path +            ); +        } + +        public static void CreateFromTemplate(string initialName, string templatePath) { +            ProjectWindowUtil.StartNameEditingIfProjectWindowExists( +                0, +                ScriptableObject.CreateInstance<DoCreateCodeFile>(), +                initialName, +                scriptIcon, +                templatePath +            ); +        } + +        /// Inherits from EndNameAction, must override EndNameAction.Action +        public class DoCreateCodeFile : UnityEditor.ProjectWindowCallback.EndNameEditAction { +            public override void Action(int instanceId, string pathName, string resourceFile) { +                Object o = CreateScript(pathName, resourceFile); +                ProjectWindowUtil.ShowCreatedAsset(o); +            } +        } + +        /// <summary>Creates Script from Template's path.</summary> +        internal static UnityEngine.Object CreateScript(string pathName, string templatePath) { +            string className = Path.GetFileNameWithoutExtension(pathName).Replace(" ", string.Empty); +            string templateText = string.Empty; + +            UTF8Encoding encoding = new UTF8Encoding(true, false); + +            if (File.Exists(templatePath)) { +                /// Read procedures. +                StreamReader reader = new StreamReader(templatePath); +                templateText = reader.ReadToEnd(); +                reader.Close(); + +                templateText = templateText.Replace("#SCRIPTNAME#", className); +                templateText = templateText.Replace("#NOTRIM#", string.Empty); +                /// You can replace as many tags you make on your templates, just repeat Replace function +                /// e.g.: +                /// templateText = templateText.Replace("#NEWTAG#", "MyText"); + +                /// Write procedures. + +                StreamWriter writer = new StreamWriter(Path.GetFullPath(pathName), false, encoding); +                writer.Write(templateText); +                writer.Close(); + +                AssetDatabase.ImportAsset(pathName); +                return AssetDatabase.LoadAssetAtPath(pathName, typeof(Object)); +            } else { +                Debug.LogError(string.Format("The template file was not found: {0}", templatePath)); +                return null; +            } +        } +    } +} diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorUtilities.cs.meta b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorUtilities.cs.meta new file mode 100644 index 00000000..a8988ef5 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorUtilities.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 120960fe5b50aba418a8e8ad3c4c4bc8 +timeCreated: 1506073499 +licenseType: Free +MonoImporter: +  serializedVersion: 2 +  defaultReferences: [] +  executionOrder: 0 +  icon: {instanceID: 0} +  userData:  +  assetBundleName:  +  assetBundleVariant:  diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorWindow.cs b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorWindow.cs new file mode 100644 index 00000000..f4b079d5 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorWindow.cs @@ -0,0 +1,216 @@ +using System.Collections.Generic; +using UnityEditor; +using UnityEditor.Callbacks; +using UnityEngine; +using System; +using Object = UnityEngine.Object; + +namespace XNodeEditor { +    [InitializeOnLoad] +    public partial class NodeEditorWindow : EditorWindow { +        public static NodeEditorWindow current; + +        /// <summary> Stores node positions for all nodePorts. </summary> +        public Dictionary<XNode.NodePort, Rect> portConnectionPoints { get { return _portConnectionPoints; } } +        private Dictionary<XNode.NodePort, Rect> _portConnectionPoints = new Dictionary<XNode.NodePort, Rect>(); +        [SerializeField] private NodePortReference[] _references = new NodePortReference[0]; +        [SerializeField] private Rect[] _rects = new Rect[0]; + +        private Func<bool> isDocked { +            get { +                if (_isDocked == null) _isDocked = this.GetIsDockedDelegate(); +                return _isDocked; +            } +        } +        private Func<bool> _isDocked; + +        [System.Serializable] private class NodePortReference { +            [SerializeField] private XNode.Node _node; +            [SerializeField] private string _name; + +            public NodePortReference(XNode.NodePort nodePort) { +                _node = nodePort.node; +                _name = nodePort.fieldName; +            } + +            public XNode.NodePort GetNodePort() { +                if (_node == null) { +                    return null; +                } +                return _node.GetPort(_name); +            } +        } + +        private void OnDisable() { +            // Cache portConnectionPoints before serialization starts +            int count = portConnectionPoints.Count; +            _references = new NodePortReference[count]; +            _rects = new Rect[count]; +            int index = 0; +            foreach (var portConnectionPoint in portConnectionPoints) { +                _references[index] = new NodePortReference(portConnectionPoint.Key); +                _rects[index] = portConnectionPoint.Value; +                index++; +            } +        } + +        private void OnEnable() { +            // Reload portConnectionPoints if there are any +            int length = _references.Length; +            if (length == _rects.Length) { +                for (int i = 0; i < length; i++) { +                    XNode.NodePort nodePort = _references[i].GetNodePort(); +                    if (nodePort != null) +                        _portConnectionPoints.Add(nodePort, _rects[i]); +                } +            } +        } + +        public Dictionary<XNode.Node, Vector2> nodeSizes { get { return _nodeSizes; } } +        private Dictionary<XNode.Node, Vector2> _nodeSizes = new Dictionary<XNode.Node, Vector2>(); +        public XNode.NodeGraph graph; +        public Vector2 panOffset { get { return _panOffset; } set { _panOffset = value; Repaint(); } } +        private Vector2 _panOffset; +        public float zoom { get { return _zoom; } set { _zoom = Mathf.Clamp(value, NodeEditorPreferences.GetSettings().minZoom, NodeEditorPreferences.GetSettings().maxZoom); Repaint(); } } +        private float _zoom = 1; + +        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(); +        } + +        [InitializeOnLoadMethod] +        private static void OnLoad() { +            Selection.selectionChanged -= OnSelectionChanged; +            Selection.selectionChanged += OnSelectionChanged; +        } + +        /// <summary> Handle Selection Change events</summary> +        private static void OnSelectionChanged() { +            XNode.NodeGraph nodeGraph = Selection.activeObject as XNode.NodeGraph; +            if (nodeGraph && !AssetDatabase.Contains(nodeGraph)) { +                Open(nodeGraph); +            } +        } + +        /// <summary> Make sure the graph editor is assigned and to the right object </summary> +        private void ValidateGraphEditor() { +            NodeGraphEditor graphEditor = NodeGraphEditor.GetEditor(graph, this); +            if (this.graphEditor != graphEditor && graphEditor != null) { +                this.graphEditor = graphEditor; +                graphEditor.OnOpen(); +            } +        } + +        /// <summary> Create editor window </summary> +        public static NodeEditorWindow Init() { +            NodeEditorWindow w = CreateInstance<NodeEditorWindow>(); +            w.titleContent = new GUIContent("xNode"); +            w.wantsMouseMove = true; +            w.Show(); +            return w; +        } + +        public void Save() { +            if (AssetDatabase.Contains(graph)) { +                EditorUtility.SetDirty(graph); +                if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); +            } else SaveAs(); +        } + +        public void SaveAs() { +            string path = EditorUtility.SaveFilePanelInProject("Save NodeGraph", "NewNodeGraph", "asset", ""); +            if (string.IsNullOrEmpty(path)) return; +            else { +                XNode.NodeGraph existingGraph = AssetDatabase.LoadAssetAtPath<XNode.NodeGraph>(path); +                if (existingGraph != null) AssetDatabase.DeleteAsset(path); +                AssetDatabase.CreateAsset(graph, path); +                EditorUtility.SetDirty(graph); +                if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); +            } +        } + +        private void DraggableWindow(int windowID) { +            GUI.DragWindow(); +        } + +        public Vector2 WindowToGridPosition(Vector2 windowPosition) { +            return (windowPosition - (position.size * 0.5f) - (panOffset / zoom)) * zoom; +        } + +        public Vector2 GridToWindowPosition(Vector2 gridPosition) { +            return (position.size * 0.5f) + (panOffset / zoom) + (gridPosition / zoom); +        } + +        public Rect GridToWindowRectNoClipped(Rect gridRect) { +            gridRect.position = GridToWindowPositionNoClipped(gridRect.position); +            return gridRect; +        } + +        public Rect GridToWindowRect(Rect gridRect) { +            gridRect.position = GridToWindowPosition(gridRect.position); +            gridRect.size /= zoom; +            return gridRect; +        } + +        public Vector2 GridToWindowPositionNoClipped(Vector2 gridPosition) { +            Vector2 center = position.size * 0.5f; +            // UI Sharpness complete fix - Round final offset not panOffset +            float xOffset = Mathf.Round(center.x * zoom + (panOffset.x + gridPosition.x)); +            float yOffset = Mathf.Round(center.y * zoom + (panOffset.y + gridPosition.y)); +            return new Vector2(xOffset, yOffset); +        } + +        public void SelectNode(XNode.Node node, bool add) { +            if (add) { +                List<Object> selection = new List<Object>(Selection.objects); +                selection.Add(node); +                Selection.objects = selection.ToArray(); +            } else Selection.objects = new Object[] { node }; +        } + +        public void DeselectNode(XNode.Node node) { +            List<Object> selection = new List<Object>(Selection.objects); +            selection.Remove(node); +            Selection.objects = selection.ToArray(); +        } + +        [OnOpenAsset(0)] +        public static bool OnOpen(int instanceID, int line) { +            XNode.NodeGraph nodeGraph = EditorUtility.InstanceIDToObject(instanceID) as XNode.NodeGraph; +            if (nodeGraph != null) { +                Open(nodeGraph); +                return true; +            } +            return false; +        } + +        /// <summary>Open the provided graph in the NodeEditor</summary> +        public static NodeEditorWindow Open(XNode.NodeGraph graph) { +            if (!graph) return null; + +            NodeEditorWindow w = GetWindow(typeof(NodeEditorWindow), false, "xNode", true) as NodeEditorWindow; +            w.wantsMouseMove = true; +            w.graph = graph; +            return w; +        } + +        /// <summary> Repaint all open NodeEditorWindows. </summary> +        public static void RepaintAll() { +            NodeEditorWindow[] windows = Resources.FindObjectsOfTypeAll<NodeEditorWindow>(); +            for (int i = 0; i < windows.Length; i++) { +                windows[i].Repaint(); +            } +        } +    } +} diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorWindow.cs.meta b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorWindow.cs.meta new file mode 100644 index 00000000..541b5c75 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeEditorWindow.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 5ce2bf59ec7a25c4ba691cad7819bf38 +timeCreated: 1505418450 +licenseType: Free +MonoImporter: +  serializedVersion: 2 +  defaultReferences: [] +  executionOrder: 0 +  icon: {instanceID: 0} +  userData:  +  assetBundleName:  +  assetBundleVariant:  diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeGraphEditor.cs b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeGraphEditor.cs new file mode 100644 index 00000000..01de70e4 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeGraphEditor.cs @@ -0,0 +1,237 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEngine; + +namespace XNodeEditor { +    /// <summary> Base class to derive custom Node Graph editors from. Use this to override how graphs are drawn in the editor. </summary> +    [CustomNodeGraphEditor(typeof(XNode.NodeGraph))] +    public class NodeGraphEditor : XNodeEditor.Internal.NodeEditorBase<NodeGraphEditor, NodeGraphEditor.CustomNodeGraphEditorAttribute, XNode.NodeGraph> { +        [Obsolete("Use window.position instead")] +        public Rect position { get { return window.position; } set { window.position = value; } } +        /// <summary> Are we currently renaming a node? </summary> +        protected bool isRenaming; + +        public virtual void OnGUI() { } + +        /// <summary> Called when opened by NodeEditorWindow </summary> +        public virtual void OnOpen() { } +         +        /// <summary> Called when NodeEditorWindow gains focus </summary> +        public virtual void OnWindowFocus() { } + +        /// <summary> Called when NodeEditorWindow loses focus </summary> +        public virtual void OnWindowFocusLost() { } + +        public virtual Texture2D GetGridTexture() { +            return NodeEditorPreferences.GetSettings().gridTexture; +        } + +        public virtual Texture2D GetSecondaryGridTexture() { +            return NodeEditorPreferences.GetSettings().crossTexture; +        } + +        /// <summary> Return default settings for this graph type. This is the settings the user will load if no previous settings have been saved. </summary> +        public virtual NodeEditorPreferences.Settings GetDefaultPreferences() { +            return new NodeEditorPreferences.Settings(); +        } + +        /// <summary> Returns context node menu path. Null or empty strings for hidden nodes. </summary> +        public virtual string GetNodeMenuName(Type type) { +            //Check if type has the CreateNodeMenuAttribute +            XNode.Node.CreateNodeMenuAttribute attrib; +            if (NodeEditorUtilities.GetAttrib(type, out attrib)) // Return custom path +                return attrib.menuName; +            else // Return generated path +                return NodeEditorUtilities.NodeDefaultPath(type); +        } + +        /// <summary> The order by which the menu items are displayed. </summary> +        public virtual int GetNodeMenuOrder(Type type) { +            //Check if type has the CreateNodeMenuAttribute +            XNode.Node.CreateNodeMenuAttribute attrib; +            if (NodeEditorUtilities.GetAttrib(type, out attrib)) // Return custom path +                return attrib.order; +            else +                return 0; +        } + +        /// <summary> Add items for the context menu when right-clicking this node. Override to add custom menu items. </summary> +        public virtual void AddContextMenuItems(GenericMenu menu) { +            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++) { +                Type type = nodeTypes[i]; + +                //Get node context menu path +                string path = GetNodeMenuName(type); +                if (string.IsNullOrEmpty(path)) continue; + +                // 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)) { +                    int typeCount = target.nodes.Count(x => x.GetType() == type); +                    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, () => { +                    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); +        } + +        /// <summary> Returned gradient is used to color noodles </summary> +        /// <param name="output"> The output this noodle comes from. Never null. </param> +        /// <param name="input"> The output this noodle comes from. Can be null if we are dragging the noodle. </param> +        public virtual Gradient GetNoodleGradient(XNode.NodePort output, XNode.NodePort input) { +            Gradient grad = new Gradient(); + +            // If dragging the noodle, draw solid, slightly transparent +            if (input == null) { +                Color a = GetTypeColor(output.ValueType); +                grad.SetKeys( +                    new GradientColorKey[] { new GradientColorKey(a, 0f) }, +                    new GradientAlphaKey[] { new GradientAlphaKey(0.6f, 0f) } +                ); +            } +            // If normal, draw gradient fading from one input color to the other +            else { +                Color a = GetTypeColor(output.ValueType); +                Color b = GetTypeColor(input.ValueType); +                // If any port is hovered, tint white +                if (window.hoveredPort == output || window.hoveredPort == input) { +                    a = Color.Lerp(a, Color.white, 0.8f); +                    b = Color.Lerp(b, Color.white, 0.8f); +                } +                grad.SetKeys( +                    new GradientColorKey[] { new GradientColorKey(a, 0f), new GradientColorKey(b, 1f) }, +                    new GradientAlphaKey[] { new GradientAlphaKey(1f, 0f), new GradientAlphaKey(1f, 1f) } +                ); +            } +            return grad; +        } + +        /// <summary> Returned float is used for noodle thickness </summary> +        /// <param name="output"> The output this noodle comes from. Never null. </param> +        /// <param name="input"> The output this noodle comes from. Can be null if we are dragging the noodle. </param> +        public virtual float GetNoodleThickness(XNode.NodePort output, XNode.NodePort input) { +            return 5f; +        } + +        public virtual NoodlePath GetNoodlePath(XNode.NodePort output, XNode.NodePort input) { +            return NodeEditorPreferences.GetSettings().noodlePath; +        } + +        public virtual NoodleStroke GetNoodleStroke(XNode.NodePort output, XNode.NodePort input) { +            return NodeEditorPreferences.GetSettings().noodleStroke; +        } + +        /// <summary> Returned color is used to color ports </summary> +        public virtual Color GetPortColor(XNode.NodePort port) { +            return GetTypeColor(port.ValueType); +        } + +        /// <summary> Returns generated color for a type. This color is editable in preferences </summary> +        public virtual Color GetTypeColor(Type type) { +            return NodeEditorPreferences.GetTypeColor(type); +        } + +        /// <summary> Override to display custom tooltips </summary> +        public virtual string GetPortTooltip(XNode.NodePort port) { +            Type portType = port.ValueType; +            string tooltip = ""; +            tooltip = portType.PrettyName(); +            if (port.IsOutput) { +                object obj = port.node.GetValue(port); +                tooltip += " = " + (obj != null ? obj.ToString() : "null"); +            } +            return tooltip; +        } + +        /// <summary> Deal with objects dropped into the graph through DragAndDrop </summary> +        public virtual void OnDropObjects(UnityEngine.Object[] objects) { +            if (GetType() != typeof(NodeGraphEditor)) Debug.Log("No OnDropObjects override defined for " + GetType()); +        } + +        /// <summary> Create a node and save it in the graph asset </summary> +        public virtual XNode.Node CreateNode(Type type, Vector2 position) { +            Undo.RecordObject(target, "Create Node"); +            XNode.Node node = target.AddNode(type); +            Undo.RegisterCreatedObjectUndo(node, "Create Node"); +            node.position = position; +            if (node.name == null || node.name.Trim() == "") node.name = NodeEditorUtilities.NodeDefaultName(type); +            if (!string.IsNullOrEmpty(AssetDatabase.GetAssetPath(target))) AssetDatabase.AddObjectToAsset(node, target); +            if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); +            NodeEditorWindow.RepaintAll(); +            return node; +        } + +        /// <summary> Creates a copy of the original node in the graph </summary> +        public virtual XNode.Node CopyNode(XNode.Node original) { +            Undo.RecordObject(target, "Duplicate Node"); +            XNode.Node node = target.CopyNode(original); +            Undo.RegisterCreatedObjectUndo(node, "Duplicate Node"); +            node.name = original.name; +            AssetDatabase.AddObjectToAsset(node, target); +            if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); +            return node; +        } + +        /// <summary> Return false for nodes that can't be removed </summary> +        public virtual bool CanRemove(XNode.Node node) { +            // Check graph attributes to see if this node is required +            Type graphType = target.GetType(); +            XNode.NodeGraph.RequireNodeAttribute[] attribs = Array.ConvertAll( +                graphType.GetCustomAttributes(typeof(XNode.NodeGraph.RequireNodeAttribute), true), x => x as XNode.NodeGraph.RequireNodeAttribute); +            if (attribs.Any(x => x.Requires(node.GetType()))) { +                if (target.nodes.Count(x => x.GetType() == node.GetType()) <= 1) { +                    return false; +                } +            } +            return true; +        } + +        /// <summary> Safely remove a node and all its connections. </summary> +        public virtual void RemoveNode(XNode.Node node) { +            if (!CanRemove(node)) return; + +            // Remove the node +            Undo.RecordObject(node, "Delete Node"); +            Undo.RecordObject(target, "Delete Node"); +            foreach (var port in node.Ports) +                foreach (var conn in port.GetConnections()) +                    Undo.RecordObject(conn.node, "Delete Node"); +            target.RemoveNode(node); +            Undo.DestroyObjectImmediate(node); +            if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); +        } + +        [AttributeUsage(AttributeTargets.Class)] +        public class CustomNodeGraphEditorAttribute : Attribute, +        XNodeEditor.Internal.NodeEditorBase<NodeGraphEditor, NodeGraphEditor.CustomNodeGraphEditorAttribute, XNode.NodeGraph>.INodeEditorAttrib { +            private Type inspectedType; +            public string editorPrefsKey; +            /// <summary> Tells a NodeGraphEditor which Graph type it is an editor for </summary> +            /// <param name="inspectedType">Type that this editor can edit</param> +            /// <param name="editorPrefsKey">Define unique key for unique layout settings instance</param> +            public CustomNodeGraphEditorAttribute(Type inspectedType, string editorPrefsKey = "xNode.Settings") { +                this.inspectedType = inspectedType; +                this.editorPrefsKey = editorPrefsKey; +            } + +            public Type GetInspectedType() { +                return inspectedType; +            } +        } +    } +} diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeGraphEditor.cs.meta b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeGraphEditor.cs.meta new file mode 100644 index 00000000..bc1c1533 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeGraphEditor.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: ddcbb5432255d3247a0718b15a9c193c +timeCreated: 1505462176 +licenseType: Free +MonoImporter: +  serializedVersion: 2 +  defaultReferences: [] +  executionOrder: 0 +  icon: {instanceID: 0} +  userData:  +  assetBundleName:  +  assetBundleVariant:  diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeGraphImporter.cs b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeGraphImporter.cs new file mode 100644 index 00000000..3faf54fb --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeGraphImporter.cs @@ -0,0 +1,45 @@ +using System; +using System.IO; +using System.Linq; +using UnityEditor; +using UnityEditor.Experimental.AssetImporters; +using UnityEngine; +using XNode; + +namespace XNodeEditor { +    /// <summary> Deals with modified assets </summary> +    class NodeGraphImporter : AssetPostprocessor { +        private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) { +            foreach (string path in importedAssets) { +                // Skip processing anything without the .asset extension +                if (Path.GetExtension(path) != ".asset") continue; + +                // Get the object that is requested for deletion +                NodeGraph graph = AssetDatabase.LoadAssetAtPath<NodeGraph>(path); +                if (graph == null) continue; + +                // Get attributes +                Type graphType = graph.GetType(); +                NodeGraph.RequireNodeAttribute[] attribs = Array.ConvertAll( +                    graphType.GetCustomAttributes(typeof(NodeGraph.RequireNodeAttribute), true), x => x as NodeGraph.RequireNodeAttribute); + +                Vector2 position = Vector2.zero; +                foreach (NodeGraph.RequireNodeAttribute attrib in attribs) { +                    if (attrib.type0 != null) AddRequired(graph, attrib.type0, ref position); +                    if (attrib.type1 != null) AddRequired(graph, attrib.type1, ref position); +                    if (attrib.type2 != null) AddRequired(graph, attrib.type2, ref position); +                } +            } +        } + +        private static void AddRequired(NodeGraph graph, Type type, ref Vector2 position) { +            if (!graph.nodes.Any(x => x.GetType() == type)) { +                XNode.Node node = graph.AddNode(type); +                node.position = position; +                position.x += 200; +                if (node.name == null || node.name.Trim() == "") node.name = NodeEditorUtilities.NodeDefaultName(type); +                if (!string.IsNullOrEmpty(AssetDatabase.GetAssetPath(graph))) AssetDatabase.AddObjectToAsset(node, graph); +            } +        } +    } +}
\ No newline at end of file diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeGraphImporter.cs.meta b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeGraphImporter.cs.meta new file mode 100644 index 00000000..b3dd1fee --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/NodeGraphImporter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7a816f2790bf3da48a2d6d0035ebc9a0 +MonoImporter: +  externalObjects: {} +  serializedVersion: 2 +  defaultReferences: [] +  executionOrder: 0 +  icon: {instanceID: 0} +  userData:  +  assetBundleName:  +  assetBundleVariant:  diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/RenamePopup.cs b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/RenamePopup.cs new file mode 100644 index 00000000..a43837f0 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/RenamePopup.cs @@ -0,0 +1,83 @@ +using UnityEditor; +using UnityEngine; + +namespace XNodeEditor { +    /// <summary> Utility for renaming assets </summary> +    public class RenamePopup : EditorWindow { +        private const string inputControlName = "nameInput"; + +        public static RenamePopup current { get; private set; } +        public Object target; +        public string input; + +        private bool firstFrame = true; + +        /// <summary> Show a rename popup for an asset at mouse position. Will trigger reimport of the asset on apply. +        public static RenamePopup Show(Object target, float width = 200) { +            RenamePopup window = EditorWindow.GetWindow<RenamePopup>(true, "Rename " + target.name, true); +            if (current != null) current.Close(); +            current = window; +            window.target = target; +            window.input = target.name; +            window.minSize = new Vector2(100, 44); +            window.position = new Rect(0, 0, width, 44); +            window.UpdatePositionToMouse(); +            return window; +        } + +        private void UpdatePositionToMouse() { +            if (Event.current == null) return; +            Vector3 mousePoint = GUIUtility.GUIToScreenPoint(Event.current.mousePosition); +            Rect pos = position; +            pos.x = mousePoint.x - position.width * 0.5f; +            pos.y = mousePoint.y - 10; +            position = pos; +        } + +        private void OnLostFocus() { +            // Make the popup close on lose focus +            Close(); +        } + +        private void OnGUI() { +            if (firstFrame) { +                UpdatePositionToMouse(); +                firstFrame = false; +            } +            GUI.SetNextControlName(inputControlName); +            input = EditorGUILayout.TextField(input); +            EditorGUI.FocusTextInControl(inputControlName); +            Event e = Event.current; +            // If input is empty, revert name to default instead +            if (input == null || input.Trim() == "") { +                if (GUILayout.Button("Revert to default") || (e.isKey && e.keyCode == KeyCode.Return)) { +                    target.name = NodeEditorUtilities.NodeDefaultName(target.GetType()); +                    NodeEditor.GetEditor((XNode.Node)target, NodeEditorWindow.current).OnRename(); +                    AssetDatabase.SetMainObject((target as XNode.Node).graph, AssetDatabase.GetAssetPath(target)); +                    AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target)); +                    Close(); +                    target.TriggerOnValidate(); +                } +            } +            // Rename asset to input text +            else { +                if (GUILayout.Button("Apply") || (e.isKey && e.keyCode == KeyCode.Return)) { +                    target.name = input; +                    NodeEditor.GetEditor((XNode.Node)target, NodeEditorWindow.current).OnRename(); +                    AssetDatabase.SetMainObject((target as XNode.Node).graph, AssetDatabase.GetAssetPath(target)); +                    AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target)); +                    Close(); +                    target.TriggerOnValidate(); +                } +            } + +            if (e.isKey && e.keyCode == KeyCode.Escape) { +                Close(); +            } +        } + +        private void OnDestroy() { +            EditorGUIUtility.editingTextField = false; +        } +    } +}
\ No newline at end of file diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/RenamePopup.cs.meta b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/RenamePopup.cs.meta new file mode 100644 index 00000000..5c40a02b --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/RenamePopup.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 4ef3ddc25518318469bce838980c64be +timeCreated: 1552067957 +licenseType: Free +MonoImporter: +  externalObjects: {} +  serializedVersion: 2 +  defaultReferences: [] +  executionOrder: 0 +  icon: {instanceID: 0} +  userData:  +  assetBundleName:  +  assetBundleVariant:  diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources.meta b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources.meta new file mode 100644 index 00000000..786ef41c --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 964fc201163fe884ca6a20094b6f3b49 +folderAsset: yes +timeCreated: 1506110871 +licenseType: Free +DefaultImporter: +  userData:  +  assetBundleName:  +  assetBundleVariant:  diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/ScriptTemplates.meta b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/ScriptTemplates.meta new file mode 100644 index 00000000..b2435e80 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/ScriptTemplates.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 86b677955452bb5449f9f4dd47b6ddfe +folderAsset: yes +timeCreated: 1519049391 +licenseType: Free +DefaultImporter: +  externalObjects: {} +  userData:  +  assetBundleName:  +  assetBundleVariant:  diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/ScriptTemplates/xNode_NodeGraphTemplate.cs.txt b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/ScriptTemplates/xNode_NodeGraphTemplate.cs.txt new file mode 100644 index 00000000..e3d7c367 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/ScriptTemplates/xNode_NodeGraphTemplate.cs.txt @@ -0,0 +1,9 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using XNode; + +[CreateAssetMenu] +public class #SCRIPTNAME# : NodeGraph {  +	#NOTRIM# +}
\ No newline at end of file diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/ScriptTemplates/xNode_NodeGraphTemplate.cs.txt.meta b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/ScriptTemplates/xNode_NodeGraphTemplate.cs.txt.meta new file mode 100644 index 00000000..b55bd754 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/ScriptTemplates/xNode_NodeGraphTemplate.cs.txt.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 8165767f64da7d94e925f61a38da668c +timeCreated: 1519049802 +licenseType: Free +TextScriptImporter: +  externalObjects: {} +  userData:  +  assetBundleName:  +  assetBundleVariant:  diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/ScriptTemplates/xNode_NodeTemplate.cs.txt b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/ScriptTemplates/xNode_NodeTemplate.cs.txt new file mode 100644 index 00000000..de791fcb --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/ScriptTemplates/xNode_NodeTemplate.cs.txt @@ -0,0 +1,18 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using XNode; + +public class #SCRIPTNAME# : Node { + +	// Use this for initialization +	protected override void Init() { +		base.Init(); +		#NOTRIM# +	} + +	// Return the correct value of an output port when requested +	public override object GetValue(NodePort port) { +		return null; // Replace this +	} +}
\ No newline at end of file diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/ScriptTemplates/xNode_NodeTemplate.cs.txt.meta b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/ScriptTemplates/xNode_NodeTemplate.cs.txt.meta new file mode 100644 index 00000000..455420a2 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/ScriptTemplates/xNode_NodeTemplate.cs.txt.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 85f6f570600a1a44d8e734cb111a8b89 +timeCreated: 1519049802 +licenseType: Free +TextScriptImporter: +  externalObjects: {} +  userData:  +  assetBundleName:  +  assetBundleVariant:  diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/xnode_dot.png b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/xnode_dot.png Binary files differnew file mode 100644 index 00000000..36c0ce93 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/xnode_dot.png diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/xnode_dot.png.meta b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/xnode_dot.png.meta new file mode 100644 index 00000000..00c23bc4 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/xnode_dot.png.meta @@ -0,0 +1,98 @@ +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/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/xnode_dot_outer.png b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/xnode_dot_outer.png Binary files differnew file mode 100644 index 00000000..538cc9fa --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/xnode_dot_outer.png diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/xnode_dot_outer.png.meta b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/xnode_dot_outer.png.meta new file mode 100644 index 00000000..0781a52f --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/xnode_dot_outer.png.meta @@ -0,0 +1,98 @@ +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/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/xnode_node.png b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/xnode_node.png Binary files differnew file mode 100644 index 00000000..6f0b42eb --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/xnode_node.png diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/xnode_node.png.meta b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/xnode_node.png.meta new file mode 100644 index 00000000..979e6f94 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/xnode_node.png.meta @@ -0,0 +1,98 @@ +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/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/xnode_node_highlight.png b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/xnode_node_highlight.png Binary files differnew file mode 100644 index 00000000..f1bb27f7 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/xnode_node_highlight.png diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/xnode_node_highlight.png.meta b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/xnode_node_highlight.png.meta new file mode 100644 index 00000000..21b60341 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/xnode_node_highlight.png.meta @@ -0,0 +1,87 @@ +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/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/xnode_node_workfile.psd b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/xnode_node_workfile.psd Binary files differnew file mode 100644 index 00000000..a578c46f --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/xnode_node_workfile.psd diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/xnode_node_workfile.psd.meta b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/xnode_node_workfile.psd.meta new file mode 100644 index 00000000..3ca04379 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/Resources/xnode_node_workfile.psd.meta @@ -0,0 +1,98 @@ +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/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/SceneGraphEditor.cs b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/SceneGraphEditor.cs new file mode 100644 index 00000000..9fb1c673 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/SceneGraphEditor.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; +using XNode; + +namespace XNodeEditor { +    [CustomEditor(typeof(SceneGraph), true)] +    public class SceneGraphEditor : Editor { +        private SceneGraph sceneGraph; +        private bool removeSafely; +        private Type graphType; + +        public override void OnInspectorGUI() { +            if (sceneGraph.graph == null) { +                if (GUILayout.Button("New graph", GUILayout.Height(40))) { +                    if (graphType == null) { +                        Type[] graphTypes = NodeEditorReflection.GetDerivedTypes(typeof(NodeGraph)); +                        GenericMenu menu = new GenericMenu(); +                        for (int i = 0; i < graphTypes.Length; i++) { +                            Type graphType = graphTypes[i]; +                            menu.AddItem(new GUIContent(graphType.Name), false, () => CreateGraph(graphType)); +                        } +                        menu.ShowAsContext(); +                    } else { +                        CreateGraph(graphType); +                    } +                } +            } else { +                if (GUILayout.Button("Open graph", GUILayout.Height(40))) { +                    NodeEditorWindow.Open(sceneGraph.graph); +                } +                if (removeSafely) { +                    GUILayout.BeginHorizontal(); +                    GUILayout.Label("Really remove graph?"); +                    GUI.color = new Color(1, 0.8f, 0.8f); +                    if (GUILayout.Button("Remove")) { +                        removeSafely = false; +                        Undo.RecordObject(sceneGraph, "Removed graph"); +                        sceneGraph.graph = null; +                    } +                    GUI.color = Color.white; +                    if (GUILayout.Button("Cancel")) { +                        removeSafely = false; +                    } +                    GUILayout.EndHorizontal(); +                } else { +                    GUI.color = new Color(1, 0.8f, 0.8f); +                    if (GUILayout.Button("Remove graph")) { +                        removeSafely = true; +                    } +                    GUI.color = Color.white; +                } +            } +        } + +        private void OnEnable() { +            sceneGraph = target as SceneGraph; +            Type sceneGraphType = sceneGraph.GetType(); +            if (sceneGraphType == typeof(SceneGraph)) { +                graphType = null; +            } else { +                Type baseType = sceneGraphType.BaseType; +                if (baseType.IsGenericType) { +                    graphType = sceneGraphType = baseType.GetGenericArguments() [0]; +                } +            } +        } + +        public void CreateGraph(Type type) { +            Undo.RecordObject(sceneGraph, "Create graph"); +            sceneGraph.graph = ScriptableObject.CreateInstance(type) as NodeGraph; +            sceneGraph.graph.name = sceneGraph.name + "-graph"; +        } +    } +}
\ No newline at end of file diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/SceneGraphEditor.cs.meta b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/SceneGraphEditor.cs.meta new file mode 100644 index 00000000..e1bf0b29 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/SceneGraphEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: aea725adabc311f44b5ea8161360a915 +MonoImporter: +  externalObjects: {} +  serializedVersion: 2 +  defaultReferences: [] +  executionOrder: 0 +  icon: {instanceID: 0} +  userData:  +  assetBundleName:  +  assetBundleVariant:  diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/XNodeEditor.asmdef b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/XNodeEditor.asmdef new file mode 100644 index 00000000..5fa1aabe --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/XNodeEditor.asmdef @@ -0,0 +1,17 @@ +{ +    "name": "XNodeEditor", +    "references": [ +        "XNode" +    ], +    "optionalUnityReferences": [], +    "includePlatforms": [ +        "Editor" +    ], +    "excludePlatforms": [], +    "allowUnsafeCode": false, +    "overrideReferences": false, +    "precompiledReferences": [], +    "autoReferenced": true, +    "defineConstraints": [], +    "versionDefines": [] +}
\ No newline at end of file diff --git a/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/XNodeEditor.asmdef.meta b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/XNodeEditor.asmdef.meta new file mode 100644 index 00000000..7bff0749 --- /dev/null +++ b/Other/NodeEditorExamples/Assets/xNode-examples/Scripts/Editor/XNodeEditor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 002c1bbed08fa44d282ef34fd5edb138 +AssemblyDefinitionImporter: +  externalObjects: {} +  userData:  +  assetBundleName:  +  assetBundleVariant:   | 
