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(crate) 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(crate) fn scope_tooltip_text(
49    wave: &WaveData,
50    scope: &ScopeRef,
51    include_parameters: bool,
52) -> String {
53    let mut parts = vec![format!("{scope}")];
54    if let Some(wave_container) = &wave.inner.as_waves() {
55        if include_parameters && let Some(waves) = &wave.inner.as_waves() {
56            for param in &waves.parameters_in_scope(scope) {
57                let value = wave_container
58                    .query_variable(param, &BigUint::ZERO)
59                    .ok()
60                    .and_then(|o| o.and_then(|q| q.current.map(|v| format!("{}", v.1))))
61                    .unwrap_or_else(|| "Undefined".to_string());
62                parts.push(format!("{}: {}", param.name, value));
63            }
64        }
65        let other = wave_container.get_scope_tooltip_data(scope);
66        if !other.is_empty() {
67            parts.push(other);
68        }
69    }
70    parts.join("\n")
71}
72
73#[must_use]
74pub(crate) fn handle_transaction_tooltip(
75    response: Response,
76    waves: &WaveData,
77    gen_ref: &TransactionStreamRef,
78    tx_ref: &TransactionRef,
79) -> Response {
80    response
81        .on_hover_ui(|ui| {
82            if let Some(tx) = find_transaction(waves, gen_ref, tx_ref) {
83                ui.set_max_width(ui.spacing().tooltip_width);
84                ui.add(egui::Label::new(transaction_tooltip_text(waves, tx)));
85            } else {
86                ui.label("Transaction unavailable");
87            }
88        })
89        .on_hover_ui(|ui| {
90            // Seemingly a bit redundant to determine tx twice, but since the
91            // alternative is to do it every frame for every transaction, this
92            // is most likely still a better approach.
93            // Feel free to use some Rust magic to only do it once though...
94            if let Some(tx) = find_transaction(waves, gen_ref, tx_ref) {
95                transaction_tooltip_table(ui, tx);
96            } else {
97                ui.label("Transaction details unavailable");
98            }
99        })
100}
101
102fn transaction_tooltip_text(waves: &WaveData, tx: &Transaction) -> String {
103    let time_scale = waves
104        .inner
105        .as_transactions()
106        .map(|t| t.inner.time_scale.to_string())
107        .unwrap_or_default();
108
109    format!(
110        "tx#{}: {}{} - {}{}\nType: {}",
111        tx.event.tx_id,
112        tx.event.start_time,
113        time_scale,
114        tx.event.end_time,
115        time_scale,
116        waves
117            .inner
118            .as_transactions()
119            .and_then(|t| t.get_generator(tx.get_gen_id()))
120            .map_or_else(|| "unknown".to_string(), |g| g.name.clone()),
121    )
122}
123
124fn transaction_tooltip_table(ui: &mut Ui, tx: &Transaction) {
125    TableBuilder::new(ui)
126        .column(Column::exact(80.))
127        .column(Column::exact(80.))
128        .header(20.0, |mut header| {
129            header.col(|ui| {
130                ui.heading("Attribute");
131            });
132            header.col(|ui| {
133                ui.heading("Value");
134            });
135        })
136        .body(|body| {
137            let total_rows = tx.attributes.len();
138            let attributes = &tx.attributes;
139            body.rows(15., total_rows, |mut row| {
140                if let Some(attribute) = attributes.get(row.index()) {
141                    row.col(|ui| {
142                        ui.label(attribute.name.clone());
143                    });
144                    row.col(|ui| {
145                        ui.label(attribute.value());
146                    });
147                }
148            });
149        });
150}