From 479f234e0f93feccc822fbce7e916ec559a03947 Mon Sep 17 00:00:00 2001 From: cinnaboot Date: Thu, 5 Feb 2026 12:45:49 -0500 Subject: [PATCH] feat: Add keyboard shortcuts and improved error messages (Phase 6) Added user experience improvements: - Keyboard shortcuts: ESC to cancel, ENTER to confirm - Enhanced error messages with detailed descriptions - Hohmann tab input validation - Error message display in Hohmann tab - Edge case handling for all input scenarios - Clear error messages when tab switches Keyboard shortcuts work for all dialog tabs and delete confirmation. --- docs/planning/maneuver_dialog_progress.md | 30 ++++----- src/main.cpp | 40 ++++++++++++ src/ui_renderer.cpp | 74 +++++++++++++++++++++-- 3 files changed, 122 insertions(+), 22 deletions(-) diff --git a/docs/planning/maneuver_dialog_progress.md b/docs/planning/maneuver_dialog_progress.md index 39d86bb..f895129 100644 --- a/docs/planning/maneuver_dialog_progress.md +++ b/docs/planning/maneuver_dialog_progress.md @@ -89,13 +89,6 @@ None currently ## Pending Tasks -### Phase 5: Enhanced Maneuver List -- [ ] Modify render_maneuver_list_ui() for selectable items -- [ ] Add maneuver preview panel to list -- [ ] Add action buttons (Create, Hohmann, Edit, Delete) -- [ ] Connect buttons to dialog -- [ ] Test enhanced list interaction - ### Phase 6: Polish - [ ] Add keyboard shortcuts (Enter to confirm, Escape to cancel) - [ ] Improve error messages @@ -122,26 +115,25 @@ None currently ### Phase 4 9. `b0d9d99` - feat: Implement Edit Maneuver tab (Phase 4) -## Next Steps +### Phase 5 +10. `0af8ece` - feat: Enhance maneuver list (Phase 5) -1. **Phase 5: Enhanced Maneuver List** - Improve list UI - - Make maneuvers selectable - - Add preview panel below list - - Add edit/delete buttons for selected maneuver +## Next Steps -2. **Phase 6: Polish** - User experience improvements +1. **Phase 6: Polish** - User experience improvements - Keyboard shortcuts - Better error messages - Tooltips and help text - Edge case handling + - Testing with various configurations ## Progress Summary - **Total Tasks:** 35 -- **Completed:** 25 (Phase 1: 6, Phase 2: 7, Phase 3: 6, Phase 4: 6) +- **Completed:** 30 (Phase 1: 6, Phase 2: 7, Phase 3: 6, Phase 4: 6, Phase 5: 5) - **In Progress:** 0 -- **Pending:** 10 (Phase 5: 5, Phase 6: 5) -- **Completion:** 71.4% +- **Pending:** 5 (Phase 6: 5) +- **Completion:** 85.7% ## Implementation Notes @@ -155,4 +147,8 @@ None currently - 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 +- Delete confirmation dialog overlay on edit tab +- Enhanced maneuver list with selectable items +- Preview panel shows selected maneuver details and orbital preview +- Action buttons for Create, Hohmann, Edit, Delete +- selected_maneuver_index tracking in UIState \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 1caa671..313d242 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -131,6 +131,46 @@ void run_gui_simulation(SimulationState* sim) { render_body_info_ui(sim, &render_state, &ui_state); render_maneuver_list_ui(sim, &render_state, &ui_state); render_maneuver_dialog(sim, &render_state, &ui_state); + + // Handle keyboard shortcuts for maneuver dialog + if (ui_state.maneuver_dialog.open) { + if (IsKeyPressed(KEY_ESCAPE)) { + if (ui_state.maneuver_dialog.show_delete_confirm) { + ui_state.maneuver_dialog.show_delete_confirm = false; + } else { + ui_state.maneuver_dialog.open = false; + } + } + + if (IsKeyPressed(KEY_ENTER)) { + if (ui_state.maneuver_dialog.show_delete_confirm) { + // Confirm delete + int edit_index = ui_state.maneuver_dialog.edit_maneuver_index; + if (edit_index >= 0 && edit_index < sim->maneuver_count) { + remove_maneuver_by_index(sim, edit_index); + ui_state.maneuver_dialog.show_delete_confirm = false; + ui_state.maneuver_dialog.open = false; + } + } else { + // Confirm action based on tab + switch (ui_state.maneuver_dialog.active_tab) { + case MD_TAB_CREATE: + create_maneuver_from_dialog(sim, &ui_state); + break; + case MD_TAB_HOHMANN: + // Calculate button functionality - trigger calculation + if (ui_state.maneuver_dialog.craft_index >= 0 && + ui_state.maneuver_dialog.craft_index < sim->craft_count) { + calculate_hohmann_for_dialog(sim, &ui_state); + } + break; + case MD_TAB_EDIT: + update_maneuver_from_dialog(sim, &ui_state); + break; + } + } + } + } end_frame(); } diff --git a/src/ui_renderer.cpp b/src/ui_renderer.cpp index 357b133..a8a81f8 100644 --- a/src/ui_renderer.cpp +++ b/src/ui_renderer.cpp @@ -693,6 +693,9 @@ void render_orbital_preview(SimulationState* sim, UIState* ui_state, Rectangle b void render_maneuver_hohmann_tab(SimulationState* sim, UIState* ui_state, int x, int y, int width, int height) { int current_y = y; + ui_state->maneuver_dialog.show_error = false; + ui_state->maneuver_dialog.error_message[0] = '\0'; + if (ui_state->maneuver_dialog.craft_index >= 0 && ui_state->maneuver_dialog.craft_index < sim->craft_count) { Spacecraft* craft = &sim->spacecraft[ui_state->maneuver_dialog.craft_index]; @@ -802,6 +805,12 @@ void render_maneuver_hohmann_tab(SimulationState* sim, UIState* ui_state, int x, } current_y += 40; + if (ui_state->maneuver_dialog.show_error) { + Rectangle error_bounds = {(float)x + 8, (float)current_y, (float)width - 16, 25}; + GuiLabel(error_bounds, TextFormat("Error: %s", ui_state->maneuver_dialog.error_message)); + current_y += 30; + } + if (ui_state->maneuver_dialog.show_hohmann_preview) { HohmannTransfer* ht = &ui_state->maneuver_dialog.last_calc; @@ -865,30 +874,85 @@ void render_maneuver_hohmann_tab(SimulationState* sim, UIState* ui_state, int x, void calculate_hohmann_for_dialog(SimulationState* sim, UIState* ui_state) { ui_state->maneuver_dialog.show_hohmann_preview = false; + ui_state->maneuver_dialog.show_error = false; + ui_state->maneuver_dialog.error_message[0] = '\0'; + + if (ui_state->maneuver_dialog.craft_index < 0 || + ui_state->maneuver_dialog.craft_index >= sim->craft_count) { + snprintf(ui_state->maneuver_dialog.error_message, sizeof(ui_state->maneuver_dialog.error_message), + "No spacecraft selected"); + ui_state->maneuver_dialog.show_error = true; + return; + } if (ui_state->maneuver_dialog.transfer_body_index < 0 || - ui_state->maneuver_dialog.transfer_body_index >= sim->body_count || - ui_state->maneuver_dialog.target_body_index < 0 || + ui_state->maneuver_dialog.transfer_body_index >= sim->body_count) { + snprintf(ui_state->maneuver_dialog.error_message, sizeof(ui_state->maneuver_dialog.error_message), + "Invalid transfer parent body selected"); + ui_state->maneuver_dialog.show_error = true; + return; + } + + if (ui_state->maneuver_dialog.current_body_index < 0 || + ui_state->maneuver_dialog.current_body_index >= sim->body_count) { + snprintf(ui_state->maneuver_dialog.error_message, sizeof(ui_state->maneuver_dialog.error_message), + "Invalid current body selected"); + ui_state->maneuver_dialog.show_error = true; + return; + } + + if (ui_state->maneuver_dialog.target_body_index < 0 || ui_state->maneuver_dialog.target_body_index >= sim->body_count) { + snprintf(ui_state->maneuver_dialog.error_message, sizeof(ui_state->maneuver_dialog.error_message), + "Invalid target body selected"); + ui_state->maneuver_dialog.show_error = true; return; } if (ui_state->maneuver_dialog.transfer_body_index == ui_state->maneuver_dialog.target_body_index) { + snprintf(ui_state->maneuver_dialog.error_message, sizeof(ui_state->maneuver_dialog.error_message), + "Current and target bodies must be different"); + ui_state->maneuver_dialog.show_error = true; + return; + } + + Spacecraft* craft = &sim->spacecraft[ui_state->maneuver_dialog.craft_index]; + if (craft->parent_index != ui_state->maneuver_dialog.current_body_index) { + snprintf(ui_state->maneuver_dialog.error_message, sizeof(ui_state->maneuver_dialog.error_message), + "Spacecraft is not orbiting the selected current body"); + ui_state->maneuver_dialog.show_error = true; return; } - CelestialBody* central_body = &sim->bodies[ui_state->maneuver_dialog.transfer_body_index]; + CelestialBody* transfer_body = &sim->bodies[ui_state->maneuver_dialog.transfer_body_index]; CelestialBody* current_body = &sim->bodies[ui_state->maneuver_dialog.current_body_index]; CelestialBody* target_body = &sim->bodies[ui_state->maneuver_dialog.target_body_index]; double r1 = current_body->orbit.semi_major_axis; double r2 = target_body->orbit.semi_major_axis; - if (r1 <= 0 || r2 <= 0) { + if (r1 <= 0) { + snprintf(ui_state->maneuver_dialog.error_message, sizeof(ui_state->maneuver_dialog.error_message), + "Current body has invalid orbit (semi-major axis <= 0)"); + ui_state->maneuver_dialog.show_error = true; + return; + } + + if (r2 <= 0) { + snprintf(ui_state->maneuver_dialog.error_message, sizeof(ui_state->maneuver_dialog.error_message), + "Target body has invalid orbit (semi-major axis <= 0)"); + ui_state->maneuver_dialog.show_error = true; + return; + } + + if (transfer_body->mass <= 0) { + snprintf(ui_state->maneuver_dialog.error_message, sizeof(ui_state->maneuver_dialog.error_message), + "Transfer parent has invalid mass"); + ui_state->maneuver_dialog.show_error = true; return; } - ui_state->maneuver_dialog.last_calc = calculate_hohmann_transfer(r1, r2, central_body->mass); + ui_state->maneuver_dialog.last_calc = calculate_hohmann_transfer(r1, r2, transfer_body->mass); ui_state->maneuver_dialog.show_hohmann_preview = true; }