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

View File

@ -13,14 +13,18 @@ namespace NanoBrain {
protected readonly ClusterPrefab prefab;
protected SerializedObject serializedBrain;
protected Nucleus currentNucleus;
protected Nucleus selectedOutput;
protected GameObject gameObject;
private List<NeuroidLayer> layers = new();
private readonly Dictionary<Nucleus, Vector2Int> neuroidPositions = new();
private bool expandArray = false;
protected ClusterPrefab prefabAsset;
protected VisualElement outputContainer;
protected readonly PopupField<string> outputsField;
protected VisualElement topMenuContainer;
protected ScrollView scrollView;
protected IMGUIContainer graphContainer;
protected readonly PopupField<string> outputsPopup;
public enum Mode {
Focus,
@ -34,37 +38,49 @@ namespace NanoBrain {
name = "content";
style.flexGrow = 1;
IMGUIContainer graphContainer = new(OnIMGUI);
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() {
topMenuContainer = new() {
style = {
flexDirection = FlexDirection.Row,
alignItems = Align.Center,
}
};
EnumField enumField = new(mode);
enumField.style.width = 80;
enumField.RegisterValueChangedCallback(OnModeChange);
outputContainer.Add(enumField);
EnumField modePopup = new(mode);
modePopup.style.width = 80;
modePopup.RegisterValueChangedCallback(OnModeChange);
topMenuContainer.Add(modePopup);
List<string> names = this.prefab.outputs.Select(output => output.name).ToList();
if (names.Count > 0 && names.First() != null) {
outputsField = new(names, names.First()) {
outputsPopup = new(names, names.First()) {
style = { flexGrow = 1 }
};
outputsField.RegisterValueChangedCallback(evt => OnOutputChanged(evt.newValue));
outputContainer.Add(outputsField);
outputsPopup.RegisterValueChangedCallback(evt => OnOutputChanged(evt.newValue));
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)
RegisterCallback<AttachToPanelEvent>(evt => Subscribe());
@ -75,7 +91,6 @@ namespace NanoBrain {
mode = (Mode)evt.newValue;
}
protected Nucleus selectedOutput;
protected virtual void OnOutputChanged(string outputName) {
if (this.currentNucleus.parent != null)
// Get nucleus in the parent instance
@ -86,6 +101,7 @@ namespace NanoBrain {
this.currentNucleus = this.selectedOutput;
}
bool subscribed = false;
void Subscribe() {
if (subscribed) return;
@ -106,8 +122,8 @@ namespace NanoBrain {
this.serializedBrain = new SerializedObject(this.prefab);
this.currentNucleus = nucleus;
Rebuild(); //inspectorContainer);
OnOutputChanged(outputsField.choices[0]);
if (outputsPopup != null)
OnOutputChanged(outputsPopup.choices[0]);
}
void Rebuild() {
@ -190,9 +206,10 @@ namespace NanoBrain {
Handles.BeginGUI();
DrawGraph();
Handles.EndGUI();
}
#region Graph
protected virtual void DrawGraph() {
if (mode == Mode.Focus)
DrawFocusGraph();
@ -200,6 +217,8 @@ namespace NanoBrain {
DrawFullGraph();
}
#region Full Graph
protected void DrawFullGraph() {
//Dag dag = GenerateGraph(this.prefab);
Dag dag = GenerateGraph(this.selectedOutput);
@ -219,6 +238,31 @@ namespace NanoBrain {
// Draw nodes
foreach (DagNode n in dag.nodes)
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) {
@ -258,6 +302,10 @@ namespace NanoBrain {
}
}
#endregion Full Graph
#region Focus Graph
protected void DrawFocusGraph() {
float size = 20;
Vector3 position = new(150, 210, 0);
@ -334,6 +382,7 @@ namespace NanoBrain {
maxValue = cluster.defaultOutput.outputMagnitude;
DrawNucleus(this.currentNucleus, position, maxValue, 20);
}
graphContainer.style.width = 300;
}
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) {
Color color;
if (Application.isPlaying) {
@ -647,19 +698,7 @@ namespace NanoBrain {
Handles.DrawLine(from, to);
}
// protected void DrawNode(Vector2 position, float size) {
// 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);
// }
#endregion Graph
void OnSceneGUI(SceneView sceneView) {
if (this.gameObject != null) {