diff --git a/docs/rendering.md b/docs/rendering.md index 6f5f4a9..b3bf9c3 100644 --- a/docs/rendering.md +++ b/docs/rendering.md @@ -177,7 +177,7 @@ float scale_radius(double radius, double scale) { 4. Spacecraft 5. Maneuver markers -## UI System (raygui) +## UI System (ui_renderer + raygui) ### Info Panel (Bottom-Left) **Content:** @@ -258,29 +258,35 @@ float scale_radius(double radius, double scale) { ## Module Functions -### Initialization +### Renderer Module (renderer.cpp/h) + +#### Initialization - `init_renderer(width, height, title)` - Setup raylib window - `close_renderer()` - Cleanup raylib - `setup_camera(render_state)` - Initialize camera parameters -### Camera +#### Camera - `update_camera(render_state, sim)` - Handle input and camera follow -### Rendering +#### Rendering - `render_body(body, render_state)` - Draw single celestial body - `render_spacecraft(craft, render_state)` - Draw spacecraft marker - `render_maneuver_marker(craft, maneuver, render_state)` - Draw burn indicator -- `render_simulation(sim, render_state)` - Full scene rendering +- `render_simulation(sim, render_state)` - Full scene rendering (3D only) + +#### Scaling +- `scale_position(pos, scale)` - Transform simulation to render position +- `scale_radius(radius, scale)` - Apply size scaling with minimum -### UI +### UI Renderer Module (ui_renderer.cpp/h) + +#### UI Rendering - `render_info(sim)` - Bottom-left info panel -- `render_body_list_ui(sim, render_state)` - Objects selection panel +- `render_body_list_ui(sim, render_state, ui_state)` - Objects selection panel - `render_body_info_ui(sim, render_state)` - Top-right info panel - `render_maneuver_list_ui(sim, render_state)` - Maneuver list panel -### Scaling -- `scale_position(pos, scale)` - Transform simulation to render position -- `scale_radius(radius, scale)` - Apply size scaling with minimum +Note: UI rendering functions are called after `render_simulation()` to ensure UI overlays appear on top of the 3D scene. ## Technical Notes diff --git a/docs/technical_reference.md b/docs/technical_reference.md index 7fb1d33..51dc1d2 100644 --- a/docs/technical_reference.md +++ b/docs/technical_reference.md @@ -307,9 +307,26 @@ Raylib 3D visualization system. See **[docs/rendering.md](rendering.md)** for co - Camera controls and follow system - Object rendering (bodies, spacecraft, maneuvers) - Orbit path rendering (elliptical, parabolic, hyperbolic) -- UI panels (info, body list, maneuver list) - Coordinate transformation and scaling +### UI Renderer (ui_renderer.cpp/h) +Raygui-based UI system for rendering interactive panels. Handles all 2D overlay elements. + +**Key functions:** +- `render_info()` - Bottom-left info panel showing simulation time, FPS, and controls +- `render_body_list_ui()` - Top-left objects list panel for selecting bodies/spacecraft +- `render_body_info_ui()` - Top-right panel showing selected object details +- `render_maneuver_list_ui()` - Maneuver list panel for spacecraft planning + +**UI State:** +- `body_list_scroll` - Scroll position for objects list +- `body_list_active` - Currently selected item index + +**Implementation:** +- Uses raygui header-only library +- All UI rendering happens after 3D scene rendering +- Manages user interaction for object selection and camera following + ### Test Utilities (test_utilities.cpp/h) Test helper functions for orbital mechanics validation. diff --git a/src/main.cpp b/src/main.cpp index bf92db1..f606338 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,6 +2,7 @@ #include "simulation.h" #include "config_loader.h" #include "renderer.h" +#include "ui_renderer.h" #include #include #include @@ -36,6 +37,10 @@ void run_gui_simulation(SimulationState* sim) { RenderState render_state; setup_camera(&render_state); + UIState ui_state; + ui_state.body_list_scroll = 0; + ui_state.body_list_active = -1; + // Find root body (body with parent_index == -1) int root_body_index = -1; for (int i = 0; i < sim->body_count; i++) { @@ -45,16 +50,18 @@ void run_gui_simulation(SimulationState* sim) { } } - // Initialize UI state + // Initialize render state render_state.selected_body_index = root_body_index >= 0 ? root_body_index : -1; render_state.selected_craft_index = -1; // No spacecraft selected initially render_state.previous_selected_body = -1; // Previous selected body index - render_state.body_list_scroll = 0; // Initial scroll position - render_state.body_list_active = root_body_index >= 0 ? root_body_index : -1; // Sync with selected body render_state.camera_follow_body = root_body_index >= 0; // Follow root body if found render_state.camera_offset = (Vector3){ 0, 50, 100 }; // Default offset render_state.was_following_body = false; + // Initialize UI state + ui_state.body_list_scroll = 0; // Initial scroll position + ui_state.body_list_active = root_body_index >= 0 ? root_body_index : -1; // Sync with selected body + bool paused = false; double speed_multiplier = 1.0; int physics_steps_per_frame = 100; @@ -94,6 +101,12 @@ void run_gui_simulation(SimulationState* sim) { update_camera(&render_state, sim); render_simulation(sim, &render_state); + + // Render UI panels + render_info(sim); + render_body_list_ui(sim, &render_state, &ui_state); + render_body_info_ui(sim, &render_state); + render_maneuver_list_ui(sim, &render_state); } close_renderer(&render_state); diff --git a/src/renderer.cpp b/src/renderer.cpp index f8d3f9e..ada2c2e 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -3,10 +3,6 @@ #include #include -// raygui implementation (header-only library) -#define RAYGUI_IMPLEMENTATION -#include "raygui.h" - // Forward declarations static Vector3 sim_to_render(Vec3 pos, double scale); @@ -553,422 +549,5 @@ void render_simulation(SimulationState* sim, RenderState* render_state) { EndMode3D(); - // Render UI panels (always shown) - render_info(sim); - render_body_list_ui(sim, render_state); - render_body_info_ui(sim, render_state); - render_maneuver_list_ui(sim, render_state); - EndDrawing(); } - -// Render simulation information overlay -void render_info(SimulationState* sim) { - // Panel dimensions - int panel_width = 300; - int panel_height = 300; - int panel_x = 10; - int panel_y = GetScreenHeight() - panel_height - 10; - - Rectangle panel_bounds = { (float)panel_x, (float)panel_y, (float)panel_width, (float)panel_height }; - - // Draw window - if (GuiWindowBox(panel_bounds, "Info")) { - // Window box close button doesn't close this panel - } - - // Prepare info text - char info_text[1024]; - char temp_buffer[512]; - - snprintf(info_text, sizeof(info_text), "Orbital Mechanics Simulation\n\n"); - - // Config name - if (sim->config_name[0] != '\0') { - snprintf(temp_buffer, sizeof(temp_buffer), "Config: %s\n\n", sim->config_name); - strcat(info_text, temp_buffer); - } - - // Simulation time (in days) - double days = sim->time / 86400.0; // seconds to days - snprintf(temp_buffer, sizeof(temp_buffer), "Time: %.2f days\n", days); - strcat(info_text, temp_buffer); - - // Body count - snprintf(temp_buffer, sizeof(temp_buffer), "Bodies: %d\n", sim->body_count); - strcat(info_text, temp_buffer); - - // FPS - snprintf(temp_buffer, sizeof(temp_buffer), "FPS: %d\n\n", GetFPS()); - strcat(info_text, temp_buffer); - - // Controls - strcat(info_text, "Controls:\n"); - strcat(info_text, " Arrows: Rotate/Zoom\n"); - strcat(info_text, " Space: Pause/Resume\n"); - strcat(info_text, " +/-: Speed control\n"); - strcat(info_text, " ESC: Quit\n"); - - // 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); -} - -// Render body list UI panel -void render_body_list_ui(SimulationState* sim, RenderState* render_state) { - // 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 list string for raygui (NULL-terminated) - // Include both bodies and spacecraft - int total_items = sim->body_count + sim->craft_count; - int buffer_size = total_items * (64 + 3 + 1) + 1; // Extra space for "šŸš€ " prefix - char* list_text = (char*)malloc(buffer_size); - if (!list_text) return; - - list_text[0] = '\0'; - int offset = 0; - int item_index = 0; - - // Add bodies to list - for (int i = 0; i < sim->body_count; i++) { - if (item_index > 0) { - offset += snprintf(list_text + offset, buffer_size - offset, ";"); - } - offset += snprintf(list_text + offset, buffer_size - offset, "%s", sim->bodies[i].name); - item_index++; - } - - // Add spacecraft to list with "šŸš€ " prefix - for (int i = 0; i < sim->craft_count; i++) { - offset += snprintf(list_text + offset, buffer_size - offset, ";"); - offset += snprintf(list_text + offset, buffer_size - offset, "šŸš€ %s", sim->spacecraft[i].name); - item_index++; - } - - // Draw window (no close button - panels always shown) - GuiWindowBox(panel_bounds, "Objects"); - - // 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 previous_active = render_state->body_list_active; - GuiListView(list_bounds, list_text, &render_state->body_list_scroll, &render_state->body_list_active); - - // Check if an item was selected - if (render_state->body_list_active >= 0 && render_state->body_list_active != previous_active) { - if (render_state->body_list_active < sim->body_count) { - // A body was selected - render_state->selected_body_index = render_state->body_list_active; - render_state->selected_craft_index = -1; - render_state->camera_follow_body = true; - printf("Camera follow enabled for body: %s\n", sim->bodies[render_state->selected_body_index].name); - } else { - // A spacecraft was selected - int craft_index = render_state->body_list_active - sim->body_count; - if (craft_index >= 0 && craft_index < sim->craft_count) { - render_state->selected_body_index = -1; - render_state->selected_craft_index = craft_index; - render_state->camera_follow_body = true; - printf("Camera follow enabled for spacecraft: %s\n", sim->spacecraft[craft_index].name); - } - } - } - - free(list_text); -} - -// Render body information UI panel -void render_body_info_ui(SimulationState* sim, RenderState* render_state) { - // Panel dimensions - int panel_width = 250; - int panel_height = 300; - int panel_x = GetScreenWidth() - panel_width - 10; - int panel_y = 10; - - if ((render_state->selected_body_index < 0 || render_state->selected_body_index >= sim->body_count) && - (render_state->selected_craft_index < 0 || render_state->selected_craft_index >= sim->craft_count)) { - // No body or spacecraft selected - render empty panel - Rectangle panel_bounds = { (float)panel_x, (float)panel_y, (float)panel_width, (float)panel_height }; - GuiWindowBox(panel_bounds, "Info"); - return; - } - - Rectangle panel_bounds = { (float)panel_x, (float)panel_y, (float)panel_width, (float)panel_height }; - - // Check if a body is selected - if (render_state->selected_body_index >= 0 && render_state->selected_body_index < sim->body_count) { - CelestialBody* body = &sim->bodies[render_state->selected_body_index]; - - // Draw window (no close button - panels always shown) - GuiWindowBox(panel_bounds, body->name); - - // 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); - - if (body->parent_index <= 0) { - // 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); - } else { - // Position (Local) - snprintf(temp_buffer, sizeof(temp_buffer), "local_position: (%.2e, %.2e, %.2e)", - body->local_position.x, body->local_position.y, body->local_position.z); - strcat(info_text, "\n"); - strcat(info_text, temp_buffer); - - // Velocity (Local) - double parent_vel_mag = vec3_magnitude(sim->bodies[body->parent_index].velocity); - double vel_mag = vec3_magnitude(body->velocity) - parent_vel_mag; - snprintf(temp_buffer, sizeof(temp_buffer), "local_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); - return; - } - - // Check if a spacecraft is selected - if (render_state->selected_craft_index >= 0 && render_state->selected_craft_index < sim->craft_count) { - Spacecraft* craft = &sim->spacecraft[render_state->selected_craft_index]; - - // Draw window (no close button - panels always shown) - GuiWindowBox(panel_bounds, craft->name); - - // Prepare info text - char info_text[1024]; - char temp_buffer[256]; - - snprintf(info_text, sizeof(info_text), "Name: %s", craft->name); - - snprintf(temp_buffer, sizeof(temp_buffer), "Mass: %.2e kg", craft->mass); - strcat(info_text, "\n"); - strcat(info_text, temp_buffer); - - // Position (Local) - snprintf(temp_buffer, sizeof(temp_buffer), "local_position: (%.2e, %.2e, %.2e)", - craft->local_position.x, craft->local_position.y, craft->local_position.z); - strcat(info_text, "\n"); - strcat(info_text, temp_buffer); - - // Velocity (Local) - double vel_mag = vec3_magnitude(craft->local_velocity); - snprintf(temp_buffer, sizeof(temp_buffer), "local_velocity: %.2f m/s", vel_mag); - strcat(info_text, "\n"); - strcat(info_text, temp_buffer); - - // Parent body - if (craft->parent_index >= 0 && craft->parent_index < sim->body_count) { - snprintf(temp_buffer, sizeof(temp_buffer), "Parent: %s", sim->bodies[craft->parent_index].name); - } else { - snprintf(temp_buffer, sizeof(temp_buffer), "Parent: None"); - } - strcat(info_text, "\n"); - strcat(info_text, temp_buffer); - - // Maneuver info - int pending_maneuvers = 0; - int executed_maneuvers = 0; - for (int i = 0; i < sim->maneuver_count; i++) { - if (sim->maneuvers[i].craft_index == render_state->selected_craft_index) { - if (sim->maneuvers[i].executed) { - executed_maneuvers++; - } else { - pending_maneuvers++; - } - } - } - - snprintf(temp_buffer, sizeof(temp_buffer), "Pending maneuvers: %d", pending_maneuvers); - strcat(info_text, "\n"); - strcat(info_text, temp_buffer); - - snprintf(temp_buffer, sizeof(temp_buffer), "Executed maneuvers: %d", executed_maneuvers); - 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); - return; - } -} - -// Render maneuver list UI panel -void render_maneuver_list_ui(SimulationState* sim, RenderState* render_state) { - if (render_state->selected_craft_index < 0 || render_state->selected_craft_index >= sim->craft_count) { - return; // No spacecraft selected - } - - int panel_width = 300; - int panel_height = 400; - int panel_x = GetScreenWidth() - panel_width - 10; - int panel_y = 320; // Below info panel - - Rectangle panel_bounds = { (float)panel_x, (float)panel_y, (float)panel_width, (float)panel_height }; - - Spacecraft* craft = &sim->spacecraft[render_state->selected_craft_index]; - - // Create title with spacecraft name - char title[128]; - snprintf(title, sizeof(title), "Maneuvers: %s", craft->name); - - // Draw window - GuiWindowBox(panel_bounds, title); - - // Count maneuvers for this spacecraft - int craft_maneuver_count = 0; - for (int i = 0; i < sim->maneuver_count; i++) { - if (sim->maneuvers[i].craft_index == render_state->selected_craft_index) { - craft_maneuver_count++; - } - } - - if (craft_maneuver_count == 0) { - // No maneuvers for this spacecraft - Rectangle text_bounds = { - (float)panel_x + 8, - (float)panel_y + 25, - (float)panel_width - 16, - (float)panel_height - 29 - }; - GuiLabel(text_bounds, "No maneuvers defined"); - return; - } - - // Create maneuver list string for raygui - int buffer_size = craft_maneuver_count * (64 + 10 + 1) + 1; - char* maneuver_list_text = (char*)malloc(buffer_size); - if (!maneuver_list_text) return; - - maneuver_list_text[0] = '\0'; - int offset = 0; - - for (int i = 0; i < sim->maneuver_count; i++) { - if (sim->maneuvers[i].craft_index == render_state->selected_craft_index) { - if (offset > 0) { - offset += snprintf(maneuver_list_text + offset, buffer_size - offset, ";"); - } - - const char* status_char = sim->maneuvers[i].executed ? "[DONE]" : "[PENDING]"; - offset += snprintf(maneuver_list_text + offset, buffer_size - offset, "%s %s", - status_char, sim->maneuvers[i].name); - } - } - - // 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 - 100 // Leave space for details below - }; - - int maneuver_scroll = 0; - int maneuver_active = -1; - GuiListView(list_bounds, maneuver_list_text, &maneuver_scroll, &maneuver_active); - - // Show details for first pending or last executed maneuver - char details_text[512]; - details_text[0] = '\0'; - - for (int i = 0; i < sim->maneuver_count; i++) { - if (sim->maneuvers[i].craft_index == render_state->selected_craft_index) { - if (sim->maneuvers[i].executed) { - snprintf(details_text, sizeof(details_text), - "Last executed:\n%s\nĪ”v: %.1f m/s\nTime: %.1f s", - sim->maneuvers[i].name, sim->maneuvers[i].delta_v, sim->maneuvers[i].executed_time); - } else { - snprintf(details_text, sizeof(details_text), - "Next pending:\n%s\nĪ”v: %.1f m/s", - sim->maneuvers[i].name, sim->maneuvers[i].delta_v); - break; // Show first pending - } - } - } - - if (details_text[0] != '\0') { - Rectangle details_bounds = { - (float)panel_x + 8, - (float)panel_y + (float)panel_height - 70, - (float)panel_width - 16, - 65 - }; - GuiLabel(details_bounds, details_text); - } - - free(maneuver_list_text); -} diff --git a/src/renderer.h b/src/renderer.h index 819325c..9e3bc0c 100644 --- a/src/renderer.h +++ b/src/renderer.h @@ -14,8 +14,6 @@ struct RenderState { int selected_body_index; // -1 = no selection int selected_craft_index; // -1 = no selection int previous_selected_body; // Previous selected body index - int body_list_scroll; // Scroll position for body list - int body_list_active; // Active item index in body list bool camera_follow_body; // Whether camera follows selected body Vector3 camera_offset; // Offset from target when following body bool was_following_body; // Previous frame follow state @@ -48,12 +46,6 @@ void render_body(CelestialBody* body, RenderState* render_state); void render_spacecraft(Spacecraft* craft, RenderState* render_state); void render_maneuver_marker(Spacecraft* craft, Maneuver* maneuver, RenderState* render_state); void render_simulation(SimulationState* sim, RenderState* render_state); -void render_info(SimulationState* sim); - -// UI rendering functions -void render_body_list_ui(SimulationState* sim, RenderState* render_state); -void render_body_info_ui(SimulationState* sim, RenderState* render_state); -void render_maneuver_list_ui(SimulationState* sim, RenderState* render_state); // Scaling functions Vector3 scale_position(Vec3 pos, double scale); diff --git a/src/ui_renderer.cpp b/src/ui_renderer.cpp new file mode 100644 index 0000000..bac8470 --- /dev/null +++ b/src/ui_renderer.cpp @@ -0,0 +1,421 @@ +#include "ui_renderer.h" +#include "spacecraft.h" +#include "maneuver.h" +#include +#include +#include + +// raygui implementation (header-only library) +#define RAYGUI_IMPLEMENTATION +#include "raygui.h" + +// Render simulation information overlay +void render_info(SimulationState* sim) { + // Panel dimensions + int panel_width = 300; + int panel_height = 300; + int panel_x = 10; + int panel_y = GetScreenHeight() - panel_height - 10; + + Rectangle panel_bounds = { (float)panel_x, (float)panel_y, (float)panel_width, (float)panel_height }; + + // Draw window + if (GuiWindowBox(panel_bounds, "Info")) { + // Window box close button doesn't close this panel + } + + // Prepare info text + char info_text[1024]; + char temp_buffer[512]; + + snprintf(info_text, sizeof(info_text), "Orbital Mechanics Simulation\n\n"); + + // Config name + if (sim->config_name[0] != '\0') { + snprintf(temp_buffer, sizeof(temp_buffer), "Config: %s\n\n", sim->config_name); + strcat(info_text, temp_buffer); + } + + // Simulation time (in days) + double days = sim->time / 86400.0; // seconds to days + snprintf(temp_buffer, sizeof(temp_buffer), "Time: %.2f days\n", days); + strcat(info_text, temp_buffer); + + // Body count + snprintf(temp_buffer, sizeof(temp_buffer), "Bodies: %d\n", sim->body_count); + strcat(info_text, temp_buffer); + + // FPS + snprintf(temp_buffer, sizeof(temp_buffer), "FPS: %d\n\n", GetFPS()); + strcat(info_text, temp_buffer); + + // Controls + strcat(info_text, "Controls:\n"); + strcat(info_text, " Arrows: Rotate/Zoom\n"); + strcat(info_text, " Space: Pause/Resume\n"); + strcat(info_text, " +/-: Speed control\n"); + strcat(info_text, " ESC: Quit\n"); + + // 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); +} + +// Render body list UI panel +void render_body_list_ui(SimulationState* sim, RenderState* render_state, UIState* ui_state) { + // 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 list string for raygui (NULL-terminated) + // Include both bodies and spacecraft + int total_items = sim->body_count + sim->craft_count; + int buffer_size = total_items * (64 + 3 + 1) + 1; // Extra space for "šŸš€ " prefix + char* list_text = (char*)malloc(buffer_size); + if (!list_text) return; + + list_text[0] = '\0'; + int offset = 0; + int item_index = 0; + + // Add bodies to list + for (int i = 0; i < sim->body_count; i++) { + if (item_index > 0) { + offset += snprintf(list_text + offset, buffer_size - offset, ";"); + } + offset += snprintf(list_text + offset, buffer_size - offset, "%s", sim->bodies[i].name); + item_index++; + } + + // Add spacecraft to list with "šŸš€ " prefix + for (int i = 0; i < sim->craft_count; i++) { + offset += snprintf(list_text + offset, buffer_size - offset, ";"); + offset += snprintf(list_text + offset, buffer_size - offset, "šŸš€ %s", sim->spacecraft[i].name); + item_index++; + } + + // Draw window (no close button - panels always shown) + GuiWindowBox(panel_bounds, "Objects"); + + // 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 previous_active = ui_state->body_list_active; + GuiListView(list_bounds, list_text, &ui_state->body_list_scroll, &ui_state->body_list_active); + + // Check if an item was selected + if (ui_state->body_list_active >= 0 && ui_state->body_list_active != previous_active) { + if (ui_state->body_list_active < sim->body_count) { + // A body was selected + render_state->selected_body_index = ui_state->body_list_active; + render_state->selected_craft_index = -1; + render_state->camera_follow_body = true; + printf("Camera follow enabled for body: %s\n", sim->bodies[render_state->selected_body_index].name); + } else { + // A spacecraft was selected + int craft_index = ui_state->body_list_active - sim->body_count; + if (craft_index >= 0 && craft_index < sim->craft_count) { + render_state->selected_body_index = -1; + render_state->selected_craft_index = craft_index; + render_state->camera_follow_body = true; + printf("Camera follow enabled for spacecraft: %s\n", sim->spacecraft[craft_index].name); + } + } + } + + free(list_text); +} + +// Render body information UI panel +void render_body_info_ui(SimulationState* sim, RenderState* render_state) { + // Panel dimensions + int panel_width = 250; + int panel_height = 300; + int panel_x = GetScreenWidth() - panel_width - 10; + int panel_y = 10; + + if ((render_state->selected_body_index < 0 || render_state->selected_body_index >= sim->body_count) && + (render_state->selected_craft_index < 0 || render_state->selected_craft_index >= sim->craft_count)) { + // No body or spacecraft selected - render empty panel + Rectangle panel_bounds = { (float)panel_x, (float)panel_y, (float)panel_width, (float)panel_height }; + GuiWindowBox(panel_bounds, "Info"); + return; + } + + Rectangle panel_bounds = { (float)panel_x, (float)panel_y, (float)panel_width, (float)panel_height }; + + // Check if a body is selected + if (render_state->selected_body_index >= 0 && render_state->selected_body_index < sim->body_count) { + CelestialBody* body = &sim->bodies[render_state->selected_body_index]; + + // Draw window (no close button - panels always shown) + GuiWindowBox(panel_bounds, body->name); + + // 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); + + if (body->parent_index <= 0) { + // 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); + } else { + // Position (Local) + snprintf(temp_buffer, sizeof(temp_buffer), "local_position: (%.2e, %.2e, %.2e)", + body->local_position.x, body->local_position.y, body->local_position.z); + strcat(info_text, "\n"); + strcat(info_text, temp_buffer); + + // Velocity (Local) + double parent_vel_mag = vec3_magnitude(sim->bodies[body->parent_index].velocity); + double vel_mag = vec3_magnitude(body->velocity) - parent_vel_mag; + snprintf(temp_buffer, sizeof(temp_buffer), "local_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); + return; + } + + // Check if a spacecraft is selected + if (render_state->selected_craft_index >= 0 && render_state->selected_craft_index < sim->craft_count) { + Spacecraft* craft = &sim->spacecraft[render_state->selected_craft_index]; + + // Draw window (no close button - panels always shown) + GuiWindowBox(panel_bounds, craft->name); + + // Prepare info text + char info_text[1024]; + char temp_buffer[256]; + + snprintf(info_text, sizeof(info_text), "Name: %s", craft->name); + + snprintf(temp_buffer, sizeof(temp_buffer), "Mass: %.2e kg", craft->mass); + strcat(info_text, "\n"); + strcat(info_text, temp_buffer); + + // Position (Local) + snprintf(temp_buffer, sizeof(temp_buffer), "local_position: (%.2e, %.2e, %.2e)", + craft->local_position.x, craft->local_position.y, craft->local_position.z); + strcat(info_text, "\n"); + strcat(info_text, temp_buffer); + + // Velocity (Local) + double vel_mag = vec3_magnitude(craft->local_velocity); + snprintf(temp_buffer, sizeof(temp_buffer), "local_velocity: %.2f m/s", vel_mag); + strcat(info_text, "\n"); + strcat(info_text, temp_buffer); + + // Parent body + if (craft->parent_index >= 0 && craft->parent_index < sim->body_count) { + snprintf(temp_buffer, sizeof(temp_buffer), "Parent: %s", sim->bodies[craft->parent_index].name); + } else { + snprintf(temp_buffer, sizeof(temp_buffer), "Parent: None"); + } + strcat(info_text, "\n"); + strcat(info_text, temp_buffer); + + // Maneuver info + int pending_maneuvers = 0; + int executed_maneuvers = 0; + for (int i = 0; i < sim->maneuver_count; i++) { + if (sim->maneuvers[i].craft_index == render_state->selected_craft_index) { + if (sim->maneuvers[i].executed) { + executed_maneuvers++; + } else { + pending_maneuvers++; + } + } + } + + snprintf(temp_buffer, sizeof(temp_buffer), "Pending maneuvers: %d", pending_maneuvers); + strcat(info_text, "\n"); + strcat(info_text, temp_buffer); + + snprintf(temp_buffer, sizeof(temp_buffer), "Executed maneuvers: %d", executed_maneuvers); + 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); + return; + } +} + +// Render maneuver list UI panel +void render_maneuver_list_ui(SimulationState* sim, RenderState* render_state) { + if (render_state->selected_craft_index < 0 || render_state->selected_craft_index >= sim->craft_count) { + return; // No spacecraft selected + } + + int panel_width = 300; + int panel_height = 400; + int panel_x = GetScreenWidth() - panel_width - 10; + int panel_y = 320; // Below info panel + + Rectangle panel_bounds = { (float)panel_x, (float)panel_y, (float)panel_width, (float)panel_height }; + + Spacecraft* craft = &sim->spacecraft[render_state->selected_craft_index]; + + // Create title with spacecraft name + char title[128]; + snprintf(title, sizeof(title), "Maneuvers: %s", craft->name); + + // Draw window + GuiWindowBox(panel_bounds, title); + + // Count maneuvers for this spacecraft + int craft_maneuver_count = 0; + for (int i = 0; i < sim->maneuver_count; i++) { + if (sim->maneuvers[i].craft_index == render_state->selected_craft_index) { + craft_maneuver_count++; + } + } + + if (craft_maneuver_count == 0) { + // No maneuvers for this spacecraft + Rectangle text_bounds = { + (float)panel_x + 8, + (float)panel_y + 25, + (float)panel_width - 16, + (float)panel_height - 29 + }; + GuiLabel(text_bounds, "No maneuvers defined"); + return; + } + + // Create maneuver list string for raygui + int buffer_size = craft_maneuver_count * (64 + 10 + 1) + 1; + char* maneuver_list_text = (char*)malloc(buffer_size); + if (!maneuver_list_text) return; + + maneuver_list_text[0] = '\0'; + int offset = 0; + + for (int i = 0; i < sim->maneuver_count; i++) { + if (sim->maneuvers[i].craft_index == render_state->selected_craft_index) { + if (offset > 0) { + offset += snprintf(maneuver_list_text + offset, buffer_size - offset, ";"); + } + + const char* status_char = sim->maneuvers[i].executed ? "[DONE]" : "[PENDING]"; + offset += snprintf(maneuver_list_text + offset, buffer_size - offset, "%s %s", + status_char, sim->maneuvers[i].name); + } + } + + // 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 - 100 // Leave space for details below + }; + + int maneuver_scroll = 0; + int maneuver_active = -1; + GuiListView(list_bounds, maneuver_list_text, &maneuver_scroll, &maneuver_active); + + // Show details for first pending or last executed maneuver + char details_text[512]; + details_text[0] = '\0'; + + for (int i = 0; i < sim->maneuver_count; i++) { + if (sim->maneuvers[i].craft_index == render_state->selected_craft_index) { + if (sim->maneuvers[i].executed) { + snprintf(details_text, sizeof(details_text), + "Last executed:\n%s\nĪ”v: %.1f m/s\nTime: %.1f s", + sim->maneuvers[i].name, sim->maneuvers[i].delta_v, sim->maneuvers[i].executed_time); + } else { + snprintf(details_text, sizeof(details_text), + "Next pending:\n%s\nĪ”v: %.1f m/s", + sim->maneuvers[i].name, sim->maneuvers[i].delta_v); + break; // Show first pending + } + } + } + + if (details_text[0] != '\0') { + Rectangle details_bounds = { + (float)panel_x + 8, + (float)panel_y + (float)panel_height - 70, + (float)panel_width - 16, + 65 + }; + GuiLabel(details_bounds, details_text); + } + + free(maneuver_list_text); +} diff --git a/src/ui_renderer.h b/src/ui_renderer.h new file mode 100644 index 0000000..e91e8d0 --- /dev/null +++ b/src/ui_renderer.h @@ -0,0 +1,20 @@ +#ifndef UI_RENDERER_H +#define UI_RENDERER_H + +#include "simulation.h" +#include "renderer.h" +#include "raylib.h" + +// UI state +struct UIState { + int body_list_scroll; // Scroll position for body list + int body_list_active; // Active item index in body list +}; + +// UI rendering functions +void render_info(SimulationState* sim); +void render_body_list_ui(SimulationState* sim, RenderState* render_state, UIState* ui_state); +void render_body_info_ui(SimulationState* sim, RenderState* render_state); +void render_maneuver_list_ui(SimulationState* sim, RenderState* render_state); + +#endif