diff --git a/src/apad/keyboard.rs b/src/apad/keyboard.rs index dae6bc6..bed1893 100644 --- a/src/apad/keyboard.rs +++ b/src/apad/keyboard.rs @@ -682,7 +682,7 @@ fn parse_layout() { [{q}{w}{e}{r}{t}{y}{u}{i}{o}{p}] [{a}{s}{d}{f}{g}{h}{j}{k}{l}] [{⇧:Modifier(Shift);1.5}{z}{x}{c}{v}{b}{n}{m}{<❌:Key(Backspace);1.5}] -[{?123:Layout(Numeric_Mobile);2.0}{,}{Space:Key(Space);3.0}{/}{.}{⮨:Key(Enter);2.0}]", +[{?123:Layout(Numeric_Mobile);2.0}{,}{ :Key(Space);3.0}{/}{.}{⮨:Key(Enter);2.0}]", ); assert!(layout_qwerty.is_ok()); @@ -691,7 +691,7 @@ fn parse_layout() { [{Q}{W}{E}{R}{T}{Y}{U}{I}{O}{P}] [{A}{S}{D}{F}{G}{H}{J}{K}{L}] [{⬆:Modifier(Shift);1.5}{Z}{X}{C}{V}{B}{N}{M}{<❌:Key(Backspace);1.5}] -[{?123:Layout(Numeric_Mobile);2.0}{,}{Space:Key(Space);3.0}{/}{.}{⮨:Key(Enter);2.0}]", +[{?123:Layout(Numeric_Mobile);2.0}{,}{ :Key(Space);3.0}{/}{.}{⮨:Key(Enter);2.0}]", ); assert!(layout_qwerty_shift_once.is_ok()); @@ -700,7 +700,7 @@ fn parse_layout() { [{Q}{W}{E}{R}{T}{Y}{U}{I}{O}{P}] [{A}{S}{D}{F}{G}{H}{J}{K}{L}] [{⮉:Modifier(Shift);1.5}{Z}{X}{C}{V}{B}{N}{M}{<❌:Key(Backspace);1.5}] -[{?123:Layout(Numeric_Mobile);2.0}{,}{Space:Key(Space);3.0}{/}{.}{⮨:Key(Enter);2.0}]", +[{?123:Layout(Numeric_Mobile);2.0}{,}{ :Key(Space);3.0}{/}{.}{⮨:Key(Enter);2.0}]", ); assert!(layout_qwerty_shift_hold.is_ok()); @@ -709,7 +709,7 @@ fn parse_layout() { [{1}{2}{3}{4}{5}{6}{7}{8}{9}{0}] [{@}{#}{$}{_}{%}{&}{-}{+}{(}{)}] [{=\\<:Layout(Signs_Mobile);1.5}{*}{\"}{\'}{\\:}{\\;}{!}{?}{<❌:Key(Backspace);1.5}] -[{ABC:Layout(QWERTY_Mobile);2.0}{,}{Space:Key(Space);3.0}{/}{.}{⮨:Key(Enter);2.0}]", +[{ABC:Layout(QWERTY_Mobile);2.0}{,}{ :Key(Space);3.0}{/}{.}{⮨:Key(Enter);2.0}]", ); assert!(layout_numbers.is_ok()); @@ -717,7 +717,7 @@ fn parse_layout() { "Signs_Mobile [{~}{`}{|}{\\}{=}{^}{<}{>}{[}{]}] [{?123:Layout(Numeric_Mobile);1.5}{*}{\"}{\'}{\\:}{\\;}{!}{?}{<❌:Key(Backspace);1.5}] -[{ABC:Layout(QWERTY_Mobile);2.0}{,}{Space:Key(Space);3.0}{/}{.}{⮨:Key(Enter);2.0}]", +[{ABC:Layout(QWERTY_Mobile);2.0}{,}{ :Key(Space);3.0}{/}{.}{⮨:Key(Enter);2.0}]", ); assert!(layout_symbols.is_ok()); } @@ -829,7 +829,7 @@ impl Default for VirtualKeyboard { [{q}{w}{e}{r}{t}{y}{u}{i}{o}{p}] [{a}{s}{d}{f}{g}{h}{j}{k}{l}] [{⇧:Modifier(Shift);1.5}{z}{x}{c}{v}{b}{n}{m}{<❌:Key(Backspace);1.5}] -[{?123:Layout(Numeric_Mobile);2.0}{,}{Space:Key(Space);3.0}{/}{.}{⮨:Key(Enter);2.0}]", +[{?123:Layout(Numeric_Mobile);2.0}{,}{ :Key(Space);3.0}{/}{.}{⮨:Key(Enter);2.0}]", ) .unwrap(); @@ -839,7 +839,7 @@ impl Default for VirtualKeyboard { [{Q}{W}{E}{R}{T}{Y}{U}{I}{O}{P}] [{A}{S}{D}{F}{G}{H}{J}{K}{L}] [{⬆:Modifier(Shift);1.5}{Z}{X}{C}{V}{B}{N}{M}{<❌:Key(Backspace);1.5}] -[{?123:Layout(Numeric_Mobile);2.0}{/}{Space:Key(Space);3.0}{/}{.}{⮨:Key(Enter);2.0}]", +[{?123:Layout(Numeric_Mobile);2.0}{/}{ :Key(Space);3.0}{/}{.}{⮨:Key(Enter);2.0}]", ) .unwrap() .with_modifiers(ModState::Off, ModState::Once, ModState::Off); @@ -850,7 +850,7 @@ impl Default for VirtualKeyboard { [{Q}{W}{E}{R}{T}{Y}{U}{I}{O}{P}] [{A}{S}{D}{F}{G}{H}{J}{K}{L}] [{⮉:Modifier(Shift);1.5}{Z}{X}{C}{V}{B}{N}{M}{<❌:Key(Backspace);1.5}] -[{?123:Layout(Numeric_Mobile);2.0}{,}{Space:Key(Space);3.0}{/}{.}{⮨:Key(Enter);2.0}]", +[{?123:Layout(Numeric_Mobile);2.0}{,}{ :Key(Space);3.0}{/}{.}{⮨:Key(Enter);2.0}]", ) .unwrap() .with_modifiers(ModState::Off, ModState::Hold, ModState::Off); @@ -860,7 +860,7 @@ impl Default for VirtualKeyboard { [{1}{2}{3}{4}{5}{6}{7}{8}{9}{0}{⇩:Close}] [{@}{#}{$}{_}{%}{&}{-}{+}{(}{)}] [{=\\<:Layout(Symbols_Mobile);1.5}{*}{\"}{\'}{\\:}{\\;}{!}{?}{<❌:Key(Backspace);1.5}] -[{ABC:Layout(QWERTY_Mobile);2.0}{,}{Space:Key(Space);3.0}{/}{.}{⮨:Key(Enter);2.0}]", +[{ABC:Layout(QWERTY_Mobile);2.0}{,}{ :Key(Space);3.0}{/}{.}{⮨:Key(Enter);2.0}]", ) .unwrap(); @@ -868,7 +868,7 @@ impl Default for VirtualKeyboard { "Symbols_Mobile [{~}{`}{|}{\\}{=}{^}{<}{>}{[}{]}] [{?123:Layout(Numeric_Mobile);1.5}{*}{\"}{\'}{\\:}{\\;}{!}{?}{<❌:Key(Backspace);1.5}] -[{ABC:Layout(QWERTY_Mobile);2.0}{,}{Space:Key(Space);3.0}{/}{.}{⮨:Key(Enter);2.0}]", +[{ABC:Layout(QWERTY_Mobile);2.0}{,}{ :Key(Space);3.0}{/}{.}{⮨:Key(Enter);2.0}]", ) .unwrap(); @@ -946,7 +946,7 @@ impl VirtualKeyboard { #[inline] /// Use provided spacing instead of context one - pub fn with_butyon_spacing(mut self, spacing: Vec2) -> Self { + pub fn with_button_spacing(mut self, spacing: Vec2) -> Self { self.button_s = Some(spacing); self } @@ -982,7 +982,7 @@ impl VirtualKeyboard { focused.is_some() && state.focus != focused } - pub fn stop(&mut self) { + pub fn deactivate_next_frame(&mut self) { self.stopping = true; } @@ -1086,13 +1086,6 @@ impl VirtualKeyboard { } let vis = ui.style().noninteractive(); - ui.painter().rect( - kbd_rect, - vis.corner_radius, - vis.weak_bg_fill, - vis.bg_stroke, - egui::StrokeKind::Middle, - ); let draw_btn = |button: &Button, offset: &mut Vec2, ui: &mut Ui| { let kbd_min = kbd_rect.min.to_vec2(); @@ -1185,7 +1178,7 @@ impl VirtualKeyboard { }; match (response.clicked(), response.double_clicked(), action) { (true, _, KeyAction::Close) => { - self.stop(); // ok so, this doesnt work + self.deactivate_next_frame(); } (true, _, KeyAction::Layout(name)) => self.switch_layout(&name), (true, false, KeyAction::Modifier(modifier)) => { diff --git a/src/apad/mod.rs b/src/apad/mod.rs index 898f533..72d8e45 100644 --- a/src/apad/mod.rs +++ b/src/apad/mod.rs @@ -32,7 +32,10 @@ use bevy_egui::{ }; use bevy_mod_openxr::session::OxrSession; use bevy_pkv::{PersistentResourceAppExtensions, PkvStore}; -use egui::{Color32, Margin, PointerButton, Pos2}; +use egui::{ + Button, Color32, Layout, Margin, PointerButton, Pos2, Rect, TextEdit, Theme, Ui, + scroll_area::ScrollSource, +}; use openxr::Path; use serde::{Deserialize, Serialize}; @@ -46,7 +49,7 @@ use crate::{ keyboard::VirtualKeyboard, otdipcplugin::{OtdIpcPlugin, PenButtons, PenDelta, PenPosition}, }, - egui_pages::{Overview, overview}, + egui_pages::*, vrcontrollerplugin::{ LeftController, LeftControllerActions, RightController, RightControllerActions, }, @@ -101,6 +104,9 @@ impl Default for KneeboardPosition { } } +#[derive(Resource, Serialize, Deserialize, Default)] +pub struct KneeboardNotepad(String); + pub struct APadPlugin; impl Plugin for APadPlugin { fn build(&self, app: &mut App) { @@ -109,10 +115,12 @@ impl Plugin for APadPlugin { .add_plugins(MaterialPlugin::::default()); app.insert_resource(PkvStore::new("Avii", "Kneeboard")) - .init_persistent_resource::(); + .init_persistent_resource::() + .init_persistent_resource::(); app.insert_resource(TabletSize(Vec2::new(210.0, 279.0))); - app.insert_resource(TabletResolutionScale(2.25)); + // app.insert_resource(TabletResolutionScale(2.25)); + app.insert_resource(TabletResolutionScale(2.5)); app.insert_resource(PointerImage(None)); app.insert_resource(Name("".to_string())); app.insert_resource(KeyboardVisible(false)); @@ -127,9 +135,11 @@ impl Plugin for APadPlugin { position_kneeboard, move_kneeboard, sync_camera_with_kneeboard, - ), + ) + .chain(), ) - .add_systems(Update, pointer.in_set(EguiInputSet::InitReading)) + .add_systems(Update, update_pointer.in_set(EguiInputSet::WriteEguiEvents)) + .add_systems(Update, render_pointer) .add_systems(WorldspaceContextPass, update); } } @@ -158,29 +168,16 @@ fn setup_pointer( pointer.0 = Some(image); } -fn pointer( +fn update_pointer( tablet_size: Res, tablet_res: Res, ent: Single>, - mesh_material: Single<&MeshMaterial3d, With>, mut pen_buttons: MessageReader, mut pen_delta: MessageReader, mut pen_position: MessageReader, mut input_writer: MessageWriter, - mut materials: ResMut>, - mut images: ResMut>, - mut pointer: ResMut, mut pen_button_pressed: ResMut, ) { - let Some(ref mut pointer) = pointer.0 else { - return; - }; - - let Some(image) = images.get_mut(&*pointer) else { - return; - }; - - image.clear(&[0u8; 4]); for pos in pen_position.read() { let x = pos.x * tablet_size.x * **tablet_res; let y = pos.y * tablet_size.y * **tablet_res; @@ -206,6 +203,7 @@ fn pointer( continue; } + // this is making me drop inputs or something if **pen_button_pressed != button.tip() { input_writer.write(EguiInputEvent { context: *ent, @@ -219,7 +217,31 @@ fn pointer( **pen_button_pressed = button.tip(); } } + } +} +fn render_pointer( + tablet_size: Res, + tablet_res: Res, + mesh_material: Single<&MeshMaterial3d, With>, + mut pen_position: MessageReader, + mut materials: ResMut>, + mut images: ResMut>, + mut pointer: ResMut, +) { + let Some(ref mut pointer) = pointer.0 else { + return; + }; + + let Some(image) = images.get_mut(&*pointer) else { + return; + }; + + image.clear(&[0u8; 4]); + + for pos in pen_position.read() { + let x = pos.x * tablet_size.x * **tablet_res; + let y = pos.y * tablet_size.y * **tablet_res; draw_pointer(image, x, y); if let Some(material) = materials.get_mut(mesh_material.0.id()) { @@ -354,44 +376,109 @@ fn update( mut input: Single<&mut bevy_egui::EguiInput>, mut keyboard: Single<&mut Keyboard>, + mut notepad: ResMut, + mo: Res, + sr: Res, + pr: Res, + pe: Res, + ta: Res, + sp: Res, + cl: Res, + or: Res, + wt: Res, + su: Res, + ro: Res, + ep: Res, ) { - let bgcolor = egui::containers::Frame { - fill: egui::Color32::from_rgb(43, 44, 47), - inner_margin: Margin { - left: 15, - right: 15, - top: 15, - bottom: 15, - }, + ctx.get_mut().options_mut(|opt| { + opt.input_options.max_click_dist = 24.0; + opt.input_options.max_click_duration = 1.0; + }); + + let margins = egui::containers::Frame { + inner_margin: Margin::same(15), ..Default::default() }; let focus = keyboard.is_active(ctx.get_mut()); let height = if focus { 200.0 } else { 0.0 }; - egui::containers::CentralPanel::default() - .frame(bgcolor) - .show(ctx.get_mut(), |ui| { - egui::containers::TopBottomPanel::bottom("bottom_panel") - .resizable(false) - .height_range(egui::Rangef::new(0., height)) - .show_inside(ui, |ui| { - ui.vertical_centered(|ui| { - keyboard.show(ui); - }); + egui::containers::CentralPanel::default().show(ctx.get_mut(), |ui| { + egui::containers::TopBottomPanel::bottom("bottom_panel") + .resizable(false) + .height_range(egui::Rangef::new(0., height)) + .show_inside(ui, |ui| { + ui.with_layout(Layout::top_down_justified(egui::Align::Center), |ui| { + keyboard.show(ui); }); - - egui::ScrollArea::vertical().show(ui, |ui| { - overview(ui, mo); - - // .. }); + + ui.with_layout(Layout::top_down_justified(egui::Align::LEFT), |ui| { + let rect = ui + .add(TextEdit::multiline(&mut notepad.0).margin(Margin::same(8))) + .rect; + global_theme_preference_switch(ui, &rect); }); + egui::containers::CentralPanel::default() + .frame(margins) + .show_inside(ui, |ui| { + egui::ScrollArea::vertical() + // .scroll_bar_visibility(egui::scroll_area::ScrollBarVisibility::AlwaysHidden) + .scroll_source(ScrollSource::MOUSE_WHEEL | ScrollSource::SCROLL_BAR) + .show(ui, |ui| { + overview(ui, &mo); + ui.add(egui::Separator::default().grow(8.0)); + sitrep(ui, &sr); + ui.add(egui::Separator::default().grow(8.0)); + pilot_roster(ui, &pr); + ui.add(egui::Separator::default().grow(8.0)); + package_elements(ui, &pe); + ui.add(egui::Separator::default().grow(8.0)); + threat_analysis(ui, &ta); + ui.add(egui::Separator::default().grow(8.0)); + steerpoints(ui, &sp); + ui.add(egui::Separator::default().grow(8.0)); + commladder(ui, &cl); + ui.add(egui::Separator::default().grow(8.0)); + ordnance(ui, &or, &mo); + ui.add(egui::Separator::default().grow(8.0)); + weather(ui, &wt); + ui.add(egui::Separator::default().grow(8.0)); + support(ui, &su); + ui.add(egui::Separator::default().grow(8.0)); + rulesofengagement(ui, &ro); + ui.add(egui::Separator::default().grow(8.0)); + emergency(ui, &ep); + }); + }); + }); + keyboard.bump_events(ctx.get_mut(), &mut input.0); } +pub fn global_theme_preference_switch(ui: &mut Ui, rect: &egui::Rect) { + let theme = ui.ctx().theme(); + let icon = if theme == Theme::Dark { "☀" } else { "🌙" }; + let new_theme = if theme == Theme::Dark { + Theme::Light + } else { + Theme::Dark + }; + + let size = 16.; + + let min = egui::pos2(rect.max.x - size, rect.min.y - 2.); + let max = egui::pos2(rect.max.x, size - 2.); + + let new_rect = Rect::from_min_max(min, max); + + if ui.put(new_rect, Button::new(icon).frame(false)).clicked() { + ui.ctx().set_theme(new_theme); + } +} + fn sync_camera_with_kneeboard( kneeboard: Query<&Transform, With>, mut cameras: Query<&mut Transform, (With, Without)>, @@ -482,8 +569,6 @@ fn move_kneeboard( if let Ok(trigger_state) = left.squeeze_click.state(&session, Path::NULL) && trigger_state.current_state { - dbg!("squeeze triggered"); - let Ok(transform) = left_transform.single() else { return; }; diff --git a/src/egui_pages/commladder.rs b/src/egui_pages/commladder.rs new file mode 100644 index 0000000..ec96ed5 --- /dev/null +++ b/src/egui_pages/commladder.rs @@ -0,0 +1,87 @@ +use bevy::{ + ecs::{resource::Resource, system::Res}, + prelude::{Deref, DerefMut}, +}; +use egui::{Label, RichText, Ui, Widget}; +use egui_extras::*; + +use crate::egui_pages::table; + +#[allow(unused)] +#[derive(Default)] +pub struct InnerCommladder { + pub agency: String, + pub callsign: String, + pub uhf: String, + pub vhf: String, + pub notes: String, +} + +#[derive(Resource, Default, Deref, DerefMut)] +pub struct Commladder(pub Vec); + +impl From<&bms_briefing_parser::Comm<'_>> for InnerCommladder { + fn from(value: &bms_briefing_parser::Comm) -> Self { + Self { + agency: value.agency.to_string(), + callsign: value.callsign.unwrap_or_default().to_string(), + uhf: value.uhf.unwrap_or_default().to_string(), + vhf: value.vhf.unwrap_or_default().to_string(), + notes: value.notes.unwrap_or_default().to_string(), + } + } +} + +pub fn commladder(ui: &mut Ui, data: &Res) { + let text_height = egui::TextStyle::Body + .resolve(ui.style()) + .size + .max(ui.spacing().interact_size.y); + + table( + ui, + "Comm Ladder", + &[ + ("Agency", Column::remainder()), + ("Callsign", Column::remainder()), + ("UFH [ch]", Column::remainder()), + ("VHF [ch]", Column::remainder()), + ("Notes", Column::remainder()), + ], + |mut body| { + for commladder in data.0.iter() { + body.row(text_height, |mut row| { + row.col(|ui| { + Label::new(RichText::from(&commladder.agency)) + .wrap_mode(egui::TextWrapMode::Extend) + .ui(ui); + }); + + row.col(|ui| { + Label::new(RichText::from(&commladder.callsign)) + .wrap_mode(egui::TextWrapMode::Extend) + .ui(ui); + }); + + row.col(|ui| { + Label::new(RichText::from(&commladder.uhf)) + .wrap_mode(egui::TextWrapMode::Extend) + .ui(ui); + }); + + row.col(|ui| { + Label::new(RichText::from(&commladder.vhf)) + .wrap_mode(egui::TextWrapMode::Extend) + .ui(ui); + }); + + row.col(|ui| { + Label::new(RichText::from(&commladder.notes)) + .wrap_mode(egui::TextWrapMode::Extend) + .ui(ui); + }); + }); + } + }, + ); +} diff --git a/src/egui_pages/emergency.rs b/src/egui_pages/emergency.rs new file mode 100644 index 0000000..4c88cc2 --- /dev/null +++ b/src/egui_pages/emergency.rs @@ -0,0 +1,25 @@ +use bevy::{ + ecs::{resource::Resource, system::Res}, + prelude::{Deref, DerefMut}, +}; +use egui::{Layout, Ui}; + +#[derive(Resource, Default, Deref, DerefMut)] +pub struct Emergency(String); + +impl From for Emergency { + fn from(value: bms_briefing_parser::Emergency) -> Self { + Self(value.0) + } +} + +pub fn emergency(ui: &mut Ui, data: &Res) { + ui.vertical_centered(|ui| { + ui.heading("Emergency Procedures"); + }); + + // let data = RichText::from(&**data); + ui.with_layout(Layout::top_down_justified(egui::Align::LEFT), |ui| { + ui.label(&data.0); + }); +} diff --git a/src/egui_pages/mod.rs b/src/egui_pages/mod.rs index b6950c0..97100d1 100644 --- a/src/egui_pages/mod.rs +++ b/src/egui_pages/mod.rs @@ -1,5 +1,31 @@ +mod commladder; +mod emergency; +mod ordnance; mod overview; +mod package_elements; +mod pilot_roster; +mod roe; +mod sitrep; +mod steerpoints; +mod support; +mod threat_analysis; +mod weather; +pub use commladder::*; +pub use emergency::*; +pub use ordnance::*; +pub use overview::*; +pub use package_elements::*; +pub use pilot_roster::*; +pub use roe::*; +pub use sitrep::*; +pub use steerpoints::*; +pub use support::*; +pub use threat_analysis::*; +pub use weather::*; + +use egui::Ui; +use egui_extras::{Column, TableBody, TableBuilder}; use std::{fs::File, io::Read, path::PathBuf}; use bevy::{ @@ -12,7 +38,6 @@ use bevy::{ }; use encoding_rs::WINDOWS_1252; use encoding_rs_io::DecodeReaderBytesBuilder; -pub use overview::*; use crate::BriefingPath; @@ -34,9 +59,21 @@ impl BmsPlugin { impl Plugin for BmsPlugin { fn build(&self, app: &mut App) { - app.init_resource::(); app.init_resource::(); + app.init_resource::(); + app.init_resource::(); + app.init_resource::(); + app.init_resource::(); + app.init_resource::(); + app.init_resource::(); + app.init_resource::(); + app.init_resource::(); + app.init_resource::(); + app.init_resource::(); + app.init_resource::(); + app.init_resource::(); + app.insert_resource(BriefingPath(self.briefing_path.clone())); let mut timer = Timer::from_seconds(5.0, TimerMode::Repeating); @@ -52,7 +89,18 @@ fn update_briefing( briefing_path: Res, mut timer: ResMut, mut briefing: ResMut, - mut overview: ResMut, + mut mo: ResMut, + mut sr: ResMut, + mut pr: ResMut, + mut pe: ResMut, + mut ta: ResMut, + mut sp: ResMut, + mut cl: ResMut, + mut or: ResMut, + mut wt: ResMut, + mut su: ResMut, + mut ro: ResMut, + mut ep: ResMut, ) { if !timer.0.tick(time.delta()).just_finished() { return; @@ -71,10 +119,84 @@ fn update_briefing( .read_to_string(&mut buf) .unwrap(); - if briefing.0 != buf { - *overview = bms_briefing_parser::Overview::from_briefing(&buf).into(); + let buf = buf.replace(|c: char| !c.is_ascii(), ""); - briefing.0 = buf; - println!("Updated..."); + if briefing.0 == buf { + return; } + + *mo = bms_briefing_parser::Overview::from_briefing(&buf).into(); + *sr = bms_briefing_parser::Sitrep::from_briefing(&buf).into(); + *pr = PilotRoster( + bms_briefing_parser::PilotRoster::from_briefing(&buf) + .iter() + .map(|f| f.into()) + .collect::>(), + ); + *pe = PackageElements( + bms_briefing_parser::PackageElement::from_briefing(&buf) + .iter() + .map(|f| f.into()) + .collect::>(), + ); + *ta = bms_briefing_parser::ThreatAnalysis::from_briefing(&buf).into(); + *sp = Steerpoints( + bms_briefing_parser::Steerpoint::from_briefing(&buf) + .iter() + .map(|f| f.into()) + .collect::>(), + ); + *cl = Commladder( + bms_briefing_parser::Comm::from_briefing(&buf) + .iter() + .map(|f| f.into()) + .collect::>(), + ); + *or = bms_briefing_parser::Ordnance::from_briefing(&buf).into(); + *wt = bms_briefing_parser::Weather::from_briefing(&buf).into(); + *su = bms_briefing_parser::Support::from_briefing(&buf).into(); + *ro = bms_briefing_parser::RulesOfEngagement::from_briefing(&buf).into(); + *ep = bms_briefing_parser::Emergency::from_briefing(&buf).into(); + + briefing.0 = buf; +} + +pub(super) fn table(ui: &mut Ui, name: &str, cols: &[(&str, Column)], add_contents: F) +where + F: for<'b> FnOnce(TableBody<'b>), +{ + if !name.starts_with('_') { + ui.vertical_centered(|ui| { + ui.heading(name); + }); + } + + let text_height = egui::TextStyle::Body + .resolve(ui.style()) + .size + .max(ui.spacing().interact_size.y); + + egui::ScrollArea::horizontal() + .id_salt(format!("{}_scroll", name)) + .scroll_bar_visibility(egui::scroll_area::ScrollBarVisibility::AlwaysHidden) + .show(ui, |ui| { + let mut builder = TableBuilder::new(ui) + .id_salt(format!("{}_table", name)) + .min_scrolled_height(f32::INFINITY) + .striped(true); + + for (_, col) in cols { + builder = builder.column(*col); + } + + builder + .header(text_height, |mut header| { + for (name, _) in cols { + header.col(|ui| { + ui.strong(*name); + }); + } + }) + .body(add_contents); + }); } diff --git a/src/egui_pages/ordnance.rs b/src/egui_pages/ordnance.rs new file mode 100644 index 0000000..f585832 --- /dev/null +++ b/src/egui_pages/ordnance.rs @@ -0,0 +1,77 @@ +use bevy::{ + ecs::{resource::Resource, system::Res}, + prelude::{Deref, DerefMut}, +}; +use egui::Ui; +use egui_extras::*; + +use crate::egui_pages::table; + +#[derive(Resource, Default, Deref, DerefMut)] +pub struct Ordnance { + pub flights: Vec>>, +} + +impl From> for Ordnance { + fn from(value: bms_briefing_parser::Ordnance) -> Self { + Self { + flights: convert(value.flights.clone()), + } + } +} + +pub fn ordnance(ui: &mut Ui, data: &Res, overview: &Res) { + let text_height = egui::TextStyle::Body + .resolve(ui.style()) + .size + .max(ui.spacing().interact_size.y); + + let mut cols = Vec::new(); + + for _ in &data.flights { + cols.push(("", Column::remainder())); + } + + table(ui, "Ordnance", &cols, |mut body| { + for group in &data.flights { + body.row(text_height, |mut row| { + for (i, ord) in group.iter().enumerate() { + row.col(|ui| { + let mut ord = ord.iter().enumerate(); + let name = ord.next().unwrap().1; + table( + ui, + &format!("_{}", i), + &[(name, Column::remainder())], + |mut inner| { + for (_, weap) in ord { + inner.row(text_height, |mut row| { + row.col(|ui| { + if name.starts_with(&overview.callsign) { + ui.label(weap); + } else { + ui.weak(weap); + } + }); + }); + } + }, + ); + }); + } + }); + } + }); +} + +// jesus +fn convert(input: Vec>>) -> Vec>> { + input + .into_iter() + .map(|v| { + v.into_iter() + .map(|v| v.into_iter().map(String::from).collect()) + .collect() + }) + .collect() +} diff --git a/src/egui_pages/overview.rs b/src/egui_pages/overview.rs index ad5130b..2cae5e1 100644 --- a/src/egui_pages/overview.rs +++ b/src/egui_pages/overview.rs @@ -43,31 +43,25 @@ impl From> for Overview { } } -// use egui::Response; - -#[rustfmt::skip] -pub fn overview(ui: &mut Ui, overview: Res) { - - let text_height = egui::TextStyle::Body - .resolve(ui.style()) - .size - .max(ui.spacing().interact_size.y); - - +pub fn overview(ui: &mut Ui, data: &Res) { ui.vertical_centered(|ui| { - ui.heading(format!("Mission Overview - {}", overview.callsign)); + ui.heading(format!("Mission Overview - {}", data.callsign)); }); - let table = TableBuilder::new(ui) - .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) - .column(Column::remainder()) - .column(Column::remainder()) - .column(Column::remainder()) - .column(Column::remainder()); + let text_height = egui::TextStyle::Body + .resolve(ui.style()) + .size + .max(ui.spacing().interact_size.y); - table + TableBuilder::new(ui) + .id_salt("overview_table") .striped(true) .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) + .cell_layout(egui::Layout::left_to_right(egui::Align::Center)) + .column(Column::remainder()) + .column(Column::remainder()) + .column(Column::remainder()) + .column(Column::remainder()) .header(20.0, |mut header| { header.col(|ui| { ui.strong("Mission Type"); @@ -85,16 +79,21 @@ pub fn overview(ui: &mut Ui, overview: Res) { .body(|mut body| { body.row(text_height, |mut row| { row.col(|ui| { - ui.add(Label::new(&overview.mission_type).wrap_mode(egui::TextWrapMode::Wrap)); + ui.add(Label::new(&data.mission_type).wrap_mode(egui::TextWrapMode::Wrap)); }); row.col(|ui| { - ui.add(Label::new(format!("{}", overview.package_id)).wrap_mode(egui::TextWrapMode::Wrap)); + ui.add( + Label::new(format!("{}", data.package_id)) + .wrap_mode(egui::TextWrapMode::Wrap), + ); }); row.col(|ui| { - ui.add(Label::new(&overview.package_description).wrap_mode(egui::TextWrapMode::Wrap)); + ui.add( + Label::new(&data.package_description).wrap_mode(egui::TextWrapMode::Wrap), + ); }); row.col(|ui| { - ui.add(Label::new(&overview.package_mission).wrap_mode(egui::TextWrapMode::Wrap)); + ui.add(Label::new(&data.package_mission).wrap_mode(egui::TextWrapMode::Wrap)); }); }); @@ -115,16 +114,16 @@ pub fn overview(ui: &mut Ui, overview: Res) { body.row(text_height, |mut row| { row.col(|ui| { - ui.add(Label::new(&overview.target_area).wrap_mode(egui::TextWrapMode::Wrap)); + ui.add(Label::new(&data.target_area).wrap_mode(egui::TextWrapMode::Wrap)); }); row.col(|ui| { - ui.add(Label::new(&overview.time_on_target).wrap_mode(egui::TextWrapMode::Wrap)); + ui.add(Label::new(&data.time_on_target).wrap_mode(egui::TextWrapMode::Wrap)); }); row.col(|ui| { - ui.add(Label::new(&overview.sunrise).wrap_mode(egui::TextWrapMode::Wrap)); + ui.add(Label::new(&data.sunrise).wrap_mode(egui::TextWrapMode::Wrap)); }); row.col(|ui| { - ui.add(Label::new(&overview.sunset).wrap_mode(egui::TextWrapMode::Wrap)); + ui.add(Label::new(&data.sunset).wrap_mode(egui::TextWrapMode::Wrap)); }); }); }); diff --git a/src/egui_pages/package_elements.rs b/src/egui_pages/package_elements.rs new file mode 100644 index 0000000..b1731e4 --- /dev/null +++ b/src/egui_pages/package_elements.rs @@ -0,0 +1,122 @@ +use bevy::{ + ecs::{resource::Resource, system::Res}, + prelude::{Deref, DerefMut}, +}; +use egui::{Label, RichText, Ui, Widget}; +use egui_extras::*; + +use crate::egui_pages::table; + +#[derive(Default)] +pub struct InnerPackageElement { + pub primary: bool, + pub callsign: String, + pub flight: String, + pub role: String, + pub aircraft: String, + pub task: String, +} + +#[derive(Resource, Default, Deref, DerefMut)] +pub struct PackageElements(pub Vec); + +impl From<&bms_briefing_parser::PackageElement> for InnerPackageElement { + fn from(value: &bms_briefing_parser::PackageElement) -> Self { + Self { + primary: value.is_primary, + callsign: value.callsign.to_string(), + flight: value.flight.to_string(), + role: value.role.to_string(), + aircraft: value.aircraft.to_string(), + task: value.task.to_string(), + } + } +} + +pub fn package_elements(ui: &mut Ui, data: &Res) { + let text_height = egui::TextStyle::Body + .resolve(ui.style()) + .size + .max(ui.spacing().interact_size.y); + + table( + ui, + "Package Elements", + &[ + ("#", Column::auto_with_initial_suggestion(0.0)), + ("Callsign", Column::auto_with_initial_suggestion(0.0)), + ( + "Flight #", + Column::auto_with_initial_suggestion(0.0).at_least(60.), + ), + ( + "Role", + Column::auto_with_initial_suggestion(0.0).at_least(60.), + ), + ("Aircraft", Column::auto_with_initial_suggestion(0.0)), + ("Task", Column::remainder()), + ], + |mut body| { + for (index, roster) in data.0.iter().enumerate() { + if roster.callsign.is_empty() { + continue; + } + body.row(text_height, |mut row| { + row.col(|ui| { + let mut text = RichText::from(format!("{}", index + 1)); + if roster.primary { + text = text.strong(); + } + Label::new(text) + .wrap_mode(egui::TextWrapMode::Extend) + .ui(ui); + }); + + row.col(|ui| { + let mut text = RichText::from(&roster.callsign); + if roster.primary { + text = text.strong(); + } + Label::new(text) + .wrap_mode(egui::TextWrapMode::Extend) + .ui(ui); + }); + + row.col(|ui| { + let mut text = RichText::from(&roster.flight); + if roster.primary { + text = text.strong(); + } + Label::new(text).wrap_mode(egui::TextWrapMode::Wrap).ui(ui); + }); + + row.col(|ui| { + let mut text = RichText::from(&roster.role); + if roster.primary { + text = text.strong(); + } + Label::new(text).wrap_mode(egui::TextWrapMode::Wrap).ui(ui); + }); + + row.col(|ui| { + let mut text = RichText::from(&roster.aircraft); + if roster.primary { + text = text.strong(); + } + Label::new(text) + .wrap_mode(egui::TextWrapMode::Extend) + .ui(ui); + }); + + row.col(|ui| { + let mut text = RichText::from(&roster.task); + if roster.primary { + text = text.strong(); + } + Label::new(text).wrap_mode(egui::TextWrapMode::Wrap).ui(ui); + }); + }); + } + }, + ); +} diff --git a/src/egui_pages/pilot_roster.rs b/src/egui_pages/pilot_roster.rs new file mode 100644 index 0000000..d06fda4 --- /dev/null +++ b/src/egui_pages/pilot_roster.rs @@ -0,0 +1,101 @@ +use bevy::{ + ecs::{resource::Resource, system::Res}, + prelude::{Deref, DerefMut}, +}; +use egui::{Label, RichText, Ui, Widget}; +use egui_extras::*; + +use crate::egui_pages::table; + +#[derive(Default)] +pub struct InnerPilotRoster { + pub primary: bool, + pub callsign: String, + pub lead: String, + pub wing: String, + pub element: String, + pub four: String, +} + +#[derive(Resource, Default, Deref, DerefMut)] +pub struct PilotRoster(pub Vec); + +impl From<&bms_briefing_parser::PilotRoster<'_>> for InnerPilotRoster { + fn from(value: &bms_briefing_parser::PilotRoster) -> Self { + Self { + primary: value.primary, + callsign: value.callsign.to_string(), + lead: value.lead.to_string(), + wing: value.wing.to_string(), + element: value.element.to_string(), + four: value.four.to_string(), + } + } +} + +pub fn pilot_roster(ui: &mut Ui, data: &Res) { + let text_height = egui::TextStyle::Body + .resolve(ui.style()) + .size + .max(ui.spacing().interact_size.y); + + table( + ui, + "Pilot Roster", + &[ + ("Callsign", Column::remainder()), + ("Lead", Column::remainder()), + ("Wing", Column::remainder()), + ("Element", Column::remainder()), + ("Four", Column::remainder()), + ], + |mut body| { + for roster in data.0.iter() { + if roster.callsign.is_empty() { + continue; + } + body.row(text_height, |mut row| { + row.col(|ui| { + let mut text = RichText::from(&roster.callsign); + if roster.primary { + text = text.strong(); + } + Label::new(text).wrap_mode(egui::TextWrapMode::Wrap).ui(ui); + }); + + row.col(|ui| { + let mut text = RichText::from(&roster.lead); + if roster.primary { + text = text.strong(); + } + Label::new(text).wrap_mode(egui::TextWrapMode::Wrap).ui(ui); + }); + + row.col(|ui| { + let mut text = RichText::from(&roster.wing); + if roster.primary { + text = text.strong(); + } + Label::new(text).wrap_mode(egui::TextWrapMode::Wrap).ui(ui); + }); + + row.col(|ui| { + let mut text = RichText::from(&roster.element); + if roster.primary { + text = text.strong(); + } + Label::new(text).wrap_mode(egui::TextWrapMode::Wrap).ui(ui); + }); + + row.col(|ui| { + let mut text = RichText::from(&roster.four); + if roster.primary { + text = text.strong(); + } + Label::new(text).wrap_mode(egui::TextWrapMode::Wrap).ui(ui); + }); + }); + } + }, + ); +} diff --git a/src/egui_pages/roe.rs b/src/egui_pages/roe.rs new file mode 100644 index 0000000..1f0f4f4 --- /dev/null +++ b/src/egui_pages/roe.rs @@ -0,0 +1,29 @@ +use bevy::{ + ecs::{resource::Resource, system::Res}, + prelude::{Deref, DerefMut}, +}; +use egui::{Layout, Ui}; + +#[derive(Resource, Default, Deref, DerefMut)] +pub struct RulesOfEngagement(String); + +impl From for RulesOfEngagement { + fn from(value: bms_briefing_parser::RulesOfEngagement) -> Self { + Self(value.0.to_string()) + } +} + +pub fn rulesofengagement(ui: &mut Ui, data: &Res) { + if data.0.is_empty() { + return; + } + + ui.vertical_centered(|ui| { + ui.heading("Rules of Engagement"); + }); + + // let data = RichText::from(&**data); + ui.with_layout(Layout::top_down_justified(egui::Align::LEFT), |ui| { + ui.label(&data.0); + }); +} diff --git a/src/egui_pages/sitrep.rs b/src/egui_pages/sitrep.rs new file mode 100644 index 0000000..45b0b05 --- /dev/null +++ b/src/egui_pages/sitrep.rs @@ -0,0 +1,25 @@ +use bevy::{ + ecs::{resource::Resource, system::Res}, + prelude::{Deref, DerefMut}, +}; +use egui::{Layout, Ui}; + +#[derive(Resource, Default, Deref, DerefMut)] +pub struct Sitrep(String); + +impl From for Sitrep { + fn from(value: bms_briefing_parser::Sitrep) -> Self { + Self(value.0) + } +} + +pub fn sitrep(ui: &mut Ui, data: &Res) { + ui.vertical_centered(|ui| { + ui.heading("Situation Report"); + }); + + // let data = RichText::from(&**data); + ui.with_layout(Layout::top_down_justified(egui::Align::LEFT), |ui| { + ui.label(&data.0); + }); +} diff --git a/src/egui_pages/steerpoints.rs b/src/egui_pages/steerpoints.rs new file mode 100644 index 0000000..0122312 --- /dev/null +++ b/src/egui_pages/steerpoints.rs @@ -0,0 +1,125 @@ +use bevy::{ + ecs::{resource::Resource, system::Res}, + prelude::{Deref, DerefMut}, +}; +use egui::{Label, RichText, Ui, Widget}; +use egui_extras::*; + +use crate::egui_pages::table; + +#[allow(unused)] +#[derive(Default)] +pub struct InnerSteerpoint { + pub steerpoint: usize, + pub description: String, + pub time: String, + pub distance: f64, + pub heading: usize, + pub cas: usize, + pub altitude: String, + pub action: String, + pub form: String, + pub comments: String, +} + +#[derive(Resource, Default, Deref, DerefMut)] +pub struct Steerpoints(pub Vec); + +impl From<&bms_briefing_parser::Steerpoint<'_>> for InnerSteerpoint { + fn from(value: &bms_briefing_parser::Steerpoint) -> Self { + Self { + steerpoint: value.steerpoint, + description: value.description.unwrap_or_default().to_string(), + time: value.time.unwrap_or_default().to_string(), + distance: value.distance.unwrap_or_default(), + heading: value.heading.unwrap_or_default(), + cas: value.cas.unwrap_or_default(), + altitude: value.altitude.unwrap_or_default().to_string(), + action: value.action.unwrap_or_default().to_string(), + form: value.form.unwrap_or_default().to_string(), + comments: value.comments.unwrap_or_default().to_string(), + } + } +} + +pub fn steerpoints(ui: &mut Ui, data: &Res) { + let text_height = egui::TextStyle::Body + .resolve(ui.style()) + .size + .max(ui.spacing().interact_size.y); + + table( + ui, + "Steerpoints", + &[ + ("#", Column::remainder()), + ("Desc", Column::remainder()), + ("Time", Column::remainder()), + ("Dist", Column::remainder()), + ("HNG", Column::remainder()), + ("ALT", Column::remainder()), + ("Action", Column::remainder()), + ("Formation", Column::remainder()), + ("Comment", Column::remainder()), + ], + |mut body| { + for steerpoint in data.0.iter() { + body.row(text_height, |mut row| { + row.col(|ui| { + Label::new(RichText::from(format!("{}", steerpoint.steerpoint))) + .wrap_mode(egui::TextWrapMode::Wrap) + .ui(ui); + }); + + row.col(|ui| { + Label::new(RichText::from(&steerpoint.description)) + .wrap_mode(egui::TextWrapMode::Extend) + .ui(ui); + }); + + row.col(|ui| { + Label::new(RichText::from(&steerpoint.time)) + .wrap_mode(egui::TextWrapMode::Extend) + .ui(ui); + }); + + row.col(|ui| { + Label::new(RichText::from(format!("{}nm", steerpoint.distance))) + .wrap_mode(egui::TextWrapMode::Wrap) + .ui(ui); + }); + + row.col(|ui| { + Label::new(RichText::from(format!("{}°", steerpoint.heading))) + .wrap_mode(egui::TextWrapMode::Wrap) + .ui(ui); + }); + + row.col(|ui| { + Label::new(RichText::from(&steerpoint.altitude)) + .wrap_mode(egui::TextWrapMode::Wrap) + .ui(ui); + }); + + row.col(|ui| { + Label::new(RichText::from(&steerpoint.action)) + .wrap_mode(egui::TextWrapMode::Extend) + .ui(ui); + }); + + row.col(|ui| { + Label::new(RichText::from(&steerpoint.form)) + .wrap_mode(egui::TextWrapMode::Extend) + .ui(ui); + }); + + row.col(|ui| { + Label::new(RichText::from(&steerpoint.comments)) + .wrap_mode(egui::TextWrapMode::Extend) + .ui(ui); + }); + }); + } + }, + ); +} diff --git a/src/egui_pages/support.rs b/src/egui_pages/support.rs new file mode 100644 index 0000000..2319eed --- /dev/null +++ b/src/egui_pages/support.rs @@ -0,0 +1,87 @@ +use bevy::{ + ecs::{resource::Resource, system::Res}, + prelude::{Deref, DerefMut}, +}; +use egui::{Label, RichText, Ui, Widget}; +use egui_extras::*; + +use crate::egui_pages::table; + +#[derive(Default)] +pub struct InnerSupport { + pub callsign: String, + pub _type: String, + pub description: String, +} + +#[derive(Resource, Default, Deref, DerefMut)] +pub struct Support(pub Vec); + +impl From> for Support { + fn from(value: bms_briefing_parser::Support) -> Self { + let mut values = value.0.iter(); + let _ = values.next(); // headers, dont need them + + let mut out = Vec::new(); + + for w in values { + let mut w = w.iter(); + if w.len() == 1 { + out.push(InnerSupport { + callsign: String::new(), + _type: String::new(), + description: w.next().unwrap().to_string(), + }); + continue; + } + + out.push(InnerSupport { + callsign: w.next().unwrap().to_string(), + _type: w.next().unwrap().to_string(), + description: w.next().unwrap().to_string(), + }); + } + + Self(out) + } +} + +pub fn support(ui: &mut Ui, data: &Res) { + let text_height = egui::TextStyle::Body + .resolve(ui.style()) + .size + .max(ui.spacing().interact_size.y); + + table( + ui, + "Support", + &[ + ("Callsign", Column::auto()), + ("Type", Column::auto()), + ("Description", Column::remainder()), + ], + |mut body| { + for support in data.0.iter() { + body.row(text_height, |mut row| { + row.col(|ui| { + Label::new(RichText::from(&support.callsign)) + .wrap_mode(egui::TextWrapMode::Extend) + .ui(ui); + }); + + row.col(|ui| { + Label::new(RichText::from(&support._type)) + .wrap_mode(egui::TextWrapMode::Extend) + .ui(ui); + }); + + row.col(|ui| { + Label::new(RichText::from(&support.description)) + .wrap_mode(egui::TextWrapMode::Wrap) + .ui(ui); + }); + }); + } + }, + ); +} diff --git a/src/egui_pages/threat_analysis.rs b/src/egui_pages/threat_analysis.rs new file mode 100644 index 0000000..c4a7a9f --- /dev/null +++ b/src/egui_pages/threat_analysis.rs @@ -0,0 +1,25 @@ +use bevy::{ + ecs::{resource::Resource, system::Res}, + prelude::{Deref, DerefMut}, +}; +use egui::{Layout, Ui}; + +#[derive(Resource, Default, Deref, DerefMut)] +pub struct ThreatAnalysis(String); + +impl From> for ThreatAnalysis { + fn from(value: bms_briefing_parser::ThreatAnalysis) -> Self { + Self(value.0.to_string()) + } +} + +pub fn threat_analysis(ui: &mut Ui, data: &Res) { + ui.vertical_centered(|ui| { + ui.heading("Threat Analysis"); + }); + + // let data = RichText::from(&**data); + ui.with_layout(Layout::top_down_justified(egui::Align::LEFT), |ui| { + ui.label(&data.0); + }); +} diff --git a/src/egui_pages/weather.rs b/src/egui_pages/weather.rs new file mode 100644 index 0000000..60cbd69 --- /dev/null +++ b/src/egui_pages/weather.rs @@ -0,0 +1,55 @@ +use bevy::{ + ecs::{resource::Resource, system::Res}, + prelude::{Deref, DerefMut}, +}; +use egui::{Label, RichText, Ui, Widget}; +use egui_extras::*; + +use crate::egui_pages::table; + +#[derive(Resource, Default, Deref, DerefMut)] +pub struct Weather(pub Vec>); + +impl From> for Weather { + fn from(value: bms_briefing_parser::Weather) -> Self { + Self( + value + .0 + .into_iter() + .map(|inner| inner.into_iter().map(|s| s.to_string()).collect()) + .collect(), + ) + } +} + +pub fn weather(ui: &mut Ui, data: &Res) { + let text_height = egui::TextStyle::Body + .resolve(ui.style()) + .size + .max(ui.spacing().interact_size.y); + + let mut data = data.0.iter(); + + let Some(headers) = data.next() else { + return; + }; + + let mut cols = Vec::new(); + for header in headers { + cols.push((header.as_str(), Column::remainder())); + } + + table(ui, "Weather", &cols, |mut body| { + for rows in data { + body.row(text_height, |mut row| { + for col in rows { + row.col(|ui| { + Label::new(RichText::from(col)) + .wrap_mode(egui::TextWrapMode::Extend) + .ui(ui); + }); + } + }); + } + }); +}