diff options
| author | Jon Santmyer <jon@jonsantmyer.com> | 2026-05-24 13:04:10 -0400 |
|---|---|---|
| committer | Jon Santmyer <jon@jonsantmyer.com> | 2026-05-24 13:04:10 -0400 |
| commit | 0b428d94e751dc4a5fbe19418bfb5994cebfa54c (patch) | |
| tree | be9c338ec6b5e40ddb96d2d8ecb498b362851a2f /src/ui | |
| parent | 14ca7b5fc15eb2618b46bde0cac85e37ebc9ebd9 (diff) | |
| download | systemic4x-0b428d94e751dc4a5fbe19418bfb5994cebfa54c.tar.gz systemic4x-0b428d94e751dc4a5fbe19418bfb5994cebfa54c.tar.bz2 systemic4x-0b428d94e751dc4a5fbe19418bfb5994cebfa54c.zip | |
Diffstat (limited to 'src/ui')
| -rw-r--r-- | src/ui/bodies_window.rs | 174 | ||||
| -rw-r--r-- | src/ui/contact.rs | 100 | ||||
| -rw-r--r-- | src/ui/contacts_widget.rs | 146 | ||||
| -rw-r--r-- | src/ui/current_system_widget.rs | 47 | ||||
| -rw-r--r-- | src/ui/fleet_schedule.rs | 150 | ||||
| -rw-r--r-- | src/ui/fleet_window.rs | 314 | ||||
| -rw-r--r-- | src/ui/fleets_widget.rs | 246 | ||||
| -rw-r--r-- | src/ui/schedule_widget.rs | 6 | ||||
| -rw-r--r-- | src/ui/time_control_widget.rs | 89 | ||||
| -rw-r--r-- | src/ui/topbar.rs | 136 |
10 files changed, 634 insertions, 774 deletions
diff --git a/src/ui/bodies_window.rs b/src/ui/bodies_window.rs deleted file mode 100644 index a93f11c..0000000 --- a/src/ui/bodies_window.rs +++ /dev/null @@ -1,174 +0,0 @@ -use crate::ntree::NTreeNode; -use crate::solar_system::body::BodyId; -use crate::solar_system::SolarSystem; -use crate::timeman::TimeMan; - - -#[derive(Default, Clone)] -pub struct BodiesWindowState -{ - pub open: bool, - selected_body: Option<BodyId> -} - -#[derive(Default, Clone)] -pub struct BodiesWindowAction -{ - pub focus_body: Option<BodyId> -} - -impl BodiesWindowState -{ - pub fn paint( - &mut self, - ui: &mut egui::Ui, - current_system: &SolarSystem) - -> BodiesWindowAction - { - let mut action = BodiesWindowAction::default(); - - let mut bodies_window_open = self.open; - egui::Window::new("Bodies") - .open(&mut &mut bodies_window_open) - .resizable(true) - .show(ui.ctx(), |ui| { - - ui.horizontal(|ui| { - self.paint_bodies_list(current_system, ui); - self.paint_body_info_panel(&mut action, current_system, ui); - }); - }); - self.open = bodies_window_open; - action - } - - fn paint_bodies_node_rec( - &self, - star_system: &SolarSystem, - node: &NTreeNode<BodyId>, - ui: &mut egui::Ui) - -> Option<BodyId> - { - let body = star_system.body(*node.value()); - let children = node.children(); - - let selected = self.selected_body.is_some_and(|v| { v == *node.value() }); - let mut new_selected = None; - - if children.is_empty() { - if ui.selectable_label(selected, body.name()).clicked() { - new_selected = Some(*node.value()); - } - }else{ - egui::collapsing_header::CollapsingState::load_with_default_open( - ui.ctx(), - ui.make_persistent_id(format!("bodies_window_body_{}", body.id())), - true) - .show_header(ui, |ui| { - if ui.selectable_label(selected, body.name()).clicked() { - new_selected = Some(*node.value()); - } - }) - .body(|ui| { - for child in children { - let child_selected = self.paint_bodies_node_rec(star_system, child, ui); - if child_selected.is_some() { - new_selected = child_selected; - } - } - }); - }; - new_selected - } - - fn paint_bodies_list( - &mut self, - star_system: &SolarSystem, - ui: &mut egui::Ui) - -> Option<egui::Response> - { - let resp = egui::ScrollArea::vertical() - .min_scrolled_height(200.0) - .show(ui, |ui| { - ui.vertical(|ui| { - let root = star_system.heirarchy(); - let new_sel = self.paint_bodies_node_rec(star_system, root, ui); - if new_sel.is_some() { - self.selected_body = new_sel; - } - }).response - }); - Some(resp.inner) - } - - fn paint_body_info_panel( - &mut self, - action: &mut BodiesWindowAction, - star_system: &SolarSystem, - ui: &mut egui::Ui) - { - let selected_body = match self.selected_body { - Some(id) => { star_system.body(id) }, - None => { return; } - }; - - ui.separator(); - ui.vertical(|ui| { - egui::Frame::canvas(ui.style()) - .show(ui, |ui| { - ui.set_width(200.0); - ui.vertical_centered(|ui| { - ui.label( - egui::RichText::new(selected_body.name()) - .heading()); - ui.separator(); - }); - - ui.vertical(|ui| { - ui.style_mut().interaction.selectable_labels = false; - egui_extras::TableBuilder::new(ui) - .column(egui_extras::Column::auto()) - .column(egui_extras::Column::remainder()) - .body(|mut body| { - body.row(16.0, |mut row| { - row.col(|col| { col.label("Mass"); }); - row.col(|col| { col.label(format!("{:.4E}", selected_body.mass())); }); - }); - body.row(16.0, |mut row| { - row.col(|col| { col.label("Radius"); }); - row.col(|col| { col.label(format!("{:.4E}", selected_body.radius())); }); - }); - - if let Some(orbit) = selected_body.get_orbit() { - let parent = star_system.body(orbit.parent()); - body.row(16.0, |mut row| { - row.col(|col| { col.label("Orbiting"); }); - row.col(|col| { col.label(parent.name()); }); - }); - body.row(16.0, |mut row| { - row.col(|col| { col.label("Period"); }); - row.col(|col| { - col.label(format!("{}", - TimeMan::format_duration(orbit.period(selected_body)) - )); - }); - }); - body.row(16.0, |mut row| { - row.col(|col| { col.label("Semi-major Axis"); }); - row.col(|col| { col.label(format!("{:.4E} km", orbit.sma())); }); - }); - } - }); - }); - }); - - ui.horizontal(|ui| { - let focus_resp = ui.button("Focus"); - - if focus_resp.clicked() { - action.focus_body = Some(selected_body.id()); - } - }); - }); - } -} diff --git a/src/ui/contact.rs b/src/ui/contact.rs new file mode 100644 index 0000000..e525541 --- /dev/null +++ b/src/ui/contact.rs @@ -0,0 +1,100 @@ +use crate::body::BodyId; +use crate::fleet::{Fleet, FleetId, FleetsManager}; +use crate::solar_system::{Kilometers, SolarSystem}; +use crate::solar_system::body::OrbitalBody; +use crate::timeman::Second; + +#[derive(Copy, Clone, Eq, PartialEq)] +pub enum ContactType +{ + Body, + Fleet +} + +#[derive(Copy, Clone, Eq, PartialEq)] +pub struct MapContact +{ + obj_type: ContactType, + id: usize +} + +impl MapContact +{ + pub fn from_body( + id: BodyId) + -> Self { + Self { + obj_type: ContactType::Body, + id: id + } + } + + pub fn from_fleet( + id: FleetId) + -> Self { + Self { + obj_type: ContactType::Fleet, + id: id + } + } + + pub fn body(&self) + -> Option<BodyId> + { + match self.obj_type { + ContactType::Body => Some(self.id), + _ => None + } + } + + pub fn fleet(&self) + -> Option<FleetId> + { + match self.obj_type { + ContactType::Fleet => Some(self.id), + _ => None + } + } + + pub fn name<'ss>( + &self, + star_system: &'ss SolarSystem, + fleets_man: &'ss FleetsManager) + -> &'ss String + { + match self.obj_type { + ContactType::Body => star_system.body(self.id).name(), + ContactType::Fleet => fleets_man.fleet(self.id).unwrap().name() + } + } + + pub fn origin_position<'ss>( + &self, + star_system: &'ss SolarSystem, + fleets_man: &'ss FleetsManager, + time: Second) + -> cgmath::Vector3<Kilometers> + { + match self.obj_type { + ContactType::Body => + star_system.body(self.id).origin_position(star_system, time), + ContactType::Fleet => + *fleets_man.fleet(self.id).unwrap().origin_position() + } + } + + pub fn offset_position<'ss>( + &self, + star_system: &'ss SolarSystem, + fleets_man: &'ss FleetsManager, + time: Second) + -> cgmath::Vector3<Kilometers> + { + match self.obj_type { + ContactType::Body => + star_system.body(self.id).offset_position(time), + ContactType::Fleet => + *fleets_man.fleet(self.id).unwrap().offset_position() + } + } +} diff --git a/src/ui/contacts_widget.rs b/src/ui/contacts_widget.rs new file mode 100644 index 0000000..70b046c --- /dev/null +++ b/src/ui/contacts_widget.rs @@ -0,0 +1,146 @@ +use crate::solar_system::SolarSystem; +use crate::fleet::FleetsManager; +use crate::ui::contact::MapContact; + +pub struct ContactsListWidget<'f> +{ + contacts: Vec<(MapContact, &'f String)>, + selected: &'f mut Option<MapContact> +} + +pub struct FocusedContactWidget<'f> +{ + contact: Option<MapContact>, + star_system: &'f SolarSystem, + fleets_man: &'f FleetsManager +} + +impl<'f> ContactsListWidget<'f> +{ + pub fn new( + selected: &'f mut Option<MapContact>, + star_system: &'f SolarSystem, + fleets_man: &'f FleetsManager) + -> Self { + let system_fleets = fleets_man.all_in_system(star_system.id()); + let system_bodies = star_system.bodies(); + + let mut contacts = system_fleets.iter().map(|id| { + let fleet = fleets_man.fleet(*id); + (MapContact::from_fleet(*id), fleet.unwrap().name()) + }).collect::<Vec<_>>(); + contacts.extend(system_bodies.iter().map(|body| { + (MapContact::from_body(body.id()), body.name()) + })); + + Self { + contacts: contacts, + selected + } + } + + pub fn show_ui( + mut self, + ui: &mut egui::Ui) + -> egui::InnerResponse<Option<MapContact>> + { + self.show_entries_box(ui) + } + + fn show_entries_box( + &mut self, + ui: &mut egui::Ui) + -> egui::InnerResponse<Option<MapContact>> + { + let mut ret = None; + egui::Frame::default() + .stroke(egui::Stroke::NONE) + .fill(egui::Color32::from_rgb(48, 48, 48)) + .outer_margin(egui::Margin::same(4)) + .inner_margin(egui::Margin::same(4)) + .show(ui, |frame| { + frame.take_available_width(); + frame.set_max_height(frame.available_height() / 2.0); + + frame.vertical(|vertical| { + let mut table = egui_extras::TableBuilder::new(vertical) + .striped(true) + .resizable(false) + .auto_shrink(false) + .column(egui_extras::Column::remainder()) + .body( + |body| { + let row_height = 20.0; + body.rows(row_height, self.contacts.len(), |mut row| { + let resp = self.show_contact(&mut row); + if resp.is_some() { + ret = resp; + } + }); + }); + }); + ret + }) + } + + fn show_contact( + &mut self, + row: &mut egui_extras::TableRow) + -> Option<MapContact> + { + let contact = &self.contacts[row.index()]; + let selected = self.selected.is_some_and(|sel| { sel == contact.0 }); + + let mut ret = None; + row.col(|col| { + let resp = col.selectable_label(selected, contact.1); + + if resp.double_clicked() { + ret = Some(contact.0); + }else if resp.clicked() { + *self.selected = match selected { + true => None, + false => Some(contact.0) + }; + } + }); + ret + } +} + +impl<'f> FocusedContactWidget<'f> +{ + pub fn new( + contact: Option<MapContact>, + star_system: &'f SolarSystem, + fleets_man: &'f FleetsManager) + -> Self { + Self { + contact, + star_system, + fleets_man + } + } +} + +impl egui::Widget for FocusedContactWidget<'_> +{ + fn ui(self, ui: &mut egui::Ui) -> egui::Response { + ui.vertical(|vertical| { + vertical.label("Focused contact"); + vertical.shrink_width_to_current(); + egui::Frame::new() + .fill(egui::Color32::from_gray(48)) + .stroke(egui::Stroke::NONE) + .inner_margin(egui::Margin::same(2)) + .show(vertical, + |frame| { + frame.take_available_width(); + match self.contact { + Some(contact) => frame.label(contact.name(self.star_system, self.fleets_man)), + None => frame.label("None") + } + }).inner + }).inner + } +} diff --git a/src/ui/current_system_widget.rs b/src/ui/current_system_widget.rs new file mode 100644 index 0000000..f7aa940 --- /dev/null +++ b/src/ui/current_system_widget.rs @@ -0,0 +1,47 @@ +use crate::solar_system::{SolarSystem, SystemId}; + +pub struct CurrentSystemWidget<'frame> +{ + star_systems: &'frame [SolarSystem], + current_system: &'frame mut Option<SystemId>, +} + +impl<'frame> CurrentSystemWidget<'frame> { + pub fn new( + star_systems: &'frame [SolarSystem], + current_system: &'frame mut Option<SystemId>) + -> Self { + Self { + star_systems, + current_system + } + } +} + +impl<'frame> egui::Widget for CurrentSystemWidget<'frame> +{ + fn ui(self, ui: &mut egui::Ui) -> egui::Response { + let selected_label = match self.current_system { + Some(id) => &self.star_systems[*id].name(), + None => "Click to open" + }; + + ui.vertical(|vertical| { + vertical.label("Current System"); + let resp = egui::ComboBox::new("current_system", egui::WidgetText::default()) + .selected_text(selected_label) + .show_ui(vertical, + |combo_box| { + for system in self.star_systems.iter() { + let entry_value = Some(system.id()); + combo_box.selectable_value( + self.current_system, + entry_value, + system.name()); + } + }); + + resp.response + }).inner + } +} diff --git a/src/ui/fleet_schedule.rs b/src/ui/fleet_schedule.rs deleted file mode 100644 index b0e5dd7..0000000 --- a/src/ui/fleet_schedule.rs +++ /dev/null @@ -1,150 +0,0 @@ -use crate::solar_system::SolarSystem; -use crate::solar_system::body::BodyId; -use crate::fleet::{Fleet, FleetId, FleetsManager}; - -#[derive(Clone)] -pub struct FleetScheduleWindow -{ - pub target: (Option<BodyId>, Option<FleetId>), - pub linear_acceleration_limit: f32, - pub angular_acceleration_limit: f32, -} - -impl Default for FleetScheduleWindow -{ - fn default() -> Self { - Self { - target: Default::default(), - linear_acceleration_limit: 9.81, - angular_acceleration_limit: 15.0 - } - } -} - -impl FleetScheduleWindow -{ - pub fn paint( - &mut self, - star_systems: &[SolarSystem], - fleets_man: &FleetsManager, - fleet: &Fleet, - ui: &mut egui::Ui) - { - ui.horizontal(|limits_ui| { - limits_ui.vertical(|linear_limit_ui| { - linear_limit_ui.label("Linear acceleration limit"); - linear_limit_ui.add( - egui::DragValue::new(&mut self.linear_acceleration_limit) - .range(0.0..=(9.8*2.0)) - .clamp_existing_to_range(true) - .suffix(" m/s^2") - .speed(0.1) - .max_decimals_opt(Some(2)) - ); - }); - limits_ui.separator(); - limits_ui.vertical(|angular_limit_ui| { - angular_limit_ui.label("Angular acceleration limit"); - angular_limit_ui.add( - egui::DragValue::new(&mut self.angular_acceleration_limit) - .range(0.0..=30.0) - .clamp_existing_to_range(true) - .suffix(" °/s^2") - .speed(0.1) - .max_decimals_opt(Some(2)) - ); - }); - }); - ui.separator(); - - let fleet_system = &star_systems[fleet.system().unwrap_or(0)]; - let bodies = fleet_system.bodies(); - let bodies_order = fleet_system.heirarchy().traverse_preorder(); - let system_fleets = fleet_system.fleets(fleets_man); - - let row_height = ui.spacing().interact_size.y; - ui.horizontal(|panel_ui| - { - panel_ui.vertical(|vertical_ui| { - vertical_ui.set_max_width(128.0); - vertical_ui.vertical_centered(|centered_ui| { - centered_ui.label("Target"); - }); - egui::ScrollArea::vertical() - .id_salt("fleet_schedule_objs") - .auto_shrink(false) - .min_scrolled_height(256.0) - .show_rows(vertical_ui, row_height, bodies.len() + system_fleets.len(), - |objs_ui, rows| { - let fleet_rows_start = 0; - let bodies_rows_start = system_fleets.len(); - - for row in rows { - match row < bodies_rows_start { - true => { - let id = system_fleets[row - fleet_rows_start]; - let selected = self.target.1 - .is_some_and(|v| { v == id }); - let fleet = fleets_man.fleet(id); - if let Some(fleet) = fleet { - if objs_ui.selectable_label(selected, fleet.name()).clicked() { - self.select_fleet_from_schedule_objs(id, selected); - } - } - }, - false => { - let id = *bodies_order[row - bodies_rows_start]; - let selected = self.target.0 - .is_some_and(|v| { v == id }); - let body = &bodies[id]; - if objs_ui.selectable_label(selected, body.name()).clicked() { - self.select_body_from_schedule_objs(id, selected); - } - } - } - } - }); - }); - - panel_ui.vertical(|vertical_ui| { - vertical_ui.set_max_width(128.0); - vertical_ui.vertical_centered(|centered_ui| { - centered_ui.label("Commands"); - }); - egui::ScrollArea::vertical() - .id_salt("fleet_schedule_commands") - .auto_shrink(false) - .min_scrolled_height(256.0) - .show_rows(vertical_ui, row_height, 0, - |cmd_ui, rows| { - - }); - }); - - panel_ui.vertical(|vertical_ui| { - vertical_ui.set_max_width(128.0); - vertical_ui.vertical_centered(|centered_ui| { - centered_ui.label("Schedule"); - }); - }); - }); - } - - fn select_body_from_schedule_objs( - &mut self, - id: BodyId, - selected: bool) - { - self.target = - (if selected { None } else { Some(id) }, None); - } - - fn select_fleet_from_schedule_objs( - &mut self, - id: FleetId, - selected: bool) - { - self.target = - (None, if selected { None } else { Some(id) }); - } -} diff --git a/src/ui/fleet_window.rs b/src/ui/fleet_window.rs deleted file mode 100644 index 4db4ce8..0000000 --- a/src/ui/fleet_window.rs +++ /dev/null @@ -1,314 +0,0 @@ -use crate::GameState; -use crate::fleet::{Fleet, FleetId, FleetsManager}; -use crate::solar_system::orbit::StaticOrbiter; -use crate::solar_system::{Kilometers, SolarSystem, SystemId}; -use crate::solar_system::body::BodyId; -use crate::ui::fleet_schedule::FleetScheduleWindow; - -#[derive(Default, Clone, PartialEq)] -enum FleetMenuPanelSel { - #[default] - Info, - Schedule -} - -#[derive(Default, Clone)] -pub struct FleetWindowState -{ - pub open: bool, - pub selected_fleet: Option<FleetId>, - - menu_panel: FleetMenuPanelSel, - fleet_schedule_window: FleetScheduleWindow, - - pub new_fleet_window: Option<NewFleetWindowState> -} - -#[derive(Default, Clone)] -pub struct FleetWindowAction -{ - pub new_fleet: Option<NewFleet> -} - -#[derive(Default, Clone)] -pub struct NewFleetWindowState -{ - pub open: bool, - pub parent_system: SystemId, - pub parent_body: BodyId, - - pub fleet_name: String, - pub fleet_sma: Kilometers -} - -#[derive(Clone)] -pub struct NewFleet -{ - pub system: SystemId, - pub orbiting: BodyId, - pub name: String, - pub sma: Kilometers -} - -impl FleetWindowState -{ - pub fn paint( - &mut self, - ui: &mut egui::Ui, - game_state: &GameState, - focused_system: &Option<SystemId>, - focused_body: &Option<BodyId>) - -> FleetWindowAction - { - let mut action = FleetWindowAction::default(); - - let star_systems = game_state.solar_systems(); - let fleets_manager = game_state.fleets(); - - let mut mgr_open = self.open; - egui::Window::new("Fleet Manager") - .open(&mut mgr_open) - .show(ui.ctx(), |ui| { - - ui.vertical(|ui| { - ui.horizontal(|ui| { - self.paint_fleet_list(fleets_manager, ui); - self.paint_fleet_menu(star_systems, fleets_manager, ui); - }); - - if focused_system.is_some() { - if ui.button("New Fleet").clicked() { - if self.new_fleet_window.is_none() { - self.new_fleet_window = Some(NewFleetWindowState::new( - focused_system.unwrap(), - focused_body.unwrap_or(0) - )); - } - } - } - }); - }); - - match &mut self.new_fleet_window { - Some(new_fleet_window) => { - action.new_fleet = new_fleet_window.paint(ui, game_state); - if action.new_fleet.is_some() || !new_fleet_window.open { - self.new_fleet_window = None; - } - } - None => {} - } - - self.open = mgr_open; - action - } - - fn paint_fleet_menu( - &mut self, - star_systems: &[SolarSystem], - fleets_man: &FleetsManager, - ui: &mut egui::Ui) - { - let fleet = match self.selected_fleet { - Some(id) => { fleets_man.fleet(id) }, - None => { return; } - }; - let fleet = match fleet { - Some(entry) => entry, - None => { - self.selected_fleet = None; - return; - } - }; - - ui.separator(); - ui.vertical(|ui| { - ui.horizontal(|ui| { - if ui.button("Info").clicked() { - self.menu_panel = FleetMenuPanelSel::Info; - } - if ui.button("Schedule").clicked() { - self.menu_panel = FleetMenuPanelSel::Schedule; - } - }); - - egui::Frame::new() - .stroke(egui::Stroke::new(1.0, egui::Color32::LIGHT_GRAY)) - .show(ui, |ui| { - ui.set_min_width(200.0); - ui.set_min_height(200.0); - ui.style_mut().interaction.selectable_labels = false; - - match self.menu_panel { - FleetMenuPanelSel::Info => - self.paint_fleet_menu_info(star_systems, fleets_man, fleet, ui), - FleetMenuPanelSel::Schedule => - self.fleet_schedule_window.paint(star_systems, fleets_man, fleet, ui) - }; - }); - }); - } - - fn paint_fleet_menu_info( - &mut self, - star_systems: &[SolarSystem], - _fleets_man: &FleetsManager, - fleet: &Fleet, - ui: &mut egui::Ui) - { - if self.menu_panel != FleetMenuPanelSel::Info { - return; - } - - if let Some(orbit) = fleet.orbit() { - let star_system = &star_systems[orbit.system()]; - let parent = star_system.body(orbit.parent()); - - ui.push_id("orbital_info_table", |ui| { - egui_extras::TableBuilder::new(ui) - .column(egui_extras::Column::auto()) - .column(egui_extras::Column::remainder()) - .body(|mut body| { - body.row(16.0, |mut row| { - row.col(|col| { col.label("System"); }); - row.col(|col| { col.label(star_system.name()); }); - }); - body.row(16.0, |mut row| { - row.col(|col| { col.label("Orbiting"); }); - row.col(|col| { col.label(parent.name()); }); - }); - body.row(16.0, |mut row| { - row.col(|col| { col.label("Radius"); }); - row.col(|col| { col.label(format!("{:.1}", orbit.sma())); }); - }); - }); - }); - - ui.separator(); - } - - let heading = fleet.heading(); - egui_extras::TableBuilder::new(ui) - .column(egui_extras::Column::auto()) - .column(egui_extras::Column::remainder()) - .body(|mut body| { - body.row(16.0, |mut row| { - row.col(|col| { col.label("Heading"); }); - row.col(|col| { - col.label(format!("{:?} by {:?}", - cgmath::Deg::<f32>::from(heading.x), - cgmath::Deg::<f32>::from(heading.y) - )); - }); - }); - }); - } - - fn paint_fleet_list( - &mut self, - fleets_man: &FleetsManager, - ui: &mut egui::Ui) - { - let fleet_ids = fleets_man.all(); - ui.vertical(|ui| { - for id in fleet_ids { - if let Some(fleet) = fleets_man.fleet(id) { - self.paint_fleet_entry(fleet, ui); - } - } - }); - } - - fn paint_fleet_entry( - &mut self, - fleet: &Fleet, - ui: &mut egui::Ui) - { - let selected = self.selected_fleet.is_some_and(|id| { id == fleet.id() }); - if ui.selectable_label(selected, fleet.name()).clicked() { - self.select_fleet_from_entries_list(fleet, selected); - } - } - - fn select_fleet_from_entries_list( - &mut self, - fleet: &Fleet, - selected: bool) - { - self.selected_fleet = if selected { None } else { Some(fleet.id()) }; - self.fleet_schedule_window.target = (None, None); - } -} // FleetWindowAction - -impl NewFleetWindowState -{ - pub fn new( - parent_system: SystemId, - parent_body: BodyId) - -> Self { - Self { - open: true, - parent_system: parent_system, - parent_body: parent_body, - ..Default::default() - } - } - - pub fn paint( - &mut self, - ui: &mut egui::Ui, - game_state: &GameState,) - -> Option<NewFleet> - { - let solar_system = &game_state.solar_systems()[self.parent_system]; - let orbiting = solar_system.body(self.parent_body); - - match egui::Window::new("New Fleet") - .collapsible(false) - .open(&mut self.open) - .show(ui.ctx(), |ui| { - ui.vertical(|ui| { - ui.horizontal(|ui| { - ui.label("Name: "); - ui.add(egui::TextEdit::singleline(&mut self.fleet_name)); - }); - ui.label(format!("Orbiting: {}", orbiting.name())); - ui.horizontal(|ui| { - ui.label("SMA: "); - ui.add(egui::DragValue::new(&mut self.fleet_sma) - .range(orbiting.radius()..=f32::INFINITY) - .clamp_existing_to_range(true) - .suffix("km")); - }); - if ui.button("Create").clicked() { - return Some(NewFleet { - system: self.parent_system, - orbiting: self.parent_body, - name: self.fleet_name.clone(), - sma: self.fleet_sma, - }); - } - None - }).inner - }) { - Some(resp) => { - resp.inner? - }, - None => None - } - } -} // impl FleetWindowState - -impl Default for NewFleet -{ - fn default() -> Self { - Self { - system: Default::default(), - orbiting: Default::default(), - name: "New Fleet".to_string(), - sma: Default::default() - } - } -} // impl NewFleet - - diff --git a/src/ui/fleets_widget.rs b/src/ui/fleets_widget.rs new file mode 100644 index 0000000..b31a04b --- /dev/null +++ b/src/ui/fleets_widget.rs @@ -0,0 +1,246 @@ +use std::str::FromStr; + +use crate::GameState; +use crate::fleet::FleetsManager; +use crate::solar_system::body::{BodyId, OrbitalBody}; +use crate::solar_system::{Kilometers, SolarSystem, SystemId}; +use crate::ui::contact::MapContact; + +pub struct FleetsListWidget<'f> +{ + fleets_man: &'f FleetsManager, + selected: &'f mut Option<MapContact>, +} + +pub struct FleetsListResponse +{ + pub response: egui::Response, + pub refocus: Option<(SystemId, MapContact)> +} + +pub struct FleetsControlPanel +{ + new_fleet_modal: Option<NewFleetModal> +} + +pub struct FleetsControlResponse +{ + pub new_fleet: Option<NewFleetModal> +} + +#[derive(Clone)] +pub struct NewFleetModal +{ + pub name: String, + pub sma: Kilometers, +} + +impl<'f> FleetsListWidget<'f> +{ + pub fn new( + fleets_man: &'f FleetsManager, + selected: &'f mut Option<MapContact>) + -> Self + { + Self { + fleets_man, + selected + } + } + + pub fn show_ui( + mut self, + ui: &mut egui::Ui) + -> FleetsListResponse + { + let frame_resp = egui::Frame::new() + .fill(egui::Color32::from_gray(48)) + .outer_margin(egui::Margin::same(4)) + .inner_margin(egui::Margin::same(4)) + .show(ui, + |frame| { + frame.set_max_height(frame.available_height() / 2.0); + self.show_fleets_list(frame) + }); + + FleetsListResponse { + response: frame_resp.response, + refocus: frame_resp.inner + } + } + + fn show_fleets_list( + &mut self, + ui: &mut egui::Ui) + -> Option<(SystemId, MapContact)> + { + egui::ScrollArea::vertical() + .auto_shrink(false) + .show(ui, + |scrollarea| { + let mut refocus: Option<(SystemId, MapContact)> = None; + for id in self.fleets_man.all() { + let fleet = match self.fleets_man.fleet(id) { + Some(v) => v, + None => { continue; } + }; + let contact = MapContact::from_fleet(id); + let selected = self.selected.is_some_and(|v| { v == contact }); + let resp = scrollarea.selectable_label(selected, fleet.name()); + + if resp.double_clicked() { + refocus = Some((fleet.system(), contact)); + }else if resp.clicked() { + *self.selected = if selected { None } else { Some(contact) }; + } + } + refocus + }).inner + } +} // impl FleetsListWidget + +impl FleetsControlPanel +{ + pub fn show( + &mut self, + selected_contact: &Option<MapContact>, + fleets_man: &FleetsManager, + star_system: &SolarSystem, + ui: &mut egui::Ui) + -> FleetsControlResponse + { + let mut response = FleetsControlResponse { + new_fleet: None + }; + + egui::Frame::new() + .inner_margin(egui::Margin::same(4)) + .outer_margin(egui::Margin::same(4)) + .fill(egui::Color32::from_gray(48)) + .show(ui, + |frame| { + frame.horizontal(|horizontal| { + horizontal.take_available_width(); + if horizontal.button("New Fleet").clicked() { + self.new_fleet_modal = Some(NewFleetModal::default()); + } + }); + }); + + if let Some(new_fleet_modal) = &mut self.new_fleet_modal { + let modal = new_fleet_modal.show( + selected_contact, + fleets_man, + star_system, + ui.ctx()); + + if modal.inner { + response.new_fleet = self.new_fleet_modal.take(); + } + + if modal.should_close() { + self.new_fleet_modal = None; + } + } + + response + } +} + +impl Default for FleetsControlPanel +{ + fn default() + -> Self + { + Self { + new_fleet_modal: None + } + } +} + +impl NewFleetModal +{ + pub fn show( + &mut self, + selected_contact: &Option<MapContact>, + fleets_man: &FleetsManager, + star_system: &SolarSystem, + ctx: &egui::Context) + -> egui::ModalResponse<bool> + { + let sma_min = match selected_contact.unwrap_or(MapContact::from_body(0)).body() { + Some(id) => star_system.body(id).radius(), + None => 0.0 + }; + + egui::Modal::new(egui::Id::new("NewFleetModal")).show(ctx, |ui| { + ui.set_width(256.0); + ui.heading("Create new debug fleet"); + + ui.horizontal(|ui| { + ui.label("Name: "); + ui.text_edit_singleline(&mut self.name); + }); + + ui.horizontal(|ui| { + ui.label("Orbiting: "); + ui.label(selected_contact.unwrap_or(MapContact::from_body(0)).name(star_system, fleets_man)); + ui.label(" above "); + ui.add( + egui::DragValue::new(&mut self.sma).range(sma_min..=f32::MAX).suffix("km") + ); + }); + + if ui.button("Create").clicked() { + ui.close(); + return true; + } + false + }) + } +} + +impl Default for NewFleetModal +{ + fn default() + -> Self + { + Self { + name: String::from_str("New Fleet").unwrap(), + sma: 0.0 + } + } +} + +impl GameState +{ + pub fn new_fleet_from_modal( + &mut self, + modal: NewFleetModal, + star_system: SystemId, + contact: Option<MapContact>) + { + let contact = contact.unwrap_or(MapContact::from_body(0)); + let star_system = &self.solar_systems[star_system]; + let body = match contact.body() { + Some(id) => id, + None => 0 + }; + self.fleets.new_fleet_from_modal(modal, star_system, body); + } +} + +impl FleetsManager +{ + pub fn new_fleet_from_modal( + &mut self, + modal: NewFleetModal, + star_system: &SolarSystem, + body: BodyId) + { + let id = self.new_fleet(modal.name, star_system.id()); + let fleet = self.fleet_mut(id).unwrap(); + + fleet.make_orbit(star_system, body, modal.sma); + } +} diff --git a/src/ui/schedule_widget.rs b/src/ui/schedule_widget.rs new file mode 100644 index 0000000..7a0fb25 --- /dev/null +++ b/src/ui/schedule_widget.rs @@ -0,0 +1,6 @@ +pub struct ScheduleWidget +{ + +} + + diff --git a/src/ui/time_control_widget.rs b/src/ui/time_control_widget.rs new file mode 100644 index 0000000..a534998 --- /dev/null +++ b/src/ui/time_control_widget.rs @@ -0,0 +1,89 @@ +use std::hash::Hash; + +use crate::timeman::{self, Second}; + +pub struct TimeControlWidget<'f> +{ + manual_tick: &'f mut Second, + auto_tick: &'f mut (bool, Second) +} + +impl<'f> TimeControlWidget<'f> +{ + pub fn new( + manual_tick: &'f mut Second, + auto_tick: &'f mut (bool, Second)) + -> Self + { + Self { + manual_tick, + auto_tick + } + } + + pub fn show_ui(mut self, + ui: &mut egui::Ui) + -> egui::InnerResponse<Option<Second>> + { + egui::Frame::new().show(ui, |frame| { + frame.set_max_width(224.0); + frame.vertical(|vertical| { + let ret = self.show_manual(vertical); + ret + }).inner + }) + } + + const TICK_OPTIONS: [(&'static str, Second);10] = [ + ("1 second", 1), + ("5 seconds", 5), + ("30 seconds", 30), + ("1 minute", timeman::MINUTE), + ("5 minutes", 5 * timeman::MINUTE), + ("30 minutes", 30 * timeman::MINUTE), + ("1 hour", timeman::HOUR), + ("1 day", timeman::DAY), + ("5 days", 5 * timeman::DAY), + ("30 days", 30 * timeman::DAY) + ]; + + fn tick_selector_combobox<T: Hash>( + ui: &mut egui::Ui, + id_salt: T, + selected: Second) + -> Second + { + let selected_value = Self::TICK_OPTIONS.iter().find(|v| { v.1 == selected }).unwrap(); + egui::ComboBox::new(id_salt, "") + .selected_text(selected_value.0) + .show_ui(ui, + |combobox| { + let mut ret = selected; + for entry in Self::TICK_OPTIONS { + combobox.selectable_value(&mut ret, entry.1, entry.0); + } + ret + }).inner.unwrap_or(selected) + } + + fn show_manual(&mut self, + ui: &mut egui::Ui) + -> Option<Second> + { + + ui.horizontal(|horizontal| { + horizontal.checkbox(&mut self.auto_tick.0, "Auto Advance "); + self.auto_tick.1 = Self::tick_selector_combobox(horizontal, "auto-tick-combobox", self.auto_tick.1); + }); + ui.shrink_width_to_current(); + ui.horizontal(|horizontal| { + let mut tick: Option<Second> = None; + if horizontal.button("Manual Advance").clicked() { + tick = Some(*self.manual_tick); + } + *self.manual_tick = Self::tick_selector_combobox(horizontal, "manual-tick-combobox", *self.manual_tick); + + tick + }).inner + } +} diff --git a/src/ui/topbar.rs b/src/ui/topbar.rs deleted file mode 100644 index f560310..0000000 --- a/src/ui/topbar.rs +++ /dev/null @@ -1,136 +0,0 @@ - -use crate::{GameState, solar_system::SystemId, timeman::{self, Second, TimeMan}, ui::{bodies_window::BodiesWindowState, fleet_window::FleetWindowState}}; - -#[derive(Default, Clone)] -pub struct TopBarState -{ - pub current_system: Option<SystemId>, - pub auto_tick: Option<Second>, - pub do_auto_tick: bool, -} - -#[derive(Default, Clone)] -pub struct TopBarAction -{ - pub advance_tick: Option<Second>, - pub toggle_bodies_window: bool, - pub toggle_fleets_window: bool, -} - -impl TopBarState -{ - pub fn paint( - &mut self, - ui: &mut egui::Ui, - game_state: &GameState, - bodies_window_state: &BodiesWindowState, - fleets_window_state: &FleetWindowState) - -> TopBarAction - { - let mut action: TopBarAction = TopBarAction::default(); - - let solar_systems = game_state.solar_systems(); - let timeman = game_state.timeman(); - - egui::Panel::top("topbar").show_inside( - ui, - |ui| { - - ui.horizontal(|ui| { - ui.vertical(|ui| { - let selected_system_label = match self.current_system { - Some(id) => solar_systems[id].name(), - None => "" - }; - - egui::ComboBox::from_label("Current System") - .selected_text(selected_system_label) - .show_ui(ui, |ui| { - - for (i, system) in solar_systems.iter().enumerate() { - ui.selectable_value( - &mut self.current_system, - Some(i), - system.name() - ); - } - }); - - ui.horizontal(|ui| { - self.paint_empire_buttons(ui, &mut action, bodies_window_state, fleets_window_state); - }); - }); - - self.paint_tick_buttons(&mut action, ui); - - }); - ui.vertical_centered_justified(|ui| { - let time_str = TimeMan::format_duration(timeman.seconds()); - ui.label( - egui::RichText::new(time_str) - .font(egui::FontId::monospace(16.0)) - ); - }); - }); - action - } - - pub fn paint_empire_buttons( - &mut self, - ui: &mut egui::Ui, - action: &mut TopBarAction, - bodies_window_state: &BodiesWindowState, - fleets_window_state: &FleetWindowState - ) - { - if ui.add(egui::Button::new("Bodies").selected(bodies_window_state.open)).clicked() { - action.toggle_bodies_window = true; - } - if ui.add(egui::Button::new("Fleets").selected(fleets_window_state.open)).clicked() { - action.toggle_fleets_window = true; - } - } - - pub fn paint_tick_buttons( - &mut self, - action: &mut TopBarAction, - ui: &mut egui::Ui) - { - let button_seconds = [ - 1, - 5, - 30, - timeman::MINUTE, - timeman::MINUTE * 5, - timeman::MINUTE * 30, - timeman::HOUR, - timeman::DAY, - timeman::DAY * 5, - timeman::YEAR - ]; - let selected_button = self.auto_tick; - - ui.vertical(|ui| { - ui.label("Manual"); - ui.checkbox(&mut self.do_auto_tick, "Auto"); - }); - - button_seconds.iter().for_each(|&seconds| { - ui.vertical(|ui| { - let auto_selected = match selected_button { - Some(o) => o == seconds, - None => false - }; - let label = TimeMan::format_duration(seconds); - - if ui.button(label.clone()).clicked() { - action.advance_tick = Some(seconds); - } - - if ui.add(egui::Button::new(label.clone()).selected(auto_selected)).clicked() { - self.auto_tick = Some(seconds); - } - }); - }); - } -} |
