Migrated ClusterEditor to ClusterView

This commit is contained in:
Pascal Serrarens 2026-05-19 12:45:11 +02:00
parent 9d43772b79
commit ebef711981
6 changed files with 1399 additions and 1232 deletions

View File

@ -1,3 +1,4 @@
/*
using UnityEditor;
using UnityEditor.UIElements;
@ -71,3 +72,4 @@ namespace NanoBrain.Unity {
}
}
*/

View File

@ -8,6 +8,73 @@ using UnityEngine.UIElements;
namespace NanoBrain.Unity {
[CustomEditor(typeof(ClusterPrefab))]
public class ClusterEditor : Editor {
const float drawAreaWidth = 300f; // adjust as needed
const float padding = 6f;
ClusterPrefab clusterPrefab;
Nucleus currentNucleus {
get { return clusterView.currentNucleus; }
set { clusterView.currentNucleus = value; }
}
Cluster currentCluster => clusterView.currentCluster;
protected Nucleus selectedOutput;
ClusterView clusterView;
void OnEnable() {
clusterPrefab = (ClusterPrefab)target;
clusterView = ClusterView.GetClusterView(serializedObject);
clusterView.currentCluster ??= clusterPrefab.cluster;
clusterView.currentNucleus = clusterPrefab.cluster.defaultOutput;
}
public override void OnInspectorGUI() {
// Begin horizontal split
EditorGUILayout.BeginHorizontal();
// Left: fixed-width drawing area
GUILayoutOption[] leftOptions = { GUILayout.Width(drawAreaWidth) };
Rect drawRect = GUILayoutUtility.GetRect(drawAreaWidth, 450f, leftOptions); // height adjustable
// add padding inside rect
Rect innerRect = new(drawRect.x + padding, drawRect.y + padding,
drawRect.width - padding * 2, drawRect.height - padding * 2);
clusterView.Render(innerRect);
// Right: info panel (takes remaining width)
EditorGUILayout.BeginVertical(GUILayout.ExpandWidth(true));
float prevLabelWidth = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth = 100f; // smaller labels -> larger fields
InspectorHandler(serializedObject);
EditorGUIUtility.labelWidth = prevLabelWidth;
EditorGUILayout.EndVertical(); // end right column
EditorGUILayout.EndHorizontal(); // end split
}
// public override void OnInspectorGUI() {
// float totalWidth = EditorGUIUtility.currentViewWidth;
// float leftW = drawAreaWidth;
// float rightW = Mathf.Max(80f, totalWidth - leftW - padding);
// Rect row = GUILayoutUtility.GetRect(totalWidth, 450f); // request full width
// Rect leftRect = new Rect(row.x, row.y, leftW, row.height);
// Rect rightRect = new Rect(row.x + leftW + padding, row.y, rightW, 450f);
// Rect innerLeft = new Rect(leftRect.x + padding, leftRect.y + padding,
// leftRect.width - padding*2, leftRect.height - padding*2);
// clusterView.Render(innerLeft);
// GUILayout.BeginArea(rightRect);
// float prev = EditorGUIUtility.labelWidth;
// EditorGUIUtility.labelWidth = 100f;
// InspectorHandler(serializedObject);
// EditorGUIUtility.labelWidth = prev;
// GUILayout.EndArea();
// }
//}
/*
public class ClusterEditor : ClusterViewer {
public override VisualElement CreateInspectorGUI() {
@ -130,10 +197,10 @@ namespace NanoBrain.Unity {
inspectorContainer.Add(inspectorIMGUIContainer);
}
*/
#region Inspector
private VisualElement inspectorIMGUIContainer;
//private VisualElement inspectorIMGUIContainer;
private bool showSynapses = true;
private bool showActivation = true;
protected bool breakOnWake = false;
@ -199,7 +266,7 @@ namespace NanoBrain.Unity {
serializedObject.ApplyModifiedProperties();
if (anythingChanged) {
EditorUtility.SetDirty(prefabAsset);
EditorUtility.SetDirty(clusterPrefab);
AssetDatabase.SaveAssets();
}
}
@ -238,12 +305,12 @@ namespace NanoBrain.Unity {
EditorGUILayout.IntField("Instances", instanceCount, GUILayout.MinWidth(150));
if (GUILayout.Button("Add")) {
Undo.RecordObject(prefabAsset, "Array add " + prefabAsset.name);
Undo.RecordObject(clusterPrefab, "Array add " + clusterPrefab.name);
cluster.AddInstance();
anythingChanged = true;
}
if (GUILayout.Button("Del")) {
Undo.RecordObject(prefabAsset, "Array delete " + prefabAsset.name);
Undo.RecordObject(clusterPrefab, "Array delete " + clusterPrefab.name);
cluster.RemoveInstance();
anythingChanged = true;
}
@ -268,7 +335,9 @@ namespace NanoBrain.Unity {
}
protected void SynapsesInspector(ref bool anythingChanged) {
showSynapses = EditorGUILayout.BeginFoldoutHeaderGroup(showSynapses, "Synapses");
EditorGUI.indentLevel++;
//showSynapses = EditorGUILayout.BeginFoldoutHeaderGroup(showSynapses, "Synapses");
showSynapses = EditorGUILayout.Foldout(showSynapses, "Synapses");
if (showSynapses) {
if (this.currentNucleus is Neuron neuron2) {
Neuron.CombinatorType newCombinator = (Neuron.CombinatorType)EditorGUILayout.EnumPopup("Combinator", neuron2.combinator);
@ -326,7 +395,9 @@ namespace NanoBrain.Unity {
}
}
else {
float indentPx = EditorGUI.indentLevel * EditorGUIUtility.singleLineHeight;
EditorGUILayout.BeginHorizontal();
GUILayout.Space(indentPx);
if (synapse.neuron.parent != this.currentNucleus.parent) {
// If it is a different cluster
@ -367,15 +438,18 @@ namespace NanoBrain.Unity {
}
EditorGUILayout.Space();
anythingChanged |= ConnectNucleus(this.prefab, this.currentNucleus);
anythingChanged |= AddSynapse(this.prefab, this.currentNucleus);
anythingChanged |= ConnectNucleus(this.clusterPrefab, this.currentNucleus);
anythingChanged |= AddSynapse(this.clusterPrefab, this.currentNucleus);
}
EditorGUILayout.EndFoldoutHeaderGroup();
//EditorGUILayout.EndFoldoutHeaderGroup();
EditorGUI.indentLevel--;
}
protected void ActivationInspector(ref bool anythingChanged) {
EditorGUILayout.Space();
showActivation = EditorGUILayout.BeginFoldoutHeaderGroup(showActivation, "Activation");
EditorGUI.indentLevel++;
showActivation = EditorGUILayout.Foldout(showActivation, "Activation");
if (showActivation) {
if (this.currentNucleus is Neuron neuron) {
if (this.currentNucleus is not MemoryCell) {
@ -398,7 +472,8 @@ namespace NanoBrain.Unity {
EditorGUILayout.Space();
}
EditorGUILayout.EndFoldoutHeaderGroup();
//EditorGUILayout.EndFoldoutHeaderGroup();
EditorGUI.indentLevel--;
}
#region Synapses
@ -427,7 +502,7 @@ namespace NanoBrain.Unity {
}
protected virtual void AddMemoryCellInput(Nucleus nucleus) {
MemoryCell newMemory = new(this.prefab.cluster, "New memory cell");
MemoryCell newMemory = new(this.clusterPrefab.cluster, "New memory cell");
newMemory.AddReceiver(nucleus);
this.currentNucleus = newMemory;
}
@ -519,10 +594,10 @@ namespace NanoBrain.Unity {
// this.prefab.nuclei.Remove(nucleus);
// Neuron.Delete(nucleus);
this.prefab.cluster.RefreshOutputs();
this.clusterPrefab.cluster.RefreshOutputs();
this.currentNucleus = this.prefab.cluster.defaultOutput;
this.currentNucleus = this.clusterPrefab.cluster.defaultOutput;
this.selectedOutput = this.currentNucleus;
}
@ -544,7 +619,7 @@ namespace NanoBrain.Unity {
protected virtual void ChangeSynapse(Synapse synapse, Neuron newNucleus) {
Neuron synapseNeuron = synapse.neuron;
if (synapse.neuron.parent is Cluster subCluster && subCluster.prefab != this.prefab) {
if (synapse.neuron.parent is Cluster subCluster && subCluster.prefab != this.clusterPrefab) {
// if (synapse.neuron.parent is ClusterReceptor receptor) {
// // the new nucleus is part of a (cluster) receptor,
// // so we have to change all synapses to this nucleus array elements
@ -586,7 +661,10 @@ namespace NanoBrain.Unity {
#endregion Synapses
#endregion Inspector
/*
}
*/
}
}

View File

@ -1,7 +1,10 @@
using System.Linq;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
using UnityEditor;
using System;
using System.Reflection;
namespace NanoBrain.Unity {
@ -58,7 +61,6 @@ namespace NanoBrain.Unity {
// content rect below header
Rect drawRect = new(fieldRect.x, headerRect.yMax + 2f, fieldRect.width, 450f);
// IMGUIContainer should be inserted here
ClusterView.Render(drawRect, prefab.cluster, property);
}
}
@ -86,4 +88,62 @@ namespace NanoBrain.Unity {
// }
}
/*
[InitializeOnLoad]
static class ClusterPrefabInspectorRepaints {
static ClusterPrefabInspectorRepaints() {
EditorApplication.update += OnEditorUpdate;
}
static double lastRepaint = 0;
const double repaintInterval = 1.0 / 15.0; // up to 15 FPS in inspector
static void OnEditorUpdate() {
if (!Application.isPlaying) return;
// throttle repaint frequency
if (EditorApplication.timeSinceStartup - lastRepaint < repaintInterval) return;
lastRepaint = EditorApplication.timeSinceStartup;
// Find all open inspectors (Editors) that target objects containing ClusterPrefab fields
var editors = Resources.FindObjectsOfTypeAll<Editor>();
foreach (var ed in editors) {
var targets = ed.targets;
if (targets == null)
continue;
bool shouldRepaint = targets.Any(t => ObjectHasClusterPrefabField(t));
if (shouldRepaint) {
try {
ed.Repaint();
}
catch {
// ignore
}
}
}
}
static bool ObjectHasClusterPrefabField(UnityEngine.Object obj) {
if (obj == null)
return false;
Type type = obj.GetType();
// search fields (instance, non-public/public)
FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
foreach (FieldInfo f in fields) {
if (f.FieldType == typeof(ClusterPrefab))
return true;
// also handle arrays/lists of ClusterPrefab or serializable wrappers:
if (f.FieldType.IsArray && f.FieldType.GetElementType() == typeof(ClusterPrefab))
return true;
if (f.FieldType.IsGenericType) {
Type[] gen = f.FieldType.GetGenericArguments();
if (gen.Length == 1 && gen[0] == typeof(ClusterPrefab))
return true;
}
}
return false;
}
}
*/
}

View File

@ -9,32 +9,46 @@ namespace NanoBrain.Unity {
private static readonly float discRadius = 20;
static readonly Dictionary<string, ClusterView> viewStates = new();
private static ClusterView GetClusterView(SerializedProperty property) {
public static ClusterView GetClusterView(SerializedProperty property) {
string key = property.propertyPath + "_" + property.serializedObject.targetObject.GetEntityId();
if (!viewStates.TryGetValue(key, out ClusterView state))
state = new() { key = key };
return state;
}
public static ClusterView GetClusterView(SerializedObject serializedObject) {
string key = serializedObject.targetObject.GetEntityId().ToString();
if (!viewStates.TryGetValue(key, out ClusterView state))
state = new() { key = key };
return state;
}
private static void UpdateViewState(ClusterView viewState) {
viewStates[viewState.key] = viewState;
private void UpdateViewState() {
viewStates[this.key] = this;
}
public static void Render(Rect drawRect, Cluster cluster, SerializedProperty property) {
ClusterView clusterView = GetClusterView(property);
clusterView.currentCluster ??= cluster;
clusterView.Render(drawRect);
}
public static void Render(Rect drawRect, Cluster cluster, SerializedObject obj) {
ClusterView clusterView = GetClusterView(obj);
clusterView.currentCluster ??= cluster;
clusterView.Render(drawRect);
}
public void Render(Rect drawRect) {
// background
EditorGUI.DrawRect(drawRect, Color.black);
const float contentWidth = 1000f;
Rect contentRect = new Rect(0f, 0f, contentWidth, drawRect.height);
Rect contentRect = new(0f, 0f, contentWidth, drawRect.height - 20);
// Begin horizontal-only scroll view
clusterView.scrollPos = GUI.BeginScrollView(drawRect, clusterView.scrollPos, contentRect, true, false);
this.scrollPos = GUI.BeginScrollView(drawRect, this.scrollPos, contentRect, false, false);
// Local content group: draw GUI content using content-local coords (0..contentWidth)
GUI.BeginGroup(new Rect(-clusterView.scrollPos.x, 0f, contentWidth, drawRect.height));
GUI.BeginGroup(new Rect(-this.scrollPos.x, 0f, contentWidth, drawRect.height));
EditorGUI.DrawRect(new Rect(0f, 0f, contentWidth, drawRect.height), new Color(0.08f, 0.08f, 0.08f, 1f));
GUI.EndGroup();
GUI.EndScrollView();
@ -43,16 +57,16 @@ namespace NanoBrain.Unity {
GUI.BeginGroup(drawRect);
// Inner group positions content origin so local coords match content space and respect scroll
GUI.BeginGroup(new Rect(-clusterView.scrollPos.x, 0f, contentWidth, drawRect.height));
GUI.BeginGroup(new Rect(-this.scrollPos.x, 0f, contentWidth, drawRect.height));
Handles.BeginGUI();
clusterView.DrawFocusGraph();
this.DrawFocusGraph();
Handles.EndGUI();
GUI.EndGroup(); // end inner group
GUI.EndGroup(); // end clipping group
UpdateViewState(clusterView);
UpdateViewState();
}
public string key = null;

View File

@ -73,7 +73,6 @@ namespace NanoBrain.Unity {
Add(scrollView);
Add(topMenuContainer);
// Subscribe when added to panel (editor UI ready)
RegisterCallback<AttachToPanelEvent>(evt => Subscribe());
RegisterCallback<DetachFromPanelEvent>(evt => Unsubscribe());
@ -83,16 +82,18 @@ namespace NanoBrain.Unity {
this.mode = (Mode)changeEvent.newValue;
}
bool subscribed = false;
private bool subscribed = false;
void Subscribe() {
if (subscribed) return;
if (subscribed)
return;
SceneView.duringSceneGui += OnSceneGUI;
subscribed = true;
SceneView.RepaintAll();
}
void Unsubscribe() {
if (!subscribed) return;
if (!subscribed)
return;
SceneView.duringSceneGui -= OnSceneGUI;
subscribed = false;
}
@ -123,25 +124,36 @@ namespace NanoBrain.Unity {
}
public void OnIMGUI() {
var rect = graphContainer.layout; // container local size
int id = GUIUtility.GetControlID(123456, FocusType.Passive, new Rect(0, 0, rect.width, rect.height));
//int id = GUIUtility.GetControlID(FocusType.Passive);
if (Application.isPlaying == false && serializedBrain != null)
serializedBrain.Update();
Handles.BeginGUI();
DrawGraph();
Handles.EndGUI();
Rect r = new Rect(0, 0, rect.width, rect.height);
ClusterView.Render(r, currentCluster, serializedBrain);
// ClusterView clusterView = ClusterView.GetClusterView(serializedBrain);
// clusterView.currentCluster ??= currentCluster;
// clusterView.DrawGraph(id);
// Handles.BeginGUI();
// DrawGraph();
// Handles.EndGUI();
}
#region Graph
public virtual void DrawGraph() {
if (mode == Mode.Focus)
DrawFocusGraph();
else
DrawFullGraph();
}
// public virtual void DrawGraph() {
// if (mode == Mode.Focus)
// DrawFocusGraph();
// // else
// // DrawFullGraph();
// }
#region Full Graph
/*
protected void DrawFullGraph() {
//Dag dag = GenerateGraph(this.prefab);
Dag dag = GenerateGraph(this.selectedOutput);
@ -230,11 +242,11 @@ namespace NanoBrain.Unity {
DescendGraph(synapseNode, ref ix, dag);
}
}
*/
#endregion Full Graph
#region Focus Graph
/*
protected void DrawFocusGraph() {
float size = 20;
Vector3 position = new(150, 210, 0);
@ -549,9 +561,9 @@ namespace NanoBrain.Unity {
row++;
}
}
*/
#endregion Focus Graph
/*
protected void DrawNucleus(Nucleus nucleus, Vector3 position, float maxValue, float size) {
Color color;
if (Application.isPlaying) {
@ -841,7 +853,7 @@ namespace NanoBrain.Unity {
this.selectedOutput = null;
expandArray = false;
}
*/
#endregion Graph
void OnSceneGUI(SceneView sceneView) {

View File

@ -1,6 +1,6 @@
using System;
using UnityEngine;
/*
namespace NanoBrain.Unity {
/// <summary>
@ -66,3 +66,4 @@ namespace NanoBrain.Unity {
}
}
*/