Adde full graph scrollbar

This commit is contained in:
Pascal Serrarens 2026-04-21 11:25:06 +02:00
parent 471ed3661c
commit 02047a4bd9
2 changed files with 86 additions and 46 deletions

View File

@ -67,16 +67,16 @@ namespace NanoBrain {
Button addButton = new(() => OnAddClusterOutput()) { Button addButton = new(() => OnAddClusterOutput()) {
text = "Add" text = "Add"
}; };
outputContainer.Add(addButton); topMenuContainer?.Add(addButton);
Add(outputContainer); Add(topMenuContainer);
} }
void OnAddClusterOutput() { void OnAddClusterOutput() {
Nucleus newOutput = new Neuron(this.prefab, "New Output"); Nucleus newOutput = new Neuron(this.prefab, "New Output");
this.prefab.RefreshOutputs(); this.prefab.RefreshOutputs();
outputsField.choices = this.prefab.outputs.Select(output => output.name).ToList(); outputsPopup.choices = this.prefab.outputs.Select(output => output.name).ToList();
outputsField.value = newOutput.name; outputsPopup.value = newOutput.name;
this.currentNucleus = newOutput; this.currentNucleus = newOutput;
} }
@ -88,7 +88,8 @@ namespace NanoBrain {
this.serializedBrain = new SerializedObject(this.prefab); this.serializedBrain = new SerializedObject(this.prefab);
this.currentNucleus = nucleus; this.currentNucleus = nucleus;
Rebuild(inspectorContainer); Rebuild(inspectorContainer);
OnOutputChanged(outputsField.choices[0]); if (outputsPopup != null)
OnOutputChanged(outputsPopup.choices[0]);
} }
void Rebuild(VisualElement inspectorContainer) { void Rebuild(VisualElement inspectorContainer) {
@ -173,7 +174,7 @@ namespace NanoBrain {
if (newName != this.currentNucleus.name) { if (newName != this.currentNucleus.name) {
this.currentNucleus.name = newName; this.currentNucleus.name = newName;
this.prefab.RefreshOutputs(); this.prefab.RefreshOutputs();
outputsField.choices = this.prefab.outputs.Select(output => output.name).ToList(); outputsPopup.choices = this.prefab.outputs.Select(output => output.name).ToList();
anythingChanged = true; anythingChanged = true;
} }
} }
@ -492,10 +493,10 @@ namespace NanoBrain {
} }
this.prefab.nuclei.Remove(nucleus); this.prefab.nuclei.Remove(nucleus);
if (outputsField.value == nucleus.name) { if (outputsPopup.value == nucleus.name) {
this.prefab.RefreshOutputs(); this.prefab.RefreshOutputs();
outputsField.choices = this.prefab.outputs.Select(output => output.name).ToList(); outputsPopup.choices = this.prefab.outputs.Select(output => output.name).ToList();
outputsField.index = 0; outputsPopup.index = 0;
} }
Neuron.Delete(nucleus); Neuron.Delete(nucleus);

View File

@ -13,14 +13,18 @@ namespace NanoBrain {
protected readonly ClusterPrefab prefab; protected readonly ClusterPrefab prefab;
protected SerializedObject serializedBrain; protected SerializedObject serializedBrain;
protected Nucleus currentNucleus; protected Nucleus currentNucleus;
protected Nucleus selectedOutput;
protected GameObject gameObject; protected GameObject gameObject;
private List<NeuroidLayer> layers = new(); private List<NeuroidLayer> layers = new();
private readonly Dictionary<Nucleus, Vector2Int> neuroidPositions = new(); private readonly Dictionary<Nucleus, Vector2Int> neuroidPositions = new();
private bool expandArray = false; private bool expandArray = false;
protected ClusterPrefab prefabAsset; protected ClusterPrefab prefabAsset;
protected VisualElement outputContainer; protected VisualElement topMenuContainer;
protected readonly PopupField<string> outputsField; protected ScrollView scrollView;
protected IMGUIContainer graphContainer;
protected readonly PopupField<string> outputsPopup;
public enum Mode { public enum Mode {
Focus, Focus,
@ -34,37 +38,49 @@ namespace NanoBrain {
name = "content"; name = "content";
style.flexGrow = 1; style.flexGrow = 1;
IMGUIContainer graphContainer = new(OnIMGUI); topMenuContainer = new() {
graphContainer.style.position = Position.Absolute;
graphContainer.style.left = 0; graphContainer.style.top = 0;
graphContainer.style.right = 0; graphContainer.style.bottom = 0;
graphContainer.pickingMode = PickingMode.Position;
graphContainer.focusable = true;
Add(graphContainer);
outputContainer = new() {
style = { style = {
flexDirection = FlexDirection.Row, flexDirection = FlexDirection.Row,
alignItems = Align.Center, alignItems = Align.Center,
} }
}; };
EnumField enumField = new(mode); EnumField modePopup = new(mode);
enumField.style.width = 80; modePopup.style.width = 80;
enumField.RegisterValueChangedCallback(OnModeChange); modePopup.RegisterValueChangedCallback(OnModeChange);
outputContainer.Add(enumField); topMenuContainer.Add(modePopup);
List<string> names = this.prefab.outputs.Select(output => output.name).ToList(); List<string> names = this.prefab.outputs.Select(output => output.name).ToList();
if (names.Count > 0 && names.First() != null) { if (names.Count > 0 && names.First() != null) {
outputsField = new(names, names.First()) { outputsPopup = new(names, names.First()) {
style = { flexGrow = 1 } style = { flexGrow = 1 }
}; };
outputsField.RegisterValueChangedCallback(evt => OnOutputChanged(evt.newValue)); outputsPopup.RegisterValueChangedCallback(evt => OnOutputChanged(evt.newValue));
outputContainer.Add(outputsField); topMenuContainer.Add(outputsPopup);
} }
Add(topMenuContainer);
scrollView = new(ScrollViewMode.Horizontal);
scrollView.style.position = Position.Absolute;
scrollView.style.left = 0; scrollView.style.top = 0;
scrollView.style.right = 0; scrollView.style.bottom = 0;
//scrollView.style.flexGrow = 1;
scrollView.horizontalScrollerVisibility = ScrollerVisibility.Auto; // Auto shows when needed
scrollView.verticalScrollerVisibility = ScrollerVisibility.Hidden;
graphContainer = new(OnIMGUI);
//graphContainer.style.position = Position.Relative; // or omit this line
//graphContainer.style.position = Position.Absolute;
// graphContainer.style.left = 0; graphContainer.style.top = 0;
// graphContainer.style.right = 0; graphContainer.style.bottom = 0;
graphContainer.pickingMode = PickingMode.Position;
graphContainer.focusable = true;
//graphContainer.style.width = 1200;
//graphContainer.style.width = new StyleLength(StyleKeyword.Null); // allow content to determine width
scrollView.contentContainer.Add(graphContainer);
Add(scrollView);
Add(outputContainer);
// Subscribe when added to panel (editor UI ready) // Subscribe when added to panel (editor UI ready)
RegisterCallback<AttachToPanelEvent>(evt => Subscribe()); RegisterCallback<AttachToPanelEvent>(evt => Subscribe());
@ -75,7 +91,6 @@ namespace NanoBrain {
mode = (Mode)evt.newValue; mode = (Mode)evt.newValue;
} }
protected Nucleus selectedOutput;
protected virtual void OnOutputChanged(string outputName) { protected virtual void OnOutputChanged(string outputName) {
if (this.currentNucleus.parent != null) if (this.currentNucleus.parent != null)
// Get nucleus in the parent instance // Get nucleus in the parent instance
@ -86,6 +101,7 @@ namespace NanoBrain {
this.currentNucleus = this.selectedOutput; this.currentNucleus = this.selectedOutput;
} }
bool subscribed = false; bool subscribed = false;
void Subscribe() { void Subscribe() {
if (subscribed) return; if (subscribed) return;
@ -106,8 +122,8 @@ namespace NanoBrain {
this.serializedBrain = new SerializedObject(this.prefab); this.serializedBrain = new SerializedObject(this.prefab);
this.currentNucleus = nucleus; this.currentNucleus = nucleus;
Rebuild(); //inspectorContainer); Rebuild(); //inspectorContainer);
OnOutputChanged(outputsField.choices[0]); if (outputsPopup != null)
OnOutputChanged(outputsPopup.choices[0]);
} }
void Rebuild() { void Rebuild() {
@ -190,9 +206,10 @@ namespace NanoBrain {
Handles.BeginGUI(); Handles.BeginGUI();
DrawGraph(); DrawGraph();
Handles.EndGUI(); Handles.EndGUI();
} }
#region Graph
protected virtual void DrawGraph() { protected virtual void DrawGraph() {
if (mode == Mode.Focus) if (mode == Mode.Focus)
DrawFocusGraph(); DrawFocusGraph();
@ -200,6 +217,8 @@ namespace NanoBrain {
DrawFullGraph(); DrawFullGraph();
} }
#region Full Graph
protected void DrawFullGraph() { protected void DrawFullGraph() {
//Dag dag = GenerateGraph(this.prefab); //Dag dag = GenerateGraph(this.prefab);
Dag dag = GenerateGraph(this.selectedOutput); Dag dag = GenerateGraph(this.selectedOutput);
@ -219,6 +238,31 @@ namespace NanoBrain {
// Draw nodes // Draw nodes
foreach (DagNode n in dag.nodes) foreach (DagNode n in dag.nodes)
DrawNucleus(n.nucleus, n.position, 1, n.radius); DrawNucleus(n.nucleus, n.position, 1, n.radius);
// Determine graph width
float width = 0;
float currentNucleusPosition = 0;
foreach (DagNode node in dag.nodes) {
if (node.position.x > width)
width = node.position.x;
if (node.nucleus == currentNucleus)
currentNucleusPosition = node.position.x;
}
// Resize the graph container to the full graph width
float margin = 50f;
graphContainer.style.width = width + 2 * margin;
// Scroll to the current nucleus
float viewportWidth = scrollView.layout.width;
// center currentNucleus in viewport
float desiredScrollX = currentNucleusPosition - viewportWidth * 0.5f;
// clamp between 0 and maximum scrollable range
float maxScrollX = Mathf.Max(0f, graphContainer.resolvedStyle.width - viewportWidth);
desiredScrollX = Mathf.Clamp(desiredScrollX, 0f, maxScrollX);
Vector2 current = scrollView.scrollOffset;
scrollView.scrollOffset = new Vector2(desiredScrollX, current.y);
} }
public Dag GenerateGraph(Nucleus rootNucleus) { public Dag GenerateGraph(Nucleus rootNucleus) {
@ -258,6 +302,10 @@ namespace NanoBrain {
} }
} }
#endregion Full Graph
#region Focus Graph
protected void DrawFocusGraph() { protected void DrawFocusGraph() {
float size = 20; float size = 20;
Vector3 position = new(150, 210, 0); Vector3 position = new(150, 210, 0);
@ -334,6 +382,7 @@ namespace NanoBrain {
maxValue = cluster.defaultOutput.outputMagnitude; maxValue = cluster.defaultOutput.outputMagnitude;
DrawNucleus(this.currentNucleus, position, maxValue, 20); DrawNucleus(this.currentNucleus, position, maxValue, 20);
} }
graphContainer.style.width = 300;
} }
private void DrawReceivers(Nucleus nucleus, Vector3 parentPos, float size) { private void DrawReceivers(Nucleus nucleus, Vector3 parentPos, float size) {
@ -455,6 +504,8 @@ namespace NanoBrain {
} }
} }
#endregion Focus Graph
protected void DrawNucleus(Nucleus nucleus, Vector3 position, float maxValue, float size) { protected void DrawNucleus(Nucleus nucleus, Vector3 position, float maxValue, float size) {
Color color; Color color;
if (Application.isPlaying) { if (Application.isPlaying) {
@ -647,19 +698,7 @@ namespace NanoBrain {
Handles.DrawLine(from, to); Handles.DrawLine(from, to);
} }
// protected void DrawNode(Vector2 position, float size) { #endregion Graph
// Handles.color = Color.black * 0.9f;
// Handles.DrawSolidDisc(position, Vector3.forward, size);
// Handles.color = Color.white;
// GUIStyle style = new(EditorStyles.label) {
// alignment = TextAnchor.UpperCenter,
// normal = { textColor = Color.white },
// fontStyle = FontStyle.Bold,
// };
// Vector3 labelPos = position - Vector3.down * (size + 10f); // below disc along up axis
// Handles.Label(labelPos, n.title, style);
// }
void OnSceneGUI(SceneView sceneView) { void OnSceneGUI(SceneView sceneView) {
if (this.gameObject != null) { if (this.gameObject != null) {