/* using UnityEditor; using UnityEngine; using UnityEngine.UIElements; using UnityEditor.Callbacks; using System.Linq; using System.Collections.Generic; public class NucleusLayer { public int ix = 0; public List neuroids = new(); } public class NanoBrainEditor : EditorWindow { public NanoBrain brain; public static VisualElement inspectorContainer; [MenuItem("Window/NanoBrain Editor")] public static void ShowWindow() { GetWindow("NanoBrain Editor"); } public static void Open(NanoBrain asset) { NanoBrainEditor editor = GetWindow("NanoBrain Editor"); editor.brain = asset; editor.Show(); } GraphBoardView board; private void OnEnable() { OnFocus(); } private void OnFocus() { if (brain == null) { // brain = CreateInstance(); // EditorUtility.SetDirty(brain); return; } VisualElement root = rootVisualElement; root.Clear(); root.styleSheets.Add(Resources.Load("GraphStyles")); VisualElement main = new() { name = "main", style = { flexDirection = FlexDirection.Row, flexGrow = 1 } }; board = new GraphBoardView(); board.style.flexGrow = 1; inspectorContainer = new VisualElement { name = "inspector", style = { width = 400 } }; main.Add(board); main.Add(inspectorContainer); root.Add(main); board.SetGraph(brain, brain.root); } } public class GraphBoardView : VisualElement { NanoBrain brain; SerializedObject serializedBrain; Nucleus currentNucleus; private List layers = new(); private Dictionary neuroidPositions = new(); Vector2 pan = Vector2.zero; //float zoom = 1f; bool draggingCanvas = false; Vector2 lastMouse; GraphNodeWrapper currentWrapper; public GraphBoardView() { name = "content"; style.flexGrow = 1; IMGUIContainer imguiContainer = new(OnIMGUI); imguiContainer.style.position = Position.Absolute; imguiContainer.style.left = 0; imguiContainer.style.top = 0; imguiContainer.style.right = 0; imguiContainer.style.bottom = 0; imguiContainer.pickingMode = PickingMode.Position; imguiContainer.focusable = true; Add(imguiContainer); //RegisterCallback(OnWheel); RegisterCallback(OnMouseDown); RegisterCallback(OnMouseMove); RegisterCallback(OnMouseUp); } public void SetGraph(NanoBrain brain, Nucleus nucleus) { this.brain = brain; this.serializedBrain = new SerializedObject(brain); this.currentNucleus = nucleus; Rebuild(); } void Rebuild() { BuildLayers(); if (currentNucleus == null) { NanoBrainEditor.inspectorContainer.Clear(); return; } if (currentWrapper != null) Object.DestroyImmediate(currentWrapper); currentWrapper = ScriptableObject.CreateInstance().Init(currentNucleus, brain); DrawInspector(); } private void BuildLayers() { // A temporary list to track what's been added to layers this.layers = new(); int layerIx = 0; Nucleus selectedNucleus = this.currentNucleus; if (selectedNucleus == null) return; NeuroidLayer currentLayer = new() { ix = layerIx }; //foreach (Nucleus outputNeuroid in selectedNucleus.receivers) { foreach (Receiver receiver in selectedNucleus.receivers) { Nucleus outputNeuroid = receiver.nucleus; if (outputNeuroid != null) { AddToLayer(currentLayer, outputNeuroid); // Debug.Log($"layer {layerIx} nucleus {outputNeuroid.name}"); } } if (currentLayer.neuroids.Count > 0) { this.layers.Add(currentLayer); layerIx++; currentLayer = new() { ix = layerIx }; } AddToLayer(currentLayer, selectedNucleus); this.layers.Add(currentLayer); // Debug.Log($"layer {layerIx} nucleus {selectedNucleus.name}"); layerIx++; currentLayer = new() { ix = layerIx }; //foreach (Nucleus input in selectedNucleus.synapses.Keys) { foreach (Synapse synapse in selectedNucleus.synapses) { Nucleus input = synapse.nucleus; AddToLayer(currentLayer, input); // Debug.Log($"layer {layerIx} nucleus {input.name}"); } if (currentLayer.neuroids.Count > 0) { this.layers.Add(currentLayer); } } private void AddToLayer(NeuroidLayer layer, Nucleus nucleus) { if (nucleus == null) return; layer.neuroids.Add(nucleus); nucleus.layerIx = layer.ix; // Store its position Vector2Int neuroidPosition = new(layer.ix, layer.neuroids.Count - 1); neuroidPositions[nucleus] = neuroidPosition; } // basic pan/zoom handling // void OnWheel(WheelEvent e) { // if (e.ctrlKey) { // float delta = -e.delta.y * 0.001f; // zoom = Mathf.Clamp(zoom + delta, 0.25f, 2f); // content.transform.rotation = Quaternion.identity; // keep transform accessible // content.transform.scale = new Vector3(zoom, zoom, 1); // e.StopPropagation(); // } // else { // pan += e.delta; // content.style.left = pan.x; // content.style.top = pan.y; // } // } void OnMouseDown(MouseDownEvent e) { if (e.button == 2) { draggingCanvas = true; lastMouse = e.mousePosition; e.StopPropagation(); } } void OnMouseMove(MouseMoveEvent e) { if (draggingCanvas) { var delta = e.mousePosition - lastMouse; pan += delta; //content.style.left = pan.x; //content.style.top = pan.y; lastMouse = e.mousePosition; } } void OnMouseUp(MouseUpEvent e) { if (e.button == 2) draggingCanvas = false; } void OnIMGUI() { if (currentNucleus == null) return; serializedBrain.Update(); Handles.BeginGUI(); foreach (NeuroidLayer layer in layers) DrawLayer(layer); Handles.EndGUI(); } private void DrawLayer(NeuroidLayer layer) { int nodeCount = layer.neuroids.Count; float maxValue = 0; foreach (Nucleus nucleus in layer.neuroids) { if (nucleus is Neuroid neuroid) { float value = neuroid.outputValue.magnitude; if (value > maxValue) maxValue = value; } } float spacing = 400f / nodeCount; float margin = 10 + spacing / 2; foreach (Nucleus layerNucleus in layer.neuroids) { Vector2Int layerNeuroidPos = this.neuroidPositions[layerNucleus]; Vector3 parentPos = new(100 + layerNeuroidPos.x * 100, margin + layerNeuroidPos.y * spacing, 0.1f); //int i = 0; float inputSpacing = 400f / layerNucleus.synapses.Count; float inputMargin = 10 + inputSpacing / 2; // int minStale = 10000; //foreach ((Nucleus nucleus, float weight) in layerNucleus.synapses) { foreach (Synapse synapse in layerNucleus.synapses) { Nucleus nucleus = synapse.nucleus; if (nucleus != null) { float weight = synapse.weight; if (this.neuroidPositions.ContainsKey(nucleus)) { Vector2Int inputNeuroidPos = this.neuroidPositions[nucleus]; if (inputNeuroidPos.x == layerNeuroidPos.x + 1) { Vector3 pos = new(100 + inputNeuroidPos.x * 100, inputMargin + inputNeuroidPos.y * inputSpacing, 0.0f); float brightness = weight / 10.0f; Handles.color = new Color(brightness, brightness, brightness); Handles.DrawLine(parentPos, pos); } } // if (nucleus is Neuroid neuroid && neuroid.stale < minStale) // minStale = neuroid.stale; } } // if (layerNucleus.synapses.Count > 0 && minStale > 2 && layerNucleus.stale < 3) // Debug.LogWarning($"Strange {minStale} is big duing update"); float size = 20; if (layerNucleus.isSleeping) Handles.color = Color.darkRed; else { float brightness = layerNucleus.outputValue.magnitude / maxValue; Handles.color = new Color(brightness, brightness, brightness); } Handles.DrawSolidDisc(parentPos, Vector3.forward, size); Vector3 labelPos = parentPos - Vector3.down * (size + 0.2f); // below disc along up axis GUIStyle style = new GUIStyle(EditorStyles.label) { alignment = TextAnchor.UpperCenter, normal = { textColor = Color.white }, fontStyle = FontStyle.Bold }; Handles.Label(labelPos, layerNucleus.name, style); Rect neuronRect = new(parentPos.x - size, parentPos.y - size, size * 2, size * 2); int id = GUIUtility.GetControlID(FocusType.Passive); Event e = Event.current; EventType et = e.GetTypeForControl(id); if (e != null && neuronRect.Contains(e.mousePosition)) { // Process Hover HandleMouseHover(layerNucleus, neuronRect); // Process click // Debug.Log($"{et} {e.type}"); if (e.type == EventType.MouseDown && e.button == 0) { // Consume the event so the scene doesn't also handle it e.Use(); HandleDiscClicked(layerNucleus); } } } } private void HandleMouseHover(Nucleus neuroid, Rect rect) { GUIContent tooltip; // if (neuroid is SensoryNeuroid sensoryNeuroid) { // tooltip = new( // $"{sensoryNeuroid.name}" + // $"\nThing {sensoryNeuroid.receptor.thingType}" + // $"\nValue: {neuroid.outputValue}"); // } // else { tooltip = new( $"{neuroid.name}" + $"\nsynapse count {neuroid.synapses.Count}" + $"\nValue: {neuroid.outputValue}"); // } Vector2 mousePosition = Event.current.mousePosition; // Display tooltip with some offset Vector2 tooltipSize = GUI.skin.box.CalcSize(tooltip); Rect tooltipRect = new Rect(mousePosition.x + 10, mousePosition.y + 10, tooltipSize.x, tooltipSize.y); GUI.Box(tooltipRect, tooltip); } private void HandleDiscClicked(Nucleus nucleus) { this.currentNucleus = nucleus; BuildLayers(); } void DrawInspector() { if (NanoBrainEditor.inspectorContainer == null) return; NanoBrainEditor.inspectorContainer.Clear(); if (this.currentNucleus == null) return; // create a SerializedObject wrapper so Unity inspector controls work (and Undo) SerializedObject so = new SerializedObject(currentWrapper); IMGUIContainer container = new IMGUIContainer(() => { so.Update(); currentNucleus.name = EditorGUILayout.TextField(currentNucleus.name); EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField("Output Value", GUILayout.Width(100)); EditorGUILayout.Vector3Field(GUIContent.none, currentNucleus.outputValue); EditorGUILayout.EndHorizontal(); if (currentNucleus.synapses.Count > 0) { EditorGUILayout.LabelField("Synapses"); EditorGUI.indentLevel++; //List nuclei = currentNucleus.synapses.Keys.ToList(); // foreach (Nucleus nucleus in nuclei) { foreach (Synapse synapse in currentNucleus.synapses) { EditorGUI.BeginDisabledGroup(synapse.nucleus.isSleeping); EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField(synapse.nucleus.name, GUILayout.Width(120)); EditorGUI.indentLevel--; EditorGUILayout.LabelField("Weight", GUILayout.Width(45)); // float weight = currentNucleus.synapses[nucleus]; // currentNucleus.synapses[nucleus] = EditorGUILayout.FloatField(weight, GUILayout.Width(40)); synapse.weight = EditorGUILayout.FloatField(synapse.weight, GUILayout.Width(40)); EditorGUI.indentLevel++; EditorGUILayout.Vector3Field(GUIContent.none, synapse.nucleus.outputValue, GUILayout.Width(180)); EditorGUILayout.EndHorizontal(); EditorGUI.EndDisabledGroup(); } EditorGUI.indentLevel--; } if (GUILayout.Button("Add Neuron")) AddInputNeuron(currentNucleus); }); NanoBrainEditor.inspectorContainer.Add(container); } protected virtual void AddInputNeuron(Nucleus receiver) { Neuroid newNeuroid = new(brain, "New neuron"); newNeuroid.AddReceiver(receiver); Rebuild(); } private Vector3 NodePosition(Nucleus nucleus, int layerNodeCount = 1) { if (this.neuroidPositions.ContainsKey(nucleus)) { Vector2Int nucleusPos = this.neuroidPositions[nucleus]; return NodePosition(nucleusPos, layerNodeCount); } else { return Vector3.zero; } } private Vector3 NodePosition(Vector2Int location, int layerNodeCount = 1) { float spacing = 400f / layerNodeCount; float margin = 10 + spacing / 2; float size = 20; Vector3 parentPos = new(100 + location.x * 100 - size, margin + location.y * spacing - size, 0.1f); return parentPos; } // public void CreateEdge(string fromId, string toId) { // if (fromId == toId) return; // Undo.RecordObject(graph, "Create Edge"); // graph.edges.Add(new GraphEdge { fromNodeId = fromId, toNodeId = toId }); // EditorUtility.SetDirty(graph); // Rebuild(); // } } public class NodeView : VisualElement { Nucleus data; GraphBoardView board; Label titleLabel; //bool dragging = false; Vector2 localDragStart; public NodeView(Nucleus node, GraphBoardView boardView) { data = node; board = boardView; name = "node"; style.width = 20; //node.size.x; style.height = 20; //node.size.y; titleLabel = new Label(node.name) { name = "title" }; Add(titleLabel); // ports // var outPort = new Button(() => StartEdgeDrag(true)) { text = "◀", name = "out" }; // var inPort = new Button(() => StartEdgeDrag(false)) { text = "▶", name = "in" }; // Add(outPort); // Add(inPort); RegisterCallback(OnMouseDown); // RegisterCallback(OnMouseMove); RegisterCallback(OnMouseUp); //RegisterCallback(e => dragging = false); } // void StartEdgeDrag(bool isOutput) { // // simplified: on first click store source; on second click on target port call board.CreateEdge // if (EdgeDragState.active == null) EdgeDragState.active = new EdgeDragState { fromNode = data, fromIsOutput = isOutput }; // else { // var src = EdgeDragState.active.fromNode; // if (src != null && src.id != data.id) board.CreateEdge(src.id, data.id); // EdgeDragState.active = null; // } // } void OnMouseDown(MouseDownEvent e) { if (e.button == 0 && e.target == this) { //dragging = true; localDragStart = e.mousePosition; e.StopPropagation(); } } // void OnMouseMove(MouseMoveEvent e) { // if (!dragging) return; // var delta = e.mousePosition - localDragStart; // var worldPos = new Vector2(layout.x + delta.x, layout.y + delta.y); // style.left = worldPos.x; // style.top = worldPos.y; // // commit on every move // board.UpdateNodePosition(data, worldPos); // } void OnMouseUp(MouseUpEvent e) { //dragging = false; } } public class GraphNodeWrapper : ScriptableObject { // expose fields that map to GraphNode public string title; public Vector2 position; Nucleus node; NanoBrain graph; // needed to write back and mark dirty public GraphNodeWrapper Init(Nucleus node, NanoBrain graphAsset) { this.node = node; this.graph = graphAsset; this.title = " A " + node.name; //position = node.position; return this; } void OnValidate() { if (node != null) { node.name = title; //node.position = position; #if UNITY_EDITOR if (graph != null) UnityEditor.EditorUtility.SetDirty(graph); #endif } } } //static class EdgeDragState { public static EdgeDragState active; public GraphNode fromNode; public bool fromIsOutput; } public static class OpenAssetHandler { // Called when an asset is double-clicked or opened. [OnOpenAsset] public static bool OpenMyScriptableObject(int instanceID, int line) { NanoBrain obj = EditorUtility.EntityIdToObject(instanceID) as NanoBrain; if (obj != null) { NanoBrainEditor.Open(obj); return true; // handled } return false; // let Unity open normally } } */