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 | |
| parent | 14ca7b5fc15eb2618b46bde0cac85e37ebc9ebd9 (diff) | |
| download | systemic4x-0b428d94e751dc4a5fbe19418bfb5994cebfa54c.tar.gz systemic4x-0b428d94e751dc4a5fbe19418bfb5994cebfa54c.tar.bz2 systemic4x-0b428d94e751dc4a5fbe19418bfb5994cebfa54c.zip | |
Diffstat (limited to 'src')
| -rw-r--r-- | src/fleet.rs | 45 | ||||
| -rw-r--r-- | src/main.rs | 2 | ||||
| -rw-r--r-- | src/solar_system/body.rs | 31 | ||||
| -rw-r--r-- | src/tacmap.rs | 28 | ||||
| -rw-r--r-- | src/tacmap/body_render.rs | 4 | ||||
| -rw-r--r-- | src/tacmap/camera.rs | 68 | ||||
| -rw-r--r-- | src/ui.rs | 225 | ||||
| -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 | ||||
| -rw-r--r-- | src/window.rs | 54 |
18 files changed, 936 insertions, 929 deletions
diff --git a/src/fleet.rs b/src/fleet.rs index 2d7dad5..f8183d4 100644 --- a/src/fleet.rs +++ b/src/fleet.rs @@ -12,7 +12,6 @@ use crate::solar_system::body::BodyId; use crate::solar_system::orbit::StaticOrbiter; use crate::solar_system::{SystemId, GRAVITATIONAL_CONSTANT, Kilometers, SolarSystem, orbit::StaticOrbit}; use crate::timeman::{HOUR, Second}; -use crate::ui::fleet_window::NewFleet; pub type FleetId = usize; @@ -31,7 +30,7 @@ pub struct Fleet angular_velocity: cgmath::Vector2<f32>, angular_acceleration: cgmath::Vector2<f32>, - system: Option<SystemId>, + system: SystemId, baked_orbit: Option<(f64, StaticOrbit)>, } @@ -65,7 +64,8 @@ impl Fleet pub fn new( id: FleetId, - name: String) + name: String, + system: SystemId) -> Self { Self { @@ -83,7 +83,7 @@ impl Fleet angular_velocity: cgmath::vec2(0.0, 0.0), angular_acceleration: cgmath::vec2(0.0, 0.0), - system: None, + system: system, baked_orbit: None } } @@ -96,7 +96,7 @@ impl Fleet { &self.offset } pub fn heading(&self) -> &cgmath::Vector2<cgmath::Rad<f32>> { &self.heading } - pub fn system(&self) -> Option<SystemId> + pub fn system(&self) -> SystemId { self.system } pub fn make_orbit( @@ -109,7 +109,7 @@ impl Fleet let sgp = body.mass() * GRAVITATIONAL_CONSTANT; self.baked_orbit = Some((sgp, StaticOrbit::new_circular(body, radius))); - self.system = Some(star_system.id()); + self.system = star_system.id(); } fn tick( @@ -118,7 +118,7 @@ impl Fleet time: &Range<Second>) { if let Some((_sgp, orbit)) = &self.baked_orbit { - let solar_system = &star_systems[self.system().unwrap()]; + let solar_system = &star_systems[self.system]; let body = solar_system.body(orbit.parent()); self.origin = body.absolute_position(solar_system, time.end); @@ -159,7 +159,7 @@ impl FleetsManager -> Vec<usize> { self.fleets.values().filter_map(|v| { - if v.system().is_some_and(|id| { id == system }) { + if v.system() == system { Some(v.id()) }else { None @@ -172,6 +172,16 @@ impl FleetsManager pub fn fleet(&self, id: FleetId) -> Option<&Fleet> { self.fleets.get(&id) } + pub fn new_fleet(&mut self, + name: String, + system: SystemId) + -> FleetId + { + let id = self.reserve_new_id(); + self.fleets.insert(id, Fleet::new(id, name, system)); + id + } + fn subtick( &mut self, star_systems: &[SolarSystem], @@ -221,25 +231,6 @@ impl FleetsManager } } // impl FleetsManager -impl GameState -{ - pub fn new_fleet_from_ui( - &mut self, - info: NewFleet) - -> Result<FleetId, Box<dyn Error>> - { - let star_system = &self.solar_systems[info.system]; - - let fleet_id = self.fleets.reserve_new_id(); - let mut fleet = Fleet::new(fleet_id, info.name); - fleet.make_orbit(star_system, info.orbiting, info.sma); - - self.fleets.fleets.insert(fleet_id, fleet); - - Ok(fleet_id) - } -} - impl SolarSystem { pub fn fleets( diff --git a/src/main.rs b/src/main.rs index e4d0518..58a263e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -159,7 +159,7 @@ impl ApplicationHandler for SystemicApp game_state.borrow_mut().update(delta_time); window.update(game_state, delta_time); - match window.render(game_state, event_resp) { + match window.render(game_state, event_resp, delta_time) { Ok(_) => {} Err(wgpu::CurrentSurfaceTexture::Suboptimal(_) | wgpu::CurrentSurfaceTexture::Outdated | diff --git a/src/solar_system/body.rs b/src/solar_system/body.rs index f3aef8c..5bf41b5 100644 --- a/src/solar_system/body.rs +++ b/src/solar_system/body.rs @@ -1,4 +1,4 @@ -use crate::timeman; +use crate::{timeman}; use super::*; @@ -17,7 +17,6 @@ pub struct OrbitalBody sgp: f64, orbit: Option<StaticOrbit>, - rel_pos: Option<(i64, cgmath::Vector3<Kilometers>)> } impl StaticOrbiter for OrbitalBody { @@ -58,8 +57,7 @@ impl OrbitalBody record.semi_major_axis )), None => None - }, - rel_pos: None + } } } @@ -70,7 +68,20 @@ impl OrbitalBody pub fn mass(&self) -> Kilograms { self.mass } pub fn sgp(&self) -> f64 { self.sgp } - pub fn relative_position( + pub fn origin_position( + &self, + star_system: &SolarSystem, + time: Second) + -> cgmath::Vector3<Kilometers> + { + if let Some(orbit) = &self.orbit { + star_system.body(orbit.parent()).absolute_position(star_system, time) + }else{ + cgmath::vec3(0.0, 0.0, 0.0) + } + } + + pub fn offset_position( &self, time: Second) -> cgmath::Vector3<f64> @@ -81,17 +92,11 @@ impl OrbitalBody pub fn absolute_position( &self, - solar_system: &SolarSystem, + star_system: &SolarSystem, time: Second) -> cgmath::Vector3<Kilometers> { - match &self.orbit { - Some(orbit) => { - let parent_pos = solar_system.bodies()[orbit.parent()].absolute_position(solar_system, time); - parent_pos + self.relative_position(time) - }, - None => self.relative_position(time) - } + self.origin_position(star_system, time) + self.offset_position(time) } pub fn get_orbit(&self) -> &Option<StaticOrbit> { &self.orbit } diff --git a/src/tacmap.rs b/src/tacmap.rs index e04db9d..d06c720 100644 --- a/src/tacmap.rs +++ b/src/tacmap.rs @@ -162,14 +162,13 @@ impl TacticalMap pub fn update( &mut self, - solar_system: &SolarSystem, + star_system: &SolarSystem, + fleets_man: &FleetsManager, ui_state: &mut ui::State, - time: Second, - dt: Duration) + _time: Second, + _dt: Duration) { self.camera.set_target(ui_state.camera_target); - - self.camera_controller.update(&mut self.camera, solar_system, ui_state, time, dt); } pub fn keyboard_input( @@ -187,6 +186,7 @@ impl TacticalMap fleets_man: &FleetsManager, solar_system: &SolarSystem, timeman: &TimeMan, + dt: Duration, ) -> Result<(), ()> { //Prepare canvas @@ -211,6 +211,13 @@ impl TacticalMap )); } + self.camera_controller.update( + &mut self.camera, + solar_system, + fleets_man, + timeman.seconds(), + dt); + self.camera.update_buffer(wgpuctx, &self.pmatrix); if self.system_for_render.is_none() @@ -298,7 +305,7 @@ impl TacticalMap &self, solar_system: &SolarSystem, time: Second, - screen_size: egui::Vec2, + panel_rect: &egui::Rect, ui: &mut egui::Ui) { let view_matrix = self.camera.view_matrix(); @@ -308,9 +315,9 @@ impl TacticalMap //Paint body labels let bodies = solar_system.bodies(); bodies.iter().for_each(|body| { - let scaled_radius = (screen_size.y * body.radius() * self.camera.get_scale()).max(16.0); + let scaled_radius = (panel_rect.height() * body.radius() * self.camera.get_scale()).max(16.0); - let offset_pos = body.relative_position(time); + let offset_pos = body.offset_position(time); let origin_pos = match body.get_orbit() { Some(orbit) => { solar_system.body_position(&bodies[orbit.parent()], time) @@ -336,8 +343,9 @@ impl TacticalMap } let screen_pos = egui::pos2( - ((ndc_pos.x + 1.0) * 0.5) * screen_size.x, - (((-ndc_pos.y + 1.0) * 0.5) * screen_size.y) + (scaled_radius / clip_pos.w).max(16.0)); + ((ndc_pos.x + 1.0) * 0.5) * panel_rect.width() + panel_rect.min.x, + (((-ndc_pos.y + 1.0) * 0.5) * panel_rect.height() + panel_rect.min.y) + + (scaled_radius / clip_pos.w).max(16.0)); if clip_pos.z < 0.0 { return; } ui.put( diff --git a/src/tacmap/body_render.rs b/src/tacmap/body_render.rs index 23a04c9..1d23a62 100644 --- a/src/tacmap/body_render.rs +++ b/src/tacmap/body_render.rs @@ -103,11 +103,11 @@ impl BodyRenderer let bodies = solar_system.bodies(); let body_instances = bodies.iter().map(|body| { - let position = body.relative_position(time); + let position = body.offset_position(time); let origin = match body.get_orbit() { Some(orbit) => { let origin_body = &bodies[orbit.parent()]; - origin_body.relative_position(time) + origin_body.offset_position(time) }, None => cgmath::Vector3::new(0.0, 0.0, 0.0) }; diff --git a/src/tacmap/camera.rs b/src/tacmap/camera.rs index d751a2a..a2adc84 100644 --- a/src/tacmap/camera.rs +++ b/src/tacmap/camera.rs @@ -4,21 +4,23 @@ use cgmath::{InnerSpace, Rad, Vector2, Vector3, Zero, perspective}; use winit::event::ElementState; use winit::keyboard::KeyCode; +use crate::fleet::FleetsManager; use crate::solar_system::body::OrbitalBody; use crate::solar_system::{self, Kilometers, SolarSystem}; use crate::timeman::Second; use crate::ui; +use crate::ui::contact::MapContact; use crate::wgpuctx::WgpuCtx; pub struct Camera { origin_position: Vector3<Kilometers>, - rel_position: Vector3<f32>, + offset_position: Vector3<f32>, pitch: Rad<f32>, yaw: Rad<f32>, scale: f32, - target: Option<solar_system::body::BodyId>, + target: Option<MapContact>, buffer: wgpu::Buffer, staging_buffer: wgpu::Buffer, @@ -99,11 +101,11 @@ impl Camera ); Self { origin_position: position.into(), - rel_position: Vector3::new(0.0, 0.0, 0.0), + offset_position: Vector3::new(0.0, 0.0, 0.0), yaw: yaw.into(), pitch: pitch.into(), scale: 1e-7, - target: Some(0), + target: Some(MapContact::from_body(0)), buffer: buffer, staging_buffer: staging_buffer, bindgroup: bind_group @@ -118,7 +120,7 @@ impl Camera pub fn get_combined_position(&self) -> Vector3<Kilometers> { - self.origin_position + self.rel_position.map(|v| { v as f64 }) + self.origin_position + self.offset_position.map(|v| { v as f64 }) } pub fn get_rotation(&self) -> Vector2<Rad<f32>> @@ -170,12 +172,12 @@ impl Camera pub fn set_target( &mut self, - target: Option<solar_system::body::BodyId>) + target: Option<MapContact>) { if target == self.target { return; } - if !target.is_some() || !self.target.is_some() { + if target.is_none() { self.origin_position = self.get_combined_position(); - self.rel_position = Vector3::new(0.0, 0.0, 0.0); + self.offset_position = Vector3::new(0.0, 0.0, 0.0); } self.target = target; } @@ -222,9 +224,9 @@ impl Camera self.origin_position.y as f32, self.origin_position.z as f32 ], rel_pos: [ - self.rel_position.x as f32, - self.rel_position.y as f32, - self.rel_position.z as f32 ], + self.offset_position.x as f32, + self.offset_position.y as f32, + self.offset_position.z as f32 ], scale: self.scale, _pad0: 0 @@ -267,26 +269,16 @@ impl CameraController fn update_orbit( &mut self, camera: &mut Camera, - solar_system: &SolarSystem, - target: &OrbitalBody, - time: Second, + target_origin: cgmath::Vector3<Kilometers>, + target_offset: cgmath::Vector3<Kilometers>, dt: Duration) { - let target_radius = (target.radius() * 2.0).max(1.0); + let target_radius = 1.0; camera.scale *= 1.0 + ((self.scale_delta.y - self.scale_delta.x) * 0.1); camera.scale = f32::max(1e-16, f32::min(1.0 / target_radius, camera.scale)); - match target.get_orbit() { - Some(orbit) => { - let parent = solar_system.body(orbit.parent()); - camera.origin_position = parent.absolute_position(solar_system, time); - camera.rel_position = target.relative_position(time).map(|v| { v as f32 }); - }, - None => { - camera.origin_position = target.absolute_position(solar_system, time); - camera.rel_position.set_zero(); - } - } + camera.origin_position = target_origin; + camera.offset_position = target_offset.map(|v| { v as f32 }); let dt = dt.as_secs_f32(); let speed = 1.0; @@ -319,9 +311,9 @@ impl CameraController let right = Vector3::new(-yaw_sin, 0.0, yaw_cos).normalize(); let up = Vector3::new(0.0, 1.0, 0.0); - camera.rel_position += forward * (self.position_pos_delta.z - self.position_neg_delta.z) * speed * dt; - camera.rel_position += right * (self.position_pos_delta.x - self.position_neg_delta.x) * speed * dt; - camera.rel_position += up * (self.position_pos_delta.y - self.position_neg_delta.y) * speed * dt; + camera.offset_position += forward * (self.position_pos_delta.z - self.position_neg_delta.z) * speed * dt; + camera.offset_position += right * (self.position_pos_delta.x - self.position_neg_delta.x) * speed * dt; + camera.offset_position += up * (self.position_pos_delta.y - self.position_neg_delta.y) * speed * dt; camera.pitch.0 += (self.rotation_pos_delta.x - self.rotation_neg_delta.x) * speed * dt; camera.yaw.0 += (self.rotation_pos_delta.y - self.rotation_neg_delta.y) * speed * dt; @@ -330,20 +322,20 @@ impl CameraController pub fn update( &mut self, camera: &mut Camera, - solar_system: &SolarSystem, - ui_state: &mut ui::State, + star_system: &SolarSystem, + fleets_man: &FleetsManager, time: Second, dt: Duration) { - match ui_state.camera_target { - Some(body_id) => { - - let body = &solar_system.bodies()[body_id]; + match camera.target { + Some(target) => { + let target_origin = target.origin_position(star_system, fleets_man, time); + let target_offset = target.offset_position(star_system, fleets_man, time); + self.update_orbit( camera, - solar_system, - body, - time, + target_origin, + target_offset, dt); } None => self.update_pan(camera, dt) @@ -1,24 +1,81 @@ -pub mod topbar; -pub mod bodies_window; -pub mod fleet_window; -pub mod fleet_schedule; +pub mod contact; +mod current_system_widget; +mod contacts_widget; +mod fleets_widget; +mod schedule_widget; +mod time_control_widget; use std::{borrow::Borrow, cell::RefCell}; use crate::GameState; -use crate::solar_system::body::BodyId; -use crate::ui::bodies_window::BodiesWindowState; -use crate::ui::fleet_window::FleetWindowState; -use crate::ui::topbar::TopBarState; +use crate::fleet::FleetsManager; +use crate::solar_system::{Kilometers, SolarSystem, SystemId}; +use crate::timeman::Second; +use crate::ui::contact::MapContact; +use crate::ui::contacts_widget::{ContactsListWidget, FocusedContactWidget}; +use crate::ui::current_system_widget::CurrentSystemWidget; +use crate::ui::fleets_widget::{FleetsControlPanel, FleetsListWidget}; +use crate::ui::time_control_widget::TimeControlWidget; -#[derive(Default, Clone)] +#[derive(Default, Eq, PartialEq)] +pub enum LeftPanelMenuMode +{ + #[default] + Contacts, + Fleets +} + +#[derive(Default)] pub struct State { - pub camera_target: Option<BodyId>, + pub current_system: Option<SystemId>, + pub camera_target: Option<MapContact>, + pub selected_contact: Option<MapContact>, + + pub manual_tick: Second, + pub auto_tick: (bool, Second), + + pub left_panel_menu: LeftPanelMenuMode, + pub fleets_control_panel: FleetsControlPanel +} + +pub fn set_theme(ctx: &egui::Context) +{ + ctx.set_theme(egui::Theme::Dark); + ctx.style_mut_of(egui::Theme::Dark, |style| { + let bg_color = egui::Color32::from_gray(64); + style.visuals.panel_fill = bg_color; + style.visuals.window_fill =bg_color; + + style.visuals.window_stroke = egui::Stroke::NONE; + + style.visuals.menu_corner_radius = egui::CornerRadius::ZERO; + style.visuals.widgets.noninteractive.corner_radius = egui::CornerRadius::ZERO; + style.visuals.widgets.inactive.corner_radius = egui::CornerRadius::ZERO; + style.visuals.widgets.hovered.corner_radius = egui::CornerRadius::ZERO; + style.visuals.widgets.active.corner_radius = egui::CornerRadius::ZERO; + style.visuals.widgets.open.corner_radius = egui::CornerRadius::ZERO; - pub topbar_sate: TopBarState, - pub bodies_window: BodiesWindowState, - pub fleet_window: FleetWindowState + style.visuals.widgets.hovered.bg_fill = egui::Color32::from_gray(128); + + style.visuals.widgets.inactive.bg_fill = egui::Color32::from_gray(96); + style.visuals.widgets.inactive.weak_bg_fill = egui::Color32::from_gray(96); + style.visuals.widgets.inactive.fg_stroke = egui::Stroke::new(2.0, egui::Color32::from_gray(196)); + + style.visuals.widgets.active.bg_fill = egui::Color32::from_gray(128); + style.visuals.widgets.active.fg_stroke = egui::Stroke::new(2.0, egui::Color32::from_gray(255)); + + style.visuals.selection.bg_fill = egui::Color32::from_gray(96); + style.visuals.selection.stroke.color = egui::Color32::WHITE; + style.visuals.override_text_color = Some(egui::Color32::WHITE); + style.override_text_style = Some(egui::TextStyle::Monospace); + + style.spacing.window_margin = egui::Margin::same(12); + + style.visuals.widgets.noninteractive.bg_stroke = egui::Stroke::NONE; + + style.interaction.selectable_labels = false; + }); } impl State @@ -28,38 +85,128 @@ impl State game_state: &RefCell<GameState>, ui: &mut egui::Ui) { - let mut game_state = game_state.borrow_mut(); - let topbar_action = self.topbar_sate.paint( - ui, - &game_state, - &self.bodies_window, - &self.fleet_window); + egui::Panel::top("Top panel") + .resizable(false) + .show_inside(ui, + |panel_ui| { + self.show_topbar(&mut game_state.borrow_mut(), panel_ui); + }); - if let Some(by) = topbar_action.advance_tick { - game_state.timeman_mut().advance(by) - } - if topbar_action.toggle_bodies_window { self.bodies_window.open = !self.bodies_window.open; } - if topbar_action.toggle_fleets_window { self.fleet_window.open = !self.fleet_window.open; } - - if let Some(current_system_id) = self.topbar_sate.current_system { - let current_system = &game_state.solar_systems()[current_system_id]; - let bodies_window_action = - self.bodies_window.paint(ui, current_system); - if bodies_window_action.focus_body.is_some() { - self.camera_target = bodies_window_action.focus_body; + let remaining_rect = ui.max_rect(); + let square_size = remaining_rect.height(); + let panel_width = ((remaining_rect.width() - square_size) / 2.0).floor().max(200.0); + + egui::Panel::left("Left panel") + .resizable(false) + .exact_size(panel_width) + .show_inside(ui, + |panel_ui| { + self.show_left_panel(&mut game_state.borrow_mut(), panel_ui); + }); + + egui::Panel::right("Right panel") + .resizable(false) + .exact_size(panel_width) + .show_inside(ui, + |panel_ui| { + + }); +} + + pub fn show_topbar( + &mut self, + game_state: &mut GameState, + ui: &mut egui::Ui) + { + ui.horizontal(|horizontal| { + let star_systems = game_state.solar_systems(); + let fleets_man = game_state.fleets(); + + horizontal.add(CurrentSystemWidget::new(star_systems, &mut self.current_system)); + + if let Some(system_id) = self.current_system { + let current_system = &star_systems[system_id]; + + horizontal.add(egui::Separator::default().spacing(12.0)); + horizontal.add(FocusedContactWidget::new( + self.camera_target.clone(), + current_system, fleets_man)); } - let fleet_window_action = - self.fleet_window.paint( - ui, - game_state.borrow(), - &self.topbar_sate.current_system, - &self.camera_target); + //Horrible hack to allow for right-alignment. + //I chose to use EGUI so i'll stick with EGUI! + horizontal.add_space(horizontal.available_width()-224.0); + let tcw = TimeControlWidget::new(&mut self.manual_tick, &mut self.auto_tick) + .show_ui(horizontal); - if let Some(new_fleet) = fleet_window_action.new_fleet { - game_state.new_fleet_from_ui(new_fleet); + if let Some(tick) = tcw.inner { + game_state.timeman_mut().advance(tick); } + }); + } + + pub fn show_left_panel( + &mut self, + game_state: &mut GameState, + ui: &mut egui::Ui) + { + if self.current_system.is_none() { + return; } + + ui.vertical(|vertical| { + let star_systems = game_state.solar_systems(); + let fleets = game_state.fleets(); + let current_system = &star_systems[self.current_system.unwrap()]; + + vertical.horizontal(|horizontal| { + if horizontal.selectable_label( + self.left_panel_menu == LeftPanelMenuMode::Contacts, + "Contacts") + .clicked() { + self.left_panel_menu = LeftPanelMenuMode::Contacts; + } + if horizontal.selectable_label( + self.left_panel_menu == LeftPanelMenuMode::Fleets, + "Fleets") + .clicked() { + self.left_panel_menu = LeftPanelMenuMode::Fleets; + } + }); + + match self.left_panel_menu { + LeftPanelMenuMode::Contacts => { + let camera_refocus = ContactsListWidget::new( + &mut self.selected_contact, + current_system, + fleets) + .show_ui(vertical); + if camera_refocus.inner.is_some() { + self.camera_target = camera_refocus.inner; + } + }, + LeftPanelMenuMode::Fleets => { + let flw_resp = + FleetsListWidget::new(fleets, &mut self.selected_contact) + .show_ui(vertical); + + + let fcp_resp = self.fleets_control_panel.show( + &self.selected_contact, + fleets, current_system, + vertical); + + if let Some(refocus) = flw_resp.refocus { + self.current_system = Some(refocus.0); + self.camera_target = Some(refocus.1); + } + + if let Some(new_fleet) = fcp_resp.new_fleet { + game_state.new_fleet_from_modal(new_fleet, current_system.id(), self.selected_contact); + } + } + }; + }); } } 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); - } - }); - }); - } -} diff --git a/src/window.rs b/src/window.rs index 18021f4..4642c13 100644 --- a/src/window.rs +++ b/src/window.rs @@ -6,6 +6,7 @@ use winit::event::{ElementState, WindowEvent}; use winit::keyboard::KeyCode; use crate::tacmap::TacticalMap; +use crate::ui::contact::MapContact; use crate::{GameState, ui}; use crate::wgpuctx::{RenderPassBuilder, SceneCtx, WgpuCtx}; use crate::eguictx::EguiCtx; @@ -37,15 +38,15 @@ impl GameWindow let wgpuctx = pollster::block_on(WgpuCtx::new(instance, window.clone())); let eguictx = EguiCtx::new(&window, &wgpuctx); + ui::set_theme(eguictx.context()); let tacmap = TacticalMap::new(&wgpuctx); let ui_state = ui::State{ - camera_target: Some(0), - topbar_sate: ui::topbar::TopBarState { - current_system : Some(0), - ..Default::default() - }, + current_system: Some(0), + camera_target: Some(MapContact::from_body(0)), + auto_tick: (false, 1), + manual_tick: 1, ..Default::default() }; @@ -65,20 +66,21 @@ impl GameWindow dt: Duration) { let mut game_state = game_state.borrow_mut(); - if self.ui_state.topbar_sate.do_auto_tick { - game_state.timeman.auto_tick = self.ui_state.topbar_sate.auto_tick; - }else{ - game_state.timeman.auto_tick = None; - } + + game_state.timeman.auto_tick = match self.ui_state.auto_tick.0 { + true => Some(self.ui_state.auto_tick.1), + false => None + }; - let current_system = match self.ui_state.topbar_sate.current_system { + let current_system = match self.ui_state.current_system { Some(id) => &game_state.solar_systems()[id], None => { return; } }; self.tactical_map.update( - current_system, - &mut self.ui_state, + current_system, + game_state.fleets(), + &mut self.ui_state, game_state.timeman().seconds(), dt); } @@ -94,7 +96,8 @@ impl GameWindow pub fn render( &mut self, game_state: &RefCell<GameState>, - _egui_event_resp: egui_winit::EventResponse) + _egui_event_resp: egui_winit::EventResponse, + dt: Duration) -> Result<(), wgpu::CurrentSurfaceTexture> { if !self.wgpuctx.is_ready() { return Ok(()); @@ -108,7 +111,7 @@ impl GameWindow let mut scene = SceneCtx::from_view_default(&self.wgpuctx, &view, Some("Systemic window scene")); self.eguictx.prepare(&self.window); - + egui::Window::new("Systemic 4X") .fixed_rect(egui::Rect::from_min_size(egui::pos2(0.0, 0.0), screen_size)) .resizable(false) @@ -120,10 +123,10 @@ impl GameWindow |ui| { self.ui_state.paint(game_state, ui); - if self.ui_state.topbar_sate.current_system.is_some() { + if self.ui_state.current_system.is_some() { let game_state = game_state.borrow(); let fleets_manager = game_state.fleets(); - let current_system = &game_state.solar_systems()[self.ui_state.topbar_sate.current_system.unwrap()]; + let current_system = &game_state.solar_systems()[self.ui_state.current_system.unwrap()]; egui::CentralPanel::no_frame().show_inside(ui, |central_panel| { let panel_rect = central_panel.max_rect(); @@ -137,7 +140,8 @@ impl GameWindow &self.wgpuctx, fleets_manager, current_system, - game_state.timeman()) + game_state.timeman(), + dt) { Ok(_) => {}, Err(_) => { @@ -145,6 +149,13 @@ impl GameWindow panic!(); } } + + self.tactical_map.paint_labels( + current_system, + game_state.timeman().seconds(), + &panel_rect, + central_panel + ); }); let mut pass = RenderPassBuilder::new(Some("Systemic window render pass"), &view) @@ -153,13 +164,6 @@ impl GameWindow //Draw the tactical map canvas. self.tactical_map.paint(&mut pass); - - self.tactical_map.paint_labels( - current_system, - game_state.timeman().seconds(), - screen_size, - ui - ); } }); self.eguictx.present( |
