pages are fun

This commit is contained in:
2026-02-25 18:32:48 +01:00
parent e258e59a75
commit 07720507f4
15 changed files with 1056 additions and 99 deletions

View File

@@ -682,7 +682,7 @@ fn parse_layout() {
[{q}{w}{e}{r}{t}{y}{u}{i}{o}{p}] [{q}{w}{e}{r}{t}{y}{u}{i}{o}{p}]
[{a}{s}{d}{f}{g}{h}{j}{k}{l}] [{a}{s}{d}{f}{g}{h}{j}{k}{l}]
[{⇧:Modifier(Shift);1.5}{z}{x}{c}{v}{b}{n}{m}{<❌:Key(Backspace);1.5}] [{⇧: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()); assert!(layout_qwerty.is_ok());
@@ -691,7 +691,7 @@ fn parse_layout() {
[{Q}{W}{E}{R}{T}{Y}{U}{I}{O}{P}] [{Q}{W}{E}{R}{T}{Y}{U}{I}{O}{P}]
[{A}{S}{D}{F}{G}{H}{J}{K}{L}] [{A}{S}{D}{F}{G}{H}{J}{K}{L}]
[{⬆:Modifier(Shift);1.5}{Z}{X}{C}{V}{B}{N}{M}{<❌:Key(Backspace);1.5}] [{⬆: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()); assert!(layout_qwerty_shift_once.is_ok());
@@ -700,7 +700,7 @@ fn parse_layout() {
[{Q}{W}{E}{R}{T}{Y}{U}{I}{O}{P}] [{Q}{W}{E}{R}{T}{Y}{U}{I}{O}{P}]
[{A}{S}{D}{F}{G}{H}{J}{K}{L}] [{A}{S}{D}{F}{G}{H}{J}{K}{L}]
[{⮉:Modifier(Shift);1.5}{Z}{X}{C}{V}{B}{N}{M}{<❌:Key(Backspace);1.5}] [{⮉: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()); assert!(layout_qwerty_shift_hold.is_ok());
@@ -709,7 +709,7 @@ fn parse_layout() {
[{1}{2}{3}{4}{5}{6}{7}{8}{9}{0}] [{1}{2}{3}{4}{5}{6}{7}{8}{9}{0}]
[{@}{#}{$}{_}{%}{&}{-}{+}{(}{)}] [{@}{#}{$}{_}{%}{&}{-}{+}{(}{)}]
[{=\\<:Layout(Signs_Mobile);1.5}{*}{\"}{\'}{\\:}{\\;}{!}{?}{<❌:Key(Backspace);1.5}] [{=\\<: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()); assert!(layout_numbers.is_ok());
@@ -717,7 +717,7 @@ fn parse_layout() {
"Signs_Mobile "Signs_Mobile
[{~}{`}{|}{\\}{=}{^}{<}{>}{[}{]}] [{~}{`}{|}{\\}{=}{^}{<}{>}{[}{]}]
[{?123:Layout(Numeric_Mobile);1.5}{*}{\"}{\'}{\\:}{\\;}{!}{?}{<❌:Key(Backspace);1.5}] [{?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()); assert!(layout_symbols.is_ok());
} }
@@ -829,7 +829,7 @@ impl Default for VirtualKeyboard {
[{q}{w}{e}{r}{t}{y}{u}{i}{o}{p}] [{q}{w}{e}{r}{t}{y}{u}{i}{o}{p}]
[{a}{s}{d}{f}{g}{h}{j}{k}{l}] [{a}{s}{d}{f}{g}{h}{j}{k}{l}]
[{⇧:Modifier(Shift);1.5}{z}{x}{c}{v}{b}{n}{m}{<❌:Key(Backspace);1.5}] [{⇧: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(); .unwrap();
@@ -839,7 +839,7 @@ impl Default for VirtualKeyboard {
[{Q}{W}{E}{R}{T}{Y}{U}{I}{O}{P}] [{Q}{W}{E}{R}{T}{Y}{U}{I}{O}{P}]
[{A}{S}{D}{F}{G}{H}{J}{K}{L}] [{A}{S}{D}{F}{G}{H}{J}{K}{L}]
[{⬆:Modifier(Shift);1.5}{Z}{X}{C}{V}{B}{N}{M}{<❌:Key(Backspace);1.5}] [{⬆: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() .unwrap()
.with_modifiers(ModState::Off, ModState::Once, ModState::Off); .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}] [{Q}{W}{E}{R}{T}{Y}{U}{I}{O}{P}]
[{A}{S}{D}{F}{G}{H}{J}{K}{L}] [{A}{S}{D}{F}{G}{H}{J}{K}{L}]
[{⮉:Modifier(Shift);1.5}{Z}{X}{C}{V}{B}{N}{M}{<❌:Key(Backspace);1.5}] [{⮉: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() .unwrap()
.with_modifiers(ModState::Off, ModState::Hold, ModState::Off); .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}] [{1}{2}{3}{4}{5}{6}{7}{8}{9}{0}{⇩:Close}]
[{@}{#}{$}{_}{%}{&}{-}{+}{(}{)}] [{@}{#}{$}{_}{%}{&}{-}{+}{(}{)}]
[{=\\<:Layout(Symbols_Mobile);1.5}{*}{\"}{\'}{\\:}{\\;}{!}{?}{<❌:Key(Backspace);1.5}] [{=\\<: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(); .unwrap();
@@ -868,7 +868,7 @@ impl Default for VirtualKeyboard {
"Symbols_Mobile "Symbols_Mobile
[{~}{`}{|}{\\}{=}{^}{<}{>}{[}{]}] [{~}{`}{|}{\\}{=}{^}{<}{>}{[}{]}]
[{?123:Layout(Numeric_Mobile);1.5}{*}{\"}{\'}{\\:}{\\;}{!}{?}{<❌:Key(Backspace);1.5}] [{?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(); .unwrap();
@@ -946,7 +946,7 @@ impl VirtualKeyboard {
#[inline] #[inline]
/// Use provided spacing instead of context one /// 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.button_s = Some(spacing);
self self
} }
@@ -982,7 +982,7 @@ impl VirtualKeyboard {
focused.is_some() && state.focus != focused focused.is_some() && state.focus != focused
} }
pub fn stop(&mut self) { pub fn deactivate_next_frame(&mut self) {
self.stopping = true; self.stopping = true;
} }
@@ -1086,13 +1086,6 @@ impl VirtualKeyboard {
} }
let vis = ui.style().noninteractive(); 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 draw_btn = |button: &Button, offset: &mut Vec2, ui: &mut Ui| {
let kbd_min = kbd_rect.min.to_vec2(); let kbd_min = kbd_rect.min.to_vec2();
@@ -1185,7 +1178,7 @@ impl VirtualKeyboard {
}; };
match (response.clicked(), response.double_clicked(), action) { match (response.clicked(), response.double_clicked(), action) {
(true, _, KeyAction::Close) => { (true, _, KeyAction::Close) => {
self.stop(); // ok so, this doesnt work self.deactivate_next_frame();
} }
(true, _, KeyAction::Layout(name)) => self.switch_layout(&name), (true, _, KeyAction::Layout(name)) => self.switch_layout(&name),
(true, false, KeyAction::Modifier(modifier)) => { (true, false, KeyAction::Modifier(modifier)) => {

View File

@@ -32,7 +32,10 @@ use bevy_egui::{
}; };
use bevy_mod_openxr::session::OxrSession; use bevy_mod_openxr::session::OxrSession;
use bevy_pkv::{PersistentResourceAppExtensions, PkvStore}; 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 openxr::Path;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -46,7 +49,7 @@ use crate::{
keyboard::VirtualKeyboard, keyboard::VirtualKeyboard,
otdipcplugin::{OtdIpcPlugin, PenButtons, PenDelta, PenPosition}, otdipcplugin::{OtdIpcPlugin, PenButtons, PenDelta, PenPosition},
}, },
egui_pages::{Overview, overview}, egui_pages::*,
vrcontrollerplugin::{ vrcontrollerplugin::{
LeftController, LeftControllerActions, RightController, RightControllerActions, LeftController, LeftControllerActions, RightController, RightControllerActions,
}, },
@@ -101,6 +104,9 @@ impl Default for KneeboardPosition {
} }
} }
#[derive(Resource, Serialize, Deserialize, Default)]
pub struct KneeboardNotepad(String);
pub struct APadPlugin; pub struct APadPlugin;
impl Plugin for APadPlugin { impl Plugin for APadPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
@@ -109,10 +115,12 @@ impl Plugin for APadPlugin {
.add_plugins(MaterialPlugin::<MyCustomMaterial>::default()); .add_plugins(MaterialPlugin::<MyCustomMaterial>::default());
app.insert_resource(PkvStore::new("Avii", "Kneeboard")) app.insert_resource(PkvStore::new("Avii", "Kneeboard"))
.init_persistent_resource::<KneeboardPosition>(); .init_persistent_resource::<KneeboardPosition>()
.init_persistent_resource::<KneeboardNotepad>();
app.insert_resource(TabletSize(Vec2::new(210.0, 279.0))); 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(PointerImage(None));
app.insert_resource(Name("".to_string())); app.insert_resource(Name("".to_string()));
app.insert_resource(KeyboardVisible(false)); app.insert_resource(KeyboardVisible(false));
@@ -127,9 +135,11 @@ impl Plugin for APadPlugin {
position_kneeboard, position_kneeboard,
move_kneeboard, move_kneeboard,
sync_camera_with_kneeboard, sync_camera_with_kneeboard,
),
) )
.add_systems(Update, pointer.in_set(EguiInputSet::InitReading)) .chain(),
)
.add_systems(Update, update_pointer.in_set(EguiInputSet::WriteEguiEvents))
.add_systems(Update, render_pointer)
.add_systems(WorldspaceContextPass, update); .add_systems(WorldspaceContextPass, update);
} }
} }
@@ -158,29 +168,16 @@ fn setup_pointer(
pointer.0 = Some(image); pointer.0 = Some(image);
} }
fn pointer( fn update_pointer(
tablet_size: Res<TabletSize>, tablet_size: Res<TabletSize>,
tablet_res: Res<TabletResolutionScale>, tablet_res: Res<TabletResolutionScale>,
ent: Single<Entity, With<bevy_egui::EguiContext>>, ent: Single<Entity, With<bevy_egui::EguiContext>>,
mesh_material: Single<&MeshMaterial3d<MyCustomMaterial>, With<Kneeboard>>,
mut pen_buttons: MessageReader<PenButtons>, mut pen_buttons: MessageReader<PenButtons>,
mut pen_delta: MessageReader<PenDelta>, mut pen_delta: MessageReader<PenDelta>,
mut pen_position: MessageReader<PenPosition>, mut pen_position: MessageReader<PenPosition>,
mut input_writer: MessageWriter<EguiInputEvent>, mut input_writer: MessageWriter<EguiInputEvent>,
mut materials: ResMut<Assets<MyCustomMaterial>>,
mut images: ResMut<Assets<Image>>,
mut pointer: ResMut<PointerImage>,
mut pen_button_pressed: ResMut<WasPenButtonPressed>, mut pen_button_pressed: ResMut<WasPenButtonPressed>,
) { ) {
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() { for pos in pen_position.read() {
let x = pos.x * tablet_size.x * **tablet_res; let x = pos.x * tablet_size.x * **tablet_res;
let y = pos.y * tablet_size.y * **tablet_res; let y = pos.y * tablet_size.y * **tablet_res;
@@ -206,6 +203,7 @@ fn pointer(
continue; continue;
} }
// this is making me drop inputs or something
if **pen_button_pressed != button.tip() { if **pen_button_pressed != button.tip() {
input_writer.write(EguiInputEvent { input_writer.write(EguiInputEvent {
context: *ent, context: *ent,
@@ -219,7 +217,31 @@ fn pointer(
**pen_button_pressed = button.tip(); **pen_button_pressed = button.tip();
} }
} }
}
}
fn render_pointer(
tablet_size: Res<TabletSize>,
tablet_res: Res<TabletResolutionScale>,
mesh_material: Single<&MeshMaterial3d<MyCustomMaterial>, With<Kneeboard>>,
mut pen_position: MessageReader<PenPosition>,
mut materials: ResMut<Assets<MyCustomMaterial>>,
mut images: ResMut<Assets<Image>>,
mut pointer: ResMut<PointerImage>,
) {
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); draw_pointer(image, x, y);
if let Some(material) = materials.get_mut(mesh_material.0.id()) { 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 input: Single<&mut bevy_egui::EguiInput>,
mut keyboard: Single<&mut Keyboard>, mut keyboard: Single<&mut Keyboard>,
mut notepad: ResMut<KneeboardNotepad>,
mo: Res<Overview>, mo: Res<Overview>,
sr: Res<Sitrep>,
pr: Res<PilotRoster>,
pe: Res<PackageElements>,
ta: Res<ThreatAnalysis>,
sp: Res<Steerpoints>,
cl: Res<Commladder>,
or: Res<Ordnance>,
wt: Res<Weather>,
su: Res<Support>,
ro: Res<RulesOfEngagement>,
ep: Res<Emergency>,
) { ) {
let bgcolor = egui::containers::Frame { ctx.get_mut().options_mut(|opt| {
fill: egui::Color32::from_rgb(43, 44, 47), opt.input_options.max_click_dist = 24.0;
inner_margin: Margin { opt.input_options.max_click_duration = 1.0;
left: 15, });
right: 15,
top: 15, let margins = egui::containers::Frame {
bottom: 15, inner_margin: Margin::same(15),
},
..Default::default() ..Default::default()
}; };
let focus = keyboard.is_active(ctx.get_mut()); let focus = keyboard.is_active(ctx.get_mut());
let height = if focus { 200.0 } else { 0.0 }; let height = if focus { 200.0 } else { 0.0 };
egui::containers::CentralPanel::default() egui::containers::CentralPanel::default().show(ctx.get_mut(), |ui| {
.frame(bgcolor)
.show(ctx.get_mut(), |ui| {
egui::containers::TopBottomPanel::bottom("bottom_panel") egui::containers::TopBottomPanel::bottom("bottom_panel")
.resizable(false) .resizable(false)
.height_range(egui::Rangef::new(0., height)) .height_range(egui::Rangef::new(0., height))
.show_inside(ui, |ui| { .show_inside(ui, |ui| {
ui.vertical_centered(|ui| { ui.with_layout(Layout::top_down_justified(egui::Align::Center), |ui| {
keyboard.show(ui); keyboard.show(ui);
}); });
}); });
egui::ScrollArea::vertical().show(ui, |ui| { ui.with_layout(Layout::top_down_justified(egui::Align::LEFT), |ui| {
overview(ui, mo); 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); 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( fn sync_camera_with_kneeboard(
kneeboard: Query<&Transform, With<Kneeboard>>, kneeboard: Query<&Transform, With<Kneeboard>>,
mut cameras: Query<&mut Transform, (With<MainCamera>, Without<Kneeboard>)>, mut cameras: Query<&mut Transform, (With<MainCamera>, Without<Kneeboard>)>,
@@ -482,8 +569,6 @@ fn move_kneeboard(
if let Ok(trigger_state) = left.squeeze_click.state(&session, Path::NULL) if let Ok(trigger_state) = left.squeeze_click.state(&session, Path::NULL)
&& trigger_state.current_state && trigger_state.current_state
{ {
dbg!("squeeze triggered");
let Ok(transform) = left_transform.single() else { let Ok(transform) = left_transform.single() else {
return; return;
}; };

View File

@@ -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<InnerCommladder>);
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<Commladder>) {
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);
});
});
}
},
);
}

View File

@@ -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<bms_briefing_parser::Emergency> for Emergency {
fn from(value: bms_briefing_parser::Emergency) -> Self {
Self(value.0)
}
}
pub fn emergency(ui: &mut Ui, data: &Res<Emergency>) {
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);
});
}

View File

@@ -1,5 +1,31 @@
mod commladder;
mod emergency;
mod ordnance;
mod overview; 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 std::{fs::File, io::Read, path::PathBuf};
use bevy::{ use bevy::{
@@ -12,7 +38,6 @@ use bevy::{
}; };
use encoding_rs::WINDOWS_1252; use encoding_rs::WINDOWS_1252;
use encoding_rs_io::DecodeReaderBytesBuilder; use encoding_rs_io::DecodeReaderBytesBuilder;
pub use overview::*;
use crate::BriefingPath; use crate::BriefingPath;
@@ -34,9 +59,21 @@ impl BmsPlugin {
impl Plugin for BmsPlugin { impl Plugin for BmsPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.init_resource::<Overview>();
app.init_resource::<RawBriefing>(); app.init_resource::<RawBriefing>();
app.init_resource::<Overview>();
app.init_resource::<Sitrep>();
app.init_resource::<PilotRoster>();
app.init_resource::<PackageElements>();
app.init_resource::<ThreatAnalysis>();
app.init_resource::<Steerpoints>();
app.init_resource::<Commladder>();
app.init_resource::<Ordnance>();
app.init_resource::<Weather>();
app.init_resource::<Support>();
app.init_resource::<RulesOfEngagement>();
app.init_resource::<Emergency>();
app.insert_resource(BriefingPath(self.briefing_path.clone())); app.insert_resource(BriefingPath(self.briefing_path.clone()));
let mut timer = Timer::from_seconds(5.0, TimerMode::Repeating); let mut timer = Timer::from_seconds(5.0, TimerMode::Repeating);
@@ -52,7 +89,18 @@ fn update_briefing(
briefing_path: Res<BriefingPath>, briefing_path: Res<BriefingPath>,
mut timer: ResMut<RefreshTimer>, mut timer: ResMut<RefreshTimer>,
mut briefing: ResMut<RawBriefing>, mut briefing: ResMut<RawBriefing>,
mut overview: ResMut<Overview>, mut mo: ResMut<Overview>,
mut sr: ResMut<Sitrep>,
mut pr: ResMut<PilotRoster>,
mut pe: ResMut<PackageElements>,
mut ta: ResMut<ThreatAnalysis>,
mut sp: ResMut<Steerpoints>,
mut cl: ResMut<Commladder>,
mut or: ResMut<Ordnance>,
mut wt: ResMut<Weather>,
mut su: ResMut<Support>,
mut ro: ResMut<RulesOfEngagement>,
mut ep: ResMut<Emergency>,
) { ) {
if !timer.0.tick(time.delta()).just_finished() { if !timer.0.tick(time.delta()).just_finished() {
return; return;
@@ -71,10 +119,84 @@ fn update_briefing(
.read_to_string(&mut buf) .read_to_string(&mut buf)
.unwrap(); .unwrap();
if briefing.0 != buf { let buf = buf.replace(|c: char| !c.is_ascii(), "");
*overview = bms_briefing_parser::Overview::from_briefing(&buf).into();
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::<Vec<InnerPilotRoster>>(),
);
*pe = PackageElements(
bms_briefing_parser::PackageElement::from_briefing(&buf)
.iter()
.map(|f| f.into())
.collect::<Vec<InnerPackageElement>>(),
);
*ta = bms_briefing_parser::ThreatAnalysis::from_briefing(&buf).into();
*sp = Steerpoints(
bms_briefing_parser::Steerpoint::from_briefing(&buf)
.iter()
.map(|f| f.into())
.collect::<Vec<InnerSteerpoint>>(),
);
*cl = Commladder(
bms_briefing_parser::Comm::from_briefing(&buf)
.iter()
.map(|f| f.into())
.collect::<Vec<InnerCommladder>>(),
);
*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; briefing.0 = buf;
println!("Updated...");
} }
pub(super) fn table<F>(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);
});
} }

View File

@@ -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<Vec<Vec<String>>>,
}
impl From<bms_briefing_parser::Ordnance<'_>> for Ordnance {
fn from(value: bms_briefing_parser::Ordnance) -> Self {
Self {
flights: convert(value.flights.clone()),
}
}
}
pub fn ordnance(ui: &mut Ui, data: &Res<Ordnance>, overview: &Res<super::Overview>) {
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<Vec<&str>>>) -> Vec<Vec<Vec<String>>> {
input
.into_iter()
.map(|v| {
v.into_iter()
.map(|v| v.into_iter().map(String::from).collect())
.collect()
})
.collect()
}

View File

@@ -43,31 +43,25 @@ impl From<bms_briefing_parser::Overview<'_>> for Overview {
} }
} }
// use egui::Response; pub fn overview(ui: &mut Ui, data: &Res<Overview>) {
ui.vertical_centered(|ui| {
#[rustfmt::skip] ui.heading(format!("Mission Overview - {}", data.callsign));
pub fn overview(ui: &mut Ui, overview: Res<Overview>) { });
let text_height = egui::TextStyle::Body let text_height = egui::TextStyle::Body
.resolve(ui.style()) .resolve(ui.style())
.size .size
.max(ui.spacing().interact_size.y); .max(ui.spacing().interact_size.y);
TableBuilder::new(ui)
ui.vertical_centered(|ui| { .id_salt("overview_table")
ui.heading(format!("Mission Overview - {}", overview.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());
table
.striped(true) .striped(true)
.cell_layout(egui::Layout::left_to_right(egui::Align::Center)) .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(20.0, |mut header| {
header.col(|ui| { header.col(|ui| {
ui.strong("Mission Type"); ui.strong("Mission Type");
@@ -85,16 +79,21 @@ pub fn overview(ui: &mut Ui, overview: Res<Overview>) {
.body(|mut body| { .body(|mut body| {
body.row(text_height, |mut row| { body.row(text_height, |mut row| {
row.col(|ui| { 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| { 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| { 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| { 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<Overview>) {
body.row(text_height, |mut row| { body.row(text_height, |mut row| {
row.col(|ui| { 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| { 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| { 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| { 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));
}); });
}); });
}); });

View File

@@ -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<InnerPackageElement>);
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<PackageElements>) {
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);
});
});
}
},
);
}

View File

@@ -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<InnerPilotRoster>);
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<PilotRoster>) {
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);
});
});
}
},
);
}

29
src/egui_pages/roe.rs Normal file
View File

@@ -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<bms_briefing_parser::RulesOfEngagement> for RulesOfEngagement {
fn from(value: bms_briefing_parser::RulesOfEngagement) -> Self {
Self(value.0.to_string())
}
}
pub fn rulesofengagement(ui: &mut Ui, data: &Res<RulesOfEngagement>) {
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);
});
}

25
src/egui_pages/sitrep.rs Normal file
View File

@@ -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<bms_briefing_parser::Sitrep> for Sitrep {
fn from(value: bms_briefing_parser::Sitrep) -> Self {
Self(value.0)
}
}
pub fn sitrep(ui: &mut Ui, data: &Res<Sitrep>) {
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);
});
}

View File

@@ -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<InnerSteerpoint>);
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<Steerpoints>) {
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);
});
});
}
},
);
}

87
src/egui_pages/support.rs Normal file
View File

@@ -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<InnerSupport>);
impl From<bms_briefing_parser::Support<'_>> 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<Support>) {
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);
});
});
}
},
);
}

View File

@@ -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<bms_briefing_parser::ThreatAnalysis<'_>> for ThreatAnalysis {
fn from(value: bms_briefing_parser::ThreatAnalysis) -> Self {
Self(value.0.to_string())
}
}
pub fn threat_analysis(ui: &mut Ui, data: &Res<ThreatAnalysis>) {
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);
});
}

55
src/egui_pages/weather.rs Normal file
View File

@@ -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<Vec<String>>);
impl From<bms_briefing_parser::Weather<'_>> 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<Weather>) {
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);
});
}
});
}
});
}