From 0af8ece6499ee40d65b09799aca453e6d00092cb Mon Sep 17 00:00:00 2001 From: cinnaboot Date: Thu, 5 Feb 2026 12:38:09 -0500 Subject: [PATCH] feat: Enhance maneuver list (Phase 5) Added full interactivity to maneuver list: - Selectable maneuvers with click-to-select - Preview panel showing selected maneuver details - Orbital preview for selected maneuver - Action buttons: Create, Hohmann, Edit, Delete - Edit button loads selected maneuver into dialog - Delete button removes maneuver directly - Integration with all dialog tabs Added selected_maneuver_index tracking to UIState. --- docs/planning/maneuver_dialog_progress.md | 43 +++--- src/main.cpp | 1 + src/ui_renderer.cpp | 161 ++++++++++++++-------- src/ui_renderer.h | 1 + 4 files changed, 134 insertions(+), 72 deletions(-) diff --git a/docs/planning/maneuver_dialog_progress.md b/docs/planning/maneuver_dialog_progress.md index b995091..39d86bb 100644 --- a/docs/planning/maneuver_dialog_progress.md +++ b/docs/planning/maneuver_dialog_progress.md @@ -52,6 +52,20 @@ Implementing a comprehensive modal dialog for creating, editing, and managing sp - Auto-generated maneuver names for both burns - Real-time calculation and display of transfer parameters +### Phase 4: Edit Tab (Complete) +- [x] Implement render_maneuver_edit_tab() skeleton +- [x] Load existing maneuver data into fields +- [x] Lock spacecraft field +- [x] Implement update_maneuver_from_dialog() +- [x] Add delete confirmation dialog +- [x] Test edit and delete workflows + +**Commit:** `b0d9d99` - feat: Implement Edit Maneuver tab (Phase 4) +- Complete Edit tab workflow with locked spacecraft field +- load_maneuver_into_dialog() loads existing maneuver data +- Delete confirmation dialog overlay on edit tab +- Full parameter editing for existing maneuvers + ### Bug Fixes (Complete) - [x] Fix segfault at ui_renderer.cpp:503 - [x] Fix widget type for input boxes (GuiValueBoxFloat) @@ -75,14 +89,6 @@ None currently ## Pending Tasks -### Phase 4: Edit Tab -- [ ] Implement render_maneuver_edit_tab() skeleton -- [ ] Load existing maneuver data into fields -- [ ] Lock spacecraft field -- [ ] Implement update_maneuver_from_dialog() -- [ ] Add delete confirmation dialog -- [ ] Test edit and delete workflows - ### Phase 5: Enhanced Maneuver List - [ ] Modify render_maneuver_list_ui() for selectable items - [ ] Add maneuver preview panel to list @@ -113,19 +119,17 @@ None currently 7. `883695c` - docs: Add maneuver management dialog design 8. `9da7e80` - docs: Add maneuver UI controls implementation plan -## Next Steps +### Phase 4 +9. `b0d9d99` - feat: Implement Edit Maneuver tab (Phase 4) -1. **Phase 4: Edit Tab** - Enable editing and deleting existing maneuvers - - Load maneuver data into form fields - - Implement update/delete logic - - Add confirmation dialogs +## Next Steps -2. **Phase 5: Enhanced Maneuver List** - Improve list UI +1. **Phase 5: Enhanced Maneuver List** - Improve list UI - Make maneuvers selectable - Add preview panel below list - Add edit/delete buttons for selected maneuver -3. **Phase 6: Polish** - User experience improvements +2. **Phase 6: Polish** - User experience improvements - Keyboard shortcuts - Better error messages - Tooltips and help text @@ -134,10 +138,10 @@ None currently ## Progress Summary - **Total Tasks:** 35 -- **Completed:** 19 (Phase 1: 6, Phase 2: 7, Phase 3: 6) +- **Completed:** 25 (Phase 1: 6, Phase 2: 7, Phase 3: 6, Phase 4: 6) - **In Progress:** 0 -- **Pending:** 16 (Phase 4: 6, Phase 5: 5, Phase 6: 5) -- **Completion:** 54.3% +- **Pending:** 10 (Phase 5: 5, Phase 6: 5) +- **Completion:** 71.4% ## Implementation Notes @@ -149,3 +153,6 @@ None currently - Hohmann transfer tab implemented with full UI for spacecraft/parent/body selection - create_hohmann_burns() creates both Hohmann maneuvers with auto-generated names - Proper initialization of current_body_index from spacecraft's parent +- Edit tab with locked spacecraft field and full parameter editing +- load_maneuver_into_dialog() loads existing maneuver data +- Delete confirmation dialog overlay on edit tab \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index c222c2d..1caa671 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -43,6 +43,7 @@ void run_gui_simulation(SimulationState* sim) { ui_state.selected_craft_index = -1; ui_state.maneuver_list_active = -1; ui_state.maneuver_list_scroll = 0; + ui_state.selected_maneuver_index = -1; ui_state.maneuver_dialog.open = false; ui_state.maneuver_dialog.active_tab = MD_TAB_CREATE; diff --git a/src/ui_renderer.cpp b/src/ui_renderer.cpp index 8d53ad7..357b133 100644 --- a/src/ui_renderer.cpp +++ b/src/ui_renderer.cpp @@ -326,29 +326,25 @@ void render_body_info_ui(SimulationState* sim, RenderState* render_state, UIStat } } -// Render maneuver list UI panel void render_maneuver_list_ui(SimulationState* sim, RenderState* render_state, UIState* ui_state) { if (ui_state->selected_craft_index < 0 || ui_state->selected_craft_index >= sim->craft_count) { - return; // No spacecraft selected + return; } int panel_width = MANEUVER_LIST_WIDTH; int panel_height = MANEUVER_LIST_HEIGHT; int panel_x = GetScreenWidth() - panel_width - 10; - int panel_y = MANEUVER_LIST_Y_OFFSET; // Below info panel + int panel_y = MANEUVER_LIST_Y_OFFSET; - Rectangle panel_bounds = { (float)panel_x, (float)panel_y, (float)panel_width, (float)panel_height }; + Rectangle panel_bounds = {(float)panel_x, (float)panel_y, (float)panel_width, (float)panel_height}; Spacecraft* craft = &sim->spacecraft[ui_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 == ui_state->selected_craft_index) { @@ -357,7 +353,6 @@ void render_maneuver_list_ui(SimulationState* sim, RenderState* render_state, UI } if (craft_maneuver_count == 0) { - // No maneuvers for this spacecraft Rectangle text_bounds = { (float)panel_x + 8, (float)panel_y + 25, @@ -366,14 +361,8 @@ void render_maneuver_list_ui(SimulationState* sim, RenderState* render_state, UI }; GuiLabel(text_bounds, "No maneuvers defined"); - // Add button to open maneuver dialog - Rectangle dialog_btn = { - (float)panel_x + 8, - (float)panel_y + (float)panel_height - 28, - (float)panel_width - 16, - 20 - }; - if (GuiButton(dialog_btn, "[+] Manage Maneuvers")) { + Rectangle create_btn = {(float)panel_x + 8, (float)panel_y + (float)panel_height - 28, (float)panel_width - 16, 20}; + if (GuiButton(create_btn, "[+] Create Maneuver")) { ui_state->maneuver_dialog.open = true; ui_state->maneuver_dialog.active_tab = MD_TAB_CREATE; ui_state->maneuver_dialog.craft_index = ui_state->selected_craft_index; @@ -387,79 +376,104 @@ void render_maneuver_list_ui(SimulationState* sim, RenderState* render_state, UI ui_state->maneuver_dialog.show_error = false; ui_state->maneuver_dialog.error_message[0] = '\0'; ui_state->maneuver_dialog.show_delete_confirm = false; + ui_state->selected_maneuver_index = -1; } 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; + int maneuver_indices[craft_maneuver_count]; + int maneuver_index_count = 0; for (int i = 0; i < sim->maneuver_count; i++) { if (sim->maneuvers[i].craft_index == ui_state->selected_craft_index) { + maneuver_indices[maneuver_index_count++] = i; 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); + 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 + (float)panel_height - 280 }; - int maneuver_scroll = 0; - int maneuver_active = -1; - GuiListView(list_bounds, maneuver_list_text, &maneuver_scroll, &maneuver_active); + int previous_active = ui_state->maneuver_list_active; + GuiListView(list_bounds, maneuver_list_text, &ui_state->maneuver_list_scroll, &ui_state->maneuver_list_active); - // Show details for first pending or last executed maneuver - char details_text[512]; - details_text[0] = '\0'; + if (ui_state->maneuver_list_active >= 0 && ui_state->maneuver_list_active != previous_active) { + if (ui_state->maneuver_list_active >= 0 && ui_state->maneuver_list_active < maneuver_index_count) { + ui_state->selected_maneuver_index = maneuver_indices[ui_state->maneuver_list_active]; + } + } - for (int i = 0; i < sim->maneuver_count; i++) { - if (sim->maneuvers[i].craft_index == ui_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); + int preview_y = panel_y + panel_height - 250; + + if (ui_state->selected_maneuver_index >= 0 && ui_state->selected_maneuver_index < sim->maneuver_count) { + Maneuver* selected = &sim->maneuvers[ui_state->selected_maneuver_index]; + + if (selected->craft_index == ui_state->selected_craft_index) { + char selected_text[512]; + char status_str[16]; + snprintf(status_str, sizeof(status_str), selected->executed ? "Executed" : "Pending"); + + snprintf(selected_text, sizeof(selected_text), + "Selected: %s\n\nStatus: %s\nDirection: %s\nΔv: %.1f m/s\n", + selected->name, status_str, get_burn_direction_name(selected->direction), selected->delta_v); + + if (selected->trigger_type == TRIGGER_TIME) { + snprintf(selected_text + strlen(selected_text), sizeof(selected_text) - strlen(selected_text), + "Trigger: Time @ %.1f s", selected->trigger_value); } 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 + snprintf(selected_text + strlen(selected_text), sizeof(selected_text) - strlen(selected_text), + "Trigger: True Anomaly @ %.2f rad", selected->trigger_value); + } + + Rectangle preview_text_bounds = { + (float)panel_x + 8, + (float)preview_y, + (float)panel_width - 16, + 80 + }; + GuiLabel(preview_text_bounds, selected_text); + + int orbital_preview_y = preview_y + 85; + + ui_state->maneuver_dialog.craft_index = ui_state->selected_craft_index; + ui_state->maneuver_dialog.direction_active = (int)selected->direction; + ui_state->maneuver_dialog.delta_v = selected->delta_v; + + update_orbital_preview(sim, ui_state); + + if (ui_state->maneuver_dialog.show_preview) { + Rectangle orbital_bounds = { + (float)panel_x + 8, + (float)orbital_preview_y, + (float)panel_width - 16, + 110 + }; + render_orbital_preview(sim, ui_state, orbital_bounds); } } } - 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); - } + int button_row_y = panel_y + panel_height - 50; - // Add button to open maneuver dialog - Rectangle dialog_btn = { - (float)panel_x + 8, - (float)panel_y + (float)panel_height - 28, - (float)panel_width - 16, - 20 - }; - if (GuiButton(dialog_btn, "[+] Manage Maneuvers")) { + int btn_width = (panel_width - 20) / 4; + + Rectangle create_btn = {(float)panel_x + 10, (float)button_row_y, (float)btn_width, 30}; + if (GuiButton(create_btn, "Create")) { ui_state->maneuver_dialog.open = true; ui_state->maneuver_dialog.active_tab = MD_TAB_CREATE; ui_state->maneuver_dialog.craft_index = ui_state->selected_craft_index; @@ -473,6 +487,45 @@ void render_maneuver_list_ui(SimulationState* sim, RenderState* render_state, UI ui_state->maneuver_dialog.show_error = false; ui_state->maneuver_dialog.error_message[0] = '\0'; ui_state->maneuver_dialog.show_delete_confirm = false; + ui_state->selected_maneuver_index = -1; + } + + Rectangle hohmann_btn = {(float)panel_x + 10 + btn_width, (float)button_row_y, (float)btn_width, 30}; + if (GuiButton(hohmann_btn, "Hohmann...")) { + ui_state->maneuver_dialog.open = true; + ui_state->maneuver_dialog.active_tab = MD_TAB_HOHMANN; + ui_state->maneuver_dialog.craft_index = ui_state->selected_craft_index; + ui_state->maneuver_dialog.edit_maneuver_index = -1; + ui_state->maneuver_dialog.name[0] = '\0'; + ui_state->maneuver_dialog.show_preview = false; + ui_state->maneuver_dialog.show_error = false; + ui_state->maneuver_dialog.error_message[0] = '\0'; + ui_state->maneuver_dialog.show_delete_confirm = false; + } + + Rectangle edit_btn = {(float)panel_x + 10 + btn_width * 2, (float)button_row_y, (float)btn_width, 30}; + if (GuiButton(edit_btn, "Edit")) { + if (ui_state->selected_maneuver_index >= 0 && ui_state->selected_maneuver_index < sim->maneuver_count) { + Maneuver* selected = &sim->maneuvers[ui_state->selected_maneuver_index]; + if (selected->craft_index == ui_state->selected_craft_index) { + load_maneuver_into_dialog(sim, ui_state, ui_state->selected_maneuver_index); + ui_state->maneuver_dialog.open = true; + ui_state->maneuver_dialog.active_tab = MD_TAB_EDIT; + ui_state->maneuver_dialog.show_delete_confirm = false; + } + } + } + + Rectangle delete_btn = {(float)panel_x + 10 + btn_width * 3, (float)button_row_y, (float)btn_width, 30}; + if (GuiButton(delete_btn, "Delete")) { + if (ui_state->selected_maneuver_index >= 0 && ui_state->selected_maneuver_index < sim->maneuver_count) { + Maneuver* selected = &sim->maneuvers[ui_state->selected_maneuver_index]; + if (selected->craft_index == ui_state->selected_craft_index) { + remove_maneuver_by_index(sim, ui_state->selected_maneuver_index); + ui_state->selected_maneuver_index = -1; + ui_state->maneuver_list_active = -1; + } + } } free(maneuver_list_text); diff --git a/src/ui_renderer.h b/src/ui_renderer.h index 56902d9..ea55a25 100644 --- a/src/ui_renderer.h +++ b/src/ui_renderer.h @@ -49,6 +49,7 @@ struct UIState { ManeuverDialogState maneuver_dialog; int maneuver_list_active; int maneuver_list_scroll; + int selected_maneuver_index; }; void gui_init();