123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419 |
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
- using UnityEditor;
- using UnityEditor.UIElements;
- using UnityEngine.UIElements;
- using UnityEditor.Experimental.GraphView;
- using System.Linq;
- using System;
- using UnityEditor.SceneManagement;
- using System.Reflection;
- using Status = UnityEngine.UIElements.DropdownMenuAction.Status;
- using Object = UnityEngine.Object;
- namespace GraphProcessor
- {
- /// <summary>
- /// Base class to write a custom view for a node
- /// </summary>
- public class BaseGraphView : GraphView, IDisposable
- {
- public delegate void ComputeOrderUpdatedDelegate();
- public delegate void NodeDuplicatedDelegate(BaseNode duplicatedNode, BaseNode newNode);
- /// <summary>
- /// Graph that owns of the node
- /// </summary>
- public BaseGraph graph;
- /// <summary>
- /// Connector listener that will create the edges between ports
- /// </summary>
- public BaseEdgeConnectorListener connectorListener;
- /// <summary>
- /// List of all node views in the graph
- /// </summary>
- /// <typeparam name="BaseNodeView"></typeparam>
- /// <returns></returns>
- public List< BaseNodeView > nodeViews = new List< BaseNodeView >();
- /// <summary>
- /// Dictionary of the node views accessed view the node instance, faster than a Find in the node view list
- /// </summary>
- /// <typeparam name="BaseNode"></typeparam>
- /// <typeparam name="BaseNodeView"></typeparam>
- /// <returns></returns>
- public Dictionary< BaseNode, BaseNodeView > nodeViewsPerNode = new Dictionary< BaseNode, BaseNodeView >();
- /// <summary>
- /// List of all edge views in the graph
- /// </summary>
- /// <typeparam name="EdgeView"></typeparam>
- /// <returns></returns>
- public List< EdgeView > edgeViews = new List< EdgeView >();
- /// <summary>
- /// List of all group views in the graph
- /// </summary>
- /// <typeparam name="GroupView"></typeparam>
- /// <returns></returns>
- public List< GroupView > groupViews = new List< GroupView >();
- #if UNITY_2020_1_OR_NEWER
- /// <summary>
- /// List of all sticky note views in the graph
- /// </summary>
- /// <typeparam name="StickyNoteView"></typeparam>
- /// <returns></returns>
- public List< StickyNoteView > stickyNoteViews = new List<StickyNoteView>();
- #endif
- /// <summary>
- /// List of all stack node views in the graph
- /// </summary>
- /// <typeparam name="BaseStackNodeView"></typeparam>
- /// <returns></returns>
- public List< BaseStackNodeView > stackNodeViews = new List< BaseStackNodeView >();
- Dictionary< Type, PinnedElementView > pinnedElements = new Dictionary< Type, PinnedElementView >();
- CreateNodeMenuWindow createNodeMenu;
- /// <summary>
- /// Triggered just after the graph is initialized
- /// </summary>
- public event Action initialized;
- /// <summary>
- /// Triggered just after the compute order of the graph is updated
- /// </summary>
- public event ComputeOrderUpdatedDelegate computeOrderUpdated;
- // Safe event relay from BaseGraph (safe because you are sure to always point on a valid BaseGraph
- // when one of these events is called), a graph switch can occur between two call tho
- /// <summary>
- /// Same event than BaseGraph.onExposedParameterListChanged
- /// Safe event (not triggered in case the graph is null).
- /// </summary>
- public event Action onExposedParameterListChanged;
- /// <summary>
- /// Same event than BaseGraph.onExposedParameterModified
- /// Safe event (not triggered in case the graph is null).
- /// </summary>
- public event Action< ExposedParameter > onExposedParameterModified;
- /// <summary>
- /// Triggered when a node is duplicated (crt-d) or copy-pasted (crtl-c/crtl-v)
- /// </summary>
- public event NodeDuplicatedDelegate nodeDuplicated;
- /// <summary>
- /// Object to handle nodes that shows their UI in the inspector.
- /// </summary>
- [SerializeField]
- protected NodeInspectorObject nodeInspector
- {
- get
- {
- if (graph.nodeInspectorReference == null)
- graph.nodeInspectorReference = CreateNodeInspectorObject();
- return graph.nodeInspectorReference as NodeInspectorObject;
- }
- }
- /// <summary>
- /// Workaround object for creating exposed parameter property fields.
- /// </summary>
- public ExposedParameterFieldFactory exposedParameterFactory { get; private set; }
- public SerializedObject serializedGraph { get; private set; }
- Dictionary<Type, (Type nodeType, MethodInfo initalizeNodeFromObject)> nodeTypePerCreateAssetType = new Dictionary<Type, (Type, MethodInfo)>();
- public BaseGraphView(EditorWindow window)
- {
- serializeGraphElements = SerializeGraphElementsCallback;
- canPasteSerializedData = CanPasteSerializedDataCallback;
- unserializeAndPaste = UnserializeAndPasteCallback;
- graphViewChanged = GraphViewChangedCallback;
- viewTransformChanged = ViewTransformChangedCallback;
- elementResized = ElementResizedCallback;
- RegisterCallback< KeyDownEvent >(KeyDownCallback);
- RegisterCallback< DragPerformEvent >(DragPerformedCallback);
- RegisterCallback< DragUpdatedEvent >(DragUpdatedCallback);
- RegisterCallback< MouseDownEvent >(MouseDownCallback);
- RegisterCallback< MouseUpEvent >(MouseUpCallback);
- InitializeManipulators();
- SetupZoom(0.05f, 2f);
- Undo.undoRedoPerformed += ReloadView;
- createNodeMenu = ScriptableObject.CreateInstance< CreateNodeMenuWindow >();
- createNodeMenu.Initialize(this, window);
- this.StretchToParentSize();
- }
- protected virtual NodeInspectorObject CreateNodeInspectorObject()
- {
- var inspector = ScriptableObject.CreateInstance<NodeInspectorObject>();
- inspector.name = "Node Inspector";
- inspector.hideFlags = HideFlags.HideAndDontSave ^ HideFlags.NotEditable;
- return inspector;
- }
- #region Callbacks
- protected override bool canCopySelection
- {
- get { return selection.Any(e => e is BaseNodeView || e is GroupView); }
- }
- protected override bool canCutSelection
- {
- get { return selection.Any(e => e is BaseNodeView || e is GroupView); }
- }
- string SerializeGraphElementsCallback(IEnumerable<GraphElement> elements)
- {
- var data = new CopyPasteHelper();
- foreach (BaseNodeView nodeView in elements.Where(e => e is BaseNodeView))
- {
- data.copiedNodes.Add(JsonSerializer.SerializeNode(nodeView.nodeTarget));
- foreach (var port in nodeView.nodeTarget.GetAllPorts())
- {
- if (port.portData.vertical)
- {
- foreach (var edge in port.GetEdges())
- data.copiedEdges.Add(JsonSerializer.Serialize(edge));
- }
- }
- }
- foreach (GroupView groupView in elements.Where(e => e is GroupView))
- data.copiedGroups.Add(JsonSerializer.Serialize(groupView.group));
- foreach (EdgeView edgeView in elements.Where(e => e is EdgeView))
- data.copiedEdges.Add(JsonSerializer.Serialize(edgeView.serializedEdge));
- ClearSelection();
- return JsonUtility.ToJson(data, true);
- }
- bool CanPasteSerializedDataCallback(string serializedData)
- {
- try {
- return JsonUtility.FromJson(serializedData, typeof(CopyPasteHelper)) != null;
- } catch {
- return false;
- }
- }
- void UnserializeAndPasteCallback(string operationName, string serializedData)
- {
- var data = JsonUtility.FromJson< CopyPasteHelper >(serializedData);
- RegisterCompleteObjectUndo(operationName);
- Dictionary<string, BaseNode> copiedNodesMap = new Dictionary<string, BaseNode>();
- var unserializedGroups = data.copiedGroups.Select(g => JsonSerializer.Deserialize<Group>(g)).ToList();
- foreach (var serializedNode in data.copiedNodes)
- {
- var node = JsonSerializer.DeserializeNode(serializedNode);
- if (node == null)
- continue ;
- string sourceGUID = node.GUID;
- graph.nodesPerGUID.TryGetValue(sourceGUID, out var sourceNode);
- //Call OnNodeCreated on the new fresh copied node
- node.createdFromDuplication = true;
- node.createdWithinGroup = unserializedGroups.Any(g => g.innerNodeGUIDs.Contains(sourceGUID));
- node.OnNodeCreated();
- //And move a bit the new node
- node.position.position += new Vector2(20, 20);
- var newNodeView = AddNode(node);
- // If the nodes were copied from another graph, then the source is null
- if (sourceNode != null)
- nodeDuplicated?.Invoke(sourceNode, node);
- copiedNodesMap[sourceGUID] = node;
- //Select the new node
- AddToSelection(nodeViewsPerNode[node]);
- }
- foreach (var group in unserializedGroups)
- {
- //Same than for node
- group.OnCreated();
- // try to centre the created node in the screen
- group.position.position += new Vector2(20, 20);
- var oldGUIDList = group.innerNodeGUIDs.ToList();
- group.innerNodeGUIDs.Clear();
- foreach (var guid in oldGUIDList)
- {
- graph.nodesPerGUID.TryGetValue(guid, out var node);
-
- // In case group was copied from another graph
- if (node == null)
- {
- copiedNodesMap.TryGetValue(guid, out node);
- group.innerNodeGUIDs.Add(node.GUID);
- }
- else
- {
- group.innerNodeGUIDs.Add(copiedNodesMap[guid].GUID);
- }
- }
- AddGroup(group);
- }
- foreach (var serializedEdge in data.copiedEdges)
- {
- var edge = JsonSerializer.Deserialize<SerializableEdge>(serializedEdge);
- edge.Deserialize();
- // Find port of new nodes:
- copiedNodesMap.TryGetValue(edge.inputNode.GUID, out var oldInputNode);
- copiedNodesMap.TryGetValue(edge.outputNode.GUID, out var oldOutputNode);
- // We avoid to break the graph by replacing unique connections:
- if (oldInputNode == null && !edge.inputPort.portData.acceptMultipleEdges || !edge.outputPort.portData.acceptMultipleEdges)
- continue;
- oldInputNode = oldInputNode ?? edge.inputNode;
- oldOutputNode = oldOutputNode ?? edge.outputNode;
- var inputPort = oldInputNode.GetPort(edge.inputPort.fieldName, edge.inputPortIdentifier);
- var outputPort = oldOutputNode.GetPort(edge.outputPort.fieldName, edge.outputPortIdentifier);
- var newEdge = SerializableEdge.CreateNewEdge(graph, inputPort, outputPort);
- if (nodeViewsPerNode.ContainsKey(oldInputNode) && nodeViewsPerNode.ContainsKey(oldOutputNode))
- {
- var edgeView = CreateEdgeView();
- edgeView.userData = newEdge;
- edgeView.input = nodeViewsPerNode[oldInputNode].GetPortViewFromFieldName(newEdge.inputFieldName, newEdge.inputPortIdentifier);
- edgeView.output = nodeViewsPerNode[oldOutputNode].GetPortViewFromFieldName(newEdge.outputFieldName, newEdge.outputPortIdentifier);
- Connect(edgeView);
- }
- }
- }
- public virtual EdgeView CreateEdgeView()
- {
- return new EdgeView();
- }
- GraphViewChange GraphViewChangedCallback(GraphViewChange changes)
- {
- if (changes.elementsToRemove != null)
- {
- RegisterCompleteObjectUndo("Remove Graph Elements");
- // Destroy priority of objects
- // We need nodes to be destroyed first because we can have a destroy operation that uses node connections
- changes.elementsToRemove.Sort((e1, e2) => {
- int GetPriority(GraphElement e)
- {
- if (e is BaseNodeView)
- return 0;
- else
- return 1;
- }
- return GetPriority(e1).CompareTo(GetPriority(e2));
- });
- //Handle ourselves the edge and node remove
- changes.elementsToRemove.RemoveAll(e => {
- switch (e)
- {
- case EdgeView edge:
- Disconnect(edge);
- return true;
- case BaseNodeView nodeView:
- // For vertical nodes, we need to delete them ourselves as it's not handled by GraphView
- foreach (var pv in nodeView.inputPortViews.Concat(nodeView.outputPortViews))
- if (pv.orientation == Orientation.Vertical)
- foreach (var edge in pv.GetEdges().ToList())
- Disconnect(edge);
- nodeInspector.NodeViewRemoved(nodeView);
- ExceptionToLog.Call(() => nodeView.OnRemoved());
- graph.RemoveNode(nodeView.nodeTarget);
- UpdateSerializedProperties();
- RemoveElement(nodeView);
- if (Selection.activeObject == nodeInspector)
- UpdateNodeInspectorSelection();
- SyncSerializedPropertyPathes();
- return true;
- case GroupView group:
- graph.RemoveGroup(group.group);
- UpdateSerializedProperties();
- RemoveElement(group);
- return true;
- case ExposedParameterFieldView blackboardField:
- graph.RemoveExposedParameter(blackboardField.parameter);
- UpdateSerializedProperties();
- return true;
- case BaseStackNodeView stackNodeView:
- graph.RemoveStackNode(stackNodeView.stackNode);
- UpdateSerializedProperties();
- RemoveElement(stackNodeView);
- return true;
- #if UNITY_2020_1_OR_NEWER
- case StickyNoteView stickyNoteView:
- graph.RemoveStickyNote(stickyNoteView.note);
- UpdateSerializedProperties();
- RemoveElement(stickyNoteView);
- return true;
- #endif
- }
- return false;
- });
- }
- return changes;
- }
- void GraphChangesCallback(GraphChanges changes)
- {
- if (changes.removedEdge != null)
- {
- var edge = edgeViews.FirstOrDefault(e => e.serializedEdge == changes.removedEdge);
- DisconnectView(edge);
- }
- }
- void ViewTransformChangedCallback(GraphView view)
- {
- if (graph != null)
- {
- graph.position = viewTransform.position;
- graph.scale = viewTransform.scale;
- }
- }
- void ElementResizedCallback(VisualElement elem)
- {
- var groupView = elem as GroupView;
- if (groupView != null)
- groupView.group.size = groupView.GetPosition().size;
- }
- public override List< Port > GetCompatiblePorts(Port startPort, NodeAdapter nodeAdapter)
- {
- var compatiblePorts = new List< Port >();
- compatiblePorts.AddRange(ports.ToList().Where(p => {
- var portView = p as PortView;
- if (portView.owner == (startPort as PortView).owner)
- return false;
- if (p.direction == startPort.direction)
- return false;
- //Check for type assignability
- if (!BaseGraph.TypesAreConnectable(startPort.portType, p.portType))
- return false;
- //Check if the edge already exists
- if (portView.GetEdges().Any(e => e.input == startPort || e.output == startPort))
- return false;
- return true;
- }));
- return compatiblePorts;
- }
- /// <summary>
- /// Build the contextual menu shown when right clicking inside the graph view
- /// </summary>
- /// <param name="evt"></param>
- public override void BuildContextualMenu(ContextualMenuPopulateEvent evt)
- {
- base.BuildContextualMenu(evt);
- BuildGroupContextualMenu(evt, 1);
- BuildStickyNoteContextualMenu(evt, 2);
- BuildViewContextualMenu(evt);
- BuildSelectAssetContextualMenu(evt);
- BuildSaveAssetContextualMenu(evt);
- BuildHelpContextualMenu(evt);
- }
- /// <summary>
- /// Add the New Group entry to the context menu
- /// </summary>
- /// <param name="evt"></param>
- protected virtual void BuildGroupContextualMenu(ContextualMenuPopulateEvent evt, int menuPosition = -1)
- {
- if (menuPosition == -1)
- menuPosition = evt.menu.MenuItems().Count;
- Vector2 position = (evt.currentTarget as VisualElement).ChangeCoordinatesTo(contentViewContainer, evt.localMousePosition);
- evt.menu.InsertAction(menuPosition, "Create Group", (e) => AddSelectionsToGroup(AddGroup(new Group("Create Group", position))), DropdownMenuAction.AlwaysEnabled);
- }
- /// <summary>
- /// -Add the New Sticky Note entry to the context menu
- /// </summary>
- /// <param name="evt"></param>
- protected virtual void BuildStickyNoteContextualMenu(ContextualMenuPopulateEvent evt, int menuPosition = -1)
- {
- if (menuPosition == -1)
- menuPosition = evt.menu.MenuItems().Count;
- #if UNITY_2020_1_OR_NEWER
- Vector2 position = (evt.currentTarget as VisualElement).ChangeCoordinatesTo(contentViewContainer, evt.localMousePosition);
- evt.menu.InsertAction(menuPosition, "Create Sticky Note", (e) => AddStickyNote(new StickyNote("Create Note", position)), DropdownMenuAction.AlwaysEnabled);
- #endif
- }
- /// <summary>
- /// Add the View entry to the context menu
- /// </summary>
- /// <param name="evt"></param>
- protected virtual void BuildViewContextualMenu(ContextualMenuPopulateEvent evt)
- {
- evt.menu.AppendAction("View/Processor", (e) => ToggleView< ProcessorView >(), (e) => GetPinnedElementStatus< ProcessorView >());
- }
- /// <summary>
- /// Add the Select Asset entry to the context menu
- /// </summary>
- /// <param name="evt"></param>
- protected virtual void BuildSelectAssetContextualMenu(ContextualMenuPopulateEvent evt)
- {
- evt.menu.AppendAction("Select Asset", (e) => EditorGUIUtility.PingObject(graph), DropdownMenuAction.AlwaysEnabled);
- }
- /// <summary>
- /// Add the Save Asset entry to the context menu
- /// </summary>
- /// <param name="evt"></param>
- protected virtual void BuildSaveAssetContextualMenu(ContextualMenuPopulateEvent evt)
- {
- evt.menu.AppendAction("Save Asset", (e) => {
- EditorUtility.SetDirty(graph);
- AssetDatabase.SaveAssets();
- }, DropdownMenuAction.AlwaysEnabled);
- }
- /// <summary>
- /// Add the Help entry to the context menu
- /// </summary>
- /// <param name="evt"></param>
- protected void BuildHelpContextualMenu(ContextualMenuPopulateEvent evt)
- {
- evt.menu.AppendAction("Help/Reset Pinned Windows", e => {
- foreach (var kp in pinnedElements)
- kp.Value.ResetPosition();
- });
- }
- protected virtual void KeyDownCallback(KeyDownEvent e)
- {
- if (e.keyCode == KeyCode.S && e.commandKey)
- {
- SaveGraphToDisk();
- e.StopPropagation();
- }
- else if(nodeViews.Count > 0 && e.commandKey && e.altKey)
- {
- // Node Aligning shortcuts
- switch(e.keyCode)
- {
- case KeyCode.LeftArrow:
- nodeViews[0].AlignToLeft();
- e.StopPropagation();
- break;
- case KeyCode.RightArrow:
- nodeViews[0].AlignToRight();
- e.StopPropagation();
- break;
- case KeyCode.UpArrow:
- nodeViews[0].AlignToTop();
- e.StopPropagation();
- break;
- case KeyCode.DownArrow:
- nodeViews[0].AlignToBottom();
- e.StopPropagation();
- break;
- case KeyCode.C:
- nodeViews[0].AlignToCenter();
- e.StopPropagation();
- break;
- case KeyCode.M:
- nodeViews[0].AlignToMiddle();
- e.StopPropagation();
- break;
- }
- }
- }
- void MouseUpCallback(MouseUpEvent e)
- {
- schedule.Execute(() => {
- if (DoesSelectionContainsInspectorNodes())
- UpdateNodeInspectorSelection();
- }).ExecuteLater(1);
- }
- void MouseDownCallback(MouseDownEvent e)
- {
- // When left clicking on the graph (not a node or something else)
- if (e.button == 0)
- {
- // Close all settings windows:
- nodeViews.ForEach(v => v.CloseSettings());
- }
- if (DoesSelectionContainsInspectorNodes())
- UpdateNodeInspectorSelection();
- }
- bool DoesSelectionContainsInspectorNodes()
- {
- var selectedNodes = selection.Where(s => s is BaseNodeView).ToList();
- var selectedNodesNotInInspector = selectedNodes.Except(nodeInspector.selectedNodes).ToList();
- var nodeInInspectorWithoutSelectedNodes = nodeInspector.selectedNodes.Except(selectedNodes).ToList();
- return selectedNodesNotInInspector.Any() || nodeInInspectorWithoutSelectedNodes.Any();
- }
- void DragPerformedCallback(DragPerformEvent e)
- {
- var mousePos = (e.currentTarget as VisualElement).ChangeCoordinatesTo(contentViewContainer, e.localMousePosition);
- var dragData = DragAndDrop.GetGenericData("DragSelection") as List< ISelectable >;
- // Drag and Drop for elements inside the graph
- if (dragData != null)
- {
- var exposedParameterFieldViews = dragData.OfType<ExposedParameterFieldView>();
- if (exposedParameterFieldViews.Any())
- {
- foreach (var paramFieldView in exposedParameterFieldViews)
- {
- RegisterCompleteObjectUndo("Create Parameter Node");
- var paramNode = BaseNode.CreateFromType< ParameterNode >(mousePos);
- paramNode.parameterGUID = paramFieldView.parameter.guid;
- AddNode(paramNode);
- }
- }
- }
- // External objects drag and drop
- if (DragAndDrop.objectReferences.Length > 0)
- {
- RegisterCompleteObjectUndo("Create Node From Object(s)");
- foreach (var obj in DragAndDrop.objectReferences)
- {
- var objectType = obj.GetType();
- foreach (var kp in nodeTypePerCreateAssetType)
- {
- if (kp.Key.IsAssignableFrom(objectType))
- {
- try
- {
- var node = BaseNode.CreateFromType(kp.Value.nodeType, mousePos);
- if ((bool)kp.Value.initalizeNodeFromObject.Invoke(node, new []{obj}))
- {
- AddNode(node);
- break;
- }
- }
- catch (Exception exception)
- {
- Debug.LogException(exception);
- }
- }
- }
- }
- }
- }
- void DragUpdatedCallback(DragUpdatedEvent e)
- {
- var dragData = DragAndDrop.GetGenericData("DragSelection") as List<ISelectable>;
- var dragObjects = DragAndDrop.objectReferences;
- bool dragging = false;
- if (dragData != null)
- {
- // Handle drag from exposed parameter view
- if (dragData.OfType<ExposedParameterFieldView>().Any())
- {
- dragging = true;
- }
- }
- if (dragObjects.Length > 0)
- dragging = true;
- if (dragging)
- DragAndDrop.visualMode = DragAndDropVisualMode.Generic;
- UpdateNodeInspectorSelection();
- }
- #endregion
- #region Initialization
- void ReloadView()
- {
- // Force the graph to reload his data (Undo have updated the serialized properties of the graph
- // so the one that are not serialized need to be synchronized)
- graph.Deserialize();
- // Get selected nodes
- var selectedNodeGUIDs = new List<string>();
- foreach (var e in selection)
- {
- if (e is BaseNodeView v && this.Contains(v))
- selectedNodeGUIDs.Add(v.nodeTarget.GUID);
- }
- // Remove everything
- RemoveNodeViews();
- RemoveEdges();
- RemoveGroups();
- #if UNITY_2020_1_OR_NEWER
- RemoveStrickyNotes();
- #endif
- RemoveStackNodeViews();
- UpdateSerializedProperties();
- // And re-add with new up to date datas
- InitializeNodeViews();
- InitializeEdgeViews();
- InitializeGroups();
- InitializeStickyNotes();
- InitializeStackNodes();
- Reload();
- UpdateComputeOrder();
- // Restore selection after re-creating all views
- // selection = nodeViews.Where(v => selectedNodeGUIDs.Contains(v.nodeTarget.GUID)).Select(v => v as ISelectable).ToList();
- foreach (var guid in selectedNodeGUIDs)
- {
- AddToSelection(nodeViews.FirstOrDefault(n => n.nodeTarget.GUID == guid));
- }
- UpdateNodeInspectorSelection();
- }
- public void Initialize(BaseGraph graph)
- {
- if (this.graph != null)
- {
- SaveGraphToDisk();
- // Close pinned windows from old graph:
- ClearGraphElements();
- NodeProvider.UnloadGraph(graph);
- }
- this.graph = graph;
- exposedParameterFactory = new ExposedParameterFieldFactory(graph);
- UpdateSerializedProperties();
- connectorListener = CreateEdgeConnectorListener();
- // When pressing ctrl-s, we save the graph
- EditorSceneManager.sceneSaved += _ => SaveGraphToDisk();
- RegisterCallback<KeyDownEvent>(e => {
- if (e.keyCode == KeyCode.S && e.actionKey)
- SaveGraphToDisk();
- });
- ClearGraphElements();
- InitializeGraphView();
- InitializeNodeViews();
- InitializeEdgeViews();
- InitializeViews();
- InitializeGroups();
- InitializeStickyNotes();
- InitializeStackNodes();
- initialized?.Invoke();
- UpdateComputeOrder();
- InitializeView();
- NodeProvider.LoadGraph(graph);
- // Register the nodes that can be created from assets
- foreach (var nodeInfo in NodeProvider.GetNodeMenuEntries(graph))
- {
- var interfaces = nodeInfo.type.GetInterfaces();
- var exceptInheritedInterfaces = interfaces.Except(interfaces.SelectMany(t => t.GetInterfaces()));
- foreach (var i in interfaces)
- {
- if (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICreateNodeFrom<>))
- {
- var genericArgumentType = i.GetGenericArguments()[0];
- var initializeFunction = nodeInfo.type.GetMethod(
- nameof(ICreateNodeFrom<Object>.InitializeNodeFromObject),
- BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
- null, new Type[]{ genericArgumentType}, null
- );
- // We only add the type that implements the interface, not it's children
- if (initializeFunction.DeclaringType == nodeInfo.type)
- nodeTypePerCreateAssetType[genericArgumentType] = (nodeInfo.type, initializeFunction);
- }
- }
- }
- }
- public void ClearGraphElements()
- {
- RemoveGroups();
- RemoveNodeViews();
- RemoveEdges();
- RemoveStackNodeViews();
- RemovePinnedElementViews();
- #if UNITY_2020_1_OR_NEWER
- RemoveStrickyNotes();
- #endif
- }
- void UpdateSerializedProperties()
- {
- serializedGraph = new SerializedObject(graph);
- }
- /// <summary>
- /// Allow you to create your own edge connector listener
- /// </summary>
- /// <returns></returns>
- protected virtual BaseEdgeConnectorListener CreateEdgeConnectorListener()
- => new BaseEdgeConnectorListener(this);
- void InitializeGraphView()
- {
- graph.onExposedParameterListChanged += OnExposedParameterListChanged;
- graph.onExposedParameterModified += (s) => onExposedParameterModified?.Invoke(s);
- graph.onGraphChanges += GraphChangesCallback;
- viewTransform.position = graph.position;
- viewTransform.scale = graph.scale;
- nodeCreationRequest = (c) => SearchWindow.Open(new SearchWindowContext(c.screenMousePosition), createNodeMenu);
- }
- void OnExposedParameterListChanged()
- {
- UpdateSerializedProperties();
- onExposedParameterListChanged?.Invoke();
- }
- void InitializeNodeViews()
- {
- graph.nodes.RemoveAll(n => n == null);
- foreach (var node in graph.nodes)
- {
- var v = AddNodeView(node);
- }
- }
- void InitializeEdgeViews()
- {
- // Sanitize edges in case a node broke something while loading
- graph.edges.RemoveAll(edge => edge == null || edge.inputNode == null || edge.outputNode == null);
- foreach (var serializedEdge in graph.edges)
- {
- nodeViewsPerNode.TryGetValue(serializedEdge.inputNode, out var inputNodeView);
- nodeViewsPerNode.TryGetValue(serializedEdge.outputNode, out var outputNodeView);
- if (inputNodeView == null || outputNodeView == null)
- continue;
- var edgeView = CreateEdgeView();
- edgeView.userData = serializedEdge;
- edgeView.input = inputNodeView.GetPortViewFromFieldName(serializedEdge.inputFieldName, serializedEdge.inputPortIdentifier);
- edgeView.output = outputNodeView.GetPortViewFromFieldName(serializedEdge.outputFieldName, serializedEdge.outputPortIdentifier);
- ConnectView(edgeView);
- }
- }
- void InitializeViews()
- {
- foreach (var pinnedElement in graph.pinnedElements)
- {
- if (pinnedElement.opened)
- OpenPinned(pinnedElement.editorType.type);
- }
- }
- void InitializeGroups()
- {
- foreach (var group in graph.groups)
- AddGroupView(group);
- }
- void InitializeStickyNotes()
- {
- #if UNITY_2020_1_OR_NEWER
- foreach (var group in graph.stickyNotes)
- AddStickyNoteView(group);
- #endif
- }
- void InitializeStackNodes()
- {
- foreach (var stackNode in graph.stackNodes)
- AddStackNodeView(stackNode);
- }
- protected virtual void InitializeManipulators()
- {
- this.AddManipulator(new ContentDragger());
- this.AddManipulator(new SelectionDragger());
- this.AddManipulator(new RectangleSelector());
- }
- protected virtual void Reload() {}
- #endregion
- #region Graph content modification
- public void UpdateNodeInspectorSelection()
- {
- if (nodeInspector.previouslySelectedObject != Selection.activeObject)
- nodeInspector.previouslySelectedObject = Selection.activeObject;
- HashSet<BaseNodeView> selectedNodeViews = new HashSet<BaseNodeView>();
- nodeInspector.selectedNodes.Clear();
- foreach (var e in selection)
- {
- if (e is BaseNodeView v && this.Contains(v) && v.nodeTarget.needsInspector)
- selectedNodeViews.Add(v);
- }
- nodeInspector.UpdateSelectedNodes(selectedNodeViews);
- if (Selection.activeObject != nodeInspector && selectedNodeViews.Count > 0)
- Selection.activeObject = nodeInspector;
- }
- public BaseNodeView AddNode(BaseNode node)
- {
- // This will initialize the node using the graph instance
- graph.AddNode(node);
- UpdateSerializedProperties();
- var view = AddNodeView(node);
- // Call create after the node have been initialized
- ExceptionToLog.Call(() => view.OnCreated());
- UpdateComputeOrder();
- return view;
- }
- public BaseNodeView AddNodeView(BaseNode node)
- {
- var viewType = NodeProvider.GetNodeViewTypeFromType(node.GetType());
- if (viewType == null)
- viewType = typeof(BaseNodeView);
- var baseNodeView = Activator.CreateInstance(viewType) as BaseNodeView;
- baseNodeView.Initialize(this, node);
- AddElement(baseNodeView);
- nodeViews.Add(baseNodeView);
- nodeViewsPerNode[node] = baseNodeView;
- return baseNodeView;
- }
- public void RemoveNode(BaseNode node)
- {
- var view = nodeViewsPerNode[node];
- RemoveNodeView(view);
- graph.RemoveNode(node);
- }
- public void RemoveNodeView(BaseNodeView nodeView)
- {
- RemoveElement(nodeView);
- nodeViews.Remove(nodeView);
- nodeViewsPerNode.Remove(nodeView.nodeTarget);
- }
- void RemoveNodeViews()
- {
- foreach (var nodeView in nodeViews)
- RemoveElement(nodeView);
- nodeViews.Clear();
- nodeViewsPerNode.Clear();
- }
- void RemoveStackNodeViews()
- {
- foreach (var stackView in stackNodeViews)
- RemoveElement(stackView);
- stackNodeViews.Clear();
- }
- void RemovePinnedElementViews()
- {
- foreach (var pinnedView in pinnedElements.Values)
- {
- if (Contains(pinnedView))
- Remove(pinnedView);
- }
- pinnedElements.Clear();
- }
- public GroupView AddGroup(Group block)
- {
- graph.AddGroup(block);
- block.OnCreated();
- return AddGroupView(block);
- }
- public GroupView AddGroupView(Group block)
- {
- var c = new GroupView();
- c.Initialize(this, block);
- AddElement(c);
- groupViews.Add(c);
- return c;
- }
- public BaseStackNodeView AddStackNode(BaseStackNode stackNode)
- {
- graph.AddStackNode(stackNode);
- return AddStackNodeView(stackNode);
- }
- public BaseStackNodeView AddStackNodeView(BaseStackNode stackNode)
- {
- var viewType = StackNodeViewProvider.GetStackNodeCustomViewType(stackNode.GetType()) ?? typeof(BaseStackNodeView);
- var stackView = Activator.CreateInstance(viewType, stackNode) as BaseStackNodeView;
- AddElement(stackView);
- stackNodeViews.Add(stackView);
- stackView.Initialize(this);
- return stackView;
- }
- public void RemoveStackNodeView(BaseStackNodeView stackNodeView)
- {
- stackNodeViews.Remove(stackNodeView);
- RemoveElement(stackNodeView);
- }
- #if UNITY_2020_1_OR_NEWER
- public StickyNoteView AddStickyNote(StickyNote note)
- {
- graph.AddStickyNote(note);
- return AddStickyNoteView(note);
- }
- public StickyNoteView AddStickyNoteView(StickyNote note)
- {
- var c = new StickyNoteView();
- c.Initialize(this, note);
- AddElement(c);
- stickyNoteViews.Add(c);
- return c;
- }
- public void RemoveStickyNoteView(StickyNoteView view)
- {
- stickyNoteViews.Remove(view);
- RemoveElement(view);
- }
- public void RemoveStrickyNotes()
- {
- foreach (var stickyNodeView in stickyNoteViews)
- RemoveElement(stickyNodeView);
- stickyNoteViews.Clear();
- }
- #endif
- public void AddSelectionsToGroup(GroupView view)
- {
- foreach (var selectedNode in selection)
- {
- if (selectedNode is BaseNodeView)
- {
- if (groupViews.Exists(x => x.ContainsElement(selectedNode as BaseNodeView)))
- continue;
- view.AddElement(selectedNode as BaseNodeView);
- }
- }
- }
- public void RemoveGroups()
- {
- foreach (var groupView in groupViews)
- RemoveElement(groupView);
- groupViews.Clear();
- }
- public bool CanConnectEdge(EdgeView e, bool autoDisconnectInputs = true)
- {
- if (e.input == null || e.output == null)
- return false;
- var inputPortView = e.input as PortView;
- var outputPortView = e.output as PortView;
- var inputNodeView = inputPortView.node as BaseNodeView;
- var outputNodeView = outputPortView.node as BaseNodeView;
- if (inputNodeView == null || outputNodeView == null)
- {
- Debug.LogError("Connect aborted !");
- return false;
- }
- return true;
- }
- public bool ConnectView(EdgeView e, bool autoDisconnectInputs = true)
- {
- if (!CanConnectEdge(e, autoDisconnectInputs))
- return false;
-
- var inputPortView = e.input as PortView;
- var outputPortView = e.output as PortView;
- var inputNodeView = inputPortView.node as BaseNodeView;
- var outputNodeView = outputPortView.node as BaseNodeView;
- //If the input port does not support multi-connection, we remove them
- if (autoDisconnectInputs && !(e.input as PortView).portData.acceptMultipleEdges)
- {
- foreach (var edge in edgeViews.Where(ev => ev.input == e.input).ToList())
- {
- // TODO: do not disconnect them if the connected port is the same than the old connected
- DisconnectView(edge);
- }
- }
- // same for the output port:
- if (autoDisconnectInputs && !(e.output as PortView).portData.acceptMultipleEdges)
- {
- foreach (var edge in edgeViews.Where(ev => ev.output == e.output).ToList())
- {
- // TODO: do not disconnect them if the connected port is the same than the old connected
- DisconnectView(edge);
- }
- }
- AddElement(e);
- e.input.Connect(e);
- e.output.Connect(e);
- // If the input port have been removed by the custom port behavior
- // we try to find if it's still here
- if (e.input == null)
- e.input = inputNodeView.GetPortViewFromFieldName(inputPortView.fieldName, inputPortView.portData.identifier);
- if (e.output == null)
- e.output = inputNodeView.GetPortViewFromFieldName(outputPortView.fieldName, outputPortView.portData.identifier);
- edgeViews.Add(e);
- inputNodeView.RefreshPorts();
- outputNodeView.RefreshPorts();
- // In certain cases the edge color is wrong so we patch it
- schedule.Execute(() => {
- e.UpdateEdgeControl();
- }).ExecuteLater(1);
- e.isConnected = true;
- return true;
- }
- public bool Connect(PortView inputPortView, PortView outputPortView, bool autoDisconnectInputs = true)
- {
- var inputPort = inputPortView.owner.nodeTarget.GetPort(inputPortView.fieldName, inputPortView.portData.identifier);
- var outputPort = outputPortView.owner.nodeTarget.GetPort(outputPortView.fieldName, outputPortView.portData.identifier);
- // Checks that the node we are connecting still exists
- if (inputPortView.owner.parent == null || outputPortView.owner.parent == null)
- return false;
- var newEdge = SerializableEdge.CreateNewEdge(graph, inputPort, outputPort);
- var edgeView = CreateEdgeView();
- edgeView.userData = newEdge;
- edgeView.input = inputPortView;
- edgeView.output = outputPortView;
- return Connect(edgeView);
- }
- public bool Connect(EdgeView e, bool autoDisconnectInputs = true)
- {
- if (!CanConnectEdge(e, autoDisconnectInputs))
- return false;
- var inputPortView = e.input as PortView;
- var outputPortView = e.output as PortView;
- var inputNodeView = inputPortView.node as BaseNodeView;
- var outputNodeView = outputPortView.node as BaseNodeView;
- var inputPort = inputNodeView.nodeTarget.GetPort(inputPortView.fieldName, inputPortView.portData.identifier);
- var outputPort = outputNodeView.nodeTarget.GetPort(outputPortView.fieldName, outputPortView.portData.identifier);
- e.userData = graph.Connect(inputPort, outputPort, autoDisconnectInputs);
- ConnectView(e, autoDisconnectInputs);
- UpdateComputeOrder();
- return true;
- }
- public void DisconnectView(EdgeView e, bool refreshPorts = true)
- {
- if (e == null)
- return ;
- RemoveElement(e);
- if (e?.input?.node is BaseNodeView inputNodeView)
- {
- e.input.Disconnect(e);
- if (refreshPorts)
- inputNodeView.RefreshPorts();
- }
- if (e?.output?.node is BaseNodeView outputNodeView)
- {
- e.output.Disconnect(e);
- if (refreshPorts)
- outputNodeView.RefreshPorts();
- }
- edgeViews.Remove(e);
- }
- public void Disconnect(EdgeView e, bool refreshPorts = true)
- {
- // Remove the serialized edge if there is one
- if (e.userData is SerializableEdge serializableEdge)
- graph.Disconnect(serializableEdge.GUID);
- DisconnectView(e, refreshPorts);
- UpdateComputeOrder();
- }
- public void RemoveEdges()
- {
- foreach (var edge in edgeViews)
- RemoveElement(edge);
- edgeViews.Clear();
- }
- public void UpdateComputeOrder()
- {
- graph.UpdateComputeOrder();
- computeOrderUpdated?.Invoke();
- }
- public void RegisterCompleteObjectUndo(string name)
- {
- Undo.RegisterCompleteObjectUndo(graph, name);
- }
- public void SaveGraphToDisk()
- {
- if (graph == null)
- return ;
- EditorUtility.SetDirty(graph);
- }
- public void ToggleView< T >() where T : PinnedElementView
- {
- ToggleView(typeof(T));
- }
- public void ToggleView(Type type)
- {
- PinnedElementView view;
- pinnedElements.TryGetValue(type, out view);
- if (view == null)
- OpenPinned(type);
- else
- ClosePinned(type, view);
- }
- public void OpenPinned< T >() where T : PinnedElementView
- {
- OpenPinned(typeof(T));
- }
- public void OpenPinned(Type type)
- {
- PinnedElementView view;
- if (type == null)
- return ;
- PinnedElement elem = graph.OpenPinned(type);
- if (!pinnedElements.ContainsKey(type))
- {
- view = Activator.CreateInstance(type) as PinnedElementView;
- if (view == null)
- return ;
- pinnedElements[type] = view;
- view.InitializeGraphView(elem, this);
- }
- view = pinnedElements[type];
- if (!Contains(view))
- Add(view);
- }
- public void ClosePinned< T >(PinnedElementView view) where T : PinnedElementView
- {
- ClosePinned(typeof(T), view);
- }
- public void ClosePinned(Type type, PinnedElementView elem)
- {
- pinnedElements.Remove(type);
- Remove(elem);
- graph.ClosePinned(type);
- }
- public Status GetPinnedElementStatus< T >() where T : PinnedElementView
- {
- return GetPinnedElementStatus(typeof(T));
- }
- public Status GetPinnedElementStatus(Type type)
- {
- var pinned = graph.pinnedElements.Find(p => p.editorType.type == type);
- if (pinned != null && pinned.opened)
- return Status.Normal;
- else
- return Status.Hidden;
- }
- public void ResetPositionAndZoom()
- {
- graph.position = Vector3.zero;
- graph.scale = Vector3.one;
- UpdateViewTransform(graph.position, graph.scale);
- }
- /// <summary>
- /// Deletes the selected content, can be called form an IMGUI container
- /// </summary>
- public void DelayedDeleteSelection() => this.schedule.Execute(() => DeleteSelectionOperation("Delete", AskUser.DontAskUser)).ExecuteLater(0);
- protected virtual void InitializeView() {}
- public virtual IEnumerable<(string path, Type type)> FilterCreateNodeMenuEntries()
- {
- // By default we don't filter anything
- foreach (var nodeMenuItem in NodeProvider.GetNodeMenuEntries(graph))
- yield return nodeMenuItem;
- // TODO: add exposed properties to this list
- }
- public RelayNodeView AddRelayNode(PortView inputPort, PortView outputPort, Vector2 position)
- {
- var relayNode = BaseNode.CreateFromType<RelayNode>(position);
- var view = AddNode(relayNode) as RelayNodeView;
- if (outputPort != null)
- Connect(view.inputPortViews[0], outputPort);
- if (inputPort != null)
- Connect(inputPort, view.outputPortViews[0]);
- return view;
- }
- /// <summary>
- /// Update all the serialized property bindings (in case a node was deleted / added, the property pathes needs to be updated)
- /// </summary>
- public void SyncSerializedPropertyPathes()
- {
- foreach (var nodeView in nodeViews)
- nodeView.SyncSerializedPropertyPathes();
- nodeInspector.RefreshNodes();
- }
- /// <summary>
- /// Call this function when you want to remove this view
- /// </summary>
- public void Dispose()
- {
- ClearGraphElements();
- RemoveFromHierarchy();
- Undo.undoRedoPerformed -= ReloadView;
- Object.DestroyImmediate(nodeInspector);
- NodeProvider.UnloadGraph(graph);
- exposedParameterFactory.Dispose();
- exposedParameterFactory = null;
- graph.onExposedParameterListChanged -= OnExposedParameterListChanged;
- graph.onExposedParameterModified += (s) => onExposedParameterModified?.Invoke(s);
- graph.onGraphChanges -= GraphChangesCallback;
- }
- #endregion
- }
- }
|