summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoryum <yum.food.vr@gmail.com>2026-01-06 16:47:18 -0800
committeryum <yum.food.vr@gmail.com>2026-01-06 16:47:18 -0800
commit1d058ef6d306f6270f4316c5d7c8ce53a2f4e298 (patch)
treec1a705f4eaf65e07f9910cde0868ec399ec7ce04
parent44fe949dab2b1c3a4f949b1711c88f52d8e9f2fc (diff)
Fold: dragging out noodles & right clicking bring up search dialogue
-rw-r--r--Scripts/Fold/Editor/FoldGraphView.cs248
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;
+ }
}