From 317f8527bc1c0aedb234ecae331aeec18aeaf9bd Mon Sep 17 00:00:00 2001 From: yum Date: Tue, 6 Jan 2026 18:18:05 -0800 Subject: Fold: big rewrite --- Scripts/Fold/Editor/FoldGraph.cs | 112 +---- Scripts/Fold/Editor/FoldGraphView.cs | 785 ++++++++--------------------------- Scripts/Fold/Editor/FoldWindow.cs | 7 - 3 files changed, 193 insertions(+), 711 deletions(-) (limited to 'Scripts/Fold/Editor') diff --git a/Scripts/Fold/Editor/FoldGraph.cs b/Scripts/Fold/Editor/FoldGraph.cs index ca985f4..5782dc7 100644 --- a/Scripts/Fold/Editor/FoldGraph.cs +++ b/Scripts/Fold/Editor/FoldGraph.cs @@ -5,10 +5,7 @@ using UnityEngine; [Serializable] public class FoldEdge { - public string outputNodeGuid; - public string outputPortName; - public string inputNodeGuid; - public string inputPortName; + public string outputNodeGuid, outputPortName, inputNodeGuid, inputPortName; } [Serializable] @@ -22,126 +19,45 @@ public abstract class FoldNodeData [Serializable] public abstract class FoldOperationData : FoldNodeData { + public abstract IEnumerable<(string name, Type type)> GetInputPorts(); public abstract FoldNodeSerialized Serialize(FoldGraphView graphView); } -[Serializable] -public class FloatValueNodeData : FoldNodeData -{ - public float value; - public override string Title => "Float"; -} - -[Serializable] -public class VectorValueNodeData : FoldNodeData -{ - public Vector4 value; - public override string Title => "Vector"; -} - -[Serializable] -public class GameObjectNodeData : FoldNodeData -{ - public GameObject output; - public override string Title => "GameObject"; -} +[Serializable] public class FloatValueNodeData : FoldNodeData { public float value; public override string Title => "Float"; } +[Serializable] public class VectorValueNodeData : FoldNodeData { public Vector4 value; public override string Title => "Vector"; } +[Serializable] public class GameObjectNodeData : FoldNodeData { public GameObject output; public override string Title => "GameObject"; } +[Serializable] public class KeyframeNodeData : FoldNodeData { public override string Title => "Keyframe"; } [Serializable] public class AxisAlignNodeData : FoldOperationData { - public Vector4 po; - public Vector4 pp; - public Vector4 r; - public float t; - public override string Title => "Axis Align"; - - public override FoldNodeSerialized Serialize(FoldGraphView graphView) - { - return new FoldNodeSerialized - { - opcode = 4, - float0 = graphView.GetInputValue(guid, "t", t), - vec0 = graphView.GetInputValue(guid, "po", po), - vec1 = graphView.GetInputValue(guid, "pp", pp), - vec2 = graphView.GetInputValue(guid, "r", r), - }; - } + public override IEnumerable<(string, Type)> GetInputPorts() { yield return ("po", typeof(Vector4)); yield return ("pp", typeof(Vector4)); yield return ("r", typeof(Vector4)); yield return ("t", typeof(float)); } + public override FoldNodeSerialized Serialize(FoldGraphView gv) => new() { opcode = 4, float0 = gv.GetInputValue(guid, "t", 0f), vec0 = gv.GetInputValue(guid, "po", Vector4.zero), vec1 = gv.GetInputValue(guid, "pp", Vector4.zero), vec2 = gv.GetInputValue(guid, "r", Vector4.zero) }; } [Serializable] public class PlaneToTubeNodeData : FoldOperationData { - public Vector4 p; - public Vector4 r; - public Vector4 s; - public float t; - public override string Title => "Plane to Tube"; - - public override FoldNodeSerialized Serialize(FoldGraphView graphView) - { - return new FoldNodeSerialized - { - opcode = 2, - float0 = graphView.GetInputValue(guid, "t", t), - vec0 = graphView.GetInputValue(guid, "p", p), - vec1 = graphView.GetInputValue(guid, "r", r), - vec2 = graphView.GetInputValue(guid, "s", s), - }; - } + public override IEnumerable<(string, Type)> GetInputPorts() { yield return ("p", typeof(Vector4)); yield return ("r", typeof(Vector4)); yield return ("s", typeof(Vector4)); yield return ("t", typeof(float)); } + public override FoldNodeSerialized Serialize(FoldGraphView gv) => new() { opcode = 2, float0 = gv.GetInputValue(guid, "t", 0f), vec0 = gv.GetInputValue(guid, "p", Vector4.zero), vec1 = gv.GetInputValue(guid, "r", Vector4.zero), vec2 = gv.GetInputValue(guid, "s", Vector4.zero) }; } [Serializable] public class PointAlignNodeData : FoldOperationData { - public Vector4 po; - public Vector4 pp; - public Vector4 r; - public float t; - public override string Title => "Point Align"; - - public override FoldNodeSerialized Serialize(FoldGraphView graphView) - { - return new FoldNodeSerialized - { - opcode = 3, - float0 = graphView.GetInputValue(guid, "t", t), - vec0 = graphView.GetInputValue(guid, "po", po), - vec1 = graphView.GetInputValue(guid, "pp", pp), - vec2 = graphView.GetInputValue(guid, "r", r), - }; - } + public override IEnumerable<(string, Type)> GetInputPorts() { yield return ("po", typeof(Vector4)); yield return ("pp", typeof(Vector4)); yield return ("r", typeof(Vector4)); yield return ("t", typeof(float)); } + public override FoldNodeSerialized Serialize(FoldGraphView gv) => new() { opcode = 3, float0 = gv.GetInputValue(guid, "t", 0f), vec0 = gv.GetInputValue(guid, "po", Vector4.zero), vec1 = gv.GetInputValue(guid, "pp", Vector4.zero), vec2 = gv.GetInputValue(guid, "r", Vector4.zero) }; } [Serializable] public class TubeToPlaneNodeData : FoldOperationData { - public Vector4 p; - public Vector4 r; - public Vector4 s; - public float t; - public override string Title => "Tube to Plane"; - - public override FoldNodeSerialized Serialize(FoldGraphView graphView) - { - return new FoldNodeSerialized - { - opcode = 1, - float0 = graphView.GetInputValue(guid, "t", t), - vec0 = graphView.GetInputValue(guid, "p", p), - vec1 = graphView.GetInputValue(guid, "r", r), - vec2 = graphView.GetInputValue(guid, "s", s), - }; - } -} - -[Serializable] -public class KeyframeNodeData : FoldNodeData -{ - public override string Title => "Keyframe"; + public override IEnumerable<(string, Type)> GetInputPorts() { yield return ("p", typeof(Vector4)); yield return ("r", typeof(Vector4)); yield return ("s", typeof(Vector4)); yield return ("t", typeof(float)); } + public override FoldNodeSerialized Serialize(FoldGraphView gv) => new() { opcode = 1, float0 = gv.GetInputValue(guid, "t", 0f), vec0 = gv.GetInputValue(guid, "p", Vector4.zero), vec1 = gv.GetInputValue(guid, "r", Vector4.zero), vec2 = gv.GetInputValue(guid, "s", Vector4.zero) }; } public class FoldGraph : ScriptableObject diff --git a/Scripts/Fold/Editor/FoldGraphView.cs b/Scripts/Fold/Editor/FoldGraphView.cs index 6602af3..2c9e933 100644 --- a/Scripts/Fold/Editor/FoldGraphView.cs +++ b/Scripts/Fold/Editor/FoldGraphView.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; using UnityEditor; using UnityEditor.Experimental.GraphView; using UnityEditor.UIElements; @@ -15,16 +14,10 @@ public interface IFoldNode IFoldNode GetPreviousFold(); } -public interface IValueNode -{ - T GetValue(); -} - public abstract class FoldNodeView : Node { public FoldNodeData Data { get; private set; } protected FoldGraphView graphView; - readonly Dictionary ports = new(); public virtual void Initialize(FoldGraphView view, FoldNodeData data) @@ -35,12 +28,9 @@ public abstract class FoldNodeView : Node viewDataKey = data.guid; SetPosition(new Rect(data.position, Vector2.zero)); - // Allow shift+click to add to the current selection without clearing it. RegisterCallback(evt => { - if (!evt.shiftKey) - return; - + if (!evt.shiftKey) return; if (!graphView.selection.Contains(this)) graphView.AddToSelection(this); evt.StopImmediatePropagation(); @@ -65,290 +55,161 @@ public abstract class FoldNodeView : Node port.portName = name; ports[name] = port; graphView.BindEdgeConnector(port); - if (direction == Direction.Input) inputContainer.Add(port); - else outputContainer.Add(port); + (direction == Direction.Input ? inputContainer : outputContainer).Add(port); return port; } - public Port GetPort(string name) => ports.TryGetValue(name, out var port) ? port : null; + public Port GetPort(string name) => ports.TryGetValue(name, out var p) ? p : null; } -public abstract class FoldOperationNodeView : FoldNodeView, IFoldNode +public class ValueNodeView : FoldNodeView { - protected Port foldInputPort; - protected Port foldOutputPort; - - public override void Initialize(FoldGraphView view, FoldNodeData data) - { - base.Initialize(view, data); - - foldInputPort = AddPort(Direction.Input, Port.Capacity.Single, "Input", typeof(IFoldNode)); - foldOutputPort = AddPort(Direction.Output, Port.Capacity.Single, "Out", typeof(IFoldNode)); - } - - public IFoldNode GetPreviousFold() + object GetValue() => Data switch { - var edge = foldInputPort.connections.FirstOrDefault(); - return edge?.output?.node as IFoldNode; - } - - public abstract FoldNodeSerialized Serialize(); -} + FloatValueNodeData f => f.value, + VectorValueNodeData v => v.value, + GameObjectNodeData g => g.output, + _ => null + }; -public class FloatValueNodeView : FoldNodeView, IValueNode -{ - FloatValueNodeData DataTyped => (FloatValueNodeData)Data; + public T GetTypedValue() => GetValue() is T v ? v : default; public override void Initialize(FoldGraphView view, FoldNodeData data) { base.Initialize(view, data); - - AddPort(Direction.Output, Port.Capacity.Multi, "Out", typeof(float)); - - var field = new FloatField("Value") { value = DataTyped.value }; - field.RegisterValueChangedCallback(evt => + var portType = data switch { - graphView.RecordUndo("Edit Float"); - DataTyped.value = evt.newValue; - graphView.MarkDirty(); - }); - mainContainer.Add(field); - } - - public float GetValue() => DataTyped.value; -} - -public class VectorValueNodeView : FoldNodeView, IValueNode -{ - VectorValueNodeData DataTyped => (VectorValueNodeData)Data; - - public override void Initialize(FoldGraphView view, FoldNodeData data) - { - base.Initialize(view, data); - - AddPort(Direction.Output, Port.Capacity.Multi, "Out", typeof(Vector4)); - - var field = new Vector4Field("Value") { value = DataTyped.value }; - field.RegisterValueChangedCallback(evt => - { - graphView.RecordUndo("Edit Vector"); - DataTyped.value = evt.newValue; - graphView.MarkDirty(); - }); - mainContainer.Add(field); - } - - public Vector4 GetValue() => DataTyped.value; -} - -public class GameObjectNodeView : FoldNodeView, IValueNode -{ - GameObjectNodeData DataTyped => (GameObjectNodeData)Data; - - public override void Initialize(FoldGraphView view, FoldNodeData data) - { - base.Initialize(view, data); - - AddPort(Direction.Output, Port.Capacity.Multi, "Out", typeof(GameObject)); + FloatValueNodeData => typeof(float), + VectorValueNodeData => typeof(Vector4), + GameObjectNodeData => typeof(GameObject), + _ => typeof(object) + }; + AddPort(Direction.Output, Port.Capacity.Multi, "Out", portType); - var field = new ObjectField("Object") + VisualElement field = data switch { - allowSceneObjects = true, - objectType = typeof(GameObject), - value = DataTyped.output + FloatValueNodeData f => CreateFloatField(f), + VectorValueNodeData v => CreateVectorField(v), + GameObjectNodeData g => CreateObjectField(g), + _ => null }; - field.RegisterValueChangedCallback(evt => - { - graphView.RecordUndo("Edit GameObject"); - DataTyped.output = evt.newValue as GameObject; - graphView.MarkDirty(); - }); - mainContainer.Add(field); - } - - public GameObject GetValue() => DataTyped.output; -} - -public class AxisAlignNodeView : FoldOperationNodeView -{ - AxisAlignNodeData DataTyped => (AxisAlignNodeData)Data; - - Port poPort, ppPort, rPort, tPort; - - public override void Initialize(FoldGraphView view, FoldNodeData data) - { - base.Initialize(view, data); - poPort = AddPort(Direction.Input, Port.Capacity.Single, "po", typeof(Vector4)); - ppPort = AddPort(Direction.Input, Port.Capacity.Single, "pp", typeof(Vector4)); - rPort = AddPort(Direction.Input, Port.Capacity.Single, "r", typeof(Vector4)); - tPort = AddPort(Direction.Input, Port.Capacity.Single, "t", typeof(float)); + if (field != null) extensionContainer.Add(field); + RefreshExpandedState(); } - public override FoldNodeSerialized Serialize() + FloatField CreateFloatField(FloatValueNodeData d) { - return DataTyped.Serialize(graphView); + var f = new FloatField("Value") { value = d.value }; + f.RegisterValueChangedCallback(e => { graphView.RecordUndo("Edit Float"); d.value = e.newValue; graphView.MarkDirty(); }); + return f; } -} -public class PlaneToTubeNodeView : FoldOperationNodeView -{ - PlaneToTubeNodeData DataTyped => (PlaneToTubeNodeData)Data; - - public override void Initialize(FoldGraphView view, FoldNodeData data) + Vector4Field CreateVectorField(VectorValueNodeData d) { - base.Initialize(view, data); - - AddPort(Direction.Input, Port.Capacity.Single, "p", typeof(Vector4)); - AddPort(Direction.Input, Port.Capacity.Single, "r", typeof(Vector4)); - AddPort(Direction.Input, Port.Capacity.Single, "s", typeof(Vector4)); - AddPort(Direction.Input, Port.Capacity.Single, "t", typeof(float)); + var f = new Vector4Field("Value") { value = d.value }; + f.RegisterValueChangedCallback(e => { graphView.RecordUndo("Edit Vector"); d.value = e.newValue; graphView.MarkDirty(); }); + return f; } - public override FoldNodeSerialized Serialize() => DataTyped.Serialize(graphView); -} - -public class PointAlignNodeView : FoldOperationNodeView -{ - PointAlignNodeData DataTyped => (PointAlignNodeData)Data; - - public override void Initialize(FoldGraphView view, FoldNodeData data) + ObjectField CreateObjectField(GameObjectNodeData d) { - base.Initialize(view, data); - - AddPort(Direction.Input, Port.Capacity.Single, "po", typeof(Vector4)); - AddPort(Direction.Input, Port.Capacity.Single, "pp", typeof(Vector4)); - AddPort(Direction.Input, Port.Capacity.Single, "r", typeof(Vector4)); - AddPort(Direction.Input, Port.Capacity.Single, "t", typeof(float)); + var f = new ObjectField("Object") { allowSceneObjects = true, objectType = typeof(GameObject), value = d.output }; + f.RegisterValueChangedCallback(e => { graphView.RecordUndo("Edit GameObject"); d.output = e.newValue as GameObject; graphView.MarkDirty(); }); + return f; } - - public override FoldNodeSerialized Serialize() => DataTyped.Serialize(graphView); } -public class TubeToPlaneNodeView : FoldOperationNodeView +public class OperationNodeView : FoldNodeView, IFoldNode { - TubeToPlaneNodeData DataTyped => (TubeToPlaneNodeData)Data; + Port foldInputPort; public override void Initialize(FoldGraphView view, FoldNodeData data) { base.Initialize(view, data); + foldInputPort = AddPort(Direction.Input, Port.Capacity.Single, "Input", typeof(IFoldNode)); + AddPort(Direction.Output, Port.Capacity.Single, "Out", typeof(IFoldNode)); - AddPort(Direction.Input, Port.Capacity.Single, "p", typeof(Vector4)); - AddPort(Direction.Input, Port.Capacity.Single, "r", typeof(Vector4)); - AddPort(Direction.Input, Port.Capacity.Single, "s", typeof(Vector4)); - AddPort(Direction.Input, Port.Capacity.Single, "t", typeof(float)); + foreach (var (name, type) in ((FoldOperationData)data).GetInputPorts()) + AddPort(Direction.Input, Port.Capacity.Single, name, type); } - public override FoldNodeSerialized Serialize() => DataTyped.Serialize(graphView); + public IFoldNode GetPreviousFold() => foldInputPort.connections.FirstOrDefault()?.output?.node as IFoldNode; + public FoldNodeSerialized Serialize() => ((FoldOperationData)Data).Serialize(graphView); } public class KeyframeNodeView : FoldNodeView { - Port gameObjectPort; - Port foldPort; - readonly List validationMessages = new(); + Port gameObjectPort, foldPort; public override void Initialize(FoldGraphView view, FoldNodeData data) { base.Initialize(view, data); - gameObjectPort = AddPort(Direction.Input, Port.Capacity.Single, "GameObject", typeof(GameObject)); foldPort = AddPort(Direction.Input, Port.Capacity.Single, "Fold Data", typeof(IFoldNode)); - - var button = new Button(OnGenerateClick) { text = "Generate Keyframe" }; - mainContainer.Add(button); - } - - GameObject GetTargetObject() - { - var edge = gameObjectPort.connections.FirstOrDefault(); - if (edge?.output?.node is IValueNode goProvider) - return goProvider.GetValue(); - return null; - } - - IFoldNode GetFoldInput() - { - var edge = foldPort.connections.FirstOrDefault(); - return edge?.output?.node as IFoldNode; + extensionContainer.Add(new Button(OnGenerateClick) { text = "Generate Keyframe" }); + RefreshExpandedState(); } void OnGenerateClick() { - validationMessages.Clear(); - - var go = GetTargetObject(); - if (go == null) - { - validationMessages.Add("GameObject input missing or empty."); - ShowValidation(); - return; - } + var goEdge = gameObjectPort.connections.FirstOrDefault(); + var go = (goEdge?.output?.node as ValueNodeView)?.GetTypedValue(); + if (go == null) { graphView.ShowNotification("GameObject input missing or empty."); return; } var renderer = go.GetComponent(); - if (renderer == null) - { - validationMessages.Add("GameObject must have a MeshRenderer component."); - ShowValidation(); - return; - } + if (renderer == null) { graphView.ShowNotification("GameObject must have a MeshRenderer component."); return; } - var foldNode = GetFoldInput(); - if (foldNode == null) - { - validationMessages.Add("Fold input not connected."); - ShowValidation(); - return; - } + var foldNode = foldPort.connections.FirstOrDefault()?.output?.node as IFoldNode; + if (foldNode == null) { graphView.ShowNotification("Fold input not connected."); return; } var stack = new Stack(); - var current = foldNode; - while (current != null) - { - stack.Push(current); - current = current.GetPreviousFold(); - } + for (var cur = foldNode; cur != null; cur = cur.GetPreviousFold()) + stack.Push(cur); Undo.RecordObject(renderer, "Generate Keyframe"); var mat = renderer.material; - const int kNumSlots = 16; - for (int i = 0; i < kNumSlots; i++) + for (int i = 0; i < 16; i++) { - string slotPrefix = $"_Vertex_Deformation_Slot_{i}_"; - IFoldNode node = stack.Count > 0 ? stack.Pop() : null; - FoldNodeSerialized data = node?.Serialize(); - - bool active = data != null; - mat.SetFloat(slotPrefix + "Enabled", active ? 1.0f : 0.0f); - mat.SetInt(slotPrefix + "Opcode", active ? data.opcode : 0); - - mat.SetFloat(slotPrefix + "Float_0", active ? data.float0 : 0.0f); - mat.SetFloat(slotPrefix + "Float_1", active ? data.float1 : 0.0f); - mat.SetFloat(slotPrefix + "Float_2", active ? data.float2 : 0.0f); - mat.SetFloat(slotPrefix + "Float_3", active ? data.float3 : 0.0f); - - mat.SetVector(slotPrefix + "Vector_0", active ? data.vec0 : Vector4.zero); - mat.SetVector(slotPrefix + "Vector_1", active ? data.vec1 : Vector4.zero); - mat.SetVector(slotPrefix + "Vector_2", active ? data.vec2 : Vector4.zero); - mat.SetVector(slotPrefix + "Vector_3", active ? data.vec3 : Vector4.zero); + var prefix = $"_Vertex_Deformation_Slot_{i}_"; + var node = stack.Count > 0 ? stack.Pop() : null; + var s = node?.Serialize(); + bool on = s != null; + + mat.SetFloat(prefix + "Enabled", on ? 1f : 0f); + mat.SetInt(prefix + "Opcode", on ? s.opcode : 0); + mat.SetFloat(prefix + "Float_0", on ? s.float0 : 0f); + mat.SetFloat(prefix + "Float_1", on ? s.float1 : 0f); + mat.SetFloat(prefix + "Float_2", on ? s.float2 : 0f); + mat.SetFloat(prefix + "Float_3", on ? s.float3 : 0f); + mat.SetVector(prefix + "Vector_0", on ? s.vec0 : Vector4.zero); + mat.SetVector(prefix + "Vector_1", on ? s.vec1 : Vector4.zero); + mat.SetVector(prefix + "Vector_2", on ? s.vec2 : Vector4.zero); + mat.SetVector(prefix + "Vector_3", on ? s.vec3 : Vector4.zero); } - EditorUtility.SetDirty(renderer); } - - void ShowValidation() - { - graphView.ShowNotification(string.Join("\n", validationMessages)); - } } public class FoldGraphView : GraphView { + static readonly (string label, Type dataType)[] AllNodeOptions = + { + ("Fold/Axis Align", typeof(AxisAlignNodeData)), + ("Fold/Plane to Tube", typeof(PlaneToTubeNodeData)), + ("Fold/Point Align", typeof(PointAlignNodeData)), + ("Fold/Tube to Plane", typeof(TubeToPlaneNodeData)), + ("Value/Float", typeof(FloatValueNodeData)), + ("Value/Vector", typeof(VectorValueNodeData)), + ("Value/GameObject", typeof(GameObjectNodeData)), + ("Output/Keyframe", typeof(KeyframeNodeData)), + }; + readonly FoldWindow window; public readonly FoldGraph graphAsset; readonly Dictionary nodeLookup = new(); - bool suppressGraphChanges; readonly FoldEdgeConnectorListener edgeConnectorListener; + bool suppressGraphChanges; public FoldGraphView(FoldWindow owner, FoldGraph graph) { @@ -362,76 +223,30 @@ public class FoldGraphView : GraphView this.AddManipulator(new SelectionDragger()); this.AddManipulator(new RectangleSelector()); - gridBackground = new GridBackground(); - Insert(0, gridBackground); - gridBackground.StretchToParentSize(); + var grid = new GridBackground(); + Insert(0, grid); + grid.StretchToParentSize(); graphViewChanged = OnGraphViewChanged; + nodeCreationRequest = ctx => ShowSearchWindow(null, ctx.screenMousePosition); RegisterCallback(OnKeyDown); + RegisterCallback(evt => { evt.menu.ClearItems(); evt.StopImmediatePropagation(); evt.PreventDefault(); ShowSearchWindow(null, evt.mousePosition); }); Undo.undoRedoPerformed += OnUndoRedo; Reload(); } - GridBackground gridBackground; - - public override List GetCompatiblePorts(Port startPort, NodeAdapter nodeAdapter) - { - return ports.ToList().Where(p => - p.node != startPort.node && - p.direction != startPort.direction && - IsTypeCompatible(startPort.portType, p.portType)).ToList(); - } - - bool IsTypeCompatible(Type a, Type b) - { - if (a == null || b == null) - return true; - 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); - } + public override List GetCompatiblePorts(Port startPort, NodeAdapter nodeAdapter) => + ports.Where(p => p.node != startPort.node && p.direction != startPort.direction && + (startPort.portType == null || p.portType == null || startPort.portType.IsAssignableFrom(p.portType) || p.portType.IsAssignableFrom(startPort.portType))).ToList(); - var connector = new EdgeConnector(edgeConnectorListener); - field?.SetValue(port, connector); - port.AddManipulator(connector); - } + internal void BindEdgeConnector(Port port) => port.AddManipulator(new EdgeConnector(edgeConnectorListener)); GraphViewChange OnGraphViewChanged(GraphViewChange change) { - if (suppressGraphChanges) - return change; - - if (change.elementsToRemove != null) - { - foreach (var element in change.elementsToRemove) - { - if (element is Edge e) - { - RemoveEdgeData(e); - } - else if (element is FoldNodeView node) - { - RemoveNode(node); - } - } - } - - if (change.edgesToCreate != null) - { - foreach (var edge in change.edgesToCreate) - { - AddEdgeData(edge); - } - } - + if (suppressGraphChanges) return change; + change.elementsToRemove?.ForEach(e => { if (e is Edge edge) RemoveEdgeData(edge); else if (e is FoldNodeView node) RemoveNode(node); }); + change.edgesToCreate?.ForEach(AddEdgeData); return change; } @@ -446,33 +261,17 @@ public class FoldGraphView : GraphView internal void AddEdgeData(Edge edge) { - if (edge.input?.node is not FoldNodeView inputNode || - edge.output?.node is not FoldNodeView outputNode) - return; - + if (edge.input?.node is not FoldNodeView inp || edge.output?.node is not FoldNodeView outp) return; RecordUndo("Add Edge"); - graphAsset.edges.Add(new FoldEdge - { - inputNodeGuid = inputNode.Data.guid, - inputPortName = edge.input.portName, - outputNodeGuid = outputNode.Data.guid, - outputPortName = edge.output.portName - }); + graphAsset.edges.Add(new FoldEdge { inputNodeGuid = inp.Data.guid, inputPortName = edge.input.portName, outputNodeGuid = outp.Data.guid, outputPortName = edge.output.portName }); MarkDirty(); } void RemoveEdgeData(Edge edge) { - if (edge.input?.node is not FoldNodeView inputNode || - edge.output?.node is not FoldNodeView outputNode) - return; - + if (edge.input?.node is not FoldNodeView inp || edge.output?.node is not FoldNodeView outp) return; RecordUndo("Remove Edge"); - graphAsset.edges.RemoveAll(e => - e.inputNodeGuid == inputNode.Data.guid && - e.outputNodeGuid == outputNode.Data.guid && - e.inputPortName == edge.input.portName && - e.outputPortName == edge.output.portName); + graphAsset.edges.RemoveAll(e => e.inputNodeGuid == inp.Data.guid && e.outputNodeGuid == outp.Data.guid && e.inputPortName == edge.input.portName && e.outputPortName == edge.output.portName); MarkDirty(); } @@ -480,350 +279,144 @@ public class FoldGraphView : GraphView { suppressGraphChanges = true; nodeLookup.Clear(); - var removable = graphElements.Where(e => e is Node || e is Edge).ToList(); - DeleteElements(removable); + DeleteElements(graphElements.Where(e => e is Node or Edge).ToList()); - foreach (var nodeData in graphAsset.nodes) + foreach (var d in graphAsset.nodes) { var n = CreateNode(d); nodeLookup[d.guid] = n; AddElement(n); } + foreach (var e in graphAsset.edges) { - var node = CreateNode(nodeData); - nodeLookup[nodeData.guid] = node; - AddElement(node); + if (nodeLookup.TryGetValue(e.outputNodeGuid, out var o) && nodeLookup.TryGetValue(e.inputNodeGuid, out var i)) + if (o.GetPort(e.outputPortName) is { } op && i.GetPort(e.inputPortName) is { } ip) + AddElement(op.ConnectTo(ip)); } - - foreach (var edgeData in graphAsset.edges) - { - if (!nodeLookup.TryGetValue(edgeData.outputNodeGuid, out var outputNode) || - !nodeLookup.TryGetValue(edgeData.inputNodeGuid, out var inputNode)) - continue; - - var outputPort = outputNode.GetPort(edgeData.outputPortName); - var inputPort = inputNode.GetPort(edgeData.inputPortName); - if (outputPort == null || inputPort == null) - continue; - - var edge = new Edge - { - output = outputPort, - input = inputPort - }; - edge.input.Connect(edge); - edge.output.Connect(edge); - AddElement(edge); - } - suppressGraphChanges = false; } void OnKeyDown(KeyDownEvent evt) { - bool isDuplicate = (evt.keyCode == KeyCode.D) && (evt.commandKey || evt.ctrlKey); - if (!isDuplicate) - return; - - evt.StopImmediatePropagation(); - DuplicateSelectedNodes(); + if (evt.keyCode == KeyCode.D && (evt.commandKey || evt.ctrlKey)) { evt.StopImmediatePropagation(); DuplicateSelectedNodes(); } } void DuplicateSelectedNodes() { - var selectedNodes = selection.OfType().ToList(); - if (selectedNodes.Count == 0) - return; + var sel = selection.OfType().ToList(); + if (sel.Count == 0) return; suppressGraphChanges = true; RecordUndo("Duplicate Nodes"); - var cloneMap = new Dictionary(); - foreach (var original in selectedNodes) + var map = new Dictionary(); + foreach (var orig in sel) { - var cloneData = CloneNodeData(original.Data); - cloneData.position += new Vector2(30f, 30f); - graphAsset.nodes.Add(cloneData); - - var cloneView = CreateNode(cloneData); - cloneMap[original.Data.guid] = cloneView; - nodeLookup[cloneData.guid] = cloneView; - AddElement(cloneView); + var clone = JsonUtility.FromJson(JsonUtility.ToJson(orig.Data)); + clone = (FoldNodeData)Activator.CreateInstance(orig.Data.GetType()); + JsonUtility.FromJsonOverwrite(JsonUtility.ToJson(orig.Data), clone); + clone.guid = Guid.NewGuid().ToString(); + clone.position += new Vector2(30f, 30f); + graphAsset.nodes.Add(clone); + var v = CreateNode(clone); + map[orig.Data.guid] = v; + nodeLookup[clone.guid] = v; + AddElement(v); } - var edgesToClone = graphAsset.edges - .Where(e => cloneMap.ContainsKey(e.outputNodeGuid) && cloneMap.ContainsKey(e.inputNodeGuid)) - .ToList(); - - foreach (var edgeData in edgesToClone) - { - var outputNode = cloneMap[edgeData.outputNodeGuid]; - var inputNode = cloneMap[edgeData.inputNodeGuid]; - var outputPort = outputNode.GetPort(edgeData.outputPortName); - var inputPort = inputNode.GetPort(edgeData.inputPortName); - if (outputPort == null || inputPort == null) - continue; - - var edge = new Edge - { - output = outputPort, - input = inputPort - }; - edge.input.Connect(edge); - edge.output.Connect(edge); - AddElement(edge); - AddEdgeData(edge); - } + foreach (var e in graphAsset.edges.Where(e => map.ContainsKey(e.outputNodeGuid) && map.ContainsKey(e.inputNodeGuid))) + if (map[e.outputNodeGuid].GetPort(e.outputPortName) is { } op && map[e.inputNodeGuid].GetPort(e.inputPortName) is { } ip) + { var edge = op.ConnectTo(ip); AddElement(edge); AddEdgeData(edge); } suppressGraphChanges = false; - ClearSelection(); - foreach (var newNode in cloneMap.Values) - AddToSelection(newNode); - + foreach (var n in map.Values) AddToSelection(n); MarkDirty(); } - FoldNodeData CloneNodeData(FoldNodeData data) - { - var json = JsonUtility.ToJson(data); - var clone = (FoldNodeData)Activator.CreateInstance(data.GetType()); - JsonUtility.FromJsonOverwrite(json, clone); - clone.guid = Guid.NewGuid().ToString(); - return clone; - } - FoldNodeView CreateNode(FoldNodeData data) { FoldNodeView view = data switch { - FloatValueNodeData => new FloatValueNodeView(), - VectorValueNodeData => new VectorValueNodeView(), - GameObjectNodeData => new GameObjectNodeView(), - AxisAlignNodeData => new AxisAlignNodeView(), - PlaneToTubeNodeData => new PlaneToTubeNodeView(), - PointAlignNodeData => new PointAlignNodeView(), - TubeToPlaneNodeData => new TubeToPlaneNodeView(), + FloatValueNodeData or VectorValueNodeData or GameObjectNodeData => new ValueNodeView(), + FoldOperationData => new OperationNodeView(), KeyframeNodeData => new KeyframeNodeView(), - _ => throw new ArgumentOutOfRangeException(nameof(data), $"Unknown node data type {data.GetType().Name}") + _ => throw new ArgumentOutOfRangeException(nameof(data)) }; - view.Initialize(this, data); return view; } - public T CreateNode(Vector2 position) where T : FoldNodeData, new() - { - RecordUndo("Create Node"); - var data = new T { position = position }; - graphAsset.nodes.Add(data); - var view = CreateNode(data); - nodeLookup[data.guid] = view; - AddElement(view); - MarkDirty(); - return data; - } - internal FoldNodeView CreateNode(Type dataType, Vector2 position) { - RecordUndo("Create Node"); + if (!suppressGraphChanges) RecordUndo("Create Node"); var data = (FoldNodeData)Activator.CreateInstance(dataType); data.position = position; graphAsset.nodes.Add(data); var view = CreateNode(data); nodeLookup[data.guid] = view; AddElement(view); + view.RefreshExpandedState(); + view.RefreshPorts(); + MarkDirty(); return view; } - public override void BuildContextualMenu(ContextualMenuPopulateEvent evt) - { - ShowSearchWindow(null, evt.mousePosition); - evt.StopPropagation(); - } - - public void MarkDirty() - { - EditorUtility.SetDirty(graphAsset); - AssetDatabase.SaveAssets(); - } - - public void ShowNodeCreationMenuFromPort(Port startPort, Vector2 worldPosition) => ShowSearchWindow(startPort, worldPosition); + public void MarkDirty() => EditorUtility.SetDirty(graphAsset); + public void ShowNotification(string msg) => window.ShowNotification(msg); + public void ShowNodeCreationMenuFromPort(Port port, Vector2 pos) => ShowSearchWindow(port, pos); + public void Dispose() => Undo.undoRedoPerformed -= OnUndoRedo; + void OnUndoRedo() => Reload(); + internal void RecordUndo(string label) { if (!suppressGraphChanges) Undo.RegisterCompleteObjectUndo(graphAsset, label); } - public void CreateNodeAndConnect(Port startPort, Type dataType, Vector2 graphPosition) + public FoldNodeView CreateNodeAndConnect(Port startPort, Type dataType, Vector2 pos) { suppressGraphChanges = true; - - var view = CreateNode(dataType, graphPosition); - - var compatible = GetCompatiblePorts(startPort, null).FirstOrDefault(p => p.node == view); - if (compatible != null) + var view = CreateNode(dataType, pos); + var compat = GetCompatiblePorts(startPort, null).FirstOrDefault(p => p.node == view); + if (compat != 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); + var (o, i) = startPort.direction == Direction.Output ? (startPort, compat) : (compat, startPort); + var edge = o.ConnectTo(i); AddElement(edge); AddEdgeData(edge); } - suppressGraphChanges = false; MarkDirty(); + return view; } - 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(string nodeGuid, string portName, T fallback) - { - var node = nodeLookup.TryGetValue(nodeGuid, out var found) ? found : null; - if (node == null) return fallback; - - var port = node.GetPort(portName); - var edge = port?.connections.FirstOrDefault(); - if (edge?.output?.node is IValueNode provider) - return provider.GetValue(); - - return fallback; - } - - public void ShowNotification(string message) - { - window.ShowNotification(message); - } - - void OnUndoRedo() - { - Reload(); - } - - public void Dispose() + internal IEnumerable<(string label, Type dataType)> GetNodeOptions(Port port) { - Undo.undoRedoPerformed -= OnUndoRedo; + if (port == null) return AllNodeOptions; + var t = port.portType; + bool @out = port.direction == Direction.Output; + return AllNodeOptions.Where(o => + (t == typeof(float) || t == typeof(Vector4)) ? (@out ? typeof(FoldOperationData).IsAssignableFrom(o.dataType) : (t == typeof(float) ? o.dataType == typeof(FloatValueNodeData) : o.dataType == typeof(VectorValueNodeData))) : + t == typeof(GameObject) ? (@out ? o.dataType == typeof(KeyframeNodeData) : o.dataType == typeof(GameObjectNodeData)) : + typeof(IFoldNode).IsAssignableFrom(t) && (typeof(FoldOperationData).IsAssignableFrom(o.dataType) || (@out && o.dataType == typeof(KeyframeNodeData)))); } - internal void RecordUndo(string label) - { - if (suppressGraphChanges) - return; - Undo.RegisterCompleteObjectUndo(graphAsset, label); - } + public T GetInputValue(string guid, string port, T fallback) => + nodeLookup.TryGetValue(guid, out var n) && n.GetPort(port)?.connections.FirstOrDefault()?.output?.node is ValueNodeView v ? v.GetTypedValue() : fallback; - void ShowSearchWindow(Port startPort, Vector2 worldPosition) + void ShowSearchWindow(Port port, Vector2 pos) { - var provider = ScriptableObject.CreateInstance(); - provider.Initialize(this, startPort, worldPosition); - if (!provider.HasEntries()) - return; - - SearchWindow.Open(new SearchWindowContext(GUIUtility.GUIToScreenPoint(worldPosition)), provider); + var p = ScriptableObject.CreateInstance(); + p.Initialize(this, port, pos); + if (p.HasEntries()) SearchWindow.Open(new SearchWindowContext(GUIUtility.GUIToScreenPoint(pos)), p); } } class FoldEdgeConnectorListener : IEdgeConnectorListener { readonly FoldGraphView graphView; + public FoldEdgeConnectorListener(FoldGraphView gv) => graphView = gv; - 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 OnDropOutsidePort(Edge edge, Vector2 pos) => graphView.ShowNodeCreationMenuFromPort(edge.output ?? edge.input, pos); public void OnDrop(GraphView gv, Edge edge) { - if (edge.input == null || edge.output == null) - return; - + 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); + graphView?.AddEdgeData(edge); } } @@ -832,50 +425,30 @@ 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(); - } + List<(string label, Type dataType)> options; - public bool HasEntries() => cachedOptions != null && cachedOptions.Count > 0; + public void Initialize(FoldGraphView gv, Port port, Vector2 pos) { graphView = gv; startPort = port; worldPosition = pos; options = gv.GetNodeOptions(port).ToList(); } + public bool HasEntries() => options?.Count > 0; - public List CreateSearchTree(SearchWindowContext context) + public List CreateSearchTree(SearchWindowContext ctx) { - var entries = new List - { - new SearchTreeGroupEntry(new GUIContent("Create Node"), 0) - }; - - foreach (var option in cachedOptions) + var entries = new List { new SearchTreeGroupEntry(new GUIContent("Create Node"), 0) }; + foreach (var grp in options.GroupBy(o => o.label.Split('/')[0]).OrderBy(g => g.Key)) { - entries.Add(new SearchTreeEntry(new GUIContent(option.label)) - { - level = 1, - userData = option.dataType - }); + entries.Add(new SearchTreeGroupEntry(new GUIContent(grp.Key), 1)); + foreach (var opt in grp) + entries.Add(new SearchTreeEntry(new GUIContent(opt.label.Split('/')[1])) { level = 2, userData = opt.dataType }); } - return entries; } - public bool OnSelectEntry(SearchTreeEntry entry, SearchWindowContext context) + public bool OnSelectEntry(SearchTreeEntry entry, SearchWindowContext ctx) { - 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); - + if (entry.userData is not Type dataType) return false; + var pos = graphView.contentViewContainer.WorldToLocal(worldPosition); + if (startPort != null) graphView.CreateNodeAndConnect(startPort, dataType, pos); + else graphView.CreateNode(dataType, pos); return true; } } + diff --git a/Scripts/Fold/Editor/FoldWindow.cs b/Scripts/Fold/Editor/FoldWindow.cs index 3ba556c..f0f9b42 100644 --- a/Scripts/Fold/Editor/FoldWindow.cs +++ b/Scripts/Fold/Editor/FoldWindow.cs @@ -21,7 +21,6 @@ public class FoldWindow : EditorWindow { graphAsset = LoadOrCreateGraph(); ConstructGraphView(); - Undo.undoRedoPerformed += OnUndoRedo; } void OnDisable() @@ -34,7 +33,6 @@ public class FoldWindow : EditorWindow } AssetDatabase.SaveAssets(); - Undo.undoRedoPerformed -= OnUndoRedo; } void OnFocus() @@ -77,11 +75,6 @@ public class FoldWindow : EditorWindow rootVisualElement.Add(graphView); } - void OnUndoRedo() - { - graphView?.Reload(); - } - public void ShowNotification(string message) { if (string.IsNullOrEmpty(message)) -- cgit v1.2.3