Files
svld/svldplotter/src/tree.rs
T
2026-05-24 15:22:58 +02:00

123 lines
4.0 KiB
Rust

use can_dbc::Dbc;
use eframe::egui;
use egui::ScrollArea;
use egui_ltreeview::{Action, TreeView};
use libsvld::DecodeResult;
pub struct SigNode {
pub id: u64,
pub name: String,
}
pub struct MsgNode {
pub id: u64,
pub label: String,
pub signals: Vec<SigNode>,
}
const SIGNAL_FLAG: u64 = 1 << 63;
pub fn signal_node_id(msg_id: u32, sig_idx: usize) -> u64 {
SIGNAL_FLAG | ((msg_id as u64) << 16) | (sig_idx as u64)
}
pub fn decode_signal_node_id(id: u64) -> Option<(u32, usize)> {
if id & SIGNAL_FLAG == 0 {
return None;
}
let msg_id = ((id >> 16) & 0xFFFF_FFFF) as u32;
let sig_idx = (id & 0xFFFF) as usize;
Some((msg_id, sig_idx))
}
pub fn build_tree(dbc: &Dbc, data: &DecodeResult) -> Vec<MsgNode> {
let mut msgs: Vec<&libsvld::motec::MessageMetadata> = data.metadata.values().collect();
msgs.sort_by_key(|m| m.id);
msgs.into_iter()
.map(|msg| {
let name = dbc
.messages
.iter()
.find(|x| x.id.raw() == msg.id)
.map(|x| x.name.as_str())
.unwrap_or("?");
let label = format!("0x{:X} {}", msg.id, name);
let signals = msg
.signals
.iter()
.enumerate()
.map(|(sig_idx, signal)| SigNode {
id: signal_node_id(msg.id, sig_idx),
name: signal.name.clone(),
})
.collect();
MsgNode {
id: msg.id as u64,
label,
signals,
}
})
.collect()
}
/// Render the side panel and return any signal IDs the user clicked to toggle.
pub fn show_side_panel(
ui: &mut egui::Ui,
filter: &mut String,
tree: &[MsgNode],
) -> Vec<u64> {
let mut toggles = Vec::new();
egui::Panel::left(egui::Id::new("Signals"))
.resizable(true)
.show_inside(ui, |ui| {
ui.add(
egui::TextEdit::singleline(filter)
.hint_text("Filter messages/signals")
.desired_width(f32::INFINITY),
);
let needle = filter.trim().to_lowercase();
ScrollArea::both()
.scroll([true, true])
.scroll_bar_visibility(
egui::scroll_area::ScrollBarVisibility::VisibleWhenNeeded,
)
.show(ui, |ui| {
let (_resp, actions) = TreeView::<u64>::new(
ui.make_persistent_id("SignalsTree"),
)
.show(ui, |builder| {
for msg in tree {
let msg_hit = needle.is_empty()
|| msg.label.to_lowercase().contains(&needle);
let matching: Vec<&SigNode> = if msg_hit {
msg.signals.iter().collect()
} else {
msg.signals
.iter()
.filter(|s| s.name.to_lowercase().contains(&needle))
.collect()
};
if !msg_hit && matching.is_empty() {
continue;
}
builder.dir(msg.id, msg.label.as_str());
for sig in matching {
builder.leaf(sig.id, sig.name.as_str());
}
builder.close_dir();
}
});
for action in actions {
if let Action::SetSelected(ids) = action {
for id in ids {
if id & SIGNAL_FLAG != 0 {
toggles.push(id);
}
}
}
}
});
});
toggles
}