summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/fleet.rs45
-rw-r--r--src/main.rs2
-rw-r--r--src/solar_system/body.rs31
-rw-r--r--src/tacmap.rs28
-rw-r--r--src/tacmap/body_render.rs4
-rw-r--r--src/tacmap/camera.rs68
-rw-r--r--src/ui.rs225
-rw-r--r--src/ui/bodies_window.rs174
-rw-r--r--src/ui/contact.rs100
-rw-r--r--src/ui/contacts_widget.rs146
-rw-r--r--src/ui/current_system_widget.rs47
-rw-r--r--src/ui/fleet_schedule.rs150
-rw-r--r--src/ui/fleet_window.rs314
-rw-r--r--src/ui/fleets_widget.rs246
-rw-r--r--src/ui/schedule_widget.rs6
-rw-r--r--src/ui/time_control_widget.rs89
-rw-r--r--src/ui/topbar.rs136
-rw-r--r--src/window.rs54
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)
diff --git a/src/ui.rs b/src/ui.rs
index e1972d9..fd40ac9 100644
--- a/src/ui.rs
+++ b/src/ui.rs
@@ -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(