fdg_macroquad/
lib.rs

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        // Draw background
73        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        // Draw edges and nodes
84        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        // Draw gui
224        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(&current_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(&current_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        // update sim
385        if running && !manual && current_force.continuous() {
386            for _ in 0..sim_speed {
387                sim.update_custom(&current_force, step_length);
388            }
389        }
390
391        // draw gui
392        egui_macroquad::draw();
393
394        // go to next frame
395        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}