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
12fn 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 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}