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, } 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 { 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 { 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::::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 }