using System; using System.Collections.Generic; using AlicizaX.AnimationFlow.Runtime; using UnityEditor; using UnityEditor.Experimental.GraphView; using UnityEngine; using UnityEngine.UIElements; namespace AlicizaX.AnimationFlow.Editor { public class GraphView : UnityEditor.Experimental.GraphView.GraphView { private readonly SearchWindowProvider searchWindowProvider; private readonly GraphWindow graphWindow; private readonly Dictionary dicNodeView = new(); private Runtime.AnimationFlow animationFlow; private const string applicationFlag = "@AnimationFlow"; private readonly Dictionary pendingNodes = new(); public GraphView(GraphWindow graphWindow) : base() { this.graphWindow = graphWindow; InitView(); SetupZoom(ContentZoomer.DefaultMinScale, ContentZoomer.DefaultMaxScale); this.AddManipulator(new SelectionDragger()); this.AddManipulator(new RectangleSelector()); this.AddManipulator(new ContentDragger()); searchWindowProvider = ScriptableObject.CreateInstance(); searchWindowProvider.Initialize(graphWindow, this); SetCopyAndPaste(); graphViewChanged += OnGraphViewChanged; } private void InitView() { this.StretchToParentSize(); Insert(0, new GridBackground()); AddStyles(); MiniMap miniMap = new(); Add(miniMap); miniMap.style.backgroundColor = new Color(0.12f, 0.12f, 0.12f, 1f); miniMap.style.borderBottomColor = new Color(0.2f, 0.2f, 0.2f, 1f); miniMap.style.borderRightColor = new Color(0.2f, 0.2f, 0.2f, 1f); miniMap.style.borderTopColor = new Color(0.2f, 0.2f, 0.2f, 1f); miniMap.style.borderLeftColor = new Color(0.2f, 0.2f, 0.2f, 1f); } private void SetCopyAndPaste() { serializeGraphElements += (IEnumerable elements) => { AnimationFlowSerialize serialize = new(); serialize.flag = applicationFlag; foreach (var ele in elements) { if (ele is NodeView nodeView) { serialize.nodes.Add(nodeView.actionNode); } } return JsonUtility.ToJson(serialize); }; canPasteSerializedData += (string data) => { return data.Contains(applicationFlag); }; unserializeAndPaste += (string operationName, string data) => { ClearSelection(); Undo.RecordObject(animationFlow, operationName); Vector2 mousePos = contentViewContainer.WorldToLocal(contentRect.center); try { AnimationFlowSerialize serialize = JsonUtility.FromJson(data); List newDatas = new(); Dictionary dicUid = new(); foreach (ActionNode node in serialize.nodes) { node.nodePos = node.nodePos + mousePos; string uuid = node.uuid; node.uuid = Guid.NewGuid().ToString(); NodeView nodeView = AddNodeView(node); AddToSelection(nodeView); newDatas.Add(node); dicUid.Add(uuid, node); } foreach (ActionNode copyNode in newDatas) { for (int i = copyNode.Childs.Count - 1; i >= 0; i--) { if (dicUid.TryGetValue(copyNode.Childs[i].uuid, out ActionNode newNode)) { copyNode.Childs[i] = newNode; if (dicNodeView.TryGetValue(copyNode.uuid, out NodeView inNode) && dicNodeView.TryGetValue(newNode.uuid, out NodeView outNode)) { AddElement(inNode.portOut.ConnectTo(outNode.portIn)); } } else { copyNode.Childs.RemoveAt(i); } } } } catch (Exception) { } }; } private void ResetEdge() { List nodes = new List(); foreach (var item in animationFlow.AnimationNodes) { nodes.Add(item); } List allNodes = new List(); LoadAllChildNode(nodes, allNodes); foreach (ActionNode data in allNodes) { foreach (ActionNode node in allNodes) { if (data.Childs.Count > 0 && data.Childs.Find(a => a.uuid == node.uuid) != null) { if (dicNodeView.TryGetValue(data.uuid, out NodeView inNode) && dicNodeView.TryGetValue(node.uuid, out NodeView outNode)) { AddElement(inNode.portOut.ConnectTo(outNode.portIn)); } } } } } private NodeView ResetNodeView(ActionNode data) { NodeView nodeView = new(data, graphWindow); AddElement(nodeView); nodeView.SetPosition(new Rect(data.nodePos, Vector2.zero)); dicNodeView.Add(data.uuid, nodeView); return nodeView; } private void RemoveNodeView(NodeView nodeView) { dicNodeView.Remove(nodeView.actionNode.uuid); pendingNodes.Remove(nodeView.actionNode.uuid); if (nodeView.actionNode is EntryNode) { animationFlow.AnimationNodes.Remove(nodeView.actionNode as EntryNode); } } private void AddStyles() { StyleSheet styleSheet = AssetDatabase.LoadAssetAtPath(EditorResourceTool.editorAssets + "/AnimationFlowStyles.uss"); styleSheets.Add(styleSheet); } private GraphViewChange OnGraphViewChanged(GraphViewChange change) { change.edgesToCreate?.ForEach(edge => { if (animationFlow == null) { ClearGraph(); return; } Undo.RecordObject(animationFlow, "Create Edge"); NodeView nodeIn = edge.input.node as NodeView; NodeView nodeOut = edge.output.node as NodeView; nodeOut.actionNode.Childs.Add(nodeIn.actionNode); EditorUtility.SetDirty(animationFlow); }); change.elementsToRemove?.ForEach(elem => { if (animationFlow == null) { ClearGraph(); return; } if (elem is Edge edge) { Undo.RecordObject(animationFlow, "Rmove Edge"); NodeView nodeIn = edge.input.node as NodeView; NodeView nodeOut = edge.output.node as NodeView; nodeOut.actionNode.Childs.Remove(nodeIn.actionNode); } if (elem is NodeView node) { Undo.RecordObject(animationFlow, "Remove Node"); RemoveNodeView(node); graphWindow.OnNodeRemove(); } EditorUtility.SetDirty(animationFlow); }); change.movedElements?.ForEach(elem => { if (animationFlow == null) { ClearGraph(); return; } if (elem is NodeView nodeView) { Undo.RecordObject(animationFlow, "Move Node"); nodeView.actionNode.nodePos = nodeView.GetPosition().position; } EditorUtility.SetDirty(animationFlow); }); return change; } public override void BuildContextualMenu(ContextualMenuPopulateEvent evt) { SearchWindow.Open(new SearchWindowContext(GUIUtility.GUIToScreenPoint(evt.mousePosition)), searchWindowProvider); } public override List GetCompatiblePorts(Port startPort, NodeAdapter nodeAdapter) { List compatiblePorts = new(); ports.ForEach(port => { if (startPort == port) { return; } if (startPort.node == port.node) { return; } if (startPort.direction == port.direction) { return; } if (startPort.portType != port.portType) { return; } compatiblePorts.Add(port); }); return compatiblePorts; } protected override bool canDeleteSelection => base.canDeleteSelection && !Application.isPlaying && animationFlow != null && !animationFlow.InPlaying; protected override bool canCopySelection => base.canCopySelection && !Application.isPlaying && animationFlow != null && !animationFlow.InPlaying; protected override bool canCutSelection => base.canCutSelection && !Application.isPlaying && animationFlow != null && !animationFlow.InPlaying; protected override bool canDuplicateSelection => base.canDuplicateSelection && !Application.isPlaying && animationFlow != null && !animationFlow.InPlaying; protected override bool canPaste => base.canPaste && !Application.isPlaying && animationFlow != null; public NodeView AddNodeView(ActionNode node) { if (animationFlow == null) { return null; } Undo.RecordObject(animationFlow, "Create Node"); if (node is EntryNode) { animationFlow.AnimationNodes.Add(node as EntryNode); } else { pendingNodes.Add(node.uuid, node); } return ResetNodeView(node); } public void ClearGraph() { graphElements.ForEach(g => RemoveElement(g)); dicNodeView.Clear(); animationFlow = null; } public void SetAnimationFlow(Runtime.AnimationFlow animationFlow) { this.animationFlow = animationFlow; List nodes = new List(); foreach (var VARIABLE in animationFlow.AnimationNodes) { nodes.Add(VARIABLE); } List allNodes = new List(); LoadAllChildNode(nodes, allNodes); foreach (ActionNode data in allNodes) { ResetNodeView(data); } ResetEdge(); } private void LoadAllChildNode(List nodes, List collector) { foreach (var item in nodes) { collector.Add(item); if (item.Childs.Count > 0) LoadAllChildNode(item.Childs, collector); } } public void ResetView() { foreach (NodeView nodeView in dicNodeView.Values) { nodeView.RemoveProgress(); nodeView.RemoveComplete(); } } public void OnNodeEnter(ActionNode actionNode) { if (dicNodeView.TryGetValue(actionNode.uuid, out NodeView nodeView)) { nodeView.AddProgress(); } } public void OnNodeExit(ActionNode actionNode) { if (dicNodeView.TryGetValue(actionNode.uuid, out NodeView nodeView)) { nodeView.RemoveProgress(); nodeView.AddComplete(); } } public void OnNodeUpdate(ActionNode actionNode) { if (dicNodeView.TryGetValue(actionNode.uuid, out NodeView nodeView)) { nodeView.UpdateProgress(); } } } }