namespace TagFighter.UI.Editor
{
using UnityEditor;
using UnityEngine.UIElements;
using UnityEditor.Experimental.GraphView;
using System;
using TagFighter.Effects;
using TagFighter.Effects.Steps;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using System.Reflection;
[Serializable]
public class EffectStepNodeGraph
{
public List<EffectStepNode> Nodes;
public Vector2 Center;
public EffectStepNodeGraph(EffectStepNodeGraphData nodesData) {
Dictionary<string, EffectStepNode> guidToNode = new();
Nodes = new();
foreach (var nodeData in nodesData.Nodes) {
var newNode = ScriptableObject.CreateInstance(nodeData.SerializedType.Type) as EffectStepNode;
Nodes.Add(newNode);
guidToNode.TryAdd(nodeData.Guid, newNode);
}
foreach (var (node, data) in Nodes.Zip(nodesData.Nodes, (node, data) => (node, data))) {
node.FromData(data, guidToNode);
}
Center = nodesData.Center;
}
}
[Serializable]
public class EffectStepNodeGraphData
{
public List<EffectStepNodeData> Nodes;
public Vector2 Center;
public EffectStepNodeGraphData() {
Nodes = new();
Center = Vector2.zero;
}
public EffectStepNodeGraphData(IEnumerable<EffectStepNodeView> nodes) {
Nodes = nodes.Select(nodeView => nodeView.Node.ToData()).ToList();
Center = new Vector2(nodes.Select(node => node.layout.center.x).Average(), nodes.Select(node => node.layout.center.y).Average());
}
}
public class RuneEffectView : GraphView
{
RuneEffect _runeEffect;
public Action<EffectStepNodeView> OnNodeSelected;
Vector2 _lastMousePosition = default;
public RuneEffectView() {
Insert(0, new GridBackground());
this.AddManipulator(new ContentZoomer());
this.AddManipulator(new ContentDragger());
this.AddManipulator(new SelectionDragger());
this.AddManipulator(new RectangleSelector());
serializeGraphElements += CutCopyOperation;
canPasteSerializedData += CanPasteOperation;
unserializeAndPaste += PasteOperation;
RegisterCallback<MouseMoveEvent>(OnMouseMove);
var styleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>("Assets/Editor/Rune/RuneEditor.uss");
styleSheets.Add(styleSheet);
}
void OnMouseMove(MouseMoveEvent evt) {
_lastMousePosition = evt.localMousePosition;
}
bool CanPasteOperation(string data) {
return data != null && _runeEffect != null;
}
void PasteOperation(string operationName, string data) {
EffectStepNodeGraphData dataGraph = new();
EditorJsonUtility.FromJsonOverwrite(data, dataGraph);
Dictionary<string, string> originalToNewGuid = new();
var mousePositionOnGraph = CalculatePositionOnGraph(_lastMousePosition);
foreach (var node in dataGraph.Nodes) {
var newGuid = Guid.NewGuid().ToString();
originalToNewGuid.TryAdd(node.Guid, newGuid);
node.Guid = newGuid;
node.Position = node.Position - dataGraph.Center + mousePositionOnGraph;
}
foreach (var port in dataGraph.Nodes.SelectMany(node => node.PortsData)) {
port.ConnectedNodesGuid = port.ConnectedNodesGuid.Select(origGuid => originalToNewGuid.GetValueOrDefault(origGuid)).Where(guid => guid != null).ToList();
}
EffectStepNodeGraph clonedGraph = new(dataGraph);
RegisterNodesEnum(clonedGraph.Nodes);
CreateNodeViews(clonedGraph.Nodes);
CreateEdges(clonedGraph.Nodes);
RefreshSelection(clonedGraph.Nodes);
}
string CutCopyOperation(IEnumerable<GraphElement> elements) {
EffectStepNodeGraphData graph = new(elements.Select(e => e as EffectStepNodeView).Where(e => e != null));
var data = EditorJsonUtility.ToJson(graph);
return data;
}
internal void PopulateView(RuneEffect runeEffect) {
graphViewChanged -= OnGraphViewChanged;
DeleteElements(graphElements);
_runeEffect = runeEffect;
if (_runeEffect == null) {
return;
}
graphViewChanged += OnGraphViewChanged;
CreateNodeViews(_runeEffect.Nodes);
CreateEdges(_runeEffect.Nodes);
}
public override List<Port> GetCompatiblePorts(Port startPort, NodeAdapter nodeAdapter) {
return ports.Where(
endPort => endPort.direction != startPort.direction
&& endPort.node != startPort.node
&& startPort.portType == endPort.portType
).ToList();
}
void CreateNodeViews(IEnumerable<EffectStepNode> nodes) {
foreach (var node in nodes) {
CreateNodeView(node);
}
}
void CreateEdges(IEnumerable<EffectStepNode> nodes) {
foreach (var inputNode in nodes) {
var inputView = GetNodeByGuid(inputNode.Guid) as EffectStepNodeView;
foreach (var inputPort in inputNode.Inputs) {
foreach (var outputNode in inputPort.Connections()) {
var outputView = GetNodeByGuid(outputNode.Guid) as EffectStepNodeView;
var outputPortView = outputView.outputContainer.Children().FirstOrDefault(portView => portView is Port) as Port;
var inputPortView = inputView.inputContainer.Children().FirstOrDefault(portView => (((IPort port, EffectStepNode _))portView.userData).port == inputPort) as Port;
var edge = outputPortView.ConnectTo(inputPortView);
AddElement(edge);
}
}
}
}
void RefreshSelection(IEnumerable<EffectStepNode> nodes) {
ClearSelection();
foreach (var nodeView in nodes.Select(n => GetNodeByGuid(n.Guid) as EffectStepNodeView)) {
AddToSelection(nodeView);
}
}
GraphViewChange OnGraphViewChanged(GraphViewChange graphViewChange) {
if (graphViewChange.elementsToRemove != null) {
foreach (var elementToRemove in graphViewChange.elementsToRemove) {
switch (elementToRemove) {
case EffectStepNodeView effectStepNodeView:
Undo.RecordObject(_runeEffect, "Rune Effect (Delete Node)");
_runeEffect.DeleteNode(effectStepNodeView.Node);
Undo.DestroyObjectImmediate(effectStepNodeView.Node);
break;
case Edge edge:
var outputNodeView = edge.output.node as EffectStepNodeView;
(var inputPort, var node) = ((IPort, EffectStepNode))edge.input.userData;
Undo.RecordObject(node, "Rune Effect (Delete Edge)");
inputPort.TryDisconnect(outputNodeView.Node);
break;
}
}
}
if (graphViewChange.edgesToCreate != null) {
foreach (var edge in graphViewChange.edgesToCreate) {
var outputNodeView = edge.output.node as EffectStepNodeView;
(var inputPort, var node) = ((IPort, EffectStepNode))edge.input.userData;
Undo.RecordObject(node, "Rune Effect (Create Edge)");
inputPort.TryConnect(outputNodeView.Node);
}
}
return graphViewChange;
}
public override void BuildContextualMenu(ContextualMenuPopulateEvent e) {
{
var mousePositionOnGraph = CalculatePositionOnGraph(this.WorldToLocal(e.mousePosition));
List<Type> additional = new();
foreach (var (type, stepType) in TypeCache.GetTypesDerivedFrom(typeof(EffectStepNode))
.Where(type => type.IsAbstract == false)
.Select(type => (type, type.GetTypeInfo().GetCustomAttribute<StepTypeAttribute>()))) {
if (stepType != null) {
e.menu.AppendAction($"{stepType}/{type.Name}", action => AddNode(type, mousePositionOnGraph));
}
else {
additional.Add(type);
}
}
foreach (var type in additional) {
e.menu.AppendAction($"{type.Name}", action => AddNode(type, mousePositionOnGraph));
}
}
}
UnityEngine.Vector2 CalculatePositionOnGraph(UnityEngine.Vector2 localPosition) {
return viewTransform.matrix.inverse.MultiplyPoint(localPosition);
}
void AddNode(System.Type type, UnityEngine.Vector2 position) {
if (_runeEffect == null) {
return;
}
var node = CreateNode(type, position);
RegisterNodes(node);
CreateNodeView(node);
}
void RegisterNodes(params EffectStepNode[] nodes) => RegisterNodesEnum(nodes);
void RegisterNodesEnum(IEnumerable<EffectStepNode> nodes) {
foreach (var node in nodes) {
Undo.RecordObject(_runeEffect, "Rune Effect (Create Node)");
_runeEffect.AddNode(node);
AssetDatabase.AddObjectToAsset(node, _runeEffect);
Undo.RegisterCreatedObjectUndo(node, "Rune Effect (Create Node)");
}
}
EffectStepNode CreateNode(System.Type type, UnityEngine.Vector2 position) {
if (_runeEffect == null) {
return null;
}
var node = ScriptableObject.CreateInstance(type) as EffectStepNode;
node.name = type.Name;
node.Position = position;
return node;
}
void CreateNodeView(EffectStepNode node) {
EffectStepNodeView nodeView = new(node) {
OnNodeSelected = OnNodeSelected,
};
AddElement(nodeView);
}
}
}