123 lines
4.0 KiB
Rust
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
|
|
}
|