diff options
Diffstat (limited to 'Scripts/Fold/Editor/FoldGraphView.cs')
| -rw-r--r-- | Scripts/Fold/Editor/FoldGraphView.cs | 248 |
1 files changed, 234 insertions, 14 deletions
diff --git a/Scripts/Fold/Editor/FoldGraphView.cs b/Scripts/Fold/Editor/FoldGraphView.cs index 947e6fa..3b8dd69 100644 --- a/Scripts/Fold/Editor/FoldGraphView.cs +++ b/Scripts/Fold/Editor/FoldGraphView.cs @@ -1,11 +1,13 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using UnityEditor; using UnityEditor.Experimental.GraphView; using UnityEditor.UIElements; using UnityEngine; using UnityEngine.UIElements; +using SearchWindow = UnityEditor.Experimental.GraphView.SearchWindow; public interface IFoldNode { @@ -49,6 +51,7 @@ public abstract class FoldNodeView : Node var port = InstantiatePort(Orientation.Horizontal, direction, capacity, type); port.portName = name; ports[name] = port; + graphView.BindEdgeConnector(port); if (direction == Direction.Input) inputContainer.Add(port); else outputContainer.Add(port); return port; @@ -329,11 +332,13 @@ public class FoldGraphView : GraphView public readonly FoldGraph graphAsset; readonly Dictionary<string, FoldNodeView> nodeLookup = new(); bool suppressGraphChanges; + readonly FoldEdgeConnectorListener edgeConnectorListener; public FoldGraphView(FoldWindow owner, FoldGraph graph) { window = owner; graphAsset = graph; + edgeConnectorListener = new FoldEdgeConnectorListener(this); style.flexGrow = 1; this.AddManipulator(new ContentZoomer()); @@ -368,6 +373,20 @@ public class FoldGraphView : GraphView return a.IsAssignableFrom(b) || b.IsAssignableFrom(a); } + internal void BindEdgeConnector(Port port) + { + var field = typeof(Port).GetField("m_EdgeConnector", BindingFlags.Instance | BindingFlags.NonPublic); + if (field != null) + { + if (field.GetValue(port) is IManipulator oldManipulator) + port.RemoveManipulator(oldManipulator); + } + + var connector = new EdgeConnector<Edge>(edgeConnectorListener); + field?.SetValue(port, connector); + port.AddManipulator(connector); + } + GraphViewChange OnGraphViewChanged(GraphViewChange change) { if (suppressGraphChanges) @@ -407,7 +426,7 @@ public class FoldGraphView : GraphView MarkDirty(); } - void AddEdgeData(Edge edge) + internal void AddEdgeData(Edge edge) { if (edge.input?.node is not FoldNodeView inputNode || edge.output?.node is not FoldNodeView outputNode) @@ -574,21 +593,21 @@ public class FoldGraphView : GraphView return data; } - public override void BuildContextualMenu(ContextualMenuPopulateEvent evt) + internal FoldNodeView CreateNode(Type dataType, Vector2 position) { - base.BuildContextualMenu(evt); - Vector2 graphPos = contentViewContainer.WorldToLocal(evt.mousePosition); + var data = (FoldNodeData)Activator.CreateInstance(dataType); + data.position = position; + graphAsset.nodes.Add(data); + var view = CreateNode(data); + nodeLookup[data.guid] = view; + AddElement(view); + return view; + } - evt.menu.AppendAction("Create/Fold/Axis Align", _ => CreateNode<AxisAlignNodeData>(graphPos)); - evt.menu.AppendAction("Create/Fold/Plane to Tube", _ => CreateNode<PlaneToTubeNodeData>(graphPos)); - evt.menu.AppendAction("Create/Fold/Point Align", _ => CreateNode<PointAlignNodeData>(graphPos)); - evt.menu.AppendAction("Create/Fold/Tube to Plane", _ => CreateNode<TubeToPlaneNodeData>(graphPos)); - evt.menu.AppendSeparator(); - evt.menu.AppendAction("Create/Value/Float", _ => CreateNode<FloatValueNodeData>(graphPos)); - evt.menu.AppendAction("Create/Value/Vector", _ => CreateNode<VectorValueNodeData>(graphPos)); - evt.menu.AppendAction("Create/Value/GameObject", _ => CreateNode<GameObjectNodeData>(graphPos)); - evt.menu.AppendSeparator(); - evt.menu.AppendAction("Create/Output/Keyframe", _ => CreateNode<KeyframeNodeData>(graphPos)); + public override void BuildContextualMenu(ContextualMenuPopulateEvent evt) + { + ShowSearchWindow(null, evt.mousePosition); + evt.StopPropagation(); } public void MarkDirty() @@ -596,6 +615,112 @@ public class FoldGraphView : GraphView EditorUtility.SetDirty(graphAsset); } + public void ShowNodeCreationMenuFromPort(Port startPort, Vector2 worldPosition) => ShowSearchWindow(startPort, worldPosition); + + public void CreateNodeAndConnect(Port startPort, Type dataType, Vector2 graphPosition) + { + suppressGraphChanges = true; + + var view = CreateNode(dataType, graphPosition); + + var compatible = GetCompatiblePorts(startPort, null).FirstOrDefault(p => p.node == view); + if (compatible != null) + { + var output = startPort.direction == Direction.Output ? startPort : compatible; + var input = startPort.direction == Direction.Output ? compatible : startPort; + + var edge = new Edge + { + output = output, + input = input + }; + edge.input.Connect(edge); + edge.output.Connect(edge); + AddElement(edge); + AddEdgeData(edge); + } + + suppressGraphChanges = false; + MarkDirty(); + } + + IEnumerable<(string label, Type dataType)> GetCompatibleNodeOptions(Port startPort) + { + Type type = startPort.portType; + bool fromOutput = startPort.direction == Direction.Output; + + if (type == typeof(float)) + { + if (fromOutput) + { + yield return ("Fold/Axis Align", typeof(AxisAlignNodeData)); + yield return ("Fold/Plane to Tube", typeof(PlaneToTubeNodeData)); + yield return ("Fold/Point Align", typeof(PointAlignNodeData)); + yield return ("Fold/Tube to Plane", typeof(TubeToPlaneNodeData)); + } + else + { + yield return ("Value/Float", typeof(FloatValueNodeData)); + } + } + else if (type == typeof(Vector4)) + { + if (fromOutput) + { + yield return ("Fold/Axis Align", typeof(AxisAlignNodeData)); + yield return ("Fold/Plane to Tube", typeof(PlaneToTubeNodeData)); + yield return ("Fold/Point Align", typeof(PointAlignNodeData)); + yield return ("Fold/Tube to Plane", typeof(TubeToPlaneNodeData)); + } + else + { + yield return ("Value/Vector", typeof(VectorValueNodeData)); + } + } + else if (type == typeof(GameObject)) + { + if (fromOutput) + yield return ("Output/Keyframe", typeof(KeyframeNodeData)); + else + yield return ("Value/GameObject", typeof(GameObjectNodeData)); + } + else if (typeof(IFoldNode).IsAssignableFrom(type)) + { + if (fromOutput) + { + yield return ("Fold/Axis Align", typeof(AxisAlignNodeData)); + yield return ("Fold/Plane to Tube", typeof(PlaneToTubeNodeData)); + yield return ("Fold/Point Align", typeof(PointAlignNodeData)); + yield return ("Fold/Tube to Plane", typeof(TubeToPlaneNodeData)); + yield return ("Output/Keyframe", typeof(KeyframeNodeData)); + } + else + { + yield return ("Fold/Axis Align", typeof(AxisAlignNodeData)); + yield return ("Fold/Plane to Tube", typeof(PlaneToTubeNodeData)); + yield return ("Fold/Point Align", typeof(PointAlignNodeData)); + yield return ("Fold/Tube to Plane", typeof(TubeToPlaneNodeData)); + } + } + } + + IEnumerable<(string label, Type dataType)> GetAllNodeOptions() + { + yield return ("Fold/Axis Align", typeof(AxisAlignNodeData)); + yield return ("Fold/Plane to Tube", typeof(PlaneToTubeNodeData)); + yield return ("Fold/Point Align", typeof(PointAlignNodeData)); + yield return ("Fold/Tube to Plane", typeof(TubeToPlaneNodeData)); + yield return ("Value/Float", typeof(FloatValueNodeData)); + yield return ("Value/Vector", typeof(VectorValueNodeData)); + yield return ("Value/GameObject", typeof(GameObjectNodeData)); + yield return ("Output/Keyframe", typeof(KeyframeNodeData)); + } + + internal IEnumerable<(string label, Type dataType)> GetNodeOptions(Port startPort) + { + return startPort == null ? GetAllNodeOptions() : GetCompatibleNodeOptions(startPort); + } + public T GetInputValue<T>(string nodeGuid, string portName, T fallback) { var node = nodeLookup.TryGetValue(nodeGuid, out var found) ? found : null; @@ -613,4 +738,99 @@ public class FoldGraphView : GraphView { window.ShowNotification(message); } + + void ShowSearchWindow(Port startPort, Vector2 worldPosition) + { + var provider = ScriptableObject.CreateInstance<FoldSearchProvider>(); + provider.Initialize(this, startPort, worldPosition); + if (!provider.HasEntries()) + return; + + SearchWindow.Open(new SearchWindowContext(GUIUtility.GUIToScreenPoint(worldPosition)), provider); + } +} + +class FoldEdgeConnectorListener : IEdgeConnectorListener +{ + readonly FoldGraphView graphView; + + public FoldEdgeConnectorListener(FoldGraphView graphView) + { + this.graphView = graphView; + } + + public void OnDropOutsidePort(Edge edge, Vector2 position) + { + var startPort = edge.output ?? edge.input; + if (startPort == null) + return; + + graphView.ShowNodeCreationMenuFromPort(startPort, position); + } + + public void OnDrop(GraphView gv, Edge edge) + { + if (edge.input == null || edge.output == null) + return; + + edge.input.Connect(edge); + edge.output.Connect(edge); + gv.AddElement(edge); + + if (graphView != null) + graphView.AddEdgeData(edge); + } +} + +class FoldSearchProvider : ScriptableObject, ISearchWindowProvider +{ + FoldGraphView graphView; + Port startPort; + Vector2 worldPosition; + List<(string label, Type dataType)> cachedOptions; + + public void Initialize(FoldGraphView graphView, Port startPort, Vector2 worldPosition) + { + this.graphView = graphView; + this.startPort = startPort; + this.worldPosition = worldPosition; + cachedOptions = graphView.GetNodeOptions(startPort).ToList(); + } + + public bool HasEntries() => cachedOptions != null && cachedOptions.Count > 0; + + public List<SearchTreeEntry> CreateSearchTree(SearchWindowContext context) + { + var entries = new List<SearchTreeEntry> + { + new SearchTreeGroupEntry(new GUIContent("Create Node"), 0) + }; + + foreach (var option in cachedOptions) + { + entries.Add(new SearchTreeEntry(new GUIContent(option.label)) + { + level = 1, + userData = option.dataType + }); + } + + return entries; + } + + public bool OnSelectEntry(SearchTreeEntry entry, SearchWindowContext context) + { + var dataType = entry.userData as Type; + if (dataType == null) + return false; + + Vector2 graphPosition = graphView.contentViewContainer.WorldToLocal(worldPosition); + + if (startPort != null) + graphView.CreateNodeAndConnect(startPort, dataType, graphPosition); + else + graphView.CreateNode(dataType, graphPosition); + + return true; + } } |
