diff --git a/.gitmodules b/.gitmodules index 5cb29c6..28d8240 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "ext/tomlc17"] path = ext/tomlc17 url = https://github.com/cktan/tomlc17 +[submodule "ext/raygui"] + path = ext/raygui + url = https://github.com/raysan5/raygui.git diff --git a/Makefile b/Makefile index 3c2ee87..4cd2c87 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # Compiler and flags CXX = g++ -CXXFLAGS = -Wall -Wextra -g -ggdb3 -std=c++14 -I./src -isystem./ext/raylib/src -I./ext/tomlc17/src +CXXFLAGS = -Wall -Wextra -g -ggdb3 -std=c++14 -I./src -isystem./ext/raylib/src -I./ext/tomlc17/src -I./ext/raygui/src LDFLAGS = -L./ext/raylib/src -lraylib -lm -lpthread -ldl -lrt -lX11 # Directories diff --git a/ext/raygui b/ext/raygui new file mode 160000 index 0000000..9a1c183 --- /dev/null +++ b/ext/raygui @@ -0,0 +1 @@ +Subproject commit 9a1c183d8539e2470635b22f92bdaefaf5218aaa diff --git a/src/main.cpp b/src/main.cpp index 8773b4f..6de4694 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -35,6 +35,11 @@ void run_gui_simulation(SimulationState* sim) { RenderState render_state; setup_camera(&render_state); + + // Initialize UI state + render_state.selected_body_index = -1; // No selection initially + render_state.show_body_list = false; + render_state.show_body_info = false; bool paused = false; double speed_multiplier = 1.0; @@ -46,6 +51,7 @@ void run_gui_simulation(SimulationState* sim) { printf(" Space: Pause/Resume\n"); printf(" +/-: Speed up/slow down simulation\n"); printf(" I: Toggle info display\n"); + printf(" B: Toggle body list\n"); printf(" ESC: Quit\n\n"); while (!WindowShouldClose()) { diff --git a/src/renderer.cpp b/src/renderer.cpp index 9f44b2f..8f60d59 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -3,6 +3,10 @@ #include #include +// raygui implementation (header-only library) +#define RAYGUI_IMPLEMENTATION +#include "raygui.h" + // Initialize raylib window void init_renderer(int width, int height, const char* title) { InitWindow(width, height, title); @@ -68,6 +72,11 @@ void update_camera(RenderState* render_state) { if (IsKeyPressed(KEY_I)) { render_state->show_info = !render_state->show_info; } + + // Toggle body list with B key + if (IsKeyPressed(KEY_B)) { + render_state->show_body_list = !render_state->show_body_list; + } } // Transform from simulation coordinates (XY plane) to render coordinates (XZ plane) @@ -316,6 +325,10 @@ void render_simulation(SimulationState* sim, RenderState* render_state) { render_info(sim, "solar_system.txt"); } + // Render UI panels + render_body_list_ui(sim, render_state); + render_body_info_ui(sim, render_state); + EndDrawing(); } @@ -350,3 +363,133 @@ void render_info(SimulationState* sim, const char* config_name) { DrawText(" I: Toggle info", 10, 210, 14, LIGHTGRAY); DrawText(" ESC: Quit", 10, 230, 14, LIGHTGRAY); } + +// Render body list UI panel +void render_body_list_ui(SimulationState* sim, RenderState* render_state) { + if (!render_state->show_body_list) return; + + // Panel dimensions + int panel_width = 200; + int panel_height = 400; + int panel_x = 10; + int panel_y = 10; + + Rectangle panel_bounds = { (float)panel_x, (float)panel_y, (float)panel_width, (float)panel_height }; + + // Create body list for raygui + const char** body_names = (const char**)malloc(sim->body_count * sizeof(char*)); + if (!body_names) return; + + for (int i = 0; i < sim->body_count; i++) { + body_names[i] = sim->bodies[i].name; + } + + // Draw window + if (GuiWindowBox(panel_bounds, "Bodies")) { + render_state->show_body_list = false; + } + + // Draw list view (inside window, leave space for title bar) + Rectangle list_bounds = { + (float)panel_x + 4, + (float)panel_y + 25, + (float)panel_width - 8, + (float)panel_height - 29 + }; + + int focus = 0; + int active = 0; + if (render_state->selected_body_index >= 0 && render_state->selected_body_index < sim->body_count) { + focus = render_state->selected_body_index; // 0-based indexing for scroll + active = 1; // Mark as active + } + + int result = GuiListView(list_bounds, (const char*)body_names, &focus, &active); + + if (result >= 0 && result < sim->body_count) { + render_state->selected_body_index = result; + render_state->show_body_info = true; + } + + free(body_names); +} + +// Render body information UI panel +void render_body_info_ui(SimulationState* sim, RenderState* render_state) { + if (!render_state->show_body_info || render_state->selected_body_index < 0 || + render_state->selected_body_index >= sim->body_count) return; + + // Panel dimensions + int panel_width = 250; + int panel_height = 300; + int panel_x = GetScreenWidth() - panel_width - 10; + int panel_y = 10; + + Rectangle panel_bounds = { (float)panel_x, (float)panel_y, (float)panel_width, (float)panel_height }; + + CelestialBody* body = &sim->bodies[render_state->selected_body_index]; + + // Draw window + if (GuiWindowBox(panel_bounds, body->name)) { + render_state->show_body_info = false; + } + + // Prepare info text + char info_text[1024]; + char temp_buffer[256]; + + snprintf(info_text, sizeof(info_text), "Name: %s", body->name); + + snprintf(temp_buffer, sizeof(temp_buffer), "Mass: %.2e kg", body->mass); + strcat(info_text, "\n"); + strcat(info_text, temp_buffer); + + snprintf(temp_buffer, sizeof(temp_buffer), "Radius: %.2e m", body->radius); + strcat(info_text, "\n"); + strcat(info_text, temp_buffer); + + // Position + snprintf(temp_buffer, sizeof(temp_buffer), "Position: (%.2e, %.2e, %.2e)", + body->position.x, body->position.y, body->position.z); + strcat(info_text, "\n"); + strcat(info_text, temp_buffer); + + // Velocity + double vel_mag = vec3_magnitude(body->velocity); + snprintf(temp_buffer, sizeof(temp_buffer), "Velocity: %.2f m/s", vel_mag); + strcat(info_text, "\n"); + strcat(info_text, temp_buffer); + + // Orbital elements + snprintf(temp_buffer, sizeof(temp_buffer), "Eccentricity: %.3f", body->eccentricity); + strcat(info_text, "\n"); + strcat(info_text, temp_buffer); + + snprintf(temp_buffer, sizeof(temp_buffer), "Semi-major axis: %.2e m", body->semi_major_axis); + strcat(info_text, "\n"); + strcat(info_text, temp_buffer); + + // Parent body + if (body->parent_index >= 0 && body->parent_index < sim->body_count) { + snprintf(temp_buffer, sizeof(temp_buffer), "Parent: %s", sim->bodies[body->parent_index].name); + } else { + snprintf(temp_buffer, sizeof(temp_buffer), "Parent: None"); + } + strcat(info_text, "\n"); + strcat(info_text, temp_buffer); + + // SOI + snprintf(temp_buffer, sizeof(temp_buffer), "SOI radius: %.2e m", body->soi_radius); + strcat(info_text, "\n"); + strcat(info_text, temp_buffer); + + // Draw info text (inside window, leave space for title bar) + Rectangle text_bounds = { + (float)panel_x + 8, + (float)panel_y + 25, + (float)panel_width - 16, + (float)panel_height - 29 + }; + + GuiLabel(text_bounds, info_text); +} diff --git a/src/renderer.h b/src/renderer.h index 22d07e8..a986937 100644 --- a/src/renderer.h +++ b/src/renderer.h @@ -10,6 +10,9 @@ struct RenderState { double distance_scale; // Scale factor for distances double size_scale; // Scale factor for body sizes bool show_info; // Display simulation info + int selected_body_index; // -1 = no selection + bool show_body_list; // Toggle for list panel + bool show_body_info; // Toggle for info panel }; // Renderer initialization and cleanup @@ -26,6 +29,10 @@ void render_body(CelestialBody* body, RenderState* render_state); void render_simulation(SimulationState* sim, RenderState* render_state); void render_info(SimulationState* sim, const char* config_name); +// UI rendering functions +void render_body_list_ui(SimulationState* sim, RenderState* render_state); +void render_body_info_ui(SimulationState* sim, RenderState* render_state); + // Scaling functions Vector3 scale_position(Vec3 pos, double scale); float scale_radius(double radius, double scale);