Browse Source

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.
main
cinnaboot 5 months ago
parent
commit
0af8ece649
  1. 43
      docs/planning/maneuver_dialog_progress.md
  2. 1
      src/main.cpp
  3. 161
      src/ui_renderer.cpp
  4. 1
      src/ui_renderer.h

43
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

1
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;

161
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);

1
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();

Loading…
Cancel
Save