something
This commit is contained in:
Generated
+57
@@ -30,6 +30,8 @@ version = "0.24.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5351dcebb14b579ccab05f288596b2ae097005be7ee50a7c3d4ca9d0d5a66f6a"
|
||||
dependencies = [
|
||||
"enumn",
|
||||
"serde",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
@@ -225,6 +227,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"getrandom 0.3.4",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
]
|
||||
@@ -1449,6 +1452,7 @@ checksum = "55f6cc0cb3b84a21232c468db972ebcddd34decbf1ff02cdebffd807c13bbd81"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"emath",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1466,6 +1470,7 @@ dependencies = [
|
||||
"egui_glow",
|
||||
"glutin",
|
||||
"glutin-winit",
|
||||
"home",
|
||||
"image",
|
||||
"js-sys",
|
||||
"log",
|
||||
@@ -1477,6 +1482,8 @@ dependencies = [
|
||||
"pollster",
|
||||
"profiling",
|
||||
"raw-window-handle",
|
||||
"ron",
|
||||
"serde",
|
||||
"static_assertions",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
@@ -1501,6 +1508,8 @@ dependencies = [
|
||||
"log",
|
||||
"nohash-hasher",
|
||||
"profiling",
|
||||
"ron",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"unicode-segmentation",
|
||||
]
|
||||
@@ -1541,6 +1550,7 @@ dependencies = [
|
||||
"objc2-ui-kit 0.3.2",
|
||||
"profiling",
|
||||
"raw-window-handle",
|
||||
"serde",
|
||||
"smithay-clipboard",
|
||||
"web-time",
|
||||
"webbrowser",
|
||||
@@ -1597,6 +1607,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a74fbbf7501c430b89df62d102b6bfa02162faaf3e155512c677c9d20f5708d1"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1635,6 +1646,17 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enumn"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "epaint"
|
||||
version = "0.34.2"
|
||||
@@ -1652,6 +1674,7 @@ dependencies = [
|
||||
"parking_lot",
|
||||
"profiling",
|
||||
"self_cell",
|
||||
"serde",
|
||||
"skrifa",
|
||||
"smallvec",
|
||||
"vello_cpu",
|
||||
@@ -1868,6 +1891,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b38ad915f6dadd993ced50848a8291a543bd41ca62bc10740d5e64e2ab4cfd7"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2360,6 +2384,15 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
|
||||
|
||||
[[package]]
|
||||
name = "home"
|
||||
version = "0.5.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "htmlparser"
|
||||
version = "0.2.1"
|
||||
@@ -4819,6 +4852,20 @@ dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ron"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4147b952f3f819eca0e99527022f7d6a8d05f111aeb0a62960c74eb283bec8fc"
|
||||
dependencies = [
|
||||
"bitflags 2.11.1",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"typeid",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rowan"
|
||||
version = "0.16.1"
|
||||
@@ -5216,6 +5263,9 @@ name = "smallvec"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smithay-client-toolkit"
|
||||
@@ -5461,6 +5511,7 @@ dependencies = [
|
||||
"egui_plot",
|
||||
"fuzzy-matcher",
|
||||
"libsvld",
|
||||
"rfd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5839,6 +5890,12 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typeid"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.20.0"
|
||||
|
||||
@@ -12,8 +12,12 @@ bench = false
|
||||
libsvld = { path = "../libsvld" }
|
||||
can-dbc = "9.1"
|
||||
|
||||
eframe = "0.34"
|
||||
eframe = {
|
||||
version = "0.34",
|
||||
features = ["persistence"]
|
||||
}
|
||||
egui = "0.34"
|
||||
egui_plot = "0.35"
|
||||
egui_ltreeview = "0.7"
|
||||
fuzzy-matcher = "0.3"
|
||||
rfd = "0.17"
|
||||
|
||||
+262
-26
@@ -3,7 +3,13 @@ mod plot_list;
|
||||
mod signal;
|
||||
mod tree;
|
||||
|
||||
use std::{fs, path::Path};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs,
|
||||
path::PathBuf,
|
||||
sync::{mpsc::{channel, Receiver}, Arc},
|
||||
thread,
|
||||
};
|
||||
|
||||
use can_dbc::Dbc;
|
||||
use eframe::egui;
|
||||
@@ -20,50 +26,171 @@ use crate::tree::{build_tree, decode_signal_node_id, show_dbc_tree, MsgNode};
|
||||
fn main() {
|
||||
let native_options = eframe::NativeOptions::default();
|
||||
eframe::run_native(
|
||||
"My egui App",
|
||||
"svldplotter",
|
||||
native_options,
|
||||
Box::new(|cc| Ok(Box::new(MyEguiApp::new(cc)))),
|
||||
Box::new(|cc| {
|
||||
let mut app = MyEguiApp::default();
|
||||
// Optional CLI quick-load: `svldplotter [DBC [ASC]]`. Both args
|
||||
// are optional and intended for dev iteration.
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
if let Some(dbc) = args.get(1) {
|
||||
app.load_dbc(PathBuf::from(dbc));
|
||||
}
|
||||
if let Some(asc) = args.get(2) {
|
||||
app.asc_paths.push(PathBuf::from(asc));
|
||||
app.set_active_asc(app.asc_paths.len() - 1, Some(&cc.egui_ctx));
|
||||
}
|
||||
Ok(Box::new(app))
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Eq)]
|
||||
enum LeftTab {
|
||||
#[default]
|
||||
Signals,
|
||||
Files,
|
||||
}
|
||||
|
||||
struct DecodeJob {
|
||||
asc_index: usize,
|
||||
asc_path: PathBuf,
|
||||
receiver: Receiver<Arc<DecodeResult>>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct MyEguiApp {
|
||||
data: DecodeResult,
|
||||
dbc: Option<Arc<Dbc>>,
|
||||
dbc_path: Option<PathBuf>,
|
||||
asc_paths: Vec<PathBuf>,
|
||||
active_asc: Option<usize>,
|
||||
data: Option<Arc<DecodeResult>>,
|
||||
decode_cache: HashMap<PathBuf, Arc<DecodeResult>>,
|
||||
pending: Option<DecodeJob>,
|
||||
tree: Vec<MsgNode>,
|
||||
filter: String,
|
||||
plots: Vec<PlotGroup>,
|
||||
next_plot_id: u64,
|
||||
cursor_x: Option<f64>,
|
||||
left_tab: LeftTab,
|
||||
}
|
||||
|
||||
impl MyEguiApp {
|
||||
fn new(_cc: &eframe::CreationContext<'_>) -> Self {
|
||||
let args: Vec<_> = std::env::args().collect();
|
||||
|
||||
let dbc_text =
|
||||
fs::read_to_string(Path::new(&args[1])).expect("Unable to read input file");
|
||||
let dbc = Dbc::try_from(dbc_text.as_str()).expect("Failed to parse dbc file");
|
||||
|
||||
let asc_path = Path::new(&args[2]);
|
||||
let data = libsvld::decode(&dbc, asc_path);
|
||||
let tree = build_tree(&dbc, &data);
|
||||
|
||||
Self {
|
||||
data,
|
||||
tree,
|
||||
filter: String::new(),
|
||||
plots: Vec::new(),
|
||||
next_plot_id: 1,
|
||||
cursor_x: None,
|
||||
fn load_dbc(&mut self, path: PathBuf) {
|
||||
let Ok(text) = fs::read_to_string(&path) else {
|
||||
return;
|
||||
};
|
||||
let Ok(dbc) = Dbc::try_from(text.as_str()) else {
|
||||
return;
|
||||
};
|
||||
self.dbc = Some(Arc::new(dbc));
|
||||
self.dbc_path = Some(path);
|
||||
// DBC change invalidates the decode cache.
|
||||
self.decode_cache.clear();
|
||||
self.pending = None;
|
||||
self.data = None;
|
||||
self.tree.clear();
|
||||
if let Some(idx) = self.active_asc {
|
||||
self.start_or_use_cached(idx, None);
|
||||
}
|
||||
}
|
||||
|
||||
fn add_asc(&mut self, path: PathBuf) {
|
||||
self.asc_paths.push(path);
|
||||
if self.active_asc.is_none() {
|
||||
self.set_active_asc(self.asc_paths.len() - 1, None);
|
||||
}
|
||||
}
|
||||
|
||||
/// Switch to a given ASC. Use the cached DecodeResult if present;
|
||||
/// otherwise spawn a background decode and show progress via the
|
||||
/// `pending` job.
|
||||
fn set_active_asc(&mut self, idx: usize, ctx: Option<&egui::Context>) {
|
||||
if idx >= self.asc_paths.len() {
|
||||
return;
|
||||
}
|
||||
self.active_asc = Some(idx);
|
||||
self.start_or_use_cached(idx, ctx);
|
||||
}
|
||||
|
||||
fn start_or_use_cached(&mut self, idx: usize, ctx: Option<&egui::Context>) {
|
||||
let Some(dbc) = self.dbc.clone() else {
|
||||
return;
|
||||
};
|
||||
let path = self.asc_paths[idx].clone();
|
||||
if self.decode_cache.contains_key(&path) {
|
||||
self.activate_decoded(idx);
|
||||
return;
|
||||
}
|
||||
let (tx, rx) = channel();
|
||||
let dbc_for_thread = dbc;
|
||||
let path_for_thread = path.clone();
|
||||
let ctx_for_thread = ctx.cloned();
|
||||
thread::spawn(move || {
|
||||
let result = libsvld::decode(&dbc_for_thread, &path_for_thread);
|
||||
let _ = tx.send(Arc::new(result));
|
||||
if let Some(ctx) = ctx_for_thread {
|
||||
ctx.request_repaint();
|
||||
}
|
||||
});
|
||||
self.pending = Some(DecodeJob {
|
||||
asc_index: idx,
|
||||
asc_path: path,
|
||||
receiver: rx,
|
||||
});
|
||||
}
|
||||
|
||||
/// Pull a finished decode out of `pending` and install it.
|
||||
fn poll_pending(&mut self) {
|
||||
let Some(job) = self.pending.as_ref() else {
|
||||
return;
|
||||
};
|
||||
let Ok(result) = job.receiver.try_recv() else {
|
||||
return;
|
||||
};
|
||||
let asc_index = job.asc_index;
|
||||
let asc_path = job.asc_path.clone();
|
||||
self.pending = None;
|
||||
self.decode_cache.insert(asc_path, result);
|
||||
if self.active_asc == Some(asc_index) {
|
||||
self.activate_decoded(asc_index);
|
||||
}
|
||||
}
|
||||
|
||||
/// Point `self.data`/`self.tree` at the cached result for `idx` and
|
||||
/// re-resolve every plotted signal against the new data.
|
||||
fn activate_decoded(&mut self, idx: usize) {
|
||||
let Some(dbc) = self.dbc.as_ref() else {
|
||||
return;
|
||||
};
|
||||
let path = &self.asc_paths[idx];
|
||||
let Some(data) = self.decode_cache.get(path).cloned() else {
|
||||
return;
|
||||
};
|
||||
self.tree = build_tree(dbc, &data);
|
||||
// Re-resolve plotted signals so existing plots survive an ASC switch.
|
||||
for plot in &mut self.plots {
|
||||
for sig in &mut plot.signals {
|
||||
if let Some((msg_id, sig_idx)) = decode_signal_node_id(sig.id) {
|
||||
if let Some(new_sig) = compute_signal(&data, msg_id, sig_idx, sig.id) {
|
||||
*sig = new_sig;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.data = Some(data);
|
||||
}
|
||||
|
||||
fn handle_dbc_toggles(&mut self, toggles: Vec<u64>) {
|
||||
let Some(data) = self.data.clone() else {
|
||||
return;
|
||||
};
|
||||
for id in toggles {
|
||||
if contains_signal(&self.plots, id) {
|
||||
remove_signal(&mut self.plots, id);
|
||||
} else if let Some((msg_id, sig_idx)) = decode_signal_node_id(id) {
|
||||
if let Some(sig) = compute_signal(&self.data, msg_id, sig_idx, id) {
|
||||
if let Some(sig) = compute_signal(&data, msg_id, sig_idx, id) {
|
||||
add_signal_auto(&mut self.plots, &mut self.next_plot_id, sig);
|
||||
}
|
||||
}
|
||||
@@ -82,8 +209,64 @@ impl MyEguiApp {
|
||||
}
|
||||
}
|
||||
|
||||
enum FilesAction {
|
||||
LoadDbc(PathBuf),
|
||||
AddAsc(PathBuf),
|
||||
SetActive(usize),
|
||||
}
|
||||
|
||||
fn show_files_tab(
|
||||
ui: &mut egui::Ui,
|
||||
dbc_path: Option<&PathBuf>,
|
||||
asc_paths: &[PathBuf],
|
||||
active: Option<usize>,
|
||||
) -> Vec<FilesAction> {
|
||||
let mut actions = Vec::new();
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("Load DBC").clicked() {
|
||||
if let Some(p) = rfd::FileDialog::new()
|
||||
.add_filter("DBC files", &["dbc"])
|
||||
.pick_file()
|
||||
{
|
||||
actions.push(FilesAction::LoadDbc(p));
|
||||
}
|
||||
}
|
||||
let label = dbc_path
|
||||
.map(|p| p.file_name().map(|s| s.to_string_lossy().into_owned()).unwrap_or_default())
|
||||
.unwrap_or_else(|| "(none)".to_string());
|
||||
ui.label(label);
|
||||
});
|
||||
ui.separator();
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("Add ASC").clicked() {
|
||||
if let Some(p) = rfd::FileDialog::new()
|
||||
.add_filter("ASC files", &["asc"])
|
||||
.pick_file()
|
||||
{
|
||||
actions.push(FilesAction::AddAsc(p));
|
||||
}
|
||||
}
|
||||
ui.label(format!("{} file(s)", asc_paths.len()));
|
||||
});
|
||||
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||
for (i, path) in asc_paths.iter().enumerate() {
|
||||
let name = path
|
||||
.file_name()
|
||||
.map(|s| s.to_string_lossy().into_owned())
|
||||
.unwrap_or_else(|| path.display().to_string());
|
||||
if ui.selectable_label(active == Some(i), name).clicked() {
|
||||
actions.push(FilesAction::SetActive(i));
|
||||
}
|
||||
}
|
||||
});
|
||||
actions
|
||||
}
|
||||
|
||||
impl eframe::App for MyEguiApp {
|
||||
fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
|
||||
self.poll_pending();
|
||||
|
||||
let ctx = ui.ctx().clone();
|
||||
egui::Panel::left(egui::Id::new("LeftPanel"))
|
||||
.resizable(true)
|
||||
.show_inside(ui, |ui| {
|
||||
@@ -92,13 +275,66 @@ impl eframe::App for MyEguiApp {
|
||||
.default_size(220.0)
|
||||
.show_inside(ui, |ui| show_plot_list(ui, &self.plots))
|
||||
.inner;
|
||||
let toggles = egui::CentralPanel::default()
|
||||
.show_inside(ui, |ui| show_dbc_tree(ui, &mut self.filter, &self.tree))
|
||||
let (toggles, files_actions) = egui::CentralPanel::default()
|
||||
.show_inside(ui, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.selectable_value(
|
||||
&mut self.left_tab,
|
||||
LeftTab::Signals,
|
||||
"Signals",
|
||||
);
|
||||
ui.selectable_value(
|
||||
&mut self.left_tab,
|
||||
LeftTab::Files,
|
||||
"Files",
|
||||
);
|
||||
});
|
||||
ui.separator();
|
||||
match self.left_tab {
|
||||
LeftTab::Signals => (
|
||||
show_dbc_tree(ui, &mut self.filter, &self.tree),
|
||||
Vec::new(),
|
||||
),
|
||||
LeftTab::Files => (
|
||||
Vec::new(),
|
||||
show_files_tab(
|
||||
ui,
|
||||
self.dbc_path.as_ref(),
|
||||
&self.asc_paths,
|
||||
self.active_asc,
|
||||
),
|
||||
),
|
||||
}
|
||||
})
|
||||
.inner;
|
||||
self.handle_dbc_toggles(toggles);
|
||||
self.handle_plot_actions(plot_actions);
|
||||
for action in files_actions {
|
||||
match action {
|
||||
FilesAction::LoadDbc(p) => self.load_dbc(p),
|
||||
FilesAction::AddAsc(p) => self.add_asc(p),
|
||||
FilesAction::SetActive(i) => self.set_active_asc(i, Some(&ctx)),
|
||||
}
|
||||
}
|
||||
});
|
||||
show_plot_panel(ui, &self.plots, &mut self.cursor_x);
|
||||
|
||||
if let Some(job) = self.pending.as_ref() {
|
||||
let name = job
|
||||
.asc_path
|
||||
.file_name()
|
||||
.map(|s| s.to_string_lossy().into_owned())
|
||||
.unwrap_or_else(|| job.asc_path.display().to_string());
|
||||
egui::Modal::new(egui::Id::new("DecodingModal")).show(&ctx, |ui| {
|
||||
ui.set_min_width(280.0);
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.heading("Decoding...");
|
||||
ui.add_space(6.0);
|
||||
ui.label(name);
|
||||
ui.add_space(6.0);
|
||||
ui.spinner();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user