diff --git a/docs/planning/maneuver_dialog_progress.md b/docs/planning/maneuver_dialog_progress.md index c72c221..b995091 100644 --- a/docs/planning/maneuver_dialog_progress.md +++ b/docs/planning/maneuver_dialog_progress.md @@ -39,6 +39,19 @@ Implementing a comprehensive modal dialog for creating, editing, and managing sp - validate_burn_parameters(): Validate burn parameters - calculate_hohmann_transfer(): Calculate optimal two-burn transfer +### Phase 3: Hohmann Tab (Complete) +- [x] Implement render_maneuver_hohmann_tab() skeleton +- [x] Add body selector widgets +- [x] Implement calculate_hohmann_for_dialog() using calculate_hohmann_transfer() +- [x] Display Hohmann results +- [x] Implement create_hohmann_burns() to add both maneuvers +- [x] Test Hohmann workflow + +**Commit:** `94a7dcb` - feat: Implement Hohmann Transfer tab (Phase 3) +- Complete Hohmann transfer workflow with body selection UI +- Auto-generated maneuver names for both burns +- Real-time calculation and display of transfer parameters + ### Bug Fixes (Complete) - [x] Fix segfault at ui_renderer.cpp:503 - [x] Fix widget type for input boxes (GuiValueBoxFloat) @@ -62,14 +75,6 @@ None currently ## Pending Tasks -### Phase 3: Hohmann Tab -- [ ] Implement render_maneuver_hohmann_tab() skeleton -- [ ] Add body selector widgets -- [ ] Implement calculate_hohmann_for_dialog() using calculate_hohmann_transfer() -- [ ] Display Hohmann results -- [ ] Implement create_hohmann_burns() to add both maneuvers -- [ ] Test Hohmann workflow - ### Phase 4: Edit Tab - [ ] Implement render_maneuver_edit_tab() skeleton - [ ] Load existing maneuver data into fields @@ -95,36 +100,32 @@ None currently ## Commits ### Core Implementation -1. `9c55c7c` - feat: Implement maneuver dialog Create tab (Phase 2) -2. `5ecd880` - feat: Add maneuver UI control functions +1. `94a7dcb` - feat: Implement Hohmann Transfer tab (Phase 3) +2. `9c55c7c` - feat: Implement maneuver dialog Create tab (Phase 2) +3. `5ecd880` - feat: Add maneuver UI control functions ### Bug Fixes -3. `351f647` - fix: Initialize UIState maneuver_dialog fields properly -4. `2874c5e` - feat: Add auto-generated maneuver names with edit capability -5. `f45b111` - fix: Add conditional rendering for GUI dropdowns +4. `351f647` - fix: Initialize UIState maneuver_dialog fields properly +5. `2874c5e` - feat: Add auto-generated maneuver names with edit capability +6. `f45b111` - fix: Add conditional rendering for GUI dropdowns ### Documentation -6. `883695c` - docs: Add maneuver management dialog design -7. `9da7e80` - docs: Add maneuver UI controls implementation plan +7. `883695c` - docs: Add maneuver management dialog design +8. `9da7e80` - docs: Add maneuver UI controls implementation plan ## Next Steps -1. **Phase 3: Hohmann Tab** - Implement Hohmann transfer calculation and UI - - Use existing calculate_hohmann_transfer() function - - Add body selector dropdowns - - Display results and create both burns - -2. **Phase 4: Edit Tab** - Enable editing and deleting existing maneuvers +1. **Phase 4: Edit Tab** - Enable editing and deleting existing maneuvers - Load maneuver data into form fields - Implement update/delete logic - Add confirmation dialogs -3. **Phase 5: Enhanced Maneuver List** - Improve list UI +2. **Phase 5: Enhanced Maneuver List** - Improve list UI - Make maneuvers selectable - Add preview panel below list - Add edit/delete buttons for selected maneuver -4. **Phase 6: Polish** - User experience improvements +3. **Phase 6: Polish** - User experience improvements - Keyboard shortcuts - Better error messages - Tooltips and help text @@ -133,10 +134,10 @@ None currently ## Progress Summary - **Total Tasks:** 35 -- **Completed:** 13 (Phase 1: 6, Phase 2: 7) +- **Completed:** 19 (Phase 1: 6, Phase 2: 7, Phase 3: 6) - **In Progress:** 0 -- **Pending:** 22 (Phase 3: 6, Phase 4: 6, Phase 5: 5, Phase 6: 5) -- **Completion:** 37.1% +- **Pending:** 16 (Phase 4: 6, Phase 5: 5, Phase 6: 5) +- **Completion:** 54.3% ## Implementation Notes @@ -145,3 +146,6 @@ None currently - Dialog framework complete with tab system - Create workflow fully functional with auto-generated names - Real-time orbital preview implemented and validated +- 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 diff --git a/src/ui_renderer.cpp b/src/ui_renderer.cpp index 60aadad..8d53ad7 100644 --- a/src/ui_renderer.cpp +++ b/src/ui_renderer.cpp @@ -386,6 +386,7 @@ void render_maneuver_list_ui(SimulationState* sim, RenderState* render_state, UI 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; } return; } @@ -471,6 +472,7 @@ void render_maneuver_list_ui(SimulationState* sim, RenderState* render_state, UI 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; } free(maneuver_list_text); @@ -891,6 +893,26 @@ void render_maneuver_dialog(SimulationState* sim, RenderState* render_state, UIS } else if (ui_state->maneuver_dialog.active_tab == MD_TAB_HOHMANN) { render_maneuver_hohmann_tab(sim, ui_state, x + 8, y + 60, width - 16, height - 100); } else if (ui_state->maneuver_dialog.active_tab == MD_TAB_EDIT) { + if (ui_state->maneuver_dialog.show_delete_confirm) { + Rectangle confirm_rect = {(float)x + width / 4, (float)y + height / 3, (float)width / 2, 120}; + + GuiWindowBox(confirm_rect, "Confirm Delete"); + + Rectangle confirm_label = {(float)x + width / 4 + 10, (float)y + height / 3 + 25, (float)width / 2 - 20, 40}; + GuiLabel(confirm_label, "Are you sure you want to delete this maneuver?"); + + Rectangle cancel_confirm_btn = {(float)x + width / 4 + 10, (float)y + height / 3 + 80, 100, 30}; + if (GuiButton(cancel_confirm_btn, "Cancel")) { + ui_state->maneuver_dialog.show_delete_confirm = false; + } + + Rectangle confirm_btn = {(float)x + width - 10 - 110, (float)y + height / 3 + 80, 100, 30}; + if (GuiButton(confirm_btn, "Delete")) { + delete_maneuver_from_dialog(sim, ui_state); + } + } else { + render_maneuver_edit_tab(sim, ui_state, x + 8, y + 60, width - 16, height - 100); + } } } @@ -1103,3 +1125,135 @@ void create_hohmann_burns(SimulationState* sim, UIState* ui_state, bool create_b ui_state->maneuver_dialog.open = false; } + +void load_maneuver_into_dialog(SimulationState* sim, UIState* ui_state, int maneuver_index) { + if (maneuver_index < 0 || maneuver_index >= sim->maneuver_count) { + return; + } + + Maneuver* maneuver = &sim->maneuvers[maneuver_index]; + + ui_state->maneuver_dialog.edit_maneuver_index = maneuver_index; + ui_state->maneuver_dialog.craft_index = maneuver->craft_index; + strncpy(ui_state->maneuver_dialog.name, maneuver->name, 64); + ui_state->maneuver_dialog.direction_active = (int)maneuver->direction; + ui_state->maneuver_dialog.delta_v = maneuver->delta_v; + ui_state->maneuver_dialog.trigger_type_active = (int)maneuver->trigger_type; + ui_state->maneuver_dialog.trigger_value = maneuver->trigger_value; + ui_state->maneuver_dialog.show_delete_confirm = false; +} + +void render_maneuver_edit_tab(SimulationState* sim, UIState* ui_state, int x, int y, int width, int height) { + int current_y = y; + + if (ui_state->maneuver_dialog.edit_maneuver_index < 0 || + ui_state->maneuver_dialog.edit_maneuver_index >= sim->maneuver_count) { + GuiLabel({(float)x, (float)current_y, (float)width, 25}, "No maneuver selected for editing"); + return; + } + + Maneuver* maneuver = &sim->maneuvers[ui_state->maneuver_dialog.edit_maneuver_index]; + + char title[128]; + snprintf(title, sizeof(title), "Edit: %s", maneuver->name); + GuiLabel({(float)x, (float)current_y, (float)width, 25}, title); + current_y += 30; + + GuiLabel({(float)x, (float)current_y, 80, 25}, "Spacecraft:"); + + if (maneuver->craft_index >= 0 && maneuver->craft_index < sim->craft_count) { + char craft_name[64]; + snprintf(craft_name, sizeof(craft_name), "%s (locked)", sim->spacecraft[maneuver->craft_index].name); + Rectangle craft_label = {(float)x + 85, (float)current_y, (float)(width - 95), 25}; + GuiLabel(craft_label, craft_name); + } + current_y += 35; + + Rectangle name_label = {(float)x, (float)current_y, 80, 25}; + GuiLabel(name_label, "Name:"); + + Rectangle name_text = {(float)x + 85, (float)current_y, (float)(width - 95), 25}; + GuiTextBox(name_text, ui_state->maneuver_dialog.name, 64, true); + current_y += 35; + + Rectangle dir_label = {(float)x, (float)current_y, 80, 25}; + GuiLabel(dir_label, "Direction:"); + + if (ui_state->maneuver_dialog.craft_index >= 0 && ui_state->maneuver_dialog.craft_index < sim->craft_count) { + Rectangle dir_combo = {(float)x + 85, (float)current_y, (float)(width - 95), 25}; + GuiDropdownBox(dir_combo, "Prograde;Retrograde;Normal;Antinormal;Radial In;Radial Out;Custom", &ui_state->maneuver_dialog.direction_active, false); + } + current_y += 35; + + Rectangle dv_label = {(float)x, (float)current_y, 80, 25}; + GuiLabel(dv_label, "Delta-V:"); + + Rectangle dv_slider = {(float)x + 85, (float)current_y, (float)(width - 180), 20}; + float dv_float = (float)ui_state->maneuver_dialog.delta_v; + GuiSlider(dv_slider, "0", "50000", &dv_float, 0, 50000); + ui_state->maneuver_dialog.delta_v = dv_float; + + char dv_text[32]; + snprintf(dv_text, sizeof(dv_text), "%.1f m/s", ui_state->maneuver_dialog.delta_v); + Rectangle dv_value = {(float)x + width - 90, (float)current_y, 85, 25}; + GuiLabel(dv_value, dv_text); + current_y += 35; + + Rectangle trigger_label = {(float)x, (float)current_y, 80, 25}; + GuiLabel(trigger_label, "Trigger:"); + + if (ui_state->maneuver_dialog.craft_index >= 0 && ui_state->maneuver_dialog.craft_index < sim->craft_count) { + Rectangle trigger_combo = {(float)x + 85, (float)current_y, (float)(width - 95), 25}; + GuiDropdownBox(trigger_combo, "Time;True Anomaly", &ui_state->maneuver_dialog.trigger_type_active, false); + } + current_y += 35; + + Rectangle value_label = {(float)x, (float)current_y, 80, 25}; + GuiLabel(value_label, "Value:"); + + Rectangle value_slider = {(float)x + 85, (float)current_y, (float)(width - 180), 20}; + float value_float = (float)ui_state->maneuver_dialog.trigger_value; + if (ui_state->maneuver_dialog.trigger_type_active == 0) { + GuiSlider(value_slider, "0", "1000000", &value_float, 0, 1000000); + } else { + GuiSlider(value_slider, "0", "6.28", &value_float, 0, 2 * M_PI); + } + ui_state->maneuver_dialog.trigger_value = value_float; + + char value_text[32]; + if (ui_state->maneuver_dialog.trigger_type_active == 0) { + snprintf(value_text, sizeof(value_text), "%.1f s", ui_state->maneuver_dialog.trigger_value); + } else { + snprintf(value_text, sizeof(value_text), "%.2f rad", ui_state->maneuver_dialog.trigger_value); + } + Rectangle value_text_label = {(float)x + width - 90, (float)current_y, 85, 25}; + GuiLabel(value_text_label, value_text); + + update_orbital_preview(sim, ui_state); + + if (ui_state->maneuver_dialog.show_preview) { + Rectangle preview_bounds = {(float)x + 8, (float)current_y + 10, (float)width - 16, 110}; + render_orbital_preview(sim, ui_state, preview_bounds); + } + + current_y += 130; + + Rectangle delete_btn = {(float)x, (float)current_y, (float)(width - 10) / 2, 30}; + if (GuiButton(delete_btn, "Delete Maneuver")) { + ui_state->maneuver_dialog.show_delete_confirm = true; + } +} + +void delete_maneuver_from_dialog(SimulationState* sim, UIState* ui_state) { + int edit_index = ui_state->maneuver_dialog.edit_maneuver_index; + if (edit_index < 0 || edit_index >= sim->maneuver_count) { + ui_state->maneuver_dialog.show_delete_confirm = false; + return; + } + + bool success = remove_maneuver_by_index(sim, edit_index); + if (success) { + ui_state->maneuver_dialog.show_delete_confirm = false; + ui_state->maneuver_dialog.open = false; + } +} diff --git a/src/ui_renderer.h b/src/ui_renderer.h index 9594b68..56902d9 100644 --- a/src/ui_renderer.h +++ b/src/ui_renderer.h @@ -37,6 +37,7 @@ struct ManeuverDialogState { char error_message[256]; bool show_error; + bool show_delete_confirm; }; // UI state @@ -59,6 +60,7 @@ void render_body_info_ui(SimulationState* sim, RenderState* render_state, UIStat void render_maneuver_list_ui(SimulationState* sim, RenderState* render_state, UIState* ui_state); void render_maneuver_dialog(SimulationState* sim, RenderState* render_state, UIState* ui_state); void render_maneuver_create_tab(SimulationState* sim, UIState* ui_state, int x, int y, int width, int height); +void render_maneuver_edit_tab(SimulationState* sim, UIState* ui_state, int x, int y, int width, int height); void update_orbital_preview(SimulationState* sim, UIState* ui_state); void render_orbital_preview(SimulationState* sim, UIState* ui_state, Rectangle bounds); bool validate_maneuver_inputs(SimulationState* sim, UIState* ui_state); @@ -68,5 +70,7 @@ void update_maneuver_from_dialog(SimulationState* sim, UIState* ui_state); void render_maneuver_hohmann_tab(SimulationState* sim, UIState* ui_state, int x, int y, int width, int height); void calculate_hohmann_for_dialog(SimulationState* sim, UIState* ui_state); void create_hohmann_burns(SimulationState* sim, UIState* ui_state, bool create_both_burns); +void load_maneuver_into_dialog(SimulationState* sim, UIState* ui_state, int maneuver_index); +void delete_maneuver_from_dialog(SimulationState* sim, UIState* ui_state); #endif