using System; using System.Collections.Generic; using System.Linq; using RMGUI.GraphView; using UnityEditor.Graphing.Util; using UnityEngine; using UnityEngine.Graphing; namespace UnityEditor.Graphing.Drawing { [Serializable] public abstract class AbstractGraphPresenter : GraphViewPresenter { protected GraphTypeMapper typeMapper { get; set; } public IGraphAsset graphAsset { get; private set; } [SerializeField] private TitleBarPresenter m_TitleBar; [SerializeField] private EditorWindow m_Container; public TitleBarPresenter titleBar { get { return m_TitleBar; } } protected AbstractGraphPresenter() { typeMapper = new GraphTypeMapper(typeof(GraphNodePresenter)); } void OnNodeChanged(INode inNode, ModificationScope scope) { var dependentNodes = new List(); NodeUtils.CollectNodesNodeFeedsInto(dependentNodes, inNode); foreach (var node in dependentNodes) { var theElements = m_Elements.OfType().ToList(); var found = theElements.Where(x => x.node.guid == node.guid).ToList(); foreach (var drawableNodeData in found) drawableNodeData.OnModified(scope); } if (scope == ModificationScope.Topological) UpdateData(); EditorUtility.SetDirty(graphAsset.GetScriptableObject()); if (m_Container != null) m_Container.Repaint(); } private void UpdateData() { // Find all nodes currently being drawn which are no longer in the graph (i.e. deleted) var deletedElements = m_Elements .OfType() .Where(nd => !graphAsset.graph.GetNodes().Contains(nd.node)) .OfType() .ToList(); var deletedEdges = m_Elements.OfType() .Where(ed => !graphAsset.graph.edges.Contains(ed.edge)); // Find all edges currently being drawn which are no longer in the graph (i.e. deleted) foreach (var edgeData in deletedEdges) { // Make sure to disconnect the node, otherwise new connections won't be allowed for the used slots edgeData.output.Disconnect(edgeData); edgeData.input.Disconnect(edgeData); var toNodeGuid = edgeData.edge.inputSlot.nodeGuid; var toNode = m_Elements.OfType().FirstOrDefault(nd => nd.node.guid == toNodeGuid); if (toNode != null) { // Make the input node (i.e. right side of the connection) re-render OnNodeChanged(toNode.node, ModificationScope.Graph); } deletedElements.Add(edgeData); } // Remove all nodes and edges marked for deletion foreach (var deletedElement in deletedElements) { m_Elements.Remove(deletedElement); } var addedNodes = new List(); // Find all new nodes and mark for addition foreach (var node in graphAsset.graph.GetNodes()) { // Check whether node already exists if (m_Elements.OfType().Any(e => e.node == node)) continue; var nodeData = (GraphNodePresenter)typeMapper.Create(node); node.onModified += OnNodeChanged; nodeData.Initialize(node); addedNodes.Add(nodeData); } // Create edge data for nodes marked for addition var drawableEdges = new List(); foreach (var addedNode in addedNodes) { var baseNode = addedNode.node; foreach (var slot in baseNode.GetOutputSlots()) { var sourceAnchors = addedNode.elements.OfType(); var sourceAnchor = sourceAnchors.FirstOrDefault(x => x.slot == slot); var edges = baseNode.owner.GetEdges(new SlotReference(baseNode.guid, slot.id)); foreach (var edge in edges) { var toNode = baseNode.owner.GetNodeFromGuid(edge.inputSlot.nodeGuid); var toSlot = toNode.FindInputSlot(edge.inputSlot.slotId); var targetNode = addedNodes.FirstOrDefault(x => x.node == toNode); var targetAnchors = targetNode.elements.OfType(); var targetAnchor = targetAnchors.FirstOrDefault(x => x.slot == toSlot); var edgeData = CreateInstance(); edgeData.Initialize(edge); edgeData.output = sourceAnchor; edgeData.output.Connect(edgeData); edgeData.input = targetAnchor; edgeData.input.Connect(edgeData); drawableEdges.Add(edgeData); } } } // Add nodes marked for addition m_Elements.AddRange(addedNodes.OfType()); // Find edges in the graph that are not being drawn and create edge data for them foreach (var edge in graphAsset.graph.edges) { if (!m_Elements.OfType().Any(ed => ed.edge == edge)) { var fromNode = graphAsset.graph.GetNodeFromGuid(edge.outputSlot.nodeGuid); var fromSlot = fromNode.FindOutputSlot(edge.outputSlot.slotId); var sourceNode = m_Elements.OfType().FirstOrDefault(x => x.node == fromNode); var sourceAnchors = sourceNode.elements.OfType(); var sourceAnchor = sourceAnchors.FirstOrDefault(x => x.slot == fromSlot); var toNode = graphAsset.graph.GetNodeFromGuid(edge.inputSlot.nodeGuid); var toSlot = toNode.FindInputSlot(edge.inputSlot.slotId); var targetNode = m_Elements.OfType().FirstOrDefault(x => x.node == toNode); var targetAnchors = targetNode.elements.OfType(); var targetAnchor = targetAnchors.FirstOrDefault(x => x.slot == toSlot); OnNodeChanged(targetNode.node, ModificationScope.Graph); var edgeData = CreateInstance(); edgeData.Initialize(edge); edgeData.output = sourceAnchor; edgeData.output.Connect(edgeData); edgeData.input = targetAnchor; edgeData.input.Connect(edgeData); drawableEdges.Add(edgeData); } } m_Elements.AddRange(drawableEdges.OfType()); } public virtual void Initialize(IGraphAsset graphAsset, AbstractGraphEditWindow container) { this.graphAsset = graphAsset; m_Container = container; m_TitleBar = CreateInstance(); m_TitleBar.Initialize(container); if (graphAsset == null) return; UpdateData(); } public void AddNode(INode node) { graphAsset.graph.AddNode(node); EditorUtility.SetDirty(graphAsset.GetScriptableObject()); UpdateData(); } public void RemoveElements(IEnumerable nodes, IEnumerable edges) { graphAsset.graph.RemoveElements(nodes.Select(x => x.node), edges.Select(x => x.edge)); graphAsset.graph.ValidateGraph(); EditorUtility.SetDirty(graphAsset.GetScriptableObject()); UpdateData(); } public void Connect(GraphAnchorPresenter left, GraphAnchorPresenter right) { if (left != null && right != null) { graphAsset.graph.Connect(left.slot.slotReference, right.slot.slotReference); EditorUtility.SetDirty(graphAsset.GetScriptableObject()); UpdateData(); } } private CopyPasteGraph CreateCopyPasteGraph(IEnumerable selection) { var graph = new CopyPasteGraph(); foreach (var presenter in selection) { var nodePresenter = presenter as GraphNodePresenter; if (nodePresenter != null) { graph.AddNode(nodePresenter.node); foreach (var edge in NodeUtils.GetAllEdges(nodePresenter.node)) graph.AddEdge(edge); } var edgePresenter = presenter as GraphEdgePresenter; if (edgePresenter != null) graph.AddEdge(edgePresenter.edge); } return graph; } private CopyPasteGraph DeserializeCopyBuffer(string copyBuffer) { try { return JsonUtility.FromJson(copyBuffer); } catch { // ignored. just means copy buffer was not a graph :( return null; } } private void InsertCopyPasteGraph(CopyPasteGraph graph) { if (graph == null || graphAsset == null || graphAsset.graph == null) return; var addedNodes = new List(); var nodeGuidMap = new Dictionary(); foreach (var node in graph.GetNodes()) { var oldGuid = node.guid; var newGuid = node.RewriteGuid(); nodeGuidMap[oldGuid] = newGuid; var drawState = node.drawState; var position = drawState.position; position.x += 30; position.y += 30; drawState.position = position; node.drawState = drawState; graphAsset.graph.AddNode(node); addedNodes.Add(node); } // only connect edges within pasted elements, discard // external edges. var addedEdges = new List(); foreach (var edge in graph.edges) { var outputSlot = edge.outputSlot; var inputSlot = edge.inputSlot; Guid remappedOutputNodeGuid; Guid remappedInputNodeGuid; if (nodeGuidMap.TryGetValue(outputSlot.nodeGuid, out remappedOutputNodeGuid) && nodeGuidMap.TryGetValue(inputSlot.nodeGuid, out remappedInputNodeGuid)) { var outputSlotRef = new SlotReference(remappedOutputNodeGuid, outputSlot.slotId); var inputSlotRef = new SlotReference(remappedInputNodeGuid, inputSlot.slotId); addedEdges.Add(graphAsset.graph.Connect(outputSlotRef, inputSlotRef)); } } graphAsset.graph.ValidateGraph(); UpdateData(); graphAsset.drawingData.selection = addedNodes.Select(n => n.guid); } public void Copy(IEnumerable selection) { var graph = CreateCopyPasteGraph(selection); EditorGUIUtility.systemCopyBuffer = JsonUtility.ToJson(graph, true); } public void Duplicate(IEnumerable selection) { var graph = DeserializeCopyBuffer(JsonUtility.ToJson(CreateCopyPasteGraph(selection), true)); InsertCopyPasteGraph(graph); } public void Paste() { var pastedGraph = DeserializeCopyBuffer(EditorGUIUtility.systemCopyBuffer); InsertCopyPasteGraph(pastedGraph); } public override void AddElement(EdgePresenter edge) { Connect(edge.output as GraphAnchorPresenter, edge.input as GraphAnchorPresenter); } public override void AddElement(GraphElementPresenter element) { throw new ArgumentException("Not supported on Serializable Graph, data comes from data store"); } public override void RemoveElement(GraphElementPresenter element) { throw new ArgumentException("Not supported on Serializable Graph, data comes from data store"); } } }