ManiaMap.Godot
Procedural generation of metroidvania style maps for Godot .NET.
LayoutGraphEditor.cs
1#if TOOLS
2using Godot;
3using MPewsey.ManiaMapGodot.Editor;
5using System.Collections.Generic;
6using System.Linq;
7
8namespace MPewsey.ManiaMapGodot.Graphs.Editor
9{
10 [Tool]
11 public partial class LayoutGraphEditor : Control
12 {
13 [Export] public GraphEdit GraphEdit { get; set; }
14 [Export] public Node EdgeLineContainer { get; set; }
15 [Export] public PackedScene NodeElementScene { get; set; }
16 [Export] public PackedScene EdgeElementScene { get; set; }
17 [Export] public PackedScene EdgeLineScene { get; set; }
18
19 [Export] public Label FileNameLabel { get; set; }
20 [Export] public Button CloseButton { get; set; }
21 [Export] public Button SaveButton { get; set; }
22 [Export] public Button EdgeDisplayButton { get; set; }
23
24 private LayoutGraphResource GraphResource { get; set; }
25 public Dictionary<int, LayoutGraphNodeElement> NodeElements { get; } = new Dictionary<int, LayoutGraphNodeElement>();
26 public HashSet<LayoutGraphEdgeElement> EdgeElements { get; } = new HashSet<LayoutGraphEdgeElement>();
27 private List<LayoutGraphEdgeLine> EdgeLines { get; } = new List<LayoutGraphEdgeLine>();
28 private HashSet<Resource> SelectedResources { get; } = new HashSet<Resource>();
29
30 public override void _Ready()
31 {
32 base._Ready();
33 GraphEdit.NodeSelected += OnNodeSelected;
34 GraphEdit.NodeDeselected += OnNodeDeselected;
35 GraphEdit.GuiInput += OnGuiInput;
36 GraphEdit.ConnectionRequest += OnConnectionRequest;
37 SaveButton.Pressed += OnSubmitSaveButton;
38 CloseButton.Pressed += OnSubmitCloseButton;
39 EdgeDisplayButton.Toggled += OnToggleEdgeDisplayButton;
40 OnToggleEdgeDisplayButton(true);
41 }
42
43 public override void _ExitTree()
44 {
45 base._ExitTree();
46 CloseOutGraphResource();
47 }
48
49 public override void _Process(double delta)
50 {
51 base._Process(delta);
52 MoveEdgesToMidpointsOfNodes();
53 PopulateEdgeLines();
54 }
55
56 private void DisplayNodes(bool visible)
57 {
58 foreach (var node in NodeElements.Values)
59 {
60 node.Visible = visible;
61
62 if (!visible)
63 node.Selected = false;
64 }
65 }
66
67 private void DisplayEdges(bool visible)
68 {
69 foreach (var edge in EdgeElements)
70 {
71 edge.Visible = visible;
72
73 if (!visible)
74 edge.Selected = false;
75 }
76 }
77
78 private void MoveEdgesToMidpointsOfNodes()
79 {
80 foreach (var element in EdgeElements)
81 {
82 var edge = element.EdgeResource;
83 var fromFound = NodeElements.TryGetValue(edge.FromNode, out var fromNode);
84 var toFound = NodeElements.TryGetValue(edge.ToNode, out var toNode);
85
86 if (fromFound && toFound)
87 {
88 var zoom = GraphEdit.Zoom;
89 var fromPosition = fromNode.Position + 0.5f * zoom * fromNode.Size;
90 var toPosition = toNode.Position + 0.5f * zoom * toNode.Size;
91 var edgePosition = (fromPosition + toPosition) * 0.5f - 0.5f * zoom * element.Size;
92 element.PositionOffset = GetPositionOffset(edgePosition);
93 }
94 }
95 }
96
97 private void PopulateEdgeLines()
98 {
99 SizeEdgeLinesList();
100 var index = 0;
101 var zoom = GraphEdit.Zoom;
102
103 foreach (var element in EdgeElements)
104 {
105 var line = EdgeLines[index++];
106 line.Populate(element, NodeElements, zoom);
107 }
108 }
109
110 private void SizeEdgeLinesList()
111 {
112 while (EdgeLines.Count > EdgeElements.Count)
113 {
114 var index = EdgeLines.Count - 1;
115 EdgeLines[index].QueueFree();
116 EdgeLines.RemoveAt(index);
117 }
118
119 while (EdgeLines.Count < EdgeElements.Count)
120 {
121 var line = EdgeLineScene.Instantiate<LayoutGraphEdgeLine>();
122 EdgeLineContainer.AddChild(line);
123 EdgeLines.Add(line);
124 }
125 }
126
127 private void OnGuiInput(InputEvent input)
128 {
129 if (input is InputEventMouseButton mouseInput)
130 {
131 if (mouseInput.ButtonIndex == MouseButton.Right && mouseInput.Pressed)
132 {
133 AddNode(GetPositionOffset(mouseInput.Position));
134 GetViewport().SetInputAsHandled();
135 }
136 }
137 else if (input.IsActionPressed(EditorInputs.DeleteAction))
138 {
139 DeleteSelectedElements();
140 GetViewport().SetInputAsHandled();
141 }
142 else if (input.IsActionPressed(EditorInputs.SelectAllAction))
143 {
144 SelectAllElements();
145 GetViewport().SetInputAsHandled();
146 }
147 else if (input.IsActionPressed(EditorInputs.SelectAllNodesAction))
148 {
149 SelectAllNodeElements();
150 GetViewport().SetInputAsHandled();
151 }
152 else if (input.IsActionPressed(EditorInputs.SelectAllEdgesAction))
153 {
154 SelectAllEdgeElements();
155 GetViewport().SetInputAsHandled();
156 }
157 }
158
159 private void SelectAllNodeElements()
160 {
161 foreach (var node in NodeElements.Values)
162 {
163 node.Selected = true;
164 }
165 }
166
167 private void SelectAllEdgeElements()
168 {
169 foreach (var edge in EdgeElements)
170 {
171 edge.Selected = EdgeDisplayButton.ButtonPressed;
172 }
173 }
174
175 private void SelectAllElements()
176 {
177 SelectAllEdgeElements();
178 SelectAllNodeElements();
179 }
180
181 private void DeleteSelectedElements()
182 {
183 SelectedResources.Clear();
184
185 foreach (var edge in EdgeElements.Where(x => x.Selected).ToList())
186 {
187 RemoveEdge(edge);
188 }
189
190 foreach (var node in NodeElements.Values.Where(x => x.Selected).ToList())
191 {
192 RemoveNode(node);
193 }
194 }
195
196 private void RemoveNode(LayoutGraphNodeElement node)
197 {
198 RemoveNodeEdges(node.NodeResource.Id);
199 GraphResource.RemoveNode(node.NodeResource.Id);
200 NodeElements.Remove(node.NodeResource.Id);
201 node.QueueFree();
202 }
203
204 private void RemoveNodeEdges(int nodeId)
205 {
206 foreach (var edge in NodeEdges(nodeId))
207 {
208 RemoveEdge(edge);
209 }
210 }
211
212 private List<LayoutGraphEdgeElement> NodeEdges(int nodeId)
213 {
214 var result = new List<LayoutGraphEdgeElement>();
215
216 foreach (var edge in EdgeElements)
217 {
218 if (edge.EdgeResource.ContainsNode(nodeId))
219 result.Add(edge);
220 }
221
222 return result;
223 }
224
225 private void RemoveEdge(LayoutGraphEdgeElement element)
226 {
227 GraphResource.RemoveEdge(element.EdgeResource.FromNode, element.EdgeResource.ToNode);
228 EdgeElements.Remove(element);
229 element.QueueFree();
230 }
231
232 private static void ToggleButton(Button button, bool toggled)
233 {
234 button.SetPressedNoSignal(toggled);
235 button.Flat = !toggled;
236 }
237
238 private void OnToggleEdgeDisplayButton(bool toggled)
239 {
240 ToggleButton(EdgeDisplayButton, toggled);
241 DisplayEdges(toggled);
242 DisplayResourceEditor();
243 }
244
245 private void OnSubmitSaveButton()
246 {
247 GraphResource?.SaveIfDirty();
248 SaveButton.Disabled = true;
249 }
250
251 private void OnSubmitCloseButton()
252 {
253 CloseOutGraphResource();
254 ClearCanvas();
255
256 if (IsInstanceValid(ManiaMapPlugin.Current))
257 ManiaMapPlugin.Current.HideGraphEditorDock();
258 }
259
260 private void OnConnectionRequest(StringName fromNodeName, long fromSlot, StringName toNodeName, long toSlot)
261 {
262 var node1 = GraphEdit.GetNode<GraphNode>(fromNodeName.ToString());
263 var node2 = GraphEdit.GetNode<GraphNode>(toNodeName.ToString());
264
265 if (node1 is LayoutGraphNodeElement fromNode && node2 is LayoutGraphNodeElement toNode)
266 AddEdge(fromNode.NodeResource.Id, toNode.NodeResource.Id);
267 }
268
269 private void OnGraphResourceChanged()
270 {
271 SaveButton.Disabled = false;
272 FileNameLabel.Text = GraphResource.ResourcePath.GetFile();
273 }
274
275 public Vector2 GetPositionOffset(Vector2 position)
276 {
277 return (position + GraphEdit.ScrollOffset) / GraphEdit.Zoom;
278 }
279
280 public void SetEditorTarget(LayoutGraphResource graphResource)
281 {
282 CloseOutGraphResource();
283 GraphResource = graphResource;
284 SaveButton.Disabled = false;
285 FileNameLabel.Text = graphResource.ResourcePath.GetFile();
286 RegisterChangedSignals();
287 ClearCanvas();
288 CreateEdgeElements();
289 CreateNodeElements();
290 }
291
292 private void CloseOutGraphResource()
293 {
294 if (GraphResource != null)
295 {
296 GraphResource.SaveIfDirty();
297 GraphResource.QuietDisconnect(Resource.SignalName.Changed, OnGraphResourceChanged);
298 GraphResource.UnregisterOnSubresourceChangedSignals();
299 GraphResource = null;
300 }
301 }
302
303 private void RegisterChangedSignals()
304 {
305 GraphResource.Changed += OnGraphResourceChanged;
306 GraphResource.RegisterOnSubresourceChangedSignals();
307 }
308
309 private void OnNodeDeselected(Node node)
310 {
311 switch (node)
312 {
313 case LayoutGraphNodeElement nodeElement:
314 SelectedResources.Remove(nodeElement.NodeResource);
315 break;
316 case LayoutGraphEdgeElement edgeElement:
317 SelectedResources.Remove(edgeElement.EdgeResource);
318 break;
319 }
320
321 DisplayResourceEditor();
322 }
323
324 private void OnNodeSelected(Node node)
325 {
326 switch (node)
327 {
328 case LayoutGraphNodeElement nodeElement:
329 SelectedResources.Add(nodeElement.NodeResource);
330 break;
331 case LayoutGraphEdgeElement edgeElement:
332 SelectedResources.Add(edgeElement.EdgeResource);
333 break;
334 }
335
336 DisplayResourceEditor();
337 }
338
339 private void DisplayResourceEditor()
340 {
341 if (SelectedResources.Count > 1)
342 {
343 var multiEditor = new LayoutGraphMultiEditor(SelectedResources);
344 EditorInterface.Singleton.EditResource(multiEditor);
345 return;
346 }
347
348 if (SelectedResources.Count == 1)
349 {
350 switch (SelectedResources.First())
351 {
352 case LayoutGraphNode nodeResource:
353 EditorInterface.Singleton.EditResource(nodeResource);
354 break;
355 case LayoutGraphEdge edgeResource:
356 EditorInterface.Singleton.EditResource(edgeResource);
357 break;
358 }
359 }
360 }
361
362 private LayoutGraphNodeElement CreateNodeElement(LayoutGraphNode node)
363 {
364 var element = NodeElementScene.Instantiate<LayoutGraphNodeElement>();
365 GraphEdit.AddChild(element);
366 NodeElements.Add(node.Id, element);
367 element.Initialize(node);
368 return element;
369 }
370
371 private LayoutGraphEdgeElement CreateEdgeElement(LayoutGraphEdge edge)
372 {
373 var element = EdgeElementScene.Instantiate<LayoutGraphEdgeElement>();
374 GraphEdit.AddChild(element);
375 GraphEdit.MoveChild(element, 1);
376 EdgeElements.Add(element);
377 element.Initialize(edge);
378 element.Visible = EdgeDisplayButton.ButtonPressed;
379 return element;
380 }
381
382 private void CreateNodeElements()
383 {
384 foreach (var node in GraphResource.Nodes.Values)
385 {
386 CreateNodeElement(node);
387 }
388 }
389
390 private void CreateEdgeElements()
391 {
392 foreach (var edge in GraphResource.Edges.Values)
393 {
394 CreateEdgeElement(edge);
395 }
396 }
397
398 public void AddNode(Vector2 position)
399 {
400 var node = GraphResource.AddNode(position);
401 CreateNodeElement(node);
402 GraphResource.SetDirty();
403 }
404
405 public void AddEdge(int fromNode, int toNode)
406 {
407 var edge = GraphResource.AddEdge(fromNode, toNode);
408
409 if (edge != null)
410 {
411 CreateEdgeElement(edge);
412 GraphResource.SetDirty();
413 }
414 }
415
416 public void ClearCanvas()
417 {
418 foreach (var node in NodeElements.Values)
419 {
420 node.QueueFree();
421 }
422
423 foreach (var edge in EdgeElements)
424 {
425 edge.QueueFree();
426 }
427
428 foreach (var line in EdgeLines)
429 {
430 line.QueueFree();
431 }
432
433 SelectedResources.Clear();
434 NodeElements.Clear();
435 EdgeElements.Clear();
436 EdgeLines.Clear();
437 }
438 }
439}
440#endif