1
Fork 0
cruisecontrol/interface/src/gui.rs

132 lines
5.2 KiB
Rust

use std::{cell::OnceCell, path::PathBuf, time::Duration};
use common::{ControlPacket, TelemetryPacket};
use eframe::{egui::{self, containers, Align2, Checkbox, Context, Id, Label}, Storage};
use tokio::{runtime::Runtime, sync::{mpsc, watch::Receiver}};
use egui_toast::{Toast, Toasts};
use crate::storage_dir::storage_dir;
pub const GUI: OnceCell<Context> = OnceCell::new();
pub fn gui(data: Receiver<GUIData>, toasts: mpsc::Receiver<Toast>, executor: Runtime) -> eframe::Result {
let options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default()
.with_inner_size([700.0, 480.0])
.with_app_id("cruisecontrol"),
..Default::default()
};
eframe::run_native(
"Cruise Control Dashboard",
options,
Box::new(|cc| {
// This gives us image support:
egui_extras::install_image_loaders(&cc.egui_ctx);
Ok(Box::new(GUI::with_receivers(data, toasts, executor, cc.storage.unwrap())))
}),
)
}
#[derive(Default)]
pub struct GUIData {
pub telemetry: Option<TelemetryPacket>,
pub last_command: Option<ControlPacket>,
}
#[cfg(target_os = "linux")]
const DEFAULT_VOLUME_THRESHOLD: f32 = 5.0;
#[cfg(target_os = "windows")]
const DEFAULT_VOLUME_THRESHOLD: f32 = 1.0;
const DEFAULT_TURN_GAIN: f32 = 0.3;
const DEFAULT_FIRE_DISTANCE: f32 = 55.0;
struct GUI {
data: Receiver<GUIData>,
toasts: mpsc::Receiver<Toast>,
executor: Option<Runtime>,
volume_threshold: f32,
auto_turn_gain: f32,
/// mm
auto_fire_distance: f32,
selected_auto: usize,
}
impl GUI {
fn with_receivers(data: Receiver<GUIData>, toasts: mpsc::Receiver<Toast>, executor: Runtime, storage: &dyn Storage) -> Self {
let volume_threshold: f32 = storage.get_string("volume_threshold").map(|s| s.parse().ok()).flatten().unwrap_or(DEFAULT_VOLUME_THRESHOLD);
let auto_turn_gain: f32 = storage.get_string("auto_turn_gain").map(|s| s.parse().ok()).flatten().unwrap_or(DEFAULT_TURN_GAIN);
let auto_fire_distance: f32 = storage.get_string("auto_fire_distance").map(|s| s.parse().ok()).flatten().unwrap_or(DEFAULT_FIRE_DISTANCE);
let selected_auto: usize = storage.get_string("selected_auto").map(|s| s.parse().ok()).flatten().unwrap_or(0);
Self {
data,
toasts,
executor: Some(executor),
volume_threshold,
auto_turn_gain,
auto_fire_distance,
selected_auto,
}
}
}
impl eframe::App for GUI {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
let _ = GUI.set(ctx.clone());
egui::SidePanel::right(Id::new("config")).resizable(false).show(ctx, |ui| {
ui.heading("configuration");
ui.add(egui::Slider::new(&mut self.volume_threshold, 0.0..=10.0).text("volume threshold"));
ui.label("higher accepts less noise (better)");
ui.add(egui::Slider::new(&mut self.auto_turn_gain, 0.0..=1.0).text("auto turn kP"));
ui.add(egui::Slider::new(&mut self.auto_fire_distance, 30.0..=100.0).text("auto fire distance (mm)"));
ui.horizontal(|ui| {
egui::ComboBox::new("auto selector", "select auto")
.show_index(ui, &mut self.selected_auto, 3, |n| ["a","b","c"][n]);
ui.button("refresh").clicked();
});
let storage = _frame.storage_mut().unwrap();
storage.set_string("volume_threshold", format!("{}",self.volume_threshold));
storage.set_string("auto_turn_gain", format!("{}",self.auto_turn_gain));
storage.set_string("auto_fire_distance", format!("{}",self.auto_fire_distance));
storage.set_string("selected_auto", format!("{}",self.selected_auto));
});
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Cruise Control");
if let Some(ref command) = self.data.borrow().last_command {
ui.label(format!("sending {command:?}"));
}
if let Some(ref telem) = self.data.borrow().telemetry {
ui.label(format!("Left tof: {}", if let Some(tof) = telem.sensors.tof_l {format!("{tof}mm")} else {"".into()}));
ui.label(format!("Right tof: {}", if let Some(tof) = telem.sensors.tof_r {format!("{tof}mm")} else {"".into()}));
ui.label(format!("Side tof: {}", if let Some(tof) = telem.sensors.tof_s {format!("{tof}mm")} else {"".into()}));
ui.label(format!("Gyro: {}", if telem.sensors.gyro.is_some() {""} else {""}));
ui.label(format!("Cam: {:?}", telem.cam_state));
} else {
ui.label("disconnected");
}
});
let mut toasts = Toasts::new()
.anchor(Align2::RIGHT_BOTTOM, (-10.0, -10.0)) // 10 units from the bottom right corner
.direction(egui::Direction::BottomUp);
while let Ok(toast) = self.toasts.try_recv() {
toasts.add(toast);
}
toasts.show(ctx);
if ctx.input(|i| i.viewport().close_requested()) {
self.executor.take().unwrap().shutdown_timeout(Duration::from_millis(100));
}
}
}