First commit
This commit is contained in:
44
Editor/Views/BlackboardPropertyView.cs
Normal file
44
Editor/Views/BlackboardPropertyView.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Editor/Views/BlackboardPropertyView.cs.meta
Normal file
3
Editor/Views/BlackboardPropertyView.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bcc0238bd86d4454bc8c03cf8feadff2
|
||||
timeCreated: 1730456603
|
||||
120
Editor/Views/GraphBlackboardView.cs
Normal file
120
Editor/Views/GraphBlackboardView.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Editor/Views/GraphBlackboardView.cs.meta
Normal file
3
Editor/Views/GraphBlackboardView.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cdc2c6e3a651497baf0a42a19619d193
|
||||
timeCreated: 1730453741
|
||||
54
Editor/Views/GraphInspectorView.cs
Normal file
54
Editor/Views/GraphInspectorView.cs
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Editor/Views/GraphInspectorView.cs.meta
Normal file
3
Editor/Views/GraphInspectorView.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8290ebaa6d394b13bda6bc4cc94763e8
|
||||
timeCreated: 1730376321
|
||||
13
Editor/Views/GraphInspectorView.uxml
Normal file
13
Editor/Views/GraphInspectorView.uxml
Normal 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>
|
||||
10
Editor/Views/GraphInspectorView.uxml.meta
Normal file
10
Editor/Views/GraphInspectorView.uxml.meta
Normal file
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f149510199380ed4e873f6f231587a38
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}
|
||||
54
Editor/Views/GraphSubWindow.cs
Normal file
54
Editor/Views/GraphSubWindow.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Editor/Views/GraphSubWindow.cs.meta
Normal file
3
Editor/Views/GraphSubWindow.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2f48cc433cb042f5b220c4e861a591e7
|
||||
timeCreated: 1730389082
|
||||
106
Editor/Views/GraphToolbarView.cs
Normal file
106
Editor/Views/GraphToolbarView.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Editor/Views/GraphToolbarView.cs.meta
Normal file
3
Editor/Views/GraphToolbarView.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2bf96f3b5f2449cea8abe7499b5a32e4
|
||||
timeCreated: 1730372923
|
||||
355
Editor/Views/GraphView.cs
Normal file
355
Editor/Views/GraphView.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Editor/Views/GraphView.cs.meta
Normal file
2
Editor/Views/GraphView.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 66c6b56270850cc458f01ab2bbd9429e
|
||||
3
Editor/Views/Nodes.meta
Normal file
3
Editor/Views/Nodes.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c72ddecdb0db44fba6cfffedc1b10cde
|
||||
timeCreated: 1730511307
|
||||
238
Editor/Views/Nodes/EditorNodeView.cs
Normal file
238
Editor/Views/Nodes/EditorNodeView.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Editor/Views/Nodes/EditorNodeView.cs.meta
Normal file
3
Editor/Views/Nodes/EditorNodeView.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1b3db9057b8d4f89a3bd97ea019b542f
|
||||
timeCreated: 1730109753
|
||||
79
Editor/Views/Nodes/PropertyInputNodeView.cs
Normal file
79
Editor/Views/Nodes/PropertyInputNodeView.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Editor/Views/Nodes/PropertyInputNodeView.cs.meta
Normal file
3
Editor/Views/Nodes/PropertyInputNodeView.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 515b9d8437a94494be82d01a49e9c1fa
|
||||
timeCreated: 1730511351
|
||||
3
Editor/Views/Properties.meta
Normal file
3
Editor/Views/Properties.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 88ca210a14c747e2a062f14edda4506d
|
||||
timeCreated: 1730525389
|
||||
60
Editor/Views/Properties/ExposedPropertyEditor.cs
Normal file
60
Editor/Views/Properties/ExposedPropertyEditor.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Editor/Views/Properties/ExposedPropertyEditor.cs.meta
Normal file
3
Editor/Views/Properties/ExposedPropertyEditor.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a08eda67318a4f12aef63836205ee36a
|
||||
timeCreated: 1730525401
|
||||
Reference in New Issue
Block a user