Added RelayNodeView;

Chnaged GraphView to multiple files for better organization;
This commit is contained in:
Misaki
2024-11-05 22:46:42 +09:00
parent c853994bf5
commit 02ae77f17a
25 changed files with 863 additions and 620 deletions

View File

@@ -1,11 +1,9 @@
using System.Collections.Generic; using UnityEditor.Experimental.GraphView;
using UnityEditor.Experimental.GraphView;
namespace Misaki.GraphView.Editor namespace Misaki.GraphView.Editor
{ {
public interface IPortContainer public interface IPortContainer
{ {
public List<Port> InputPorts { get; } public Port GetPort(int index, Direction direction);
public List<Port> OutputPorts { get; }
} }
} }

View File

@@ -1,441 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEditor.Experimental.GraphView;
using UnityEngine;
using UnityEngine.UIElements;
namespace Misaki.GraphView.Editor
{
public class GraphView : UnityEditor.Experimental.GraphView.GraphView
{
private readonly Dictionary<string, Node> _nodeViewsMap = new();
private readonly Dictionary<Edge, SlotConnection> _slotConnections = new();
private readonly GraphBlackboardView _blackboardView;
private readonly GraphInspectorView _graphInspectorView;
private readonly GraphViewConfig _graphViewConfig;
private GraphObject _graphObject => _graphViewConfig.graphObject;
public EditorWindow EditorWindow { get; }
public GraphViewConfig GraphViewConfig => _graphViewConfig;
public GraphView(EditorWindow editorWindow, GraphViewConfig graphViewConfig)
{
EditorWindow = editorWindow;
_graphViewConfig = graphViewConfig;
var gridBackground = new GridBackground { name = "gridBackground" };
Add(gridBackground);
gridBackground.SendToBack();
var minimapConfig = _graphViewConfig.miniMapConfig;
if (minimapConfig is { enable: true })
{
var minimap = new MiniMap()
{
anchored = true
};
minimap.SetPosition(minimapConfig.position);
Add(minimap);
}
_blackboardView = new GraphBlackboardView(_graphObject, this, _graphViewConfig.serializedObject, _graphViewConfig.exposedPropertyTypeManager);
_blackboardView.OnPropertySelected += ChangeInspectorView;
foreach (var property in _graphObject.ExposedProperties)
{
_blackboardView.AddProperty(property);
}
_graphInspectorView = new GraphInspectorView();
Add(_blackboardView);
Add(_graphInspectorView);
this.StretchToParentSize();
this.AddManipulator(new ContentDragger());
this.AddManipulator(new SelectionDragger());
this.AddManipulator(new RectangleSelector());
this.AddManipulator(new ClickSelector());
var zoomConfig = _graphViewConfig.zoomConfig;
var zoomer = new ContentZoomer();
if (zoomConfig !=null)
{
zoomer.minScale = zoomConfig.minScale;
zoomer.maxScale = zoomConfig.maxScale;
zoomer.scaleStep = zoomConfig.scaleStep;
}
this.AddManipulator(zoomer);
InitializeAssetElements();
graphViewChanged += OnGraphViewChanged;
RegisterCallback<DragPerformEvent>(OnDragPerform);
RegisterCallbackOnce<GeometryChangedEvent>(_ => _graphInspectorView?.DockToParent(layout, DockingPosition.Right, false));
}
public override void BuildContextualMenu(ContextualMenuPopulateEvent evt)
{
base.BuildContextualMenu(evt);
var mousePosition = (evt.currentTarget as VisualElement).ChangeCoordinatesTo(contentViewContainer, evt.localMousePosition);
if (selection.Count == 0)
{
evt.menu.InsertAction(1, "Create Sticky Note", e =>
{
var stickyNote = new StickyNoteData
{
title = "Sticky Note",
contents = "Contents",
theme = StickyNoteTheme.Classic,
fontSize = StickyNoteFontSize.Medium,
position = new Rect(mousePosition, new Vector2(200, 200))
};
AddStickyNote(stickyNote);
}, DropdownMenuAction.AlwaysEnabled);
}
}
private void InitializeAssetElements()
{
var searchProvider = ScriptableObject.CreateInstance<NodeSearchProvider>();
searchProvider.SetOwner(this);
nodeCreationRequest = context =>
{
SearchWindow.Open(new SearchWindowContext(context.screenMousePosition), searchProvider);
};
foreach (var node in _graphObject.Nodes)
{
AddNodeView(node);
}
foreach (var noteData in _graphObject.StickyNotes)
{
AddStickyNoteView(noteData);
}
foreach (var connection in _graphObject.Connections)
{
AddConnectionView(connection);
}
}
private GraphViewChange OnGraphViewChanged(GraphViewChange graphViewChange)
{
if (graphViewChange.elementsToRemove != null)
{
var removedElements = graphViewChange.elementsToRemove;
Undo.RecordObject(_graphObject, "Remove elements");
for (var i = removedElements.Count - 1; i >= 0; i--)
{
if (removedElements[i] is Node { userData: ExecutableNode node })
{
RemoveNode(node);
}
if (removedElements[i] is StickyNote {userData : StickyNoteData stickyNote})
{
RemoveStickyNote(stickyNote);
}
if (removedElements[i] is Edge edge)
{
if (_slotConnections.Remove(edge, out var connection))
{
var inputNode = _graphObject.GetNode(connection.InputSlotData.nodeID);
var outputNode = _graphObject.GetNode(connection.OutputSlotData.nodeID);
if (inputNode is ISlotContainer inputSlotContainer && outputNode is ISlotContainer outputSlotContainer)
{
var inputSlot = inputSlotContainer.GetSlot(connection.InputSlotData.slotIndex, connection.InputSlotData.direction);
var outputSlot = outputSlotContainer.GetSlot(connection.OutputSlotData.slotIndex, connection.OutputSlotData.direction);
inputSlot.Unlink(outputSlot);
}
RemoveConnection(connection);
}
}
}
}
// if (graphViewChange.movedElements != null)
// {
// var movedElements = graphViewChange.movedElements;
// Undo.RecordObject(_graphObject, $"Move {movedElements.FirstOrDefault()?.GetType().Name}");
//
// foreach (var element in graphViewChange.movedElements)
// {
// element.SetPosition(element.GetPosition());
// }
// }
if (graphViewChange.edgesToCreate != null)
{
var createdEdges = graphViewChange.edgesToCreate;
Undo.RecordObject(_graphObject, $"Connect {createdEdges.FirstOrDefault()?.GetType().Name}");
foreach (var edge in createdEdges)
if (edge.input.userData is Slot inputSlot && edge.output.userData is Slot outputSlot)
{
var connection = new SlotConnection(inputSlot.slotData, outputSlot.slotData);
_slotConnections.Add(edge, connection);
outputSlot.Link(inputSlot);
AddConnection(connection);
}
}
_graphObject.SetGraphTransform(viewTransform);
_graphViewConfig.serializedObject.Update();
EditorUtility.SetDirty(_graphObject);
return graphViewChange;
}
public override List<Port> GetCompatiblePorts(Port startPort, NodeAdapter nodeAdapter)
{
var compatiblePorts = new List<Port>();
foreach (var port in ports)
{
if (startPort == port || startPort.node == port.node || startPort.direction == port.direction)
{
continue;
}
if (startPort.portType != port.portType &&
_graphObject.ValueConverterManager != null &&
!_graphObject.ValueConverterManager.CanConvert(startPort.portType, port.portType))
{
continue;
}
compatiblePorts.Add(port);
}
return compatiblePorts;
}
private void OnDragPerform(DragPerformEvent evt)
{
var data = DragAndDrop.GetGenericData("DragSelection");
if (data is List<ISelectable> selectables)
{
var propertyViews = selectables.OfType<BlackboardPropertyView>().ToArray();
if (propertyViews.Length <= 0)
{
return;
}
var position = (evt.currentTarget as VisualElement).ChangeCoordinatesTo(contentViewContainer, evt.localMousePosition);
foreach (var view in propertyViews)
{
if (view.userData is not ExposedProperty property)
{
continue;
}
var baseNode = new PropertyInput(property)
{
position = new Rect(position, Vector2.zero)
};
AddNode(baseNode);
}
}
}
public void AddNode(ExecutableNode executableNode)
{
Undo.RecordObject(_graphObject, $"Add {executableNode.GetType().Name}");
_graphObject.AddNode(executableNode);
AddNodeView(executableNode);
EditorUtility.SetDirty(_graphObject);
}
private void AddNodeView(DataNode node)
{
var nodeView = CreateNodeView(node);
if (nodeView == null)
{
return;
}
nodeView.SetPosition(node.position);
if (nodeView is IInspectable inspectable)
{
inspectable.OnItemSelected += ChangeInspectorView;
}
AddElement(nodeView);
_nodeViewsMap.Add(node.Id, nodeView);
}
protected virtual Node CreateNodeView(DataNode node)
{
var types = TypeCache.GetTypesWithAttribute<CustomInspectorAttribute>();
var type = types.FirstOrDefault(t =>
t.GetCustomAttribute<CustomInspectorAttribute>().InspectorType == node.GetType());
if (node is PropertyInput propertyInputNode)
{
return PropertyInputNodeView.Create(propertyInputNode, _graphViewConfig.portColorManager);
}
else if (node is ExecutableNode executableNode)
{
type ??= typeof(ExecutableNodeView);
return Activator.CreateInstance(type, executableNode, _graphViewConfig.serializedObject, _graphViewConfig.portColorManager, _graphObject.Logger) as ExecutableNodeView;
}
return null;
}
private void RemoveNode(ExecutableNode executableNode)
{
Undo.RecordObject(_graphObject, $"Remove {executableNode.GetType().Name}");
_graphObject.RemoveNode(executableNode);
RemoveNodeView(executableNode);
EditorUtility.SetDirty(_graphObject);
}
private void RemoveNodeView(ExecutableNode executableNode)
{
if (_nodeViewsMap.Remove(executableNode.Id, out var nodeView))
{
RemoveElement(nodeView);
if (nodeView is IInspectable inspectable)
{
inspectable.OnItemSelected -= ChangeInspectorView;
}
}
}
public void AddStickyNote(StickyNoteData stickyNote)
{
Undo.RecordObject(_graphObject, $"Add {stickyNote.title}");
_graphObject.AddStickyNote(stickyNote);
AddStickyNoteView(stickyNote);
}
private void AddStickyNoteView(StickyNoteData stickyNote)
{
var stickyNoteView = new StickyNoteView(stickyNote);
stickyNoteView.SetPosition(stickyNote.position);
AddElement(stickyNoteView);
}
public void RemoveStickyNote(StickyNoteData stickyNote)
{
Undo.RecordObject(_graphObject, $"Remove {stickyNote.title}");
_graphObject.RemoveStickyNote(stickyNote);
RemoveStickyNoteView(stickyNote);
}
private void RemoveStickyNoteView(StickyNoteData stickyNote)
{
// var stickyNoteView = GetStickyNoteView(stickyNote);
// if (stickyNoteView != null)
// {
// RemoveElement(stickyNoteView);
// }
}
public void AddConnection(SlotConnection connection)
{
Undo.RecordObject(_graphObject, $"Add {connection.GetType().Name}");
_graphObject.AddConnection(connection);
EditorUtility.SetDirty(_graphObject);
}
private void RemoveConnection(SlotConnection connection)
{
Undo.RecordObject(_graphObject, $"Remove {connection.GetType().Name}");
_graphObject.RemoveConnection(connection);
EditorUtility.SetDirty(_graphObject);
}
private void AddConnectionView(SlotConnection connection)
{
var inputSlotData = connection.InputSlotData;
var outputSlotData = connection.OutputSlotData;
if (!_nodeViewsMap.TryGetValue(inputSlotData.nodeID, out var inputNodeView) ||
!_nodeViewsMap.TryGetValue(outputSlotData.nodeID, out var outputNodeView))
{
return;
}
if (inputNodeView is not IPortContainer inputPortContainer || outputNodeView is not IPortContainer outputPortContainer)
{
return;
}
var inputPort = inputPortContainer.InputPorts[inputSlotData.slotIndex];
var outputPort = outputPortContainer.OutputPorts[outputSlotData.slotIndex];
var edge = inputPort.ConnectTo(outputPort);
AddElement(edge);
_slotConnections.Add(edge, connection);
}
public void ToggleBlackboardViewVisibility()
{
if (_blackboardView == null)
{
return;
}
_blackboardView.style.visibility = _blackboardView.style.visibility == Visibility.Hidden
? Visibility.Visible
: Visibility.Hidden;
}
private void ChangeInspectorView(IInspectable inspectable)
{
if (_graphInspectorView == null)
{
return;
}
_graphInspectorView.OnNodeSelectionChanged(inspectable);
}
public void ToggleInspectorViewVisibility()
{
if (_graphInspectorView == null)
{
return;
}
_graphInspectorView.style.visibility = _graphInspectorView.style.visibility == Visibility.Hidden
? Visibility.Visible
: Visibility.Hidden;
}
}
}

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 66c6b56270850cc458f01ab2bbd9429e

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e691d14381aaee54fb5214d0fcc9cfd4
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,197 @@
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.Experimental.GraphView;
using UnityEngine;
using UnityEngine.UIElements;
namespace Misaki.GraphView.Editor
{
public partial class GraphView : UnityEditor.Experimental.GraphView.GraphView
{
private readonly Dictionary<string, Node> _nodeViewsMap = new();
private readonly Dictionary<Edge, SlotConnection> _slotConnections = new();
private readonly GraphBlackboardView _blackboardView;
private readonly GraphInspectorView _graphInspectorView;
private readonly GraphViewConfig _graphViewConfig;
private GraphObject _graphObject => _graphViewConfig.graphObject;
public EditorWindow EditorWindow
{
get;
}
public GraphViewConfig GraphViewConfig => _graphViewConfig;
public GraphView(EditorWindow editorWindow, GraphViewConfig graphViewConfig)
{
EditorWindow = editorWindow;
_graphViewConfig = graphViewConfig;
var gridBackground = new GridBackground { name = "gridBackground" };
Add(gridBackground);
gridBackground.SendToBack();
var minimapConfig = _graphViewConfig.miniMapConfig;
if (minimapConfig is { enable: true })
{
var minimap = new MiniMap()
{
anchored = true
};
minimap.SetPosition(minimapConfig.position);
Add(minimap);
}
_blackboardView = new GraphBlackboardView(_graphObject, this, _graphViewConfig.serializedObject, _graphViewConfig.exposedPropertyTypeManager);
_blackboardView.OnPropertySelected += ChangeInspectorView;
foreach (var property in _graphObject.ExposedProperties)
{
_blackboardView.AddProperty(property);
}
_graphInspectorView = new GraphInspectorView();
Add(_blackboardView);
Add(_graphInspectorView);
this.StretchToParentSize();
this.AddManipulator(new ContentDragger());
this.AddManipulator(new SelectionDragger());
this.AddManipulator(new RectangleSelector());
this.AddManipulator(new ClickSelector());
var zoomConfig = _graphViewConfig.zoomConfig;
var zoomer = new ContentZoomer();
if (zoomConfig != null)
{
zoomer.minScale = zoomConfig.minScale;
zoomer.maxScale = zoomConfig.maxScale;
zoomer.scaleStep = zoomConfig.scaleStep;
}
this.AddManipulator(zoomer);
InitializeAssetElements();
graphViewChanged += OnGraphViewChanged;
RegisterCallback<DragPerformEvent>(OnDragPerform);
RegisterCallbackOnce<GeometryChangedEvent>(_ => _graphInspectorView?.DockToParent(layout, DockingPosition.Right, false));
}
private void InitializeAssetElements()
{
var searchProvider = ScriptableObject.CreateInstance<NodeSearchProvider>();
searchProvider.SetOwner(this);
nodeCreationRequest = context =>
{
SearchWindow.Open(new SearchWindowContext(context.screenMousePosition), searchProvider);
};
foreach (var node in _graphObject.Nodes)
{
AddNodeView(node);
}
foreach (var noteData in _graphObject.StickyNotes)
{
AddStickyNoteView(noteData);
}
foreach (var connection in _graphObject.Connections)
{
AddConnectionView(connection);
}
}
public override List<Port> GetCompatiblePorts(Port startPort, NodeAdapter nodeAdapter)
{
var compatiblePorts = new List<Port>();
foreach (var port in ports)
{
if (startPort == port || startPort.node == port.node || startPort.direction == port.direction)
{
continue;
}
if (startPort.portType != port.portType &&
_graphObject.ValueConverterManager != null &&
!_graphObject.ValueConverterManager.CanConvert(startPort.portType, port.portType))
{
continue;
}
compatiblePorts.Add(port);
}
return compatiblePorts;
}
private void OnDragPerform(DragPerformEvent evt)
{
var data = DragAndDrop.GetGenericData("DragSelection");
if (data is List<ISelectable> selectables)
{
var propertyViews = selectables.OfType<BlackboardPropertyView>().ToArray();
if (propertyViews.Length <= 0)
{
return;
}
var position = (evt.currentTarget as VisualElement).ChangeCoordinatesTo(contentViewContainer, evt.localMousePosition);
foreach (var view in propertyViews)
{
if (view.userData is not ExposedProperty property)
{
continue;
}
var baseNode = new PropertyInput(property)
{
position = new Rect(position, Vector2.zero)
};
AddNode(baseNode);
}
}
}
public void ToggleBlackboardViewVisibility()
{
if (_blackboardView == null)
{
return;
}
_blackboardView.style.visibility = _blackboardView.style.visibility == Visibility.Hidden
? Visibility.Visible
: Visibility.Hidden;
}
private void ChangeInspectorView(IInspectable inspectable)
{
if (_graphInspectorView == null)
{
return;
}
_graphInspectorView.OnNodeSelectionChanged(inspectable);
}
public void ToggleInspectorViewVisibility()
{
if (_graphInspectorView == null)
{
return;
}
_graphInspectorView.style.visibility = _graphInspectorView.style.visibility == Visibility.Hidden
? Visibility.Visible
: Visibility.Hidden;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 85dea80825e410d4093cc4d10f33931b

View File

@@ -0,0 +1,45 @@
using UnityEditor.Experimental.GraphView;
using UnityEngine;
using UnityEngine.UIElements;
namespace Misaki.GraphView.Editor
{
public partial class GraphView
{
public override void BuildContextualMenu(ContextualMenuPopulateEvent evt)
{
base.BuildContextualMenu(evt);
var mousePosition = (evt.currentTarget as VisualElement).ChangeCoordinatesTo(contentViewContainer, evt.localMousePosition);
if (evt.target is GraphView)
{
evt.menu.InsertAction(1, "Create Sticky Note", e =>
{
var stickyNote = new StickyNoteData
{
title = "Sticky Note",
contents = "Contents",
theme = StickyNoteTheme.Classic,
fontSize = StickyNoteFontSize.Medium,
position = new Rect(mousePosition, new Vector2(200, 200))
};
AddStickyNote(stickyNote);
}, DropdownMenuAction.AlwaysEnabled);
}
if (evt.target is Edge)
{
evt.menu.AppendAction("Create Relay Node", e =>
{
var relayNode = new RelayNode
{
position = new Rect(mousePosition, Vector2.zero)
};
AddRelayNode(relayNode);
}, DropdownMenuAction.AlwaysEnabled);
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d8daa8d0ebadc4a40872f6613533e4bd

View File

@@ -0,0 +1,84 @@
using System.Linq;
using UnityEditor;
using UnityEditor.Experimental.GraphView;
namespace Misaki.GraphView.Editor
{
public partial class GraphView
{
private GraphViewChange OnGraphViewChanged(GraphViewChange graphViewChange)
{
if (graphViewChange.elementsToRemove != null)
{
var removedElements = graphViewChange.elementsToRemove;
Undo.RecordObject(_graphObject, "Remove elements");
for (var i = removedElements.Count - 1; i >= 0; i--)
{
if (removedElements[i] is Node { userData: DataNode node })
{
RemoveNode(node);
}
if (removedElements[i] is StickyNote { userData: StickyNoteData stickyNote })
{
RemoveStickyNote(stickyNote);
}
if (removedElements[i] is Edge edge)
{
if (_slotConnections.Remove(edge, out var connection))
{
var inputNode = _graphObject.GetNode(connection.InputSlotData.nodeID);
var outputNode = _graphObject.GetNode(connection.OutputSlotData.nodeID);
if (inputNode is ISlotContainer inputSlotContainer && outputNode is ISlotContainer outputSlotContainer)
{
var inputSlot = inputSlotContainer.GetSlot(connection.InputSlotData.slotIndex, connection.InputSlotData.direction);
var outputSlot = outputSlotContainer.GetSlot(connection.OutputSlotData.slotIndex, connection.OutputSlotData.direction);
inputSlot.Unlink(outputSlot);
}
RemoveConnection(connection);
}
}
}
}
// if (graphViewChange.movedElements != null)
// {
// var movedElements = graphViewChange.movedElements;
// Undo.RecordObject(_graphObject, $"Move {movedElements.FirstOrDefault()?.GetType().Name}");
//
// foreach (var element in graphViewChange.movedElements)
// {
// element.SetPosition(element.GetPosition());
// }
// }
if (graphViewChange.edgesToCreate != null)
{
var createdEdges = graphViewChange.edgesToCreate;
Undo.RecordObject(_graphObject, $"Connect {createdEdges.FirstOrDefault()?.GetType().Name}");
foreach (var edge in createdEdges)
if (edge.input.userData is Slot inputSlot && edge.output.userData is Slot outputSlot)
{
var connection = new SlotConnection(inputSlot.slotData, outputSlot.slotData);
_slotConnections.Add(edge, connection);
outputSlot.Link(inputSlot);
AddConnection(connection);
}
}
_graphObject.SetGraphTransform(viewTransform);
_graphViewConfig.serializedObject.Update();
EditorUtility.SetDirty(_graphObject);
return graphViewChange;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c31e6279987ed0f449f1b23a385eb4be

View File

@@ -0,0 +1,176 @@
using System;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEditor.Experimental.GraphView;
namespace Misaki.GraphView.Editor
{
public partial class GraphView
{
public void AddNode(ExecutableNode executableNode)
{
Undo.RecordObject(_graphObject, $"Add {executableNode.GetType().Name}");
_graphObject.AddNode(executableNode);
AddNodeView(executableNode);
EditorUtility.SetDirty(_graphObject);
}
private void AddNodeView(DataNode node)
{
var nodeView = CreateNodeView(node);
if (nodeView == null)
{
return;
}
nodeView.SetPosition(node.position);
if (nodeView is IInspectable inspectable)
{
inspectable.OnItemSelected += ChangeInspectorView;
}
AddElement(nodeView);
_nodeViewsMap.Add(node.Id, nodeView);
}
protected virtual Node CreateNodeView(DataNode node)
{
var types = TypeCache.GetTypesWithAttribute<CustomInspectorAttribute>();
var type = types.FirstOrDefault(t =>
t.GetCustomAttribute<CustomInspectorAttribute>().InspectorType == node.GetType());
if (node is PropertyInput propertyInputNode)
{
return PropertyInputNodeView.Create(propertyInputNode, _graphViewConfig.portColorManager);
}
else if (node is ExecutableNode executableNode)
{
type ??= typeof(ExecutableNodeView);
return Activator.CreateInstance(type, executableNode, _graphViewConfig.serializedObject, _graphViewConfig.portColorManager, _graphObject.Logger) as ExecutableNodeView;
}
return null;
}
private void RemoveNode(DataNode dataNode)
{
Undo.RecordObject(_graphObject, $"Remove {dataNode.GetType().Name}");
_graphObject.RemoveNode(dataNode);
RemoveNodeView(dataNode);
EditorUtility.SetDirty(_graphObject);
}
private void RemoveNodeView(DataNode dataNode)
{
if (_nodeViewsMap.Remove(dataNode.Id, out var nodeView))
{
RemoveElement(nodeView);
if (nodeView is IInspectable inspectable)
{
inspectable.OnItemSelected -= ChangeInspectorView;
}
}
}
public void AddStickyNote(StickyNoteData stickyNote)
{
Undo.RecordObject(_graphObject, $"Add {stickyNote.title}");
_graphObject.AddStickyNote(stickyNote);
AddStickyNoteView(stickyNote);
EditorUtility.SetDirty(_graphObject);
}
private void AddStickyNoteView(StickyNoteData stickyNote)
{
var stickyNoteView = new StickyNoteView(stickyNote);
stickyNoteView.SetPosition(stickyNote.position);
AddElement(stickyNoteView);
}
public void RemoveStickyNote(StickyNoteData stickyNote)
{
Undo.RecordObject(_graphObject, $"Remove {stickyNote.title}");
_graphObject.RemoveStickyNote(stickyNote);
RemoveStickyNoteView(stickyNote);
}
private void RemoveStickyNoteView(StickyNoteData stickyNote)
{
// var stickyNoteView = GetStickyNoteView(stickyNote);
// if (stickyNoteView != null)
// {
// RemoveElement(stickyNoteView);
// }
}
public void AddConnection(SlotConnection connection)
{
Undo.RecordObject(_graphObject, $"Add {connection.GetType().Name}");
_graphObject.AddConnection(connection);
EditorUtility.SetDirty(_graphObject);
}
private void RemoveConnection(SlotConnection connection)
{
Undo.RecordObject(_graphObject, $"Remove {connection.GetType().Name}");
_graphObject.RemoveConnection(connection);
EditorUtility.SetDirty(_graphObject);
}
private void AddConnectionView(SlotConnection connection)
{
var inputSlotData = connection.InputSlotData;
var outputSlotData = connection.OutputSlotData;
if (!_nodeViewsMap.TryGetValue(inputSlotData.nodeID, out var inputNodeView) ||
!_nodeViewsMap.TryGetValue(outputSlotData.nodeID, out var outputNodeView))
{
return;
}
if (inputNodeView is not IPortContainer inputPortContainer || outputNodeView is not IPortContainer outputPortContainer)
{
return;
}
var inputPort = inputPortContainer.GetPort(inputSlotData.slotIndex, Direction.Input);
var outputPort = outputPortContainer.GetPort(outputSlotData.slotIndex, Direction.Output);
var edge = inputPort.ConnectTo(outputPort);
AddElement(edge);
_slotConnections.Add(edge, connection);
}
public void AddRelayNode(RelayNode relayNode)
{
Undo.RecordObject(_graphObject, $"Add {relayNode.GetType().Name}");
_graphObject.AddNode(relayNode);
AddRelayNodeView(relayNode);
EditorUtility.SetDirty(_graphObject);
}
private void AddRelayNodeView(RelayNode relayNode)
{
var relayNodeView = new RelayNodeView(relayNode, _graphViewConfig.portColorManager);
relayNodeView.SetPosition(relayNode.position);
AddElement(relayNodeView);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d01a10830ebed08498c09668ee5d0c74

View File

@@ -16,18 +16,18 @@ namespace Misaki.GraphView.Editor
private readonly Type _nodeType; private readonly Type _nodeType;
private readonly NodeInfoAttribute _nodeInfo; private readonly NodeInfoAttribute _nodeInfo;
private readonly List<Port> _inputPorts = new (); private readonly List<Port> _inputPorts = new();
private readonly List<Port> _outputPorts = new (); private readonly List<Port> _outputPorts = new();
private readonly IPortColorManager _portColorManager; private readonly IPortColorManager _portColorManager;
private readonly SerializedObject _serializedObject; private readonly SerializedObject _serializedObject;
private readonly VisualElement _logContainer = new(); private readonly VisualElement _logContainer = new();
public List<Port> InputPorts => _inputPorts; public Action<IInspectable> OnItemSelected
public List<Port> OutputPorts => _outputPorts; {
get; set;
public Action<IInspectable> OnItemSelected { get; set; } }
public string InspectorName => _nodeInfo.Name ?? _nodeType.Name; public string InspectorName => _nodeInfo.Name ?? _nodeType.Name;
@@ -107,7 +107,7 @@ namespace Misaki.GraphView.Editor
} }
} }
private void CreateLogElement(ExecutableNode node, string message, LogType type) private void CreateLogElement(DataNode node, string message, LogType type)
{ {
if (node.Id != _dataNode.Id) if (node.Id != _dataNode.Id)
{ {
@@ -212,6 +212,16 @@ namespace Misaki.GraphView.Editor
OnItemSelected?.Invoke(null); OnItemSelected?.Invoke(null);
} }
public Port GetPort(int index, Direction direction)
{
return direction switch
{
Direction.Input => _inputPorts[index],
Direction.Output => _outputPorts[index],
_ => null
};
}
/// <summary> /// <summary>
/// Displays the inspector for the node. /// Displays the inspector for the node.
/// </summary> /// </summary>

View File

@@ -1,7 +1,4 @@
using System; using UnityEditor.Experimental.GraphView;
using System.Collections.Generic;
using NUnit.Framework;
using UnityEditor.Experimental.GraphView;
using UnityEngine; using UnityEngine;
using UnityEngine.UIElements; using UnityEngine.UIElements;
@@ -9,28 +6,23 @@ namespace Misaki.GraphView.Editor
{ {
public class PropertyInputNodeView : TokenNode, IPortContainer public class PropertyInputNodeView : TokenNode, IPortContainer
{ {
private readonly List<Port> _outputPorts = new List<Port>(); private readonly Port _outputPort;
private readonly PropertyInput _data; private readonly PropertyInput _data;
//private readonly ExposedPropertyEditor _editor; //private readonly ExposedPropertyEditor _editor;
public PropertyInput Data => _data; public PropertyInput Data => _data;
public List<Port> InputPorts => null;
public List<Port> OutputPorts => _outputPorts;
public PropertyInputNodeView(PropertyInput data, Port output) : base(null, output) public PropertyInputNodeView(PropertyInput data, Port output) : base(null, output)
{ {
_data = data; _data = data;
//_editor = editor; _outputPort = output;
name = data.Property.propertyName; name = data.Property.propertyName;
title = data.Property.propertyName; title = data.Property.propertyName;
userData = data; userData = data;
this.Q<VisualElement>("top").style.minHeight = 24; this.Q<VisualElement>("top").style.minHeight = 24;
_outputPorts.Add(output);
} }
public static PropertyInputNodeView Create(PropertyInput data, IPortColorManager portColorManager) public static PropertyInputNodeView Create(PropertyInput data, IPortColorManager portColorManager)
@@ -75,5 +67,10 @@ namespace Misaki.GraphView.Editor
base.SetPosition(newPos); base.SetPosition(newPos);
_data.position = newPos; _data.position = newPos;
} }
public Port GetPort(int index, Direction direction)
{
return _outputPort;
}
} }
} }

View File

@@ -0,0 +1,51 @@
using System;
using UnityEditor.Experimental.GraphView;
namespace Misaki.GraphView.Editor
{
public class RelayNodeView : Node, IPortContainer
{
private RelayNode _dataNode;
private IPortColorManager _portColorManager;
private readonly Port _inputPort;
private readonly Port _outputPort;
public RelayNodeView(RelayNode dataNode, IPortColorManager portColorManager)
{
_dataNode = dataNode;
_portColorManager = portColorManager;
_inputPort = Port.Create<Edge>(Orientation.Horizontal, Direction.Input, Port.Capacity.Single, typeof(object));
_outputPort = Port.Create<Edge>(Orientation.Horizontal, Direction.Output, Port.Capacity.Multi, typeof(object));
_inputPort.portName = string.Empty;
_outputPort.portName = string.Empty;
SetPortsTypeAndColor(typeof(object));
inputContainer.Add(_inputPort);
outputContainer.Add(_outputPort);
}
private void SetPortsTypeAndColor(Type portType)
{
_inputPort.portType = portType;
_outputPort.portType = portType;
if (_portColorManager != null)
{
if (_portColorManager.TryGetColor(portType, out var portColor))
{
_inputPort.portColor = portColor;
_outputPort.portColor = portColor;
}
}
}
public Port GetPort(int index, Direction direction)
{
return direction == Direction.Input ? _inputPort : _outputPort;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ba1646416a4d47148947945befe2b938

View File

@@ -1,15 +1,17 @@
using System; using System;
using UnityEngine;
namespace Misaki.GraphView namespace Misaki.GraphView
{ {
public interface ILogger public interface ILogger
{ {
public Action<ExecutableNode, string, LogType> OnLog { get; set; } public Action<DataNode, string, LogType> OnLog
{
get; set;
}
public void LogInfo(ExecutableNode node, string message); public void LogInfo(DataNode node, string message);
public void LogWarning(ExecutableNode node, string message); public void LogWarning(DataNode node, string message);
public void LogError(ExecutableNode node, string message); public void LogError(DataNode node, string message);
public void ClearLogs(); public void ClearLogs();
} }

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
namespace Misaki.GraphView namespace Misaki.GraphView
{ {
@@ -7,6 +8,7 @@ namespace Misaki.GraphView
/// <summary> /// <summary>
/// Unlink all slots from this slot. /// Unlink all slots from this slot.
/// </summary> /// </summary>
/// <param name="slot">The slot to unlink all connections from.</param>
public static void UnlinkAll(this Slot slot) public static void UnlinkAll(this Slot slot)
{ {
var slotCount = slot.LinkedSlotData.Count; var slotCount = slot.LinkedSlotData.Count;
@@ -25,10 +27,108 @@ namespace Misaki.GraphView
/// <summary> /// <summary>
/// Get the value type of the slot data. /// Get the value type of the slot data.
/// </summary> /// </summary>
/// <returns><see cref="Type"/> The type of the slot value </returns> /// <param name="slotData">The slot data to get the value type from.</param>
/// <returns><see cref="Type"/> The type of the slot value.</returns>
public static Type GetValueType(this SlotData slotData) public static Type GetValueType(this SlotData slotData)
{ {
return Type.GetType(slotData.valueType); return Type.GetType(slotData.valueType);
} }
public static void ReceiveData(this Slot slot, object data)
{
slot.value = data;
}
/// <summary>
/// Pull data from the slot and execute the provided action.
/// </summary>
/// <param name="slot">The slot to pull data from.</param>
/// <param name="OnPullData">The action to execute when pulling data.</param>
public static void PullData(this Slot slot, Action<Slot> OnPullData)
{
var property = slot.owner.GetType().GetField(slot.slotData.slotName, ConstResource.NODE_FIELD_BINDING_FLAGS);
if (property == null)
{
return;
}
OnPullData(slot);
if (slot.LinkedSlotData.Count == 0)
{
return;
}
property.SetValue(slot.owner, slot.value);
}
/// <summary>
/// Push data to the slot and execute the provided action.
/// </summary>
/// <param name="slot">The slot to push data to.</param>
/// <param name="OnPushData">The action to execute when pushing data.</param>
public static void PushData(this Slot slot, Action<Slot> OnPushData)
{
var property = slot.owner.GetType().GetField(slot.slotData.slotName, ConstResource.NODE_FIELD_BINDING_FLAGS);
if (property == null)
{
return;
}
OnPushData(slot);
slot.value = property.GetValue(slot.owner);
foreach (var slotData in slot.LinkedSlotData)
{
var node = slot.owner.GraphObject.GetNode(slotData.nodeID);
if (node is not ISlotContainer slotContainer)
{
continue;
}
var otherSlot = slotContainer.GetSlot(slotData.slotIndex, slotData.direction);
if (slotData.GetValueType() == slot.slotData.GetValueType() || slot.slotData.GetValueType() == typeof(object))
{
otherSlot.ReceiveData(slot.value);
}
else if (slot.owner.GraphObject.ValueConverterManager != null && slot.owner.GraphObject.ValueConverterManager.TryConvert(slot.slotData.GetValueType(),
slotData.GetValueType(), slot.value, out var data))
{
otherSlot.ReceiveData(data);
}
else
{
slot.owner.GraphObject.Logger?.LogError(slot.owner, $"Failed to convert value from {otherSlot.slotData.valueType} to {slotData.valueType}");
}
}
}
/// <summary>
/// Pull data from a collection of slots and execute the provided action.
/// </summary>
/// <param name="slots">The collection of slots to pull data from.</param>
/// <param name="OnPullData">The action to execute when pulling data.</param>
public static void PullData(this IEnumerable<Slot> slots, Action<Slot> OnPullData)
{
foreach (var slot in slots)
{
slot.PullData(OnPullData);
}
}
/// <summary>
/// Push data to a collection of slots and execute the provided action.
/// </summary>
/// <param name="slots">The collection of slots to push data to.</param>
/// <param name="OnPushData">The action to execute when pushing data.</param>
public static void PushData(this IEnumerable<Slot> slots, Action<Slot> OnPushData)
{
foreach (var slot in slots)
{
slot.PushData(OnPushData);
}
}
} }
} }

View File

@@ -10,16 +10,16 @@ namespace Misaki.GraphView
public abstract class ExecutableNode : DataNode, ISlotContainer, IExecutable public abstract class ExecutableNode : DataNode, ISlotContainer, IExecutable
{ {
[SerializeField] [SerializeField]
private List<Slot> _inputs = new (); private List<Slot> _inputs = new();
[SerializeField] [SerializeField]
private List<Slot> _outputs = new (); private List<Slot> _outputs = new();
public ReadOnlyCollection<Slot> Inputs => _inputs.AsReadOnly(); public ReadOnlyCollection<Slot> Inputs => _inputs.AsReadOnly();
public ReadOnlyCollection<Slot> Outputs => _outputs.AsReadOnly(); public ReadOnlyCollection<Slot> Outputs => _outputs.AsReadOnly();
private bool _isExecuted; private bool _isExecuted;
public Action OnExecutoinStarted; public Action OnExecutionStarted;
public Action OnExecutionCompleted; public Action OnExecutionCompleted;
public Action OnExecutionFailed; public Action OnExecutionFailed;
public Action OnExecuteFlagCleared; public Action OnExecuteFlagCleared;
@@ -135,9 +135,8 @@ namespace Misaki.GraphView
return; return;
} }
OnExecutoinStarted?.Invoke(); OnExecutionStarted?.Invoke();
Inputs.PullData(OnPullData);
PullData();
if (!graphObject.GraphProcessor.IsRunning) if (!graphObject.GraphProcessor.IsRunning)
{ {
@@ -151,7 +150,7 @@ namespace Misaki.GraphView
return; return;
} }
PushData(); Outputs.PushData(OnPushData);
_isExecuted = true; _isExecuted = true;
OnExecutionCompleted?.Invoke(); OnExecutionCompleted?.Invoke();
@@ -164,66 +163,10 @@ namespace Misaki.GraphView
OnExecuteFlagCleared?.Invoke(); OnExecuteFlagCleared?.Invoke();
} }
private void PullData()
{
foreach (var input in Inputs)
{
var property = GetType().GetField(input.slotData.slotName, ConstResource.NODE_FIELD_BINDING_FLAGS);
if (property == null)
{
continue;
}
OnPullData(input);
if (input.LinkedSlotData.Count == 0)
{
continue;
}
property.SetValue(this, input.value);
}
}
protected virtual void OnPullData(Slot input) protected virtual void OnPullData(Slot input)
{ {
} }
private void PushData()
{
foreach (var output in Outputs)
{
var property = GetType().GetField(output.slotData.slotName, ConstResource.NODE_FIELD_BINDING_FLAGS);
if (property == null)
{
continue;
}
OnPushData(output);
output.value = property.GetValue(this);
foreach (var slotData in output.LinkedSlotData)
{
var node = graphObject.GetNode(slotData.nodeID);
if (node is not ISlotContainer slotContainer)
{
continue;
}
var slot = slotContainer.GetSlot(slotData.slotIndex, SlotDirection.Input);
if (slotData.valueType == output.slotData.valueType || output.slotData.valueType == typeof(object).FullName)
{
slot.ReceiveData(output.value);
}
else if (graphObject.ValueConverterManager != null && graphObject.ValueConverterManager.TryConvert(output.slotData.GetValueType(),
slotData.GetValueType(), output.value, out var data))
{
slot.ReceiveData(data);
}
}
}
}
protected virtual void OnPushData(Slot output) protected virtual void OnPushData(Slot output)
{ {
} }

View File

@@ -1,9 +1,78 @@
using UnityEngine; using System;
namespace Misaki.GraphView namespace Misaki.GraphView
{ {
public class RelayNode [Serializable]
public class RelayNode : DataNode, ISlotContainer, IExecutable
{ {
private Slot _inputSlot;
private Slot _outputSlot;
private bool _isExecuted;
public override void Initialize(GraphObject graph)
{
graphObject = graph;
_inputSlot = new(this, new()
{
slotName = "Input",
nodeID = Id,
slotIndex = 0,
direction = SlotDirection.Input,
valueType = typeof(object).FullName
});
_outputSlot = new(this, new()
{
slotName = "Output",
nodeID = Id,
slotIndex = 0,
direction = SlotDirection.Output,
valueType = typeof(object).FullName
});
}
public void AddSlot(Slot slot)
{
}
public void RemoveSlot(Slot slot)
{
}
public Slot GetSlot(int index, SlotDirection direction)
{
return direction switch
{
SlotDirection.Input => _inputSlot,
SlotDirection.Output => _outputSlot,
_ => null
};
}
public void UnlinkAllSlots()
{
_inputSlot.UnlinkAll();
_outputSlot.UnlinkAll();
}
public void Execute()
{
if (_isExecuted)
{
return;
}
_outputSlot.ReceiveData(_inputSlot.value);
_outputSlot.PushData(null);
_isExecuted = true;
}
public void ClearExecutionFlag()
{
_isExecuted = false;
}
} }
} }

View File

@@ -89,12 +89,5 @@ namespace Misaki.GraphView
_linkedSlotData.Remove(other.slotData); _linkedSlotData.Remove(other.slotData);
other._linkedSlotData.Remove(slotData); other._linkedSlotData.Remove(slotData);
} }
public void ReceiveData(object data)
{
value = data;
// We move this to PullData method in BaseNode
//owner.GetType().GetField(slotData.slotName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.SetValue(owner, data);
}
} }
} }

View File

@@ -22,8 +22,8 @@ MonoBehaviour:
contents: Test contents: Test
position: position:
serializedVersion: 2 serializedVersion: 2
x: 764.2733 x: 624.0001
y: 223.30664 y: 217.33333
width: 164 width: 164
height: 156.66666 height: 156.66666
theme: 0 theme: 0
@@ -55,7 +55,7 @@ MonoBehaviour:
valueType: System.Object valueType: System.Object
_exposedProperties: _exposedProperties:
- rid: 299037523270959195 - rid: 299037523270959195
graphPosition: {x: 72, y: 28.666666, z: 0} graphPosition: {x: 131.33333, y: -8, z: 0}
graphScale: {x: 1, y: 1, z: 1} graphScale: {x: 1, y: 1, z: 1}
references: references:
version: 2 version: 2
@@ -118,8 +118,8 @@ MonoBehaviour:
slotIndex: 0 slotIndex: 0
direction: 1 direction: 1
valueType: System.Single valueType: System.Single
a: 0 a: 1
b: 0 b: 1
- rid: 299037570321612881 - rid: 299037570321612881
type: {class: Output, ns: Misaki.GraphView.Sample, asm: GraphView.Sample} type: {class: Output, ns: Misaki.GraphView.Sample, asm: GraphView.Sample}
data: data:
@@ -154,8 +154,8 @@ MonoBehaviour:
id: 5ee073fb-e411-4061-867a-9ce3afd9a188 id: 5ee073fb-e411-4061-867a-9ce3afd9a188
position: position:
serializedVersion: 2 serializedVersion: 2
x: 335.3333 x: 357.66666
y: 484.6667 y: 484.6665
width: 98.66666 width: 98.66666
height: 36 height: 36
_inputs: [] _inputs: []

View File

@@ -5,23 +5,26 @@ namespace Misaki.GraphView.Sample
{ {
public class Logger : ILogger public class Logger : ILogger
{ {
private readonly List<string> _logs = new (); private readonly List<string> _logs = new();
public Action<ExecutableNode, string, LogType> OnLog { get; set; } public Action<DataNode, string, LogType> OnLog
{
get; set;
}
public void LogInfo(ExecutableNode node, string message) public void LogInfo(DataNode node, string message)
{ {
_logs.Add($"Log Info from node {node.GetType().Name}: {message}"); _logs.Add($"Log Info from node {node.GetType().Name}: {message}");
OnLog?.Invoke(node, message, LogType.Info); OnLog?.Invoke(node, message, LogType.Info);
} }
public void LogWarning(ExecutableNode node, string message) public void LogWarning(DataNode node, string message)
{ {
_logs.Add($"Log Warning from node {node.GetType().Name}: {message}"); _logs.Add($"Log Warning from node {node.GetType().Name}: {message}");
OnLog?.Invoke(node, message, LogType.Warning); OnLog?.Invoke(node, message, LogType.Warning);
} }
public void LogError(ExecutableNode node, string message) public void LogError(DataNode node, string message)
{ {
_logs.Add($"Log Error from node {node.GetType().Name}: {message}"); _logs.Add($"Log Error from node {node.GetType().Name}: {message}");
OnLog?.Invoke(node, message, LogType.Error); OnLog?.Invoke(node, message, LogType.Error);