Added defualt graph properties inspector;

Added sticky note;

Changed the name of BaseNode to SlotContainerNode in case we need other type of nodes in the future;
This commit is contained in:
Misaki
2024-11-04 01:02:30 +09:00
parent 5a9d8b9420
commit 7eec130b39
53 changed files with 517 additions and 436 deletions

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 51c9ebcd3b3149aeb61c479e1898561b
timeCreated: 1730603598

View File

@@ -0,0 +1,109 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
namespace Misaki.GraphView.Editor
{
[CustomEditor(typeof(GraphObject))]
public class GraphObjectEditor : UnityEditor.Editor
{
private readonly Dictionary<string, List<SerializedProperty>> _inspectorPropertyMap = new ();
private GraphObject _graphObject;
private void OnEnable()
{
_graphObject = target as GraphObject;
if (_graphObject == null)
{
return;
}
GetSerializedProperty();
}
protected virtual void GetSerializedProperty()
{
foreach (var property in _graphObject.ExposedProperties)
{
var showInInspectorField = property.GetType().GetField(nameof(ExposedProperty.showInInspector));
if (showInInspectorField == null)
{
continue;
}
var showInInspectorValue = showInInspectorField.GetValue(property);
if (showInInspectorValue is not bool or bool and false)
{
continue;
}
var fields = property.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);
var i = _graphObject.ExposedProperties.IndexOf(property);
foreach (var field in fields)
{
var serializedProperty = serializedObject.FindProperty("_exposedProperties")?.GetArrayElementAtIndex(i)?.FindPropertyRelative(field.Name);
if (serializedProperty == null)
{
continue;
}
if (!_inspectorPropertyMap.ContainsKey(property.propertyName))
{
_inspectorPropertyMap[property.propertyName] = new ();
}
_inspectorPropertyMap[property.propertyName].Add(serializedProperty);
}
}
}
protected virtual VisualElement CreateInspectorProperty()
{
var graphPropertyFoldout = new Foldout()
{
text = "Graph Properties",
value = true
};
foreach (var property in _inspectorPropertyMap)
{
var label = new Label(property.Key)
{
style =
{
unityFontStyleAndWeight = FontStyle.Bold,
marginTop = 4,
marginBottom = 2,
}
};
var propertyContainer = new VisualElement()
{
style =
{
marginLeft = 15
}
};
foreach (var serializedProperty in property.Value)
{
var inputField = new PropertyField(serializedProperty);
inputField.Bind(serializedObject);
propertyContainer.Add(inputField);
}
graphPropertyFoldout.Add(label);
graphPropertyFoldout.Add(propertyContainer);
}
return graphPropertyFoldout;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 032ca62a690e4cb4be9c91fe2f4a955a
timeCreated: 1730603608

View File

@@ -78,6 +78,29 @@ namespace Misaki.GraphView.Editor
graphViewChanged += OnGraphViewChanged;
}
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>();
@@ -88,12 +111,19 @@ namespace Misaki.GraphView.Editor
SearchWindow.Open(new SearchWindowContext(context.screenMousePosition), searchProvider);
};
if (_graphObject.Nodes.Count > 0)
foreach (var node in _graphObject.Nodes)
{
foreach (var node in _graphObject.Nodes) AddNodeView(node);
AddNodeView(node);
}
foreach (var connection in _graphObject.Connections)
AddConnectionView(connection.InputSlotData, connection.OutputSlotData);
foreach (var noteData in _graphObject.StickyNotes)
{
AddStickyNoteView(noteData);
}
foreach (var connection in _graphObject.Connections)
{
AddConnectionView(connection);
}
RegisterCallback<DragPerformEvent>(OnDragPerform);
@@ -110,11 +140,16 @@ namespace Misaki.GraphView.Editor
for (var i = removedElements.Count - 1; i >= 0; i--)
{
if (removedElements[i] is Node { userData: BaseNode node })
if (removedElements[i] is Node { userData: SlotContainerNode 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))
@@ -133,19 +168,16 @@ namespace Misaki.GraphView.Editor
}
}
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.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)
{
@@ -163,10 +195,10 @@ namespace Misaki.GraphView.Editor
}
}
_graphObject.SetTransform(viewTransform);
_graphObject.SetGraphTransform(viewTransform);
EditorUtility.SetDirty(_graphObject);
_graphViewConfig.serializedObject.Update();
EditorUtility.SetDirty(_graphObject);
return graphViewChange;
}
@@ -210,24 +242,24 @@ namespace Misaki.GraphView.Editor
}
}
public void AddNode(BaseNode baseNode)
public void AddNode(SlotContainerNode slotContainerNode)
{
Undo.RecordObject(_graphObject, $"Add {baseNode.GetType().Name}");
Undo.RecordObject(_graphObject, $"Add {slotContainerNode.GetType().Name}");
_graphObject.AddNode(baseNode);
AddNodeView(baseNode);
_graphObject.AddNode(slotContainerNode);
AddNodeView(slotContainerNode);
EditorUtility.SetDirty(_graphObject);
}
public virtual void AddNodeView(BaseNode baseNode)
public virtual void AddNodeView(SlotContainerNode slotContainerNode)
{
Node nodeView;
var types = TypeCache.GetTypesWithAttribute<CustomInspectorAttribute>();
var type = types.FirstOrDefault(t =>
t.GetCustomAttribute<CustomInspectorAttribute>().InspectorType == baseNode.GetType());
t.GetCustomAttribute<CustomInspectorAttribute>().InspectorType == slotContainerNode.GetType());
if (baseNode is PropertyInputNode propertyInputNode)
if (slotContainerNode is PropertyInputNode propertyInputNode)
{
// type ??= typeof(PropertyInputNodeView);
// var slot = propertyInputNode.GetSlot(0, SlotDirection.Output);
@@ -237,7 +269,7 @@ namespace Misaki.GraphView.Editor
else
{
type ??= typeof(EditorNodeView);
nodeView = Activator.CreateInstance(type, baseNode, _graphViewConfig.serializedObject, _graphViewConfig.portColorManager, _graphObject.Logger) as EditorNodeView;
nodeView = Activator.CreateInstance(type, slotContainerNode, _graphViewConfig.serializedObject, _graphViewConfig.portColorManager, _graphObject.Logger) as EditorNodeView;
}
@@ -246,7 +278,7 @@ namespace Misaki.GraphView.Editor
return;
}
nodeView.SetPosition(baseNode.position);
nodeView.SetPosition(slotContainerNode.position);
if (nodeView is IInspectable inspectable)
{
@@ -254,22 +286,22 @@ namespace Misaki.GraphView.Editor
}
AddElement(nodeView);
_nodeViewsMap.Add(baseNode.Id, nodeView);
_nodeViewsMap.Add(slotContainerNode.Id, nodeView);
}
private void RemoveNode(BaseNode baseNode)
private void RemoveNode(SlotContainerNode slotContainerNode)
{
Undo.RecordObject(_graphObject, $"Remove {baseNode.GetType().Name}");
Undo.RecordObject(_graphObject, $"Remove {slotContainerNode.GetType().Name}");
_graphObject.RemoveNode(baseNode);
RemoveNodeView(baseNode);
_graphObject.RemoveNode(slotContainerNode);
RemoveNodeView(slotContainerNode);
EditorUtility.SetDirty(_graphObject);
}
private void RemoveNodeView(BaseNode baseNode)
private void RemoveNodeView(SlotContainerNode slotContainerNode)
{
if (_nodeViewsMap.Remove(baseNode.Id, out var nodeView))
if (_nodeViewsMap.Remove(slotContainerNode.Id, out var nodeView))
{
RemoveElement(nodeView);
@@ -280,6 +312,82 @@ namespace Misaki.GraphView.Editor
}
}
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)
@@ -313,42 +421,5 @@ namespace Misaki.GraphView.Editor
? 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

@@ -12,7 +12,7 @@ namespace Misaki.GraphView.Editor
{
public class EditorNodeView : Node, IInspectable, IPortContainer
{
private readonly BaseNode _dataNode;
private readonly SlotContainerNode _dataNode;
private readonly Type _nodeType;
private readonly NodeInfoAttribute _nodeInfo;
@@ -24,7 +24,7 @@ namespace Misaki.GraphView.Editor
private readonly VisualElement _logContainer = new();
public BaseNode DataNode => _dataNode;
public SlotContainerNode DataNode => _dataNode;
public List<Port> InputPorts => _inputPorts;
public List<Port> OutputPorts => _outputPorts;
@@ -32,7 +32,7 @@ namespace Misaki.GraphView.Editor
public string InspectorName => _nodeInfo.Name ?? _nodeType.Name;
public EditorNodeView(BaseNode dataNode, SerializedObject serializedObject, IPortColorManager portColorManager, ILogger logger)
public EditorNodeView(SlotContainerNode dataNode, SerializedObject serializedObject, IPortColorManager portColorManager, ILogger logger)
{
if (dataNode == null)
{
@@ -59,7 +59,7 @@ namespace Misaki.GraphView.Editor
AddToClassList(depth.ToLower().Replace(" ", "-"));
}
var inputs = _nodeType.GetProperty(nameof(BaseNode.Inputs));
var inputs = _nodeType.GetProperty(nameof(SlotContainerNode.Inputs));
if (inputs != null)
{
@@ -78,7 +78,7 @@ namespace Misaki.GraphView.Editor
}
}
var outputs = _nodeType.GetProperty(nameof(BaseNode.Outputs));
var outputs = _nodeType.GetProperty(nameof(SlotContainerNode.Outputs));
if (outputs != null)
{
@@ -108,7 +108,7 @@ namespace Misaki.GraphView.Editor
}
}
private void CreateLogElement(BaseNode node, string message, LogType type)
private void CreateLogElement(SlotContainerNode node, string message, LogType type)
{
if (node.Id != _dataNode.Id)
{
@@ -236,9 +236,9 @@ namespace Misaki.GraphView.Editor
return root;
}
var i = graphObject.Nodes.IndexOf(_dataNode);
foreach (var field in fields)
{
var i = graphObject.Nodes.IndexOf(_dataNode);
var serializedProperty = _serializedObject.FindProperty("_nodes")?.GetArrayElementAtIndex(i)?.FindPropertyRelative(field.Name);
if (serializedProperty == null)

View File

@@ -0,0 +1,51 @@
using UnityEditor.Experimental.GraphView;
using UnityEngine;
using UnityEngine.UIElements;
namespace Misaki.GraphView.Editor
{
public class StickyNoteView : StickyNote
{
private readonly StickyNoteData _data;
public StickyNoteView(StickyNoteData data)
{
_data = data;
userData = data;
title = data.title;
contents = data.contents;
theme = data.theme;
fontSize = data.fontSize;
this.Q<TextField>("title-field").RegisterCallback<ChangeEvent<string>>(e => {
_data.title = e.newValue;
});
this.Q<TextField>("contents-field").RegisterCallback<ChangeEvent<string>>(e => {
_data.contents = e.newValue;
});
}
public override void SetPosition(Rect rect)
{
base.SetPosition(rect);
if (_data == null)
{
return;
}
_data.position = rect;
}
public override void OnResized()
{
base.OnResized();
if (_data == null)
{
return;
}
_data.position = layout;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2d669992c46c4ca497549fa31f567db1
timeCreated: 1730597994

View File

@@ -36,10 +36,10 @@ namespace Misaki.GraphView.Editor
return root;
}
var i = graphObject.ExposedProperties.IndexOf(_property);
foreach (var field in fields)
{
var i = graphObject.ExposedProperties.IndexOf(_property);
var serializedProperty = _serializedObject.FindProperty("_exposedProperties")?.GetArrayElementAtIndex(i)?.FindPropertyRelative(field.Name);
if (serializedProperty == null)
@@ -54,6 +54,15 @@ namespace Misaki.GraphView.Editor
root.Add(inputField);
}
var showInInspectorProperty = _serializedObject.FindProperty("_exposedProperties")?.GetArrayElementAtIndex(i)?.FindPropertyRelative("showInInspector");
if (showInInspectorProperty != null)
{
var showInInspectorField = new PropertyField(showInInspectorProperty, "Show In Inspector");
showInInspectorField.Bind(_serializedObject);
root.Add(showInInspectorField);
}
return root;
}
}