First commit

This commit is contained in:
Misaki
2024-11-02 17:58:52 +09:00
commit e645a5327b
153 changed files with 3729 additions and 0 deletions

View File

@@ -0,0 +1,44 @@
using System;
using System.Reflection;
using UnityEditor;
using UnityEditor.Experimental.GraphView;
using UnityEditor.UIElements;
using UnityEngine.UIElements;
namespace Misaki.GraphView.Editor
{
public class BlackboardPropertyView : BlackboardField, IInspectable
{
private readonly ExposedPropertyEditor _editor;
public Action<IInspectable> OnItemSelected { get; set; }
public string InspectorName => text;
public BlackboardPropertyView(ExposedPropertyEditor editor)
{
_editor = editor;
}
public override void OnSelected()
{
base.OnSelected();
OnItemSelected?.Invoke(this);
}
public override void OnUnselected()
{
base.OnUnselected();
OnItemSelected?.Invoke(null);
}
public virtual EditorNodeView CreateNodeView()
{
return null;
}
public virtual VisualElement CreateInspector()
{
return _editor.CreateInspector();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: bcc0238bd86d4454bc8c03cf8feadff2
timeCreated: 1730456603

View File

@@ -0,0 +1,120 @@
using System;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEditor.Experimental.GraphView;
using UnityEngine;
using UnityEngine.UIElements;
namespace Misaki.GraphView.Editor
{
public class GraphBlackboardView : Blackboard
{
private readonly GraphObject _graphObject;
private readonly GraphView _owner;
private readonly SerializedObject _serializedObject;
private readonly IExposedPropertyTypeManager _exposedPropertyTypeManager;
public Action<IInspectable> OnPropertySelected;
public GraphBlackboardView(GraphObject graphObject, GraphView owner, SerializedObject serializedObject, IExposedPropertyTypeManager exposedPropertyTypeManager)
{
style.marginBottom = 8;
style.marginTop = 8;
style.marginLeft = 8;
style.marginRight = 8;
_graphObject = graphObject;
_owner = owner;
_serializedObject = serializedObject;
_exposedPropertyTypeManager = exposedPropertyTypeManager;
title = "Exposed Properties";
subTitle = graphObject.name;
addItemRequested = OnAddItemRequested;
editTextRequested += OnEditTextRequested;
}
private void OnAddItemRequested(Blackboard blackboard)
{
if (_exposedPropertyTypeManager == null)
{
return;
}
var menu = new GenericMenu();
foreach (var type in _exposedPropertyTypeManager.GetPropertyTypes())
{
menu.AddItem(new GUIContent(type.Value.Name), false, () =>
{
AddProperty(type.Key);
});
}
menu.ShowAsContext();
}
private void OnEditTextRequested(Blackboard blackboard, VisualElement element, string newValue)
{
if (element is BlackboardPropertyView propertyView)
{
propertyView.text = newValue;
if (propertyView.userData is not ExposedProperty exposedProperty)
{
return;
}
exposedProperty.propertyName = newValue;
_owner.Query<PropertyInputNodeView>().ForEach(n =>
{
if (n.DataNode.Property.Equals(exposedProperty))
{
n.title = newValue;
}
});
_serializedObject.Update();
}
}
private void AddProperty(Type type)
{
var property = Activator.CreateInstance(type) as ExposedProperty;
if (property == null)
{
return;
}
property.propertyName = $"New {property.GetValueType().Name} Property";
property.propertyType = type.FullName;
AddProperty(property);
_graphObject.AddExposedProperty(property);
_serializedObject.Update();
}
public void AddProperty(ExposedProperty property)
{
var shortTypeName = property.GetValueType().Name;
var editorTypes = TypeCache.GetTypesWithAttribute<CustomInspectorAttribute>();
var type = editorTypes.FirstOrDefault(t => t.GetCustomAttribute<CustomInspectorAttribute>().InspectorType == property.GetType()) ?? typeof(ExposedPropertyEditor);
var editor = Activator.CreateInstance(type, property, _serializedObject) as ExposedPropertyEditor;
var blackboardField = new BlackboardPropertyView (editor)
{
text = property.propertyName,
typeText = shortTypeName,
userData = property,
OnItemSelected = OnPropertySelected
};
Add(blackboardField);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: cdc2c6e3a651497baf0a42a19619d193
timeCreated: 1730453741

View File

@@ -0,0 +1,54 @@
using System.Reflection;
using UnityEditor;
using UnityEditor.Experimental.GraphView;
using UnityEngine;
using UnityEngine.UIElements;
namespace Misaki.GraphView.Editor
{
public sealed class GraphInspectorView : GraphSubWindow
{
private const string UIDocumentPath = "Packages/com.misaki.graph-view/Editor/Views/GraphInspectorView.uxml";
private readonly Label _header;
private readonly VisualElement _inspectorPropertiesContainer;
private Vector2 _startMousePosition;
private Vector2 _startElementPosition;
public GraphInspectorView()
{
style.minWidth = 300;
style.minHeight = 500;
var uiDocument = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(UIDocumentPath);
var inspectorView = uiDocument.Instantiate();
inspectorView.style.flexGrow = 1;
_header = inspectorView.Q<Label>("node-name-label");
_header.text = string.Empty;
_inspectorPropertiesContainer = inspectorView.Q<VisualElement>("inspector-properties-container");
Add(inspectorView);
}
private void ClearInspector()
{
_header.text = string.Empty;
_inspectorPropertiesContainer.Clear();
}
public void OnNodeSelectionChanged(IInspectable selection)
{
ClearInspector();
if (selection == null)
{
return;
}
_header.text = selection.InspectorName ?? "Inspector";
_inspectorPropertiesContainer.Add(selection.CreateInspector());
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8290ebaa6d394b13bda6bc4cc94763e8
timeCreated: 1730376321

View File

@@ -0,0 +1,13 @@
<engine:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:engine="UnityEngine.UIElements" xmlns:editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="True">
<engine:VisualElement picking-mode="Ignore" style="flex-grow: 1; border-top-left-radius: 4px; border-top-right-radius: 4px; border-bottom-right-radius: 4px; border-bottom-left-radius: 4px; border-top-width: 1px; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px; border-left-color: rgba(0, 0, 0, 0.35); border-right-color: rgba(0, 0, 0, 0.35); border-top-color: rgba(0, 0, 0, 0.35); border-bottom-color: rgba(0, 0, 0, 0.35); overflow: hidden; background-color: rgb(46, 46, 46); margin-top: 8px; margin-right: 8px; margin-bottom: 8px; margin-left: 8px;">
<engine:VisualElement style="padding-top: 8px; padding-right: 8px; padding-bottom: 8px; padding-left: 8px; background-color: rgba(255, 255, 255, 0.05);">
<engine:Label text="Graph Inspector" name="header" style="font-size: 14px; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; margin-top: 4px; margin-right: 4px; margin-bottom: 4px; margin-left: 4px;" />
</engine:VisualElement>
<engine:ScrollView>
<engine:VisualElement style="flex-grow: 1; padding-top: 8px; padding-right: 8px; padding-bottom: 8px; padding-left: 8px;">
<engine:Label text="Label" name="node-name-label" style="-unity-font-style: bold; margin-bottom: 8px;" />
<engine:VisualElement name="inspector-properties-container" style="flex-grow: 1; margin-left: 8px;" />
</engine:VisualElement>
</engine:ScrollView>
</engine:VisualElement>
</engine:UXML>

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: f149510199380ed4e873f6f231587a38
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}

View File

@@ -0,0 +1,54 @@
using UnityEditor.Experimental.GraphView;
using UnityEngine;
using UnityEngine.UIElements;
namespace Misaki.GraphView.Editor
{
public abstract class GraphSubWindow : GraphElement
{
private readonly VisualElement _contentContainer = new();
private readonly Dragger _dragger = new();
private readonly ResizableElement _resizableElement = new();
protected GraphSubWindow()
{
style.position = Position.Absolute;
style.marginBottom = 0;
style.marginTop = 0;
style.marginLeft = 0;
style.marginRight = 0;
style.paddingBottom = 0;
style.paddingTop = 0;
style.paddingLeft = 0;
style.paddingRight = 0;
_contentContainer.style.flexGrow = 1;
_contentContainer.pickingMode = PickingMode.Ignore;
capabilities = Capabilities.Movable | Capabilities.Resizable;
_dragger.clampToParentEdges = true;
hierarchy.Add(_contentContainer);
hierarchy.Add(_resizableElement);
this.AddManipulator(_dragger);
}
public override VisualElement contentContainer => _contentContainer;
public override void SetPosition(Rect rect)
{
style.left = rect.x;
style.top = rect.y;
style.width = rect.width;
style.height = rect.height;
}
public override Rect GetPosition()
{
return new Rect(resolvedStyle.left, resolvedStyle.top, resolvedStyle.width, resolvedStyle.height);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2f48cc433cb042f5b220c4e861a591e7
timeCreated: 1730389082

View File

@@ -0,0 +1,106 @@
using System;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
namespace Misaki.GraphView.Editor
{
public class GraphToolbarView : Toolbar
{
private readonly GraphObject _graphObject;
private readonly ToolbarMenu _assetActionMenu = new();
private readonly ToolbarButton _saveButton = new();
private readonly ToolbarButton _executeButton = new();
private readonly VisualElement _customElementContainer = new ();
private readonly ToolbarButton _blackboardButton = new();
private readonly ToolbarButton _inspectorButton = new();
public Action BlackboardButtonClicked;
public Action InspectButtonClicked;
public GraphToolbarView(GraphObject graphObject)
{
_graphObject = graphObject;
_saveButton.iconImage = new () {texture = (Texture2D)EditorGUIUtility.IconContent("SaveAs").image};
_saveButton.tooltip = "Save";
_saveButton.clicked += SaveAsset;
_saveButton.style.borderRightWidth = 0;
_assetActionMenu.tooltip = "Asset Actions";
_assetActionMenu.menu.AppendAction("Save As", a => SaveAsAsset());
_assetActionMenu.menu.AppendAction("Select Asset", a => Selection.activeObject = _graphObject);
_assetActionMenu.style.borderLeftWidth = 0;
_executeButton.iconImage = new () {texture = (Texture2D)EditorGUIUtility.IconContent("PlayButton").image};
_executeButton.tooltip = "Execute";
_executeButton.clicked += () => _graphObject.Execute();
_customElementContainer.style.flexGrow = 1;
_blackboardButton.iconImage = new () {texture = (Texture2D)EditorGUIUtility.IconContent("Audio Mixer").image};
_blackboardButton.tooltip = "Blackboard";
_blackboardButton.clicked += () => BlackboardButtonClicked?.Invoke();
_inspectorButton.iconImage = new () {texture = (Texture2D)EditorGUIUtility.IconContent("UnityEditor.InspectorWindow").image};
_inspectorButton.tooltip = "Inspector";
_inspectorButton.clicked += () => InspectButtonClicked?.Invoke();
Add(_saveButton);
Add(CreateSmallSplitter());
Add(_assetActionMenu);
Add(new ToolbarSpacer());
Add(_executeButton);
Add(_customElementContainer);
Add(_blackboardButton);
Add(_inspectorButton);
}
private void SaveAsset()
{
AssetDatabase.SaveAssetIfDirty(_graphObject);
}
private void SaveAsAsset()
{
var path = EditorUtility.SaveFilePanelInProject("Save Graph", "NewGraph", "asset", "Save Graph");
if (string.IsNullOrEmpty(path))
{
return;
}
AssetDatabase.CreateAsset(_graphObject, path);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
public static VisualElement CreateSmallSplitter()
{
var splitter = new VisualElement()
{
style =
{
marginTop = 4,
marginBottom = 4,
backgroundColor = new Color(0.15f, 0.15f, 0.15f),
width = 2
}
};
return splitter;
}
public void SetCustomElement(VisualElement element)
{
_customElementContainer.Clear();
_customElementContainer.Add(element);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2bf96f3b5f2449cea8abe7499b5a32e4
timeCreated: 1730372923

355
Editor/Views/GraphView.cs Normal file
View File

@@ -0,0 +1,355 @@
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 != null && minimapConfig.enable)
{
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;
}
private void InitializeAssetElements()
{
var searchProvider = ScriptableObject.CreateInstance<NodeSearchProvider>();
searchProvider.SetOwner(this);
nodeCreationRequest = context =>
{
SearchWindow.Open(new SearchWindowContext(context.screenMousePosition), searchProvider);
};
if (_graphObject.Nodes.Count > 0)
{
foreach (var node in _graphObject.Nodes) AddNodeView(node);
foreach (var connection in _graphObject.Connections)
AddConnectionView(connection.InputSlotData, connection.OutputSlotData);
}
RegisterCallback<DragPerformEvent>(OnDragPerform);
RegisterCallbackOnce<GeometryChangedEvent>(_ => _graphInspectorView?.DockToParent(layout, DockingPosition.Right, false));
}
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: BaseNode node })
{
RemoveNode(node);
}
if (removedElements[i] is Edge edge)
{
if (_slotConnections.Remove(edge, out var connection))
{
var inputSlotData = connection.InputSlotData;
var outputSlotData = connection.OutputSlotData;
var inputSlot = _graphObject.GetNode(inputSlotData.nodeID)
.GetSlot(inputSlotData.slotIndex, inputSlotData.direction);
var outputSlot = _graphObject.GetNode(outputSlotData.nodeID)
.GetSlot(outputSlotData.slotIndex, 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)
{
if (element is Node node)
{
node.SetPosition(node.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.SetTransform(viewTransform);
EditorUtility.SetDirty(_graphObject);
_graphViewConfig.serializedObject.ApplyModifiedProperties();
_graphViewConfig.serializedObject.Update();
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<ExposedProperty> properties)
{
var position = contentViewContainer.WorldToLocal(evt.mousePosition);
foreach (var property in properties)
{
var baseNode = new PropertyInputNode(property);
baseNode.position = new Rect(position, Vector2.zero);
AddNode(baseNode);
}
}
}
public void AddNode(BaseNode baseNode)
{
Undo.RecordObject(_graphObject, $"Add {baseNode.GetType().Name}");
_graphObject.AddNode(baseNode);
AddNodeView(baseNode);
EditorUtility.SetDirty(_graphObject);
}
public virtual void AddNodeView(BaseNode baseNode)
{
Node nodeView;
var types = TypeCache.GetTypesWithAttribute<CustomInspectorAttribute>();
var type = types.FirstOrDefault(t =>
t.GetCustomAttribute<CustomInspectorAttribute>().InspectorType == baseNode.GetType());
if (baseNode is PropertyInputNode propertyInputNode)
{
// type ??= typeof(PropertyInputNodeView);
// var slot = propertyInputNode.GetSlot(0, SlotDirection.Output);
// nodeView = Activator.CreateInstance(type, propertyInputNode, _graphViewConfig.portColorManager) as PropertyInputNodeView;
nodeView = PropertyInputNodeView.Create(propertyInputNode, _graphViewConfig.portColorManager);
}
else
{
type ??= typeof(EditorNodeView);
nodeView = Activator.CreateInstance(type, baseNode, _graphViewConfig.serializedObject, _graphViewConfig.portColorManager) as EditorNodeView;
}
if (nodeView == null)
{
return;
}
nodeView.SetPosition(baseNode.position);
if (nodeView is IInspectable inspectable)
{
inspectable.OnItemSelected += ChangeInspectorView;
}
AddElement(nodeView);
_nodeViewsMap.Add(baseNode.Id, nodeView);
}
private void RemoveNode(BaseNode baseNode)
{
Undo.RecordObject(_graphObject, $"Remove {baseNode.GetType().Name}");
_graphObject.RemoveNode(baseNode);
RemoveNodeView(baseNode);
EditorUtility.SetDirty(_graphObject);
}
private void RemoveNodeView(BaseNode baseNode)
{
if (_nodeViewsMap.Remove(baseNode.Id, out var nodeView))
{
RemoveElement(nodeView);
if (nodeView is IInspectable inspectable)
{
inspectable.OnItemSelected -= ChangeInspectorView;
}
}
}
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;
}
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(SlotData a, SlotData b)
{
if (_nodeViewsMap.TryGetValue(a.nodeID, out var inputNodeView) &&
_nodeViewsMap.TryGetValue(b.nodeID, out var outputNodeView))
{
if (inputNodeView is not IPortContainer inputPortContainer || outputNodeView is not IPortContainer outputPortContainer)
{
return;
}
var inputPort = inputPortContainer.InputPorts[a.slotIndex];
var outputPort = outputPortContainer.OutputPorts[b.slotIndex];
var edge = inputPort.ConnectTo(outputPort);
AddElement(edge);
_slotConnections.Add(edge, new SlotConnection(a, b));
}
}
}
}

View File

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

3
Editor/Views/Nodes.meta Normal file
View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c72ddecdb0db44fba6cfffedc1b10cde
timeCreated: 1730511307

View File

@@ -0,0 +1,238 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEditor.Experimental.GraphView;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
namespace Misaki.GraphView.Editor
{
public class EditorNodeView : Node, IInspectable, IPortContainer
{
private readonly BaseNode _dataNode;
private readonly Type _nodeType;
private readonly NodeInfoAttribute _nodeInfo;
private readonly List<Port> _inputPorts = new ();
private readonly List<Port> _outputPorts = new ();
private readonly IPortColorManager _portColorManager;
private readonly SerializedObject _serializedObject;
public BaseNode DataNode => _dataNode;
public List<Port> InputPorts => _inputPorts;
public List<Port> OutputPorts => _outputPorts;
public Action<IInspectable> OnItemSelected { get; set; }
public string InspectorName => _nodeInfo.Name ?? _nodeType.Name;
public EditorNodeView(BaseNode dataNode, SerializedObject serializedObject, IPortColorManager portColorManager)
{
_dataNode = dataNode;
_portColorManager = portColorManager;
_serializedObject = serializedObject;
userData = dataNode;
_nodeType = dataNode.GetType();
_nodeInfo = _nodeType.GetCustomAttribute<NodeInfoAttribute>();
name = _nodeInfo.Name ?? _nodeType.Name;
title = _nodeInfo.Name ?? _nodeType.Name;
// Add the category as a class to the node so that we can style the node based on the category
var depths = _nodeInfo.Category.Split('/').ToList();
depths.Add(_nodeInfo.Name);
foreach (var depth in depths)
{
AddToClassList(depth.ToLower().Replace(" ", "-"));
}
var inputs = _nodeType.GetProperty(nameof(BaseNode.Inputs));
if (inputs != null)
{
var inputSlots = (IList<Slot>)inputs.GetValue(_dataNode);
if (inputSlots == null || inputSlots.Count == 0)
{
inputContainer.style.display = DisplayStyle.None;
}
else
{
foreach (var slot in inputSlots)
{
CreateInputPort(slot);
}
}
}
var outputs = _nodeType.GetProperty(nameof(BaseNode.Outputs));
if (outputs != null)
{
var outputSlots = (IList<Slot>)outputs.GetValue(_dataNode);
if (outputSlots == null || outputSlots.Count == 0)
{
outputContainer.style.display = DisplayStyle.None;
}
else
{
foreach (var slot in outputSlots)
{
CreateOutputPort(slot);
}
}
}
}
private void CreateInputPort(Slot slot)
{
var valueType = Type.GetType(slot.slotData.valueType);
var inputPort = Port.Create<Edge>(Orientation.Horizontal, Direction.Input, Port.Capacity.Single, valueType);
inputPort.portName = ObjectNames.NicifyVariableName(slot.slotData.slotName);
inputPort.portType = valueType;
inputPort.userData = slot;
if (_portColorManager != null && _portColorManager.TryGetColor(valueType, out var portColor))
{
inputPort.portColor = portColor;
}
inputContainer.Add(inputPort);
_inputPorts.Add(inputPort);
}
private void CreateOutputPort(Slot slot)
{
var valueType = Type.GetType(slot.slotData.valueType);
var outputPort = Port.Create<Edge>(Orientation.Horizontal, Direction.Output, Port.Capacity.Multi, valueType);
outputPort.portName = ObjectNames.NicifyVariableName(slot.slotData.slotName);
outputPort.portType = valueType;
outputPort.userData = slot;
if (_portColorManager != null && _portColorManager.TryGetColor(valueType, out var portColor))
{
outputPort.portColor = portColor;
}
outputContainer.Add(outputPort);
_outputPorts.Add(outputPort);
}
public override void SetPosition(Rect newPos)
{
base.SetPosition(newPos);
_dataNode.position = newPos;
}
public override void OnSelected()
{
base.OnSelected();
OnItemSelected?.Invoke(this);
}
public override void OnUnselected()
{
base.OnUnselected();
OnItemSelected?.Invoke(null);
}
/// <summary>
/// Displays the inspector for the node.
/// </summary>
public virtual VisualElement CreateInspector()
{
var root = new VisualElement();
if (_serializedObject.targetObject is not GraphObject graphObject)
{
return root;
}
// Use reflection to get the inspector input fields
var fields = _nodeType.GetFields().Where(f => f.GetCustomAttribute<InspectorInputAttribute>() != null).ToArray();
if (fields.Length == 0)
{
var label = new Label("No properties to display.");
root.Add(label);
return root;
}
foreach (var field in fields)
{
var i = graphObject.Nodes.IndexOf(_dataNode);
var serializedProperty = _serializedObject.FindProperty("_nodes")?.GetArrayElementAtIndex(i)?.FindPropertyRelative(field.Name);
if (serializedProperty == null)
{
continue;
}
if (field.GetCustomAttribute<NodeOutputAttribute>() is not null)
{
continue;
}
var propertyName = field.GetCustomAttribute<InspectorInputAttribute>().Name ?? ObjectNames.NicifyVariableName(field.Name);
if (field.GetCustomAttribute<NodeInputAttribute>() is not null)
{
if (_dataNode.Inputs.FirstOrDefault(x => x.slotData.slotName == field.Name) is { } inputSlot)
{
if (inputSlot.LinkedSlotData.Count > 0)
{
root.Add(CreateFieldForConnectedSlot(inputSlot, propertyName));
continue;
}
}
}
var inputField = new PropertyField(serializedProperty, propertyName);
inputField.Bind(_serializedObject);
root.Add(inputField);
}
return root;
}
protected VisualElement CreateFieldForConnectedSlot(Slot slot, string propertyName)
{
var root = new VisualElement()
{
style =
{
height = 21,
flexDirection = FlexDirection.Row,
alignItems = Align.Center,
paddingBottom = 1,
paddingTop = 1,
marginLeft = 3,
marginRight = 3
}
};
var label = new Label(propertyName)
{
style =
{
width = 120
}
};
label.AddToClassList("unity-base-field__label");
var value = new Label($"Connected to {ObjectNames.NicifyVariableName(slot.LinkedSlotData[0].slotName)}");
value.AddToClassList("unity-base-field__input");
root.Add(label);
root.Add(value);
return root;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1b3db9057b8d4f89a3bd97ea019b542f
timeCreated: 1730109753

View File

@@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using NUnit.Framework;
using UnityEditor.Experimental.GraphView;
using UnityEngine;
using UnityEngine.UIElements;
namespace Misaki.GraphView.Editor
{
public class PropertyInputNodeView : TokenNode, IPortContainer
{
private readonly List<Port> _outputPorts = new List<Port>();
private readonly PropertyInputNode _dataNode;
//private readonly ExposedPropertyEditor _editor;
public PropertyInputNode DataNode => _dataNode;
public List<Port> InputPorts => null;
public List<Port> OutputPorts => _outputPorts;
public PropertyInputNodeView(PropertyInputNode dataNode, Port output) : base(null, output)
{
_dataNode = dataNode;
//_editor = editor;
name = dataNode.Property.propertyName;
title = dataNode.Property.propertyName;
userData = dataNode;
this.Q<VisualElement>("top").style.minHeight = 24;
_outputPorts.Add(output);
}
public static PropertyInputNodeView Create(PropertyInputNode dataNode, IPortColorManager portColorManager)
{
if (dataNode == null)
{
return null;
}
var outputSlot = dataNode.GetSlot(0, SlotDirection.Output);
var outputPort = CreateOutputPort(dataNode.Property, outputSlot, portColorManager);
var nodeView = new PropertyInputNodeView(dataNode, outputPort);
return nodeView;
}
private static Port CreateOutputPort(ExposedProperty property, Slot slot, IPortColorManager portColorManager)
{
if (property == null)
{
return null;
}
var portType = property.GetValueType();
var port = Port.Create<Edge>(Orientation.Horizontal, Direction.Output, Port.Capacity.Single, portType);
port.portName = string.Empty;
if (portColorManager != null && portColorManager.TryGetColor(portType, out var portColor))
{
port.portColor = portColor;
}
port.portType = portType;
port.userData = slot;
return port;
}
public string InspectorName => _dataNode.Property.propertyName;
public override void SetPosition(Rect newPos)
{
base.SetPosition(newPos);
_dataNode.position = newPos;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 515b9d8437a94494be82d01a49e9c1fa
timeCreated: 1730511351

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 88ca210a14c747e2a062f14edda4506d
timeCreated: 1730525389

View File

@@ -0,0 +1,60 @@
using System;
using System.Reflection;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine.UIElements;
namespace Misaki.GraphView.Editor
{
public class ExposedPropertyEditor
{
private readonly ExposedProperty _property;
private readonly SerializedObject _serializedObject;
public ExposedPropertyEditor(ExposedProperty property, SerializedObject serializedObject)
{
_property = property;
_serializedObject = serializedObject;
}
public virtual VisualElement CreateInspector()
{
var root = new VisualElement();
if (_serializedObject.targetObject is not GraphObject graphObject)
{
return root;
}
// Use reflection to get the inspector input fields
var fields = _property.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);
if (fields.Length == 0)
{
var label = new Label("No properties to display.");
root.Add(label);
return root;
}
foreach (var field in fields)
{
var i = graphObject.ExposedProperties.IndexOf(_property);
var serializedProperty = _serializedObject.FindProperty("_exposedProperties")?.GetArrayElementAtIndex(i)?.FindPropertyRelative(field.Name);
if (serializedProperty == null)
{
continue;
}
var propertyName = ObjectNames.NicifyVariableName(field.Name);
var inputField = new PropertyField(serializedProperty, propertyName);
inputField.Bind(_serializedObject);
root.Add(inputField);
}
return root;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a08eda67318a4f12aef63836205ee36a
timeCreated: 1730525401