diff --git a/Editor/ClusterInspector.cs b/Editor/ClusterInspector.cs index 556f6a2..3f8e52e 100644 --- a/Editor/ClusterInspector.cs +++ b/Editor/ClusterInspector.cs @@ -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); diff --git a/Editor/ClusterViewer.cs b/Editor/ClusterViewer.cs index 972738c..50f5fdd 100644 --- a/Editor/ClusterViewer.cs +++ b/Editor/ClusterViewer.cs @@ -13,14 +13,18 @@ namespace NanoBrain { protected readonly ClusterPrefab prefab; protected SerializedObject serializedBrain; protected Nucleus currentNucleus; + protected Nucleus selectedOutput; + protected GameObject gameObject; private List layers = new(); private readonly Dictionary neuroidPositions = new(); private bool expandArray = false; protected ClusterPrefab prefabAsset; - protected VisualElement outputContainer; - protected readonly PopupField outputsField; + protected VisualElement topMenuContainer; + protected ScrollView scrollView; + protected IMGUIContainer graphContainer; + protected readonly PopupField 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 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(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) {