Skip to main content

libsurfer/
tooltips.rs

1use egui::{Response, Ui};
2use egui_extras::{Column, TableBuilder};
3use ftr_parser::types::Transaction;
4use num::BigUint;
5
6use crate::{
7    transaction_container::{TransactionRef, TransactionStreamRef},
8    wave_container::{ScopeRef, VariableMeta, VariableRef, VariableRefExt},
9    wave_data::WaveData,
10};
11
12// Try to locate a transaction for the tooltip without panicking
13fn find_transaction<'a>(
14    waves: &'a WaveData,
15    gen_ref: &TransactionStreamRef,
16    tx_ref: &TransactionRef,
17) -> Option<&'a Transaction> {
18    let txs = waves.inner.as_transactions()?;
19    let gen_id = gen_ref.gen_id?;
20    let generator = txs.get_generator(gen_id)?;
21    generator
22        .transactions
23        .iter()
24        .find(|transaction| transaction.get_tx_id() == tx_ref.id)
25}
26
27#[must_use]
28pub fn variable_tooltip_text(meta: Option<&VariableMeta>, variable: &VariableRef) -> String {
29    if let Some(meta) = meta {
30        format!(
31            "{}\nNum bits: {}\nType: {}\nDirection: {}",
32            variable.full_path_string(),
33            meta.num_bits
34                .map_or_else(|| "unknown".to_string(), |bits| bits.to_string()),
35            meta.variable_type_name
36                .clone()
37                .or_else(|| meta.variable_type.map(|t| t.to_string()))
38                .unwrap_or_else(|| "unknown".to_string()),
39            meta.direction
40                .map_or_else(|| "unknown".to_string(), |direction| format!("{direction}"))
41        )
42    } else {
43        variable.full_path_string()
44    }
45}
46
47#[must_use]
48pub fn scope_tooltip_text(wave: &WaveData, scope: &ScopeRef, include_parameters: bool) -> String {
49    let mut parts = vec![format!("{scope}")];
50    if let Some(wave_container) = &wave.inner.as_waves() {
51        if include_parameters && let Some(waves) = &wave.inner.as_waves() {
52            for param in &waves.parameters_in_scope(scope) {
53                let value = wave_container
54                    .query_variable(param, &BigUint::ZERO)
55                    .ok()
56                    .and_then(|o| o.and_then(|q| q.current.map(|v| format!("{}", v.1))))
57                    .unwrap_or_else(|| "Undefined".to_string());
58                parts.push(format!("{}: {}", param.name, value));
59            }
60        }
61        let other = wave_container.get_scope_tooltip_data(scope);
62        if !other.is_empty() {
63            parts.push(other);
64        }
65    }
66    parts.join("\n")
67}
68
69#[must_use]
70pub fn handle_transaction_tooltip(
71    response: Response,
72    waves: &WaveData,
73    gen_ref: &TransactionStreamRef,
74    tx_ref: &TransactionRef,
75) -> Response {
76    response
77        .on_hover_ui(|ui| {
78            if let Some(tx) = find_transaction(waves, gen_ref, tx_ref) {
79                ui.set_max_width(ui.spacing().tooltip_width);
80                ui.add(egui::Label::new(transaction_tooltip_text(waves, tx)));
81            } else {
82                ui.label("Transaction unavailable");
83            }
84        })
85        .on_hover_ui(|ui| {
86            // Seemingly a bit redundant to determine tx twice, but since the
87            // alternative is to do it every frame for every transaction, this
88            // is most likely still a better approach.
89            // Feel free to use some Rust magic to only do it once though...
90            if let Some(tx) = find_transaction(waves, gen_ref, tx_ref) {
91                transaction_tooltip_table(ui, tx);
92            } else {
93                ui.label("Transaction details unavailable");
94            }
95        })
96}
97
98fn transaction_tooltip_text(waves: &WaveData, tx: &Transaction) -> String {
99    let time_scale = waves
100        .inner
101        .as_transactions()
102        .map(|t| t.inner.time_scale.to_string())
103        .unwrap_or_default();
104
105    format!(
106        "tx#{}: {}{} - {}{}\nType: {}",
107        tx.event.tx_id,
108        tx.event.start_time,
109        time_scale,
110        tx.event.end_time,
111        time_scale,
112        waves
113            .inner
114            .as_transactions()
115            .and_then(|t| t.get_generator(tx.get_gen_id()))
116            .map_or_else(|| "unknown".to_string(), |g| g.name.clone()),
117    )
118}
119
120fn transaction_tooltip_table(ui: &mut Ui, tx: &Transaction) {
121    TableBuilder::new(ui)
122        .column(Column::exact(80.))
123        .column(Column::exact(80.))
124        .header(20.0, |mut header| {
125            header.col(|ui| {
126                ui.heading("Attribute");
127            });
128            header.col(|ui| {
129                ui.heading("Value");
130            });
131        })
132        .body(|body| {
133            let total_rows = tx.attributes.len();
134            let attributes = &tx.attributes;
135            body.rows(15., total_rows, |mut row| {
136                if let Some(attribute) = attributes.get(row.index()) {
137                    row.col(|ui| {
138                        ui.label(attribute.name.clone());
139                    });
140                    row.col(|ui| {
141                        ui.label(attribute.value());
142                    });
143                }
144            });
145        });
146}