using Godot; using Godot.Collections; using System.Collections.Generic; public partial class CodingWindow : PanelContainer { private Robot robot; [Export] VBoxContainer codeBlocks; [Export] GraphEdit editorWindow; [Export] OptionButton availableScripts; [Export] LineEdit scriptName; [Export] LineEdit nameInput; public System.Collections.Generic.Dictionary DSLNodes; private System.Collections.Generic.Dictionary availableNodes; public override void _Ready() { DSLNodes = ResourceLoader.LoadDSLNodes(); GenerateCodingBlocks(); } public override void _Notification(int id) { if (id == NotificationVisibilityChanged) { if (Visible) LoadWindow(); } } private void LoadWindow() { if (robot == null) return; nameInput.Text = robot.Name; SetupScriptOptions(); ClearWindow(); LoadTemporaryProgram(); } private void SetupScriptOptions() { availableScripts.Clear(); availableScripts.AddItem("Select script to load..."); List scripts = FileHandler.LoadProgramNames(); scripts.Sort((a, b) => a.CompareTo(b)); foreach (string script in scripts) { availableScripts.AddItem(script); } } public void SaveRobotName() { if (robot == null) return; robot.Name = nameInput.Text; } public void CloseWindow() { Hide(); } public void GenerateCodingBlocks() { Button nodeListButton; foreach (ProgramNode nodeTemplate in DSLNodes.Keys) { nodeListButton = new Button { Name = nodeTemplate.DisplayText, Text = nodeTemplate.DisplayText }; nodeListButton.Pressed += () => { AddEditorNode(nodeTemplate); }; codeBlocks.AddChild(nodeListButton); } } private void AddEditorNode(ProgramNode node) { NodeDisplay editorDisplay = DSLNodes[node].Instantiate(); editorDisplay.PositionOffset = (editorWindow.ScrollOffset + editorWindow.Size / 2) / editorWindow.Zoom - editorDisplay.Size / 2; editorWindow.AddChild(editorDisplay); RegisterEditorNode(editorDisplay); } private void MoveNodeToVisibleGraphCenter(NodeDisplay nodeDisplay) { Vector2 visibleCenter = editorWindow.ScrollOffset + editorWindow.Size / (2f * editorWindow.Zoom); nodeDisplay.PositionOffset = visibleCenter - nodeDisplay.Size / (2f * editorWindow.Zoom); } private void RegisterEditorNode(NodeDisplay editorDisplay) { editorDisplay.OnDeleteNode += () => { editorWindow.RemoveChild(editorDisplay); editorDisplay.QueueFree(); }; } public void ClearWindow() { foreach (Dictionary connection in editorWindow.GetConnectionList()) { editorWindow.DisconnectNode( connection["from_node"].AsStringName(), (int)connection["from_port"], connection["to_node"].AsStringName(), (int)connection["to_port"] ); } foreach (Node child in editorWindow.GetChildren()) { if (child is GraphNode) { editorWindow.RemoveChild(child); child.QueueFree(); } } scriptName.Text = ""; } public void CompileProgram() { if (robot == null) return; NodeDisplay startNode = FindStartNode(); if (startNode == null) { robot.StopExecution("(FAILED) Script needs exactly one Start node"); return; } availableNodes = BuildAvailableNodeLookup(); List nodes = BuildScriptOrder(startNode, new List()); if (nodes.Count > 0) robot.SetupExecution(nodes); robot.currentProgram = scriptName.Text.Length <= 0 ? $"Script{availableScripts.ItemCount}" : scriptName.Text; } private System.Collections.Generic.Dictionary BuildAvailableNodeLookup() { System.Collections.Generic.Dictionary availableNodes = new System.Collections.Generic.Dictionary(); for (int i = 0; i < editorWindow.GetChildCount(); i++) { NodeDisplay nodeDisplay = editorWindow.GetChild(i) as NodeDisplay; if (nodeDisplay == null) continue; nodeDisplay.ReadParameters(); availableNodes.Add(nodeDisplay.Name, nodeDisplay.node); } return availableNodes; } private List BuildScriptOrder(NodeDisplay node, List program) { program.Add(node.node); if (editorWindow.GetConnectionListFromNode(node.Name).Count <= 0) return program; List nextConnections = CheckNodeConnections(node); if (nextConnections.Count <= 0) return program; node.node.SetNextNode(nextConnections, availableNodes); foreach (Dictionary connection in nextConnections) { program = BuildScriptOrder( editorWindow.GetNode(new NodePath(connection["to_node"].AsStringName())), program ); } return program; } private List CheckNodeConnections(NodeDisplay node) { List result = new List(); Array connections = editorWindow.GetConnectionListFromNode(node.Name); for (int i = 0; i < connections.Count; i++) { if (connections[i]["from_node"].AsStringName() == node.Name) { result.Add(connections[i]); } } return result; } private NodeDisplay FindStartNode() { NodeDisplay startNode = null; for (int i = 0; i < editorWindow.GetChildCount(); i++) { NodeDisplay nodeDisplay = editorWindow.GetChild(i) as NodeDisplay; if (nodeDisplay == null) continue; if (nodeDisplay.node is not StartNode) continue; if (startNode != null) return null; startNode = nodeDisplay; } return startNode; } public void SetRobot(Robot robot) { this.robot = robot; } public void LoadProgram(int index) { if (index <= 0) return; ClearWindow(); string scriptContent = FileHandler.LoadProgram(availableScripts.GetItemText(index)); string[] nodes = scriptContent.Split(";"); foreach (string node in nodes) { NodeDisplay nodeDisplay = NodeDisplay.Load(node, DSLNodes); if (nodeDisplay != null) { editorWindow.AddChild(nodeDisplay); RegisterEditorNode(nodeDisplay); } } scriptName.Text = availableScripts.GetItemText(index); availableScripts.Select(0); } public void LoadTemporaryProgram() { if (robot == null) return; if (robot.currentNode == null) return; HashSet loadedNodes = new HashSet(); ProgramNode nodeToLoad = robot.currentNode; while (nodeToLoad != null && !loadedNodes.Contains(nodeToLoad)) { loadedNodes.Add(nodeToLoad); NodeDisplay nodeDisplay = NodeDisplay.Load(nodeToLoad.Save(), DSLNodes); if (nodeDisplay != null) { editorWindow.AddChild(nodeDisplay); RegisterEditorNode(nodeDisplay); } nodeToLoad = nodeToLoad.nextNode; } scriptName.Text = robot.currentProgram ?? ""; } public void DeleteProgram() { string filename = scriptName.Text; int selectedIndex = availableScripts.GetSelectedId(); if (selectedIndex > 0) { filename = availableScripts.GetItemText(selectedIndex); } if (filename.Length <= 0) return; if (!FileHandler.DeleteProgram(filename)) return; ClearWindow(); SetupScriptOptions(); } public void SaveProgram() { string result = ""; for (int i = 0; i < editorWindow.GetChildCount(); i++) { NodeDisplay nodeDisplay = editorWindow.GetChild(i) as NodeDisplay; if (nodeDisplay == null) continue; nodeDisplay.ReadParameters(); result += nodeDisplay.node.Save(); result += ";\r\n"; } if (result.Length <= 0) return; string filename = scriptName.Text.Length <= 0 ? $"Script{availableScripts.ItemCount}" : scriptName.Text; FileHandler.SaveProgram(filename, result); SetupScriptOptions(); } public void OnNodeConnect(StringName from, int fromPort, StringName to, int toPort) { if (to == from) return; foreach (Dictionary connection in editorWindow.GetConnectionList()) { if (connection["from_node"].AsStringName() == from && (int)connection["from_port"] == fromPort) return; } editorWindow.ConnectNode(from, fromPort, to, toPort); } public void OnNodeDisconnect(StringName from, int fromPort, StringName to, int toPort) { editorWindow.DisconnectNode(from, fromPort, to, toPort); } }