1#![doc = include_str!("../README.md")]
2
3use egui_macroquad::{
4 egui::{self, Checkbox, CollapsingHeader, ComboBox, Slider},
5 macroquad::prelude::*,
6};
7use fdg_sim::{
8 force::{self, Value},
9 glam::Vec3,
10 json,
11 petgraph::graph::NodeIndex,
12 Dimensions, ForceGraph, Node, Simulation, SimulationParameters,
13};
14use serde::Serialize;
15pub use serde_json::Value as JsonValue;
16
17pub use {egui_macroquad::macroquad, fdg_sim};
18
19pub async fn run_window<
20 N: Serialize + Clone + Default + PartialEq,
21 E: Serialize + Clone + Default + PartialEq,
22>(
23 graph: &ForceGraph<N, E>,
24) {
25 let mut sim = Simulation::from_graph(graph.clone(), SimulationParameters::default());
26
27 let orig_params = sim.parameters().clone();
28 let orig_graph = sim.get_graph().clone();
29 let mut current_force = force::fruchterman_reingold(45.0, 0.975);
30
31 let forces = vec![
32 current_force.clone(),
33 force::handy(45.0, 0.975, true, true),
34 force::scale(),
35 force::translate(),
36 ];
37
38 let mut dark = true;
39
40 let mut sim_speed: u8 = 1;
41 let mut zoom: f32 = 1.0;
42 let mut json = false;
43
44 let default_node_size = 5.0;
45 let default_edge_size = 1.5;
46 let mut node_size = default_node_size;
47 let mut edge_size = default_edge_size;
48
49 let mut angle: f32 = 0.0;
50 let radius = 200.0;
51
52 let mut orbit_speed: f32 = 1.0;
53 let mut orbit = true;
54 let mut show_grid = true;
55
56 let mut show_edges = true;
57 let mut show_nodes = true;
58
59 let mut dragging_node: Option<NodeIndex> = None;
60 let mut manual = false;
61 let mut running = true;
62 let default_step_length: f32 = 0.035;
63 let mut step_length = default_step_length;
64
65 let mut json_buffer = String::new();
66
67 if json {
68 json_buffer = update_json_buffer(sim.get_graph());
69 }
70
71 loop {
72 clear_background(if dark {
74 Color::from_rgba(15, 23, 42, 255)
75 } else {
76 LIGHTGRAY
77 });
78
79 if is_key_down(KeyCode::R) {
80 sim.reset_node_placement();
81 }
82
83 if sim.parameters().dimensions == Dimensions::Two {
85 let w = screen_width() * (1.0 / zoom);
86 let h = screen_height() * (1.0 / zoom);
87
88 set_camera(&Camera2D::from_display_rect(Rect::new(
89 -(w / 2.0),
90 -(h / 2.0),
91 w,
92 h,
93 )));
94
95 let mut mouse = mouse_position();
96 mouse.0 = (mouse.0 - (screen_width() / 2.0)) * (1.0 / zoom);
97 mouse.1 = (mouse.1 - (screen_height() / 2.0)) * (1.0 / zoom);
98
99 let hovered_node = match sim.find(Vec3::new(mouse.0, mouse.1, 0.0), node_size) {
100 Some(hovered) => {
101 if dragging_node.is_none() && is_mouse_button_down(MouseButton::Left) {
102 dragging_node = Some(hovered);
103 }
104
105 Some(hovered)
106 }
107 None => None,
108 };
109
110 if let Some(index) = dragging_node {
111 let node = &mut sim.get_graph_mut()[index];
112
113 if is_mouse_button_down(MouseButton::Left) {
114 node.locked = true;
115 node.location.x = mouse.0;
116 node.location.y = mouse.1;
117 } else if is_mouse_button_released(MouseButton::Left) {
118 node.locked = false;
119 dragging_node = None;
120 }
121 }
122
123 if show_edges {
124 sim.visit_edges(&mut |source, target| {
125 draw_line(
126 source.location.x,
127 source.location.y,
128 target.location.x,
129 target.location.y,
130 edge_size,
131 RED,
132 );
133 });
134 }
135
136 if show_nodes {
137 sim.visit_nodes(&mut |node| {
138 draw_circle(
139 node.location.x,
140 node.location.y,
141 node_size,
142 Color::from_rgba(
143 mode_color_convert(dark, 0),
144 mode_color_convert(dark, 0),
145 mode_color_convert(dark, 0),
146 255,
147 ),
148 );
149 });
150 }
151
152 if dragging_node.is_some() || hovered_node.is_some() {
153 set_default_camera();
154
155 if let Some(index) = dragging_node {
156 let node = &sim.get_graph()[index];
157 let screen_mouse = mouse_position();
158 draw_text(
159 &node.name,
160 screen_mouse.0 + 10.0,
161 screen_mouse.1 - 10.0,
162 30.0,
163 if dark { LIGHTGRAY } else { DARKBLUE },
164 );
165 } else if let Some(index) = hovered_node {
166 let node: &Node<N> = &sim.get_graph()[index];
167 let screen_mouse = mouse_position();
168 draw_text(
169 &node.name,
170 screen_mouse.0 + 10.0,
171 screen_mouse.1 - 10.0,
172 30.0,
173 if dark { LIGHTGRAY } else { DARKBLUE },
174 );
175 }
176 }
177 } else {
178 let adj_radius = radius * (1.0 / (zoom / 2.0));
179 let (x, y) = (adj_radius * angle.cos(), adj_radius * angle.sin());
180
181 if orbit {
182 angle += 0.0015 * orbit_speed;
183 }
184
185 set_camera(&Camera3D {
186 position: vec3(x, radius * 1.5, y),
187 up: vec3(0., 1.0, 0.),
188 target: vec3(0.0, 0.0, 0.0),
189 ..Default::default()
190 });
191
192 if show_grid {
193 draw_grid(200, 25.0, DARKBLUE, GRAY);
194 }
195
196 if show_edges {
197 sim.visit_edges(&mut |source, target| {
198 draw_line_3d(
199 vec3(source.location.x, source.location.y, source.location.z),
200 vec3(target.location.x, target.location.y, target.location.z),
201 RED,
202 );
203 });
204 }
205
206 if show_nodes {
207 sim.visit_nodes(&mut |node| {
208 draw_sphere(
209 vec3(node.location.x, node.location.y, node.location.z),
210 node_size,
211 None,
212 Color::from_rgba(
213 mode_color_convert(dark, 0),
214 mode_color_convert(dark, 0),
215 mode_color_convert(dark, 0),
216 255,
217 ),
218 );
219 });
220 }
221 }
222
223 egui_macroquad::ui(|egui_ctx| {
225 if json {
226 egui::Window::new("Json")
227 .anchor(egui::Align2::RIGHT_TOP, [-20.0, 20.0])
228 .fixed_size([200.0, 500.0])
229 .show(egui_ctx, |ui| {
230 egui::ScrollArea::new([true, true]).show(ui, |ui| {
231 ui.text_edit_multiline(&mut json_buffer);
232 });
233 });
234 }
235
236 egui::Window::new("Settings")
237 .anchor(egui::Align2::LEFT_TOP, [20.0, 20.0])
238 .fixed_size([50.0, 50.0])
239 .show(egui_ctx, |ui| {
240 ui.horizontal(|ui| {
241 if ui.button("Restart Simulation").clicked() {
242 sim.set_graph(orig_graph.clone());
243 sim.reset_node_placement();
244
245 json_buffer = update_json_buffer(sim.get_graph());
246 }
247
248 if ui.button("Reset Settings").clicked() {
249 let mut p = sim.parameters_mut();
250 p.node_start_size = orig_params.node_start_size;
251 current_force.reset();
252 sim_speed = 1;
253 orbit_speed = 1.0;
254 zoom = 1.0;
255 node_size = default_node_size;
256 edge_size = default_edge_size;
257 step_length = default_step_length;
258 }
259
260 if ui
261 .button(match sim.parameters().dimensions {
262 Dimensions::Two => "View in 3D",
263 Dimensions::Three => "View in 2D",
264 })
265 .clicked()
266 {
267 sim.parameters_mut().dimensions = match sim.parameters().dimensions {
268 Dimensions::Two => Dimensions::Three,
269 Dimensions::Three => Dimensions::Two,
270 };
271
272 sim.reset_node_placement();
273 }
274 });
275 ui.separator();
276 if ui
277 .button(if dark {
278 "Switch to Light Mode"
279 } else {
280 "Switch to Dark Mode"
281 })
282 .clicked()
283 {
284 if dark {
285 dark = false;
286 } else {
287 dark = true;
288 }
289 }
290 ui.separator();
291 if current_force.continuous() {
292 ui.add(Checkbox::new(&mut manual, "Manual"));
293 ui.add(Slider::new(&mut step_length, 0.001..=0.5).text("Step Length"));
294
295 if manual {
296 if ui.button("Step").clicked() {
297 sim.update_custom(¤t_force, step_length);
298 }
299 } else {
300 ui.add(Slider::new(&mut sim_speed, 1..=10).text("Simulation Speed"));
301 let running_text = if running { "Stop" } else { "Start" };
302
303 if ui.button(running_text).clicked() {
304 if running {
305 running = false;
306 } else {
307 running = true;
308 }
309 }
310 }
311 } else if ui.button("Run").clicked() {
312 sim.update_custom(¤t_force, 0.0);
313 }
314 ui.separator();
315 ui.add(Slider::new(&mut zoom, 0.05..=2.0).text("Zoom"));
316 match sim.parameters().dimensions {
317 Dimensions::Three => {
318 ui.add_enabled(
319 orbit,
320 Slider::new(&mut orbit_speed, 0.1..=5.0).text("Orbit Speed"),
321 );
322 ui.checkbox(&mut orbit, "Orbit");
323 ui.checkbox(&mut show_grid, "Show Grid");
324 }
325 Dimensions::Two => {
326 ui.add_enabled(
327 show_edges,
328 Slider::new(&mut edge_size, 1.0..=10.0).text("Edge Size"),
329 );
330 }
331 }
332 ui.add_enabled(
333 show_nodes,
334 Slider::new(&mut node_size, 1.0..=25.0).text("Node Size"),
335 );
336 ui.add(
337 Slider::new(&mut sim.parameters_mut().node_start_size, 0.5..=1000.0)
338 .text("Node Start Area"),
339 );
340 ui.checkbox(&mut show_edges, "Show Edges");
341 ui.checkbox(&mut show_nodes, "Show Nodes");
342 if ui.checkbox(&mut json, "Show Json").changed() {
343 json_buffer = update_json_buffer(sim.get_graph());
344 };
345 ui.separator();
346 ComboBox::new("force_selector", "")
347 .selected_text(current_force.name())
348 .show_ui(ui, |ui| {
349 for force in &forces {
350 ui.selectable_value(
351 &mut current_force,
352 force.clone(),
353 force.name(),
354 );
355 }
356 });
357 if let Some(info) = current_force.info() {
358 CollapsingHeader::new("Info")
359 .default_open(false)
360 .show(ui, |ui| {
361 ui.label(info);
362 });
363 }
364 for (name, value) in current_force.dict_mut() {
365 match value {
366 Value::Number(value, range) => {
367 ui.add(Slider::new(value, range.clone()).text(name))
368 }
369 Value::Bool(value) => ui.add(Checkbox::new(value, name.to_string())),
370 };
371 }
372 ui.separator();
373 ui.horizontal(|ui| {
374 let g = sim.get_graph();
375 ui.label(format!("Node Count: {}", g.node_count()));
376 ui.separator();
377 ui.label(format!("Edge Count: {}", g.edge_count()));
378 ui.separator();
379 ui.label(format!("FPS: {}", get_fps()));
380 });
381 });
382 });
383
384 if running && !manual && current_force.continuous() {
386 for _ in 0..sim_speed {
387 sim.update_custom(¤t_force, step_length);
388 }
389 }
390
391 egui_macroquad::draw();
393
394 next_frame().await;
396 }
397}
398
399fn mode_color_convert(dark: bool, i: u8) -> u8 {
400 if dark {
401 255 - i
402 } else {
403 i
404 }
405}
406
407fn update_json_buffer<N: Serialize, E: Serialize>(graph: &ForceGraph<N, E>) -> String {
408 match json::graph_to_json(graph) {
409 Ok(s) => match serde_json::to_string_pretty(&s) {
410 Ok(s) => s,
411 Err(err) => format!("json string formatting error: {err}"),
412 },
413 Err(err) => format!("json serializing error: {err}"),
414 }
415}