1use ecolor::Color32;
2use egui::{FontId, PointerButton, Response, Sense, Ui};
3use egui_extras::{Column, TableBuilder};
4use emath::{Align2, Pos2, Rect, RectTransform, Vec2};
5use epaint::{CornerRadiusF32, CubicBezierShape, PathShape, PathStroke, RectShape, Shape, Stroke};
6use eyre::WrapErr;
7use ftr_parser::types::{Transaction, TxGenerator};
8use itertools::Itertools;
9use log::{error, warn};
10use num::bigint::{ToBigInt, ToBigUint};
11use num::{BigInt, BigUint, ToPrimitive};
12use rayon::prelude::{IntoParallelRefIterator, ParallelBridge, ParallelIterator};
13use std::cmp::Ordering;
14use std::collections::HashMap;
15use std::f32::consts::PI;
16use surfer_translation_types::{
17 SubFieldFlatTranslationResult, TranslatedValue, ValueKind, VariableInfo, VariableType,
18};
19
20use crate::clock_highlighting::draw_clock_edge_marks;
21use crate::config::SurferTheme;
22use crate::data_container::DataContainer;
23use crate::displayed_item::{DisplayedFieldRef, DisplayedItemRef, DisplayedVariable};
24use crate::displayed_item_tree::VisibleItemIndex;
25use crate::transaction_container::{TransactionRef, TransactionStreamRef};
26use crate::translation::{TranslationResultExt, TranslatorList, ValueKindExt, VariableInfoExt};
27use crate::view::{DrawConfig, DrawingContext, ItemDrawingInfo};
28use crate::viewport::Viewport;
29use crate::wave_container::{QueryResult, VariableRefExt};
30use crate::wave_data::WaveData;
31use crate::CachedDrawData::TransactionDrawData;
32use crate::{
33 displayed_item::DisplayedItem, CachedDrawData, CachedTransactionDrawData, CachedWaveDrawData,
34 Message, SystemState,
35};
36
37pub struct DrawnRegion {
38 inner: Option<TranslatedValue>,
39 force_anti_alias: bool,
43}
44
45pub struct DrawingCommands {
48 is_bool: bool,
49 is_clock: bool,
50 values: Vec<(f32, DrawnRegion)>,
51}
52
53impl DrawingCommands {
54 pub fn new_bool() -> Self {
55 Self {
56 values: vec![],
57 is_bool: true,
58 is_clock: false,
59 }
60 }
61
62 pub fn new_clock() -> Self {
63 Self {
64 values: vec![],
65 is_bool: true,
66 is_clock: true,
67 }
68 }
69
70 pub fn new_wide() -> Self {
71 Self {
72 values: vec![],
73 is_bool: false,
74 is_clock: false,
75 }
76 }
77
78 pub fn push(&mut self, val: (f32, DrawnRegion)) {
79 self.values.push(val);
80 }
81}
82
83pub struct TxDrawingCommands {
84 min: Pos2,
85 max: Pos2,
86 gen_ref: TransactionStreamRef, }
88
89struct VariableDrawCommands {
90 clock_edges: Vec<f32>,
91 display_id: DisplayedItemRef,
92 local_commands: HashMap<Vec<String>, DrawingCommands>,
93 local_msgs: Vec<Message>,
94}
95
96fn variable_draw_commands(
97 displayed_variable: &DisplayedVariable,
98 display_id: DisplayedItemRef,
99 timestamps: &[(f32, num::BigUint)],
100 waves: &WaveData,
101 translators: &TranslatorList,
102 view_width: f32,
103 viewport_idx: usize,
104) -> Option<VariableDrawCommands> {
105 let mut clock_edges = vec![];
106 let mut local_msgs = vec![];
107
108 let meta = match waves
109 .inner
110 .as_waves()
111 .unwrap()
112 .variable_meta(&displayed_variable.variable_ref)
113 .context("failed to get variable meta")
114 {
115 Ok(meta) => meta,
116 Err(e) => {
117 warn!("{e:#?}");
118 return None;
119 }
120 };
121
122 let displayed_field_ref: DisplayedFieldRef = display_id.into();
123 let translator = waves.variable_translator(&displayed_field_ref, translators);
124 let info = translator.variable_info(&meta).unwrap();
126 let num_timestamps = waves.num_timestamps().unwrap_or(1.into());
127
128 let mut local_commands: HashMap<Vec<_>, _> = HashMap::new();
129
130 let mut prev_values = HashMap::new();
131
132 let end_pixel = timestamps.iter().last().map(|t| t.0).unwrap_or_default();
135 let start_pixel = timestamps.get(1).map(|t| t.0).unwrap_or_default();
138
139 let mut next_change = timestamps.first().map(|t| t.0).unwrap_or_default();
141 for ((_, prev_time), (pixel, time)) in timestamps.iter().zip(timestamps.iter().skip(1)) {
142 let is_last_timestep = pixel == &end_pixel;
143 let is_first_timestep = pixel == &start_pixel;
144
145 if *pixel < next_change && !is_first_timestep && !is_last_timestep {
146 continue;
147 }
148
149 let query_result = waves
150 .inner
151 .as_waves()
152 .unwrap()
153 .query_variable(&displayed_variable.variable_ref, time);
154 next_change = match &query_result {
155 Ok(Some(QueryResult {
156 next: Some(timestamp),
157 ..
158 })) => waves.viewports[viewport_idx].pixel_from_time(
159 ×tamp.to_bigint().unwrap(),
160 view_width,
161 &num_timestamps,
162 ),
163 Ok(_) => timestamps.last().map(|t| t.0).unwrap_or_default(),
166 _ => timestamps.first().map(|t| t.0).unwrap_or_default(),
169 };
170
171 let (change_time, val) = match query_result {
172 Ok(Some(QueryResult {
173 current: Some((change_time, val)),
174 ..
175 })) => (change_time, val),
176 Ok(Some(QueryResult { current: None, .. })) | Ok(None) => continue,
177 Err(e) => {
178 error!("Variable query error {e:#?}");
179 continue;
180 }
181 };
182
183 if &change_time < prev_time && !is_first_timestep && !is_last_timestep {
186 continue;
187 }
188
189 let translation_result = match translator.translate(&meta, &val) {
190 Ok(result) => result,
191 Err(e) => {
192 error!(
193 "{translator_name} for {variable_name} failed. Disabling:",
194 translator_name = translator.name(),
195 variable_name = displayed_variable.variable_ref.full_path_string()
196 );
197 error!("{e:#}");
198 local_msgs.push(Message::ResetVariableFormat(displayed_field_ref));
199 return None;
200 }
201 };
202
203 let fields = translation_result.format_flat(
204 &displayed_variable.format,
205 &displayed_variable.field_formats,
206 translators,
207 );
208
209 for SubFieldFlatTranslationResult { names, value } in fields {
210 let entry = local_commands.entry(names.clone()).or_insert_with(|| {
211 match info.get_subinfo(&names) {
212 VariableInfo::Bool => DrawingCommands::new_bool(),
213 VariableInfo::Clock => DrawingCommands::new_clock(),
214 _ => DrawingCommands::new_wide(),
215 }
216 });
217
218 let prev = prev_values.get(&names);
219
220 let anti_alias = &change_time > prev_time
226 && names.is_empty()
227 && waves.inner.as_waves().unwrap().wants_anti_aliasing();
228 let new_value = prev != Some(&value);
229
230 if new_value || is_last_timestep || anti_alias {
232 prev_values
233 .entry(names.clone())
234 .or_insert(value.clone())
235 .clone_from(&value);
236
237 if let VariableInfo::Clock = info.get_subinfo(&names) {
238 match value.as_ref().map(|result| result.value.as_str()) {
239 Some("1") => {
240 if !is_last_timestep && !is_first_timestep {
241 clock_edges.push(*pixel);
242 }
243 }
244 Some(_) => {}
245 None => {}
246 }
247 }
248
249 entry.push((
250 *pixel,
251 DrawnRegion {
252 inner: value,
253 force_anti_alias: anti_alias && !new_value,
254 },
255 ));
256 }
257 }
258 }
259 Some(VariableDrawCommands {
260 clock_edges,
261 display_id,
262 local_commands,
263 local_msgs,
264 })
265}
266
267impl SystemState {
268 pub fn invalidate_draw_commands(&mut self) {
269 if let Some(waves) = &self.user.waves {
270 for viewport in 0..waves.viewports.len() {
271 self.draw_data.borrow_mut()[viewport] = None;
272 }
273 }
274 }
275
276 pub fn generate_draw_commands(
277 &self,
278 cfg: &DrawConfig,
279 frame_width: f32,
280 msgs: &mut Vec<Message>,
281 viewport_idx: usize,
282 ) {
283 #[cfg(feature = "performance_plot")]
284 self.timing.borrow_mut().start("Generate draw commands");
285 if let Some(waves) = &self.user.waves {
286 let draw_data = match waves.inner {
287 DataContainer::Waves(_) => {
288 self.generate_wave_draw_commands(waves, cfg, frame_width, msgs, viewport_idx)
289 }
290 DataContainer::Transactions(_) => self.generate_transaction_draw_commands(
291 waves,
292 cfg,
293 frame_width,
294 msgs,
295 viewport_idx,
296 ),
297 DataContainer::Empty => None,
298 };
299 self.draw_data.borrow_mut()[viewport_idx] = draw_data;
300 }
301 #[cfg(feature = "performance_plot")]
302 self.timing.borrow_mut().end("Generate draw commands");
303 }
304
305 fn generate_wave_draw_commands(
306 &self,
307 waves: &WaveData,
308 cfg: &DrawConfig,
309 frame_width: f32,
310 msgs: &mut Vec<Message>,
311 viewport_idx: usize,
312 ) -> Option<CachedDrawData> {
313 let mut draw_commands = HashMap::new();
314
315 let num_timestamps = waves.num_timestamps().unwrap_or(1.into());
316 let max_time = num_timestamps.to_f64().unwrap_or(f64::MAX);
317 let mut clock_edges = vec![];
318 let mut timestamps = (-cfg.extra_draw_width..(frame_width as i32 + cfg.extra_draw_width))
321 .par_bridge()
322 .filter_map(|x| {
323 let time = waves.viewports[viewport_idx]
324 .as_absolute_time(x as f64, frame_width, &num_timestamps)
325 .0;
326 if time < 0. || time > max_time {
327 None
328 } else {
329 Some((x as f32, time.to_biguint().unwrap_or_default()))
330 }
331 })
332 .collect::<Vec<_>>();
333 timestamps.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
334
335 let translators = &self.translators;
336 let commands = waves
337 .items_tree
338 .iter_visible()
339 .map(|node| (node.item_ref, waves.displayed_items.get(&node.item_ref)))
340 .filter_map(|(id, item)| match item {
341 Some(DisplayedItem::Variable(variable_ref)) => Some((id, variable_ref)),
342 _ => None,
343 })
344 .collect::<Vec<_>>()
345 .par_iter()
346 .cloned()
347 .filter_map(|(id, displayed_variable)| {
350 variable_draw_commands(
351 displayed_variable,
352 id,
353 ×tamps,
354 waves,
355 translators,
356 frame_width,
357 viewport_idx,
358 )
359 })
360 .collect::<Vec<_>>();
361
362 for VariableDrawCommands {
363 clock_edges: mut new_clock_edges,
364 display_id,
365 local_commands,
366 mut local_msgs,
367 } in commands
368 {
369 msgs.append(&mut local_msgs);
370 for (field, val) in local_commands {
371 draw_commands.insert(
372 DisplayedFieldRef {
373 item: display_id,
374 field,
375 },
376 val,
377 );
378 }
379 clock_edges.append(&mut new_clock_edges);
380 }
381 let ticks = waves.get_ticks(
382 &waves.viewports[viewport_idx],
383 &waves.inner.metadata().timescale,
384 frame_width,
385 cfg.text_size,
386 &self.user.wanted_timeunit,
387 &self.get_time_format(),
388 &self.user.config,
389 );
390
391 Some(CachedDrawData::WaveDrawData(CachedWaveDrawData {
392 draw_commands,
393 clock_edges,
394 ticks,
395 }))
396 }
397
398 fn generate_transaction_draw_commands(
399 &self,
400 waves: &WaveData,
401 cfg: &DrawConfig,
402 frame_width: f32,
403 msgs: &mut Vec<Message>,
404 viewport_idx: usize,
405 ) -> Option<CachedDrawData> {
406 let mut draw_commands = HashMap::new();
407 let mut stream_to_displayed_txs = HashMap::new();
408 let mut inc_relation_tx_ids = vec![];
409 let mut out_relation_tx_ids = vec![];
410
411 let (focused_tx_ref, old_focused_tx) = &waves.focused_transaction;
412 let mut new_focused_tx: Option<&Transaction> = None;
413
414 let viewport = waves.viewports[viewport_idx];
415 let num_timestamps = waves.num_timestamps().unwrap_or(1.into());
416
417 let displayed_streams = waves
418 .items_tree
419 .iter_visible()
420 .map(|node| node.item_ref)
421 .collect::<Vec<_>>()
422 .par_iter()
423 .map(|id| waves.displayed_items.get(id))
424 .filter_map(|item| match item {
425 Some(DisplayedItem::Stream(stream_ref)) => Some(stream_ref),
426 _ => None,
427 })
428 .collect::<Vec<_>>();
429
430 let first_visible_timestamp = viewport
431 .curr_left
432 .absolute(&num_timestamps)
433 .0
434 .to_biguint()
435 .unwrap_or(BigUint::ZERO);
436
437 for displayed_stream in displayed_streams {
438 let tx_stream_ref = &displayed_stream.transaction_stream_ref;
439
440 let mut generators: Vec<&TxGenerator> = vec![];
441 let mut displayed_transactions = vec![];
442
443 if tx_stream_ref.is_stream() {
444 let stream = waves
445 .inner
446 .as_transactions()
447 .unwrap()
448 .get_stream(tx_stream_ref.stream_id)
449 .unwrap();
450
451 for gen_id in &stream.generators {
452 generators.push(
453 waves
454 .inner
455 .as_transactions()
456 .unwrap()
457 .get_generator(*gen_id)
458 .unwrap(),
459 );
460 }
461 } else {
462 generators.push(
463 waves
464 .inner
465 .as_transactions()
466 .unwrap()
467 .get_generator(tx_stream_ref.gen_id.unwrap())
468 .unwrap(),
469 );
470 }
471
472 for gen in &generators {
473 let first_visible_transaction_index = match gen
475 .transactions
476 .binary_search_by_key(&first_visible_timestamp, |tx| tx.get_end_time())
477 {
478 Ok(i) => i,
479 Err(i) => i,
480 }
481 .saturating_sub(1);
482 let transactions = gen
483 .transactions
484 .iter()
485 .skip(first_visible_transaction_index);
486
487 let mut last_px = f32::NAN;
488
489 for tx in transactions {
490 let start_time = tx.get_start_time();
491 let end_time = tx.get_end_time();
492 let curr_tx_id = tx.get_tx_id();
493
494 if start_time.to_f64().unwrap()
496 > viewport.curr_right.absolute(&num_timestamps).0
497 {
498 break;
499 }
500
501 if let Some(focused_tx_ref) = focused_tx_ref {
502 if curr_tx_id == focused_tx_ref.id {
503 new_focused_tx = Some(tx);
504 }
505 }
506
507 let min_px = viewport.pixel_from_time(
508 &start_time.to_bigint().unwrap(),
509 frame_width - 1.,
510 &num_timestamps,
511 );
512 let max_px = viewport.pixel_from_time(
513 &end_time.to_bigint().unwrap(),
514 frame_width - 1.,
515 &num_timestamps,
516 );
517
518 if (min_px == max_px) && (min_px == last_px) {
520 last_px = max_px;
521 continue;
522 }
523 last_px = max_px;
524
525 displayed_transactions.push(TransactionRef { id: curr_tx_id });
526 let min = Pos2::new(min_px, cfg.line_height * tx.row as f32 + 4.0);
527 let max = Pos2::new(max_px, cfg.line_height * (tx.row + 1) as f32 - 4.0);
528
529 let tx_ref = TransactionRef { id: curr_tx_id };
530 draw_commands.insert(
531 tx_ref,
532 TxDrawingCommands {
533 min,
534 max,
535 gen_ref: TransactionStreamRef::new_gen(
536 tx_stream_ref.stream_id,
537 gen.id,
538 gen.name.clone(),
539 ),
540 },
541 );
542 }
543 }
544 stream_to_displayed_txs.insert(tx_stream_ref.clone(), displayed_transactions);
545 }
546
547 if let Some(focused_tx) = new_focused_tx {
548 for rel in &focused_tx.inc_relations {
549 inc_relation_tx_ids.push(TransactionRef {
550 id: rel.source_tx_id,
551 });
552 }
553 for rel in &focused_tx.out_relations {
554 out_relation_tx_ids.push(TransactionRef { id: rel.sink_tx_id });
555 }
556 if old_focused_tx.is_none() || Some(focused_tx) != old_focused_tx.as_ref() {
557 msgs.push(Message::FocusTransaction(
558 focused_tx_ref.clone(),
559 Some(focused_tx.clone()),
560 ));
561 }
562 }
563
564 Some(TransactionDrawData(CachedTransactionDrawData {
565 draw_commands,
566 stream_to_displayed_txs,
567 inc_relation_tx_ids,
568 out_relation_tx_ids,
569 }))
570 }
571
572 fn transform_pos(&self, to_screen: RectTransform, p: Pos2, ui: &Ui) -> Pos2 {
574 to_screen
575 .inverse()
576 .transform_pos(if self.show_default_timeline() {
577 Pos2 {
578 x: p.x,
579 y: p.y - ui.text_style_height(&egui::TextStyle::Body),
580 }
581 } else {
582 p
583 })
584 }
585
586 pub fn draw_items(
587 &mut self,
588 egui_ctx: &egui::Context,
589 msgs: &mut Vec<Message>,
590 ui: &mut Ui,
591 viewport_idx: usize,
592 ) {
593 let Some(waves) = &self.user.waves else {
594 return;
595 };
596
597 let (response, mut painter) =
598 ui.allocate_painter(ui.available_size(), Sense::click_and_drag());
599
600 if response.rect.size().x < 1. {
601 return;
602 }
603
604 let cfg = match waves.inner {
605 DataContainer::Waves(_) => DrawConfig::new(
606 response.rect.size().y,
607 self.user.config.layout.waveforms_line_height,
608 self.user.config.layout.waveforms_text_size,
609 ),
610 DataContainer::Transactions(_) => DrawConfig::new(
611 response.rect.size().y,
612 self.user.config.layout.transactions_line_height,
613 self.user.config.layout.waveforms_text_size,
614 ),
615 DataContainer::Empty => return,
616 };
617 if self.draw_data.borrow()[viewport_idx].is_none()
619 || Some(response.rect) != *self.last_canvas_rect.borrow()
620 {
621 self.generate_draw_commands(&cfg, response.rect.width(), msgs, viewport_idx);
622 *self.last_canvas_rect.borrow_mut() = Some(response.rect);
623 }
624
625 let container_rect = Rect::from_min_size(Pos2::ZERO, response.rect.size());
626 let to_screen = RectTransform::from_to(container_rect, response.rect);
627 let frame_width = response.rect.width();
628 let pointer_pos_global = ui.input(|i| i.pointer.interact_pos());
629 let pointer_pos_canvas = pointer_pos_global.map(|p| self.transform_pos(to_screen, p, ui));
630 let num_timestamps = waves.num_timestamps().unwrap_or(1.into());
631
632 if ui.ui_contains_pointer() {
633 let pointer_pos = pointer_pos_global.unwrap();
634 let scroll_delta = ui.input(|i| i.smooth_scroll_delta);
635 let mouse_ptr_pos = to_screen.inverse().transform_pos(pointer_pos);
636 if scroll_delta != Vec2::ZERO {
637 msgs.push(Message::CanvasScroll {
638 delta: ui.input(|i| i.smooth_scroll_delta),
639 viewport_idx,
640 });
641 }
642
643 if ui.input(egui::InputState::zoom_delta) != 1. {
644 let mouse_ptr = Some(waves.viewports[viewport_idx].as_time_bigint(
645 mouse_ptr_pos.x,
646 frame_width,
647 &num_timestamps,
648 ));
649
650 msgs.push(Message::CanvasZoom {
651 mouse_ptr,
652 delta: ui.input(egui::InputState::zoom_delta),
653 viewport_idx,
654 });
655 }
656 }
657
658 ui.input(|i| {
659 let touch = i.any_touches() && i.multi_touch().is_none();
661 let right_mouse = i.pointer.button_down(PointerButton::Secondary);
662 if touch || right_mouse {
663 msgs.push(Message::CanvasScroll {
664 delta: Vec2 {
665 x: i.pointer.delta().y,
666 y: i.pointer.delta().x,
667 },
668 viewport_idx: 0,
669 });
670 }
671 });
672
673 let modifiers = egui_ctx.input(|i| i.modifiers);
674 if !modifiers.command
676 && ((response.dragged_by(PointerButton::Primary) && !self.do_measure(&modifiers))
677 || response.clicked_by(PointerButton::Primary))
678 {
679 if let Some(snap_point) =
680 self.snap_to_edge(pointer_pos_canvas, waves, frame_width, viewport_idx)
681 {
682 msgs.push(Message::CursorSet(snap_point));
683 }
684 }
685
686 painter.rect_filled(
688 response.rect,
689 CornerRadiusF32::ZERO,
690 self.user.config.theme.canvas_colors.background,
691 );
692
693 if response.drag_started_by(PointerButton::Middle)
695 || modifiers.command && response.drag_started_by(PointerButton::Primary)
696 {
697 msgs.push(Message::SetMouseGestureDragStart(
698 ui.input(|i| i.pointer.press_origin())
699 .map(|p| self.transform_pos(to_screen, p, ui)),
700 ));
701 }
702
703 if response.drag_started_by(PointerButton::Primary) && self.do_measure(&modifiers) {
705 msgs.push(Message::SetMeasureDragStart(
706 ui.input(|i| i.pointer.press_origin())
707 .map(|p| self.transform_pos(to_screen, p, ui)),
708 ));
709 }
710
711 let mut ctx = DrawingContext {
712 painter: &mut painter,
713 cfg: &cfg,
714 to_screen: &|x, y| {
715 let offset = if (self.user.config.theme.linewidth as i32) % 2 == 1 {
721 Vec2::new(0.5, 0.5)
722 } else {
723 Vec2::ZERO
724 };
725 to_screen.transform_pos(Pos2::new(x, y) + offset)
726 },
727 theme: &self.user.config.theme,
728 };
729
730 let gap = ui.spacing().item_spacing.y * 0.5;
731 let y_zero = to_screen.transform_pos(Pos2::ZERO).y;
735 for (vidx, drawing_info) in waves.drawing_infos.iter().enumerate() {
736 let background_color =
738 &self.get_background_color(waves, drawing_info, VisibleItemIndex(vidx));
739
740 self.draw_background(
741 drawing_info,
742 y_zero,
743 &ctx,
744 gap,
745 frame_width,
746 background_color,
747 );
748 }
749
750 #[cfg(feature = "performance_plot")]
751 self.timing.borrow_mut().start("Wave drawing");
752
753 match &self.draw_data.borrow()[viewport_idx] {
754 Some(CachedDrawData::WaveDrawData(draw_data)) => {
755 self.draw_wave_data(waves, draw_data, &mut ctx);
756 }
757 Some(CachedDrawData::TransactionDrawData(draw_data)) => {
758 self.draw_transaction_data(
759 waves,
760 draw_data,
761 viewport_idx,
762 frame_width,
763 &cfg,
764 ui,
765 msgs,
766 &mut ctx,
767 );
768 }
769 None => {}
770 }
771 #[cfg(feature = "performance_plot")]
772 self.timing.borrow_mut().end("Wave drawing");
773
774 waves.draw_graphics(
775 &mut ctx,
776 response.rect.size(),
777 &waves.viewports[viewport_idx],
778 &self.user.config.theme,
779 );
780
781 waves.draw_cursor(
782 &self.user.config.theme,
783 &mut ctx,
784 response.rect.size(),
785 &waves.viewports[viewport_idx],
786 );
787
788 waves.draw_markers(
789 &self.user.config.theme,
790 &mut ctx,
791 response.rect.size(),
792 &waves.viewports[viewport_idx],
793 );
794
795 self.draw_marker_boxes(
796 waves,
797 &mut ctx,
798 response.rect.size().x,
799 gap,
800 &waves.viewports[viewport_idx],
801 y_zero,
802 );
803
804 if self.show_default_timeline() {
805 let rect = Rect {
806 min: Pos2 { x: 0.0, y: y_zero },
807 max: Pos2 {
808 x: response.rect.max.x,
809 y: y_zero + ui.text_style_height(&egui::TextStyle::Body),
810 },
811 };
812 ctx.painter
813 .rect_filled(rect, 0.0, self.user.config.theme.canvas_colors.background);
814 self.draw_default_timeline(waves, &ctx, viewport_idx, frame_width, &cfg);
815 }
816
817 self.draw_mouse_gesture_widget(
818 egui_ctx,
819 waves,
820 pointer_pos_canvas,
821 &response,
822 msgs,
823 &mut ctx,
824 viewport_idx,
825 );
826
827 self.draw_measure_widget(
828 egui_ctx,
829 waves,
830 pointer_pos_canvas,
831 &response,
832 msgs,
833 &mut ctx,
834 viewport_idx,
835 );
836 self.handle_canvas_context_menu(response, waves, to_screen, &mut ctx, msgs, viewport_idx);
837 }
838
839 fn draw_wave_data(
840 &self,
841 waves: &WaveData,
842 draw_data: &CachedWaveDrawData,
843 ctx: &mut DrawingContext,
844 ) {
845 let clock_edges = &draw_data.clock_edges;
846 let draw_commands = &draw_data.draw_commands;
847 let draw_clock_edges = match clock_edges.as_slice() {
848 [] => false,
849 [_single] => true,
850 [first, second, ..] => second - first > 20.,
851 };
852 let draw_clock_rising_marker =
853 draw_clock_edges && self.user.config.theme.clock_rising_marker;
854 let ticks = &draw_data.ticks;
855 if !ticks.is_empty() && self.show_ticks() {
856 let stroke = Stroke {
857 color: self.user.config.theme.ticks.style.color,
858 width: self.user.config.theme.ticks.style.width,
859 };
860
861 for (_, x) in ticks {
862 waves.draw_tick_line(*x, ctx, &stroke);
863 }
864 }
865
866 if draw_clock_edges {
867 draw_clock_edge_marks(
868 clock_edges,
869 ctx,
870 &self.user.config,
871 self.clock_highlight_type(),
872 );
873 }
874 let zero_y = (ctx.to_screen)(0., 0.).y;
875 for (vidx, drawing_info) in waves.drawing_infos.iter().enumerate() {
876 let vidx = VisibleItemIndex(vidx);
877 let y_offset = drawing_info.top() - zero_y;
881
882 let displayed_item = waves
883 .items_tree
884 .get_visible(drawing_info.item_list_idx())
885 .and_then(|node| waves.displayed_items.get(&node.item_ref));
886 let color = displayed_item
887 .and_then(super::displayed_item::DisplayedItem::color)
888 .and_then(|color| self.user.config.theme.get_color(color));
889
890 match drawing_info {
891 ItemDrawingInfo::Variable(variable_info) => {
892 if let Some(commands) = draw_commands.get(&variable_info.displayed_field_ref) {
893 let background_color = self.get_background_color(waves, drawing_info, vidx);
895 let text_color = self
896 .user
897 .config
898 .theme
899 .get_best_text_color(&background_color);
900 let height_scaling_factor = displayed_item
901 .map(super::displayed_item::DisplayedItem::height_scaling_factor)
902 .unwrap();
903
904 let color = *color.unwrap_or_else(|| {
905 if let Some(DisplayedItem::Variable(variable)) = displayed_item {
906 waves
907 .inner
908 .as_waves()
909 .unwrap()
910 .variable_meta(&variable.variable_ref)
911 .ok()
912 .and_then(|meta| meta.variable_type)
913 .and_then(|var_type| {
914 if var_type == VariableType::VCDParameter {
915 Some(&self.user.config.theme.variable_parameter)
916 } else {
917 None
918 }
919 })
920 .unwrap_or(&self.user.config.theme.variable_default)
921 } else {
922 &self.user.config.theme.variable_default
923 }
924 });
925 for (old, new) in commands.values.iter().zip(commands.values.iter().skip(1))
926 {
927 if commands.is_bool {
928 self.draw_bool_transition(
929 (old, new),
930 new.1.force_anti_alias,
931 color,
932 y_offset,
933 height_scaling_factor,
934 commands.is_clock && draw_clock_rising_marker,
935 self.fill_high_values(),
936 ctx,
937 );
938 } else {
939 self.draw_region(
940 (old, new),
941 color,
942 y_offset,
943 height_scaling_factor,
944 ctx,
945 *text_color,
946 );
947 }
948 }
949 }
950 }
951 ItemDrawingInfo::Divider(_) => {}
952 ItemDrawingInfo::Marker(_) => {}
953 ItemDrawingInfo::TimeLine(_) => {
954 let text_color = color.unwrap_or(
955 self.user
957 .config
958 .theme
959 .get_best_text_color(&self.get_background_color(
960 waves,
961 drawing_info,
962 vidx,
963 )),
964 );
965 waves.draw_ticks(
966 Some(text_color),
967 ticks,
968 ctx,
969 y_offset,
970 Align2::CENTER_TOP,
971 &self.user.config,
972 );
973 }
974 ItemDrawingInfo::Stream(_) => {}
975 ItemDrawingInfo::Group(_) => {}
976 }
977 }
978 }
979
980 #[allow(clippy::too_many_arguments)]
981 fn draw_transaction_data(
982 &self,
983 waves: &WaveData,
984 draw_data: &CachedTransactionDrawData,
985 viewport_idx: usize,
986 frame_width: f32,
987 cfg: &DrawConfig,
988 ui: &mut Ui,
989 msgs: &mut Vec<Message>,
990 ctx: &mut DrawingContext,
991 ) {
992 let draw_commands = &draw_data.draw_commands;
993 let stream_to_displayed_txs = &draw_data.stream_to_displayed_txs;
994 let inc_relation_tx_ids = &draw_data.inc_relation_tx_ids;
995 let out_relation_tx_ids = &draw_data.out_relation_tx_ids;
996
997 let mut inc_relation_starts = vec![];
998 let mut out_relation_starts = vec![];
999 let mut focused_transaction_start: Option<Pos2> = None;
1000
1001 let ticks = &waves.get_ticks(
1002 &waves.viewports[viewport_idx],
1003 &waves.inner.metadata().timescale,
1004 frame_width,
1005 cfg.text_size,
1006 &self.user.wanted_timeunit,
1007 &self.get_time_format(),
1008 &self.user.config,
1009 );
1010
1011 if !ticks.is_empty() && self.show_ticks() {
1012 let stroke = Stroke {
1013 color: self.user.config.theme.ticks.style.color,
1014 width: self.user.config.theme.ticks.style.width,
1015 };
1016
1017 for (_, x) in ticks {
1018 waves.draw_tick_line(*x, ctx, &stroke);
1019 }
1020 }
1021
1022 let zero_y = (ctx.to_screen)(0., 0.).y;
1023 for (vidx, drawing_info) in waves.drawing_infos.iter().enumerate() {
1024 let y_offset = drawing_info.top() - zero_y;
1025
1026 let displayed_item = waves
1027 .items_tree
1028 .get_visible(drawing_info.item_list_idx())
1029 .and_then(|node| waves.displayed_items.get(&node.item_ref));
1030 let color = displayed_item
1031 .and_then(super::displayed_item::DisplayedItem::color)
1032 .and_then(|color| self.user.config.theme.get_color(color));
1033 let border_stroke = Stroke::new(
1035 self.user.config.theme.linewidth,
1036 self.user.config.theme.foreground,
1037 );
1038
1039 match drawing_info {
1040 ItemDrawingInfo::Stream(stream) => {
1041 if let Some(tx_refs) =
1042 stream_to_displayed_txs.get(&stream.transaction_stream_ref)
1043 {
1044 for tx_ref in tx_refs {
1045 if let Some(tx_draw_command) = draw_commands.get(tx_ref) {
1046 let mut min = tx_draw_command.min;
1047 let mut max = tx_draw_command.max;
1048
1049 min.x = min.x.max(0.);
1050 max.x = max.x.min(frame_width - 1.);
1051
1052 let min = (ctx.to_screen)(min.x, y_offset + min.y);
1053 let max = (ctx.to_screen)(max.x, y_offset + max.y);
1054
1055 let start = Pos2::new(min.x, (min.y + max.y) / 2.);
1056
1057 let is_transaction_focused = waves
1058 .focused_transaction
1059 .0
1060 .as_ref()
1061 .is_some_and(|t| t == tx_ref);
1062
1063 if inc_relation_tx_ids.contains(tx_ref) {
1064 inc_relation_starts.push(start);
1065 } else if out_relation_tx_ids.contains(tx_ref) {
1066 out_relation_starts.push(start);
1067 } else if is_transaction_focused {
1068 focused_transaction_start = Some(start);
1069 }
1070
1071 let transaction_rect = Rect { min, max };
1072 if (max.x - min.x) > 1.0 {
1073 let mut response =
1074 ui.allocate_rect(transaction_rect, Sense::click());
1075
1076 response = handle_transaction_tooltip(
1077 response,
1078 waves,
1079 &tx_draw_command.gen_ref,
1080 tx_ref,
1081 );
1082
1083 if response.clicked() {
1084 msgs.push(Message::FocusTransaction(
1085 Some(tx_ref.clone()),
1086 None,
1087 ));
1088 }
1089
1090 let tx_fill_color = if is_transaction_focused {
1091 let c = color
1092 .unwrap_or(&self.user.config.theme.transaction_default);
1093 Color32::from_rgb(255 - c.r(), 255 - c.g(), 255 - c.b())
1094 } else {
1095 *color
1096 .unwrap_or(&self.user.config.theme.transaction_default)
1097 };
1098
1099 let stroke =
1100 Stroke::new(1.5, tx_fill_color.gamma_multiply(1.2));
1101 ctx.painter.rect(
1102 transaction_rect,
1103 CornerRadiusF32::same(5.0),
1104 tx_fill_color,
1105 stroke,
1106 egui::StrokeKind::Middle,
1107 );
1108 } else {
1109 let tx_fill_color = color
1110 .unwrap_or(&self.user.config.theme.transaction_default)
1111 .gamma_multiply(1.2);
1112
1113 let stroke = Stroke::new(1.5, tx_fill_color);
1114 ctx.painter.rect(
1115 transaction_rect,
1116 CornerRadiusF32::ZERO,
1117 tx_fill_color,
1118 stroke,
1119 egui::StrokeKind::Middle,
1120 );
1121 }
1122 }
1123 }
1124 ctx.painter.hline(
1125 0.0..=((ctx.to_screen)(frame_width, 0.0).x),
1126 drawing_info.bottom(),
1127 border_stroke,
1128 );
1129 }
1130 }
1131 ItemDrawingInfo::TimeLine(_) => {
1132 let text_color = color.unwrap_or(
1133 self.user
1135 .config
1136 .theme
1137 .get_best_text_color(&self.get_background_color(
1138 waves,
1139 drawing_info,
1140 VisibleItemIndex(vidx),
1141 )),
1142 );
1143 waves.draw_ticks(
1144 Some(text_color),
1145 ticks,
1146 ctx,
1147 y_offset,
1148 Align2::CENTER_TOP,
1149 &self.user.config,
1150 );
1151 }
1152 ItemDrawingInfo::Variable(_) => {}
1153 ItemDrawingInfo::Divider(_) => {}
1154 ItemDrawingInfo::Marker(_) => {}
1155 ItemDrawingInfo::Group(_) => {}
1156 }
1157 }
1158
1159 if let Some(focused_pos) = focused_transaction_start {
1161 let arrow_color = self.user.config.theme.relation_arrow.style.color;
1162 for start_pos in inc_relation_starts {
1163 self.draw_arrow(start_pos, focused_pos, arrow_color, ctx);
1164 }
1165
1166 for end_pos in out_relation_starts {
1167 self.draw_arrow(focused_pos, end_pos, arrow_color, ctx);
1168 }
1169 }
1170 }
1171
1172 fn draw_region(
1173 &self,
1174 ((old_x, prev_region), (new_x, _)): (&(f32, DrawnRegion), &(f32, DrawnRegion)),
1175 user_color: Color32,
1176 offset: f32,
1177 height_scaling_factor: f32,
1178 ctx: &mut DrawingContext,
1179 text_color: Color32,
1180 ) {
1181 if let Some(prev_result) = &prev_region.inner {
1182 let color = prev_result.kind.color(user_color, ctx.theme);
1183 let stroke = Stroke {
1184 color,
1185 width: self.user.config.theme.linewidth,
1186 };
1187
1188 let transition_width = (new_x - old_x).min(ctx.theme.vector_transition_width);
1189
1190 let trace_coords =
1191 |x, y| (ctx.to_screen)(x, y * ctx.cfg.line_height * height_scaling_factor + offset);
1192
1193 let points = vec![
1194 trace_coords(*old_x, 0.5),
1195 trace_coords(old_x + transition_width / 2., 0.0),
1196 trace_coords(new_x - transition_width / 2., 0.0),
1197 trace_coords(*new_x, 0.5),
1198 trace_coords(new_x - transition_width / 2., 1.0),
1199 trace_coords(old_x + transition_width / 2., 1.0),
1200 trace_coords(*old_x, 0.5),
1201 ];
1202
1203 if self.user.config.theme.wide_opacity != 0.0 {
1204 ctx.painter.add(PathShape::convex_polygon(
1207 points.clone(),
1208 color.gamma_multiply(self.user.config.theme.wide_opacity),
1209 PathStroke::NONE,
1210 ));
1211 }
1212
1213 ctx.painter.add(PathShape::line(points, stroke));
1214
1215 let text_size = ctx.cfg.text_size;
1216 let char_width = text_size * (20. / 31.);
1217
1218 let text_area = (new_x - old_x) - transition_width;
1219 let num_chars = (text_area / char_width).floor() as usize;
1220 let fits_text = num_chars >= 1;
1221
1222 if fits_text {
1223 let content = if prev_result.value.len() > num_chars {
1224 prev_result
1225 .value
1226 .chars()
1227 .take(num_chars - 1)
1228 .chain(['…'])
1229 .collect::<String>()
1230 } else {
1231 prev_result.value.to_string()
1232 };
1233
1234 ctx.painter.text(
1235 trace_coords(*old_x + transition_width, 0.5),
1236 Align2::LEFT_CENTER,
1237 content,
1238 FontId::monospace(text_size),
1239 text_color,
1240 );
1241 }
1242 }
1243 }
1244
1245 #[allow(clippy::too_many_arguments)]
1246 fn draw_bool_transition(
1247 &self,
1248 ((old_x, prev_region), (new_x, new_region)): (&(f32, DrawnRegion), &(f32, DrawnRegion)),
1249 force_anti_alias: bool,
1250 color: Color32,
1251 offset: f32,
1252 height_scaling_factor: f32,
1253 draw_clock_marker: bool,
1254 draw_background: bool,
1255 ctx: &mut DrawingContext,
1256 ) {
1257 if let (Some(prev_result), Some(new_result)) = (&prev_region.inner, &new_region.inner) {
1258 let trace_coords =
1259 |x, y| (ctx.to_screen)(x, y * ctx.cfg.line_height * height_scaling_factor + offset);
1260
1261 let (old_height, old_color, old_bg) = prev_result.value.bool_drawing_spec(
1262 color,
1263 &self.user.config.theme,
1264 prev_result.kind,
1265 );
1266 let (new_height, _, _) =
1267 new_result
1268 .value
1269 .bool_drawing_spec(color, &self.user.config.theme, new_result.kind);
1270
1271 if let (Some(old_bg), true) = (old_bg, draw_background) {
1272 ctx.painter.add(RectShape::new(
1273 Rect {
1274 min: (ctx.to_screen)(*old_x, offset),
1275 max: (ctx.to_screen)(
1276 *new_x,
1277 offset
1278 + ctx.cfg.line_height * height_scaling_factor
1279 + ctx.theme.linewidth / 2.,
1280 ),
1281 },
1282 CornerRadiusF32::ZERO,
1283 old_bg,
1284 Stroke::NONE,
1285 egui::StrokeKind::Middle,
1286 ));
1287 }
1288
1289 let stroke = Stroke {
1290 color: old_color,
1291 width: self.user.config.theme.linewidth,
1292 };
1293
1294 if force_anti_alias {
1295 ctx.painter.add(PathShape::line(
1296 vec![trace_coords(*new_x, 0.0), trace_coords(*new_x, 1.0)],
1297 stroke,
1298 ));
1299 }
1300
1301 ctx.painter.add(PathShape::line(
1302 vec![
1303 trace_coords(*old_x, 1. - old_height),
1304 trace_coords(*new_x, 1. - old_height),
1305 trace_coords(*new_x, 1. - new_height),
1306 ],
1307 stroke,
1308 ));
1309
1310 if draw_clock_marker && (old_height < new_height) {
1311 ctx.painter.add(PathShape::convex_polygon(
1312 vec![
1313 trace_coords(*new_x - 2.5, 0.6),
1314 trace_coords(*new_x, 0.4),
1315 trace_coords(*new_x + 2.5, 0.6),
1316 ],
1317 old_color,
1318 stroke,
1319 ));
1320 }
1321 }
1322 }
1323
1324 fn draw_arrow(&self, start: Pos2, end: Pos2, color: Color32, ctx: &DrawingContext) {
1326 let mut anchor1 = Pos2::default();
1327 let mut anchor2 = Pos2::default();
1328
1329 let x_diff = (end.x - start.x).max(100.);
1330
1331 anchor1.x = start.x + (2. / 5.) * x_diff;
1332 anchor1.y = start.y;
1333
1334 anchor2.x = end.x - (2. / 5.) * x_diff;
1335 anchor2.y = end.y;
1336
1337 let stroke = PathStroke::new(ctx.theme.relation_arrow.style.width, color);
1338
1339 ctx.painter.add(Shape::CubicBezier(CubicBezierShape {
1340 points: [start, anchor1, anchor2, end],
1341 closed: false,
1342 fill: Default::default(),
1343 stroke,
1344 }));
1345
1346 let stroke = Stroke::new(ctx.theme.relation_arrow.style.width, color);
1347 self.draw_arrowheads(anchor2, end, ctx, stroke);
1348 }
1349
1350 fn draw_arrowheads(
1353 &self,
1354 vec_start: Pos2,
1355 vec_tip: Pos2,
1356 ctx: &DrawingContext,
1357 stroke: Stroke,
1358 ) {
1359 let head_length = ctx.theme.relation_arrow.head_length;
1360
1361 let vec_x = vec_tip.x - vec_start.x;
1362 let vec_y = vec_tip.y - vec_start.y;
1363
1364 let alpha = 2. * PI / 360. * ctx.theme.relation_arrow.head_angle;
1365
1366 let vec_angled_x = vec_x * alpha.cos() + vec_y * alpha.sin();
1368 let vec_angled_y = -vec_x * alpha.sin() + vec_y * alpha.cos();
1369
1370 let vec_angled_x = (1. / (vec_angled_y - vec_angled_x).abs()) * vec_angled_x * head_length;
1372 let vec_angled_y = (1. / (vec_angled_y - vec_angled_x).abs()) * vec_angled_y * head_length;
1373
1374 let arrowhead_left_x = vec_tip.x - vec_angled_x;
1375 let arrowhead_left_y = vec_tip.y - vec_angled_y;
1376
1377 let arrowhead_right_x = vec_tip.x + vec_angled_y;
1378 let arrowhead_right_y = vec_tip.y - vec_angled_x;
1379
1380 ctx.painter.add(Shape::line_segment(
1381 [vec_tip, Pos2::new(arrowhead_left_x, arrowhead_left_y)],
1382 stroke,
1383 ));
1384
1385 ctx.painter.add(Shape::line_segment(
1386 [vec_tip, Pos2::new(arrowhead_right_x, arrowhead_right_y)],
1387 stroke,
1388 ));
1389 }
1390
1391 fn handle_canvas_context_menu(
1392 &self,
1393 response: Response,
1394 waves: &WaveData,
1395 to_screen: RectTransform,
1396 ctx: &mut DrawingContext,
1397 msgs: &mut Vec<Message>,
1398 viewport_idx: usize,
1399 ) {
1400 let size = response.rect.size();
1401 response.context_menu(|ui| {
1402 let offset = ui.spacing().menu_margin.left as f32;
1403 let top_left = to_screen.inverse().transform_rect(ui.min_rect()).left_top()
1404 - Pos2 {
1405 x: offset,
1406 y: offset,
1407 };
1408
1409 let snap_pos = self.snap_to_edge(Some(top_left.to_pos2()), waves, size.x, viewport_idx);
1410
1411 if let Some(time) = snap_pos {
1412 self.draw_line(&time, ctx, size, &waves.viewports[viewport_idx], waves);
1413 ui.menu_button("Set marker", |ui| {
1414 macro_rules! close_menu {
1415 () => {{
1416 ui.close_menu();
1417 }};
1418 }
1419
1420 for id in waves.markers.keys().sorted() {
1421 ui.button(format!("{id}")).clicked().then(|| {
1422 msgs.push(Message::SetMarker {
1423 id: *id,
1424 time: time.clone(),
1425 });
1426 close_menu!();
1427 });
1428 }
1429 if waves.can_add_marker() {
1431 ui.button("New").clicked().then(|| {
1432 msgs.push(Message::AddMarker {
1433 time,
1434 name: None,
1435 move_focus: true,
1436 });
1437 close_menu!();
1438 });
1439 }
1440 });
1441 }
1442 });
1443 }
1444
1445 fn snap_to_edge(
1450 &self,
1451 pointer_pos_canvas: Option<Pos2>,
1452 waves: &WaveData,
1453 frame_width: f32,
1454 viewport_idx: usize,
1455 ) -> Option<BigInt> {
1456 let pos = pointer_pos_canvas?;
1457 let viewport = &waves.viewports[viewport_idx];
1458 let num_timestamps = waves.num_timestamps().unwrap_or(1.into());
1459 let timestamp = viewport.as_time_bigint(pos.x, frame_width, &num_timestamps);
1460 if let Some(utimestamp) = timestamp.to_biguint() {
1461 if let Some(vidx) = waves.get_item_at_y(pos.y) {
1462 if let Some(node) = waves.items_tree.get_visible(vidx) {
1463 if let Some(DisplayedItem::Variable(variable)) =
1464 &waves.displayed_items.get(&node.item_ref)
1465 {
1466 if let Ok(Some(res)) = waves
1467 .inner
1468 .as_waves()
1469 .unwrap()
1470 .query_variable(&variable.variable_ref, &utimestamp)
1471 {
1472 let prev_time = if let Some(v) = res.current {
1473 v.0.to_bigint().unwrap()
1474 } else {
1475 BigInt::ZERO
1476 };
1477 let next_time = &res.next.unwrap_or_default().to_bigint().unwrap();
1478 let prev =
1479 viewport.pixel_from_time(&prev_time, frame_width, &num_timestamps);
1480 let next =
1481 viewport.pixel_from_time(next_time, frame_width, &num_timestamps);
1482 if (prev - pos.x).abs() < (next - pos.x).abs() {
1483 if (prev - pos.x).abs() <= self.user.config.snap_distance {
1484 return Some(prev_time.clone());
1485 }
1486 } else if (next - pos.x).abs() <= self.user.config.snap_distance {
1487 return Some(next_time.clone());
1488 }
1489 }
1490 }
1491 }
1492 }
1493 }
1494 Some(timestamp)
1495 }
1496
1497 pub fn draw_line(
1498 &self,
1499 time: &BigInt,
1500 ctx: &mut DrawingContext,
1501 size: Vec2,
1502 viewport: &Viewport,
1503 waves: &WaveData,
1504 ) {
1505 let x = viewport.pixel_from_time(time, size.x, &waves.num_timestamps().unwrap_or(1.into()));
1506
1507 let stroke = Stroke {
1508 color: self.user.config.theme.cursor.color,
1509 width: self.user.config.theme.cursor.width,
1510 };
1511 ctx.painter.line_segment(
1512 [
1513 (ctx.to_screen)(x + 0.5, -0.5),
1514 (ctx.to_screen)(x + 0.5, size.y),
1515 ],
1516 stroke,
1517 );
1518 }
1519}
1520
1521impl WaveData {}
1522
1523trait VariableExt {
1524 fn bool_drawing_spec(
1525 &self,
1526 user_color: Color32,
1527 theme: &SurferTheme,
1528 value_kind: ValueKind,
1529 ) -> (f32, Color32, Option<Color32>);
1530}
1531
1532impl VariableExt for String {
1533 fn bool_drawing_spec(
1535 &self,
1536 user_color: Color32,
1537 theme: &SurferTheme,
1538 value_kind: ValueKind,
1539 ) -> (f32, Color32, Option<Color32>) {
1540 let color = value_kind.color(user_color, theme);
1541 let (height, background) = match (value_kind, self) {
1542 (ValueKind::HighImp, _) => (0.5, None),
1543 (ValueKind::Undef, _) => (0.5, None),
1544 (ValueKind::DontCare, _) => (0.5, None),
1545 (ValueKind::Warn, _) => (0.5, None),
1546 (ValueKind::Custom(_), _) => (0.5, None),
1547 (ValueKind::Weak, other) => {
1548 if other.to_lowercase() == "l" {
1549 (0., None)
1550 } else {
1551 (1., Some(color.gamma_multiply(theme.waveform_opacity)))
1552 }
1553 }
1554 (ValueKind::Normal, other) => {
1555 if other == "0" {
1556 (0., None)
1557 } else {
1558 (1., Some(color.gamma_multiply(theme.waveform_opacity)))
1559 }
1560 }
1561 };
1562 (height, color, background)
1563 }
1564}
1565
1566fn handle_transaction_tooltip(
1567 response: Response,
1568 waves: &WaveData,
1569 gen_ref: &TransactionStreamRef,
1570 tx_ref: &TransactionRef,
1571) -> Response {
1572 response
1573 .on_hover_ui(|ui| {
1574 let tx = waves
1575 .inner
1576 .as_transactions()
1577 .unwrap()
1578 .get_generator(gen_ref.gen_id.unwrap())
1579 .unwrap()
1580 .transactions
1581 .iter()
1582 .find(|transaction| transaction.get_tx_id() == tx_ref.id)
1583 .unwrap();
1584
1585 ui.set_max_width(ui.spacing().tooltip_width);
1586 ui.add(egui::Label::new(transaction_tooltip_text(waves, tx)));
1587 })
1588 .on_hover_ui(|ui| {
1589 let tx = waves
1594 .inner
1595 .as_transactions()
1596 .unwrap()
1597 .get_generator(gen_ref.gen_id.unwrap())
1598 .unwrap()
1599 .transactions
1600 .iter()
1601 .find(|transaction| transaction.get_tx_id() == tx_ref.id)
1602 .unwrap();
1603
1604 transaction_tooltip_table(ui, tx)
1605 })
1606}
1607
1608fn transaction_tooltip_text(waves: &WaveData, tx: &Transaction) -> String {
1609 let time_scale = waves.inner.as_transactions().unwrap().inner.time_scale;
1610
1611 format!(
1612 "tx#{}: {}{} - {}{}\nType: {}",
1613 tx.event.tx_id,
1614 tx.event.start_time,
1615 time_scale,
1616 tx.event.end_time,
1617 time_scale,
1618 waves
1619 .inner
1620 .as_transactions()
1621 .unwrap()
1622 .get_generator(tx.get_gen_id())
1623 .unwrap()
1624 .name
1625 .clone(),
1626 )
1627}
1628
1629fn transaction_tooltip_table(ui: &mut Ui, tx: &Transaction) {
1630 TableBuilder::new(ui)
1631 .column(Column::exact(80.))
1632 .column(Column::exact(80.))
1633 .header(20.0, |mut header| {
1634 header.col(|ui| {
1635 ui.heading("Attribute");
1636 });
1637 header.col(|ui| {
1638 ui.heading("Value");
1639 });
1640 })
1641 .body(|body| {
1642 let total_rows = tx.attributes.len();
1643 let attributes = &tx.attributes;
1644 body.rows(15., total_rows, |mut row| {
1645 let attribute = attributes.get(row.index()).unwrap();
1646 row.col(|ui| {
1647 ui.label(attribute.name.clone());
1648 });
1649 row.col(|ui| {
1650 ui.label(attribute.value());
1651 });
1652 });
1653 });
1654}