libsurfer/
drawing_canvas.rs

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    /// True if a transition should be drawn even if there is no change in the value
40    /// between the previous and next pixels. Only used by the bool drawing logic to
41    /// draw draw a vertical line and prevent apparent aliasing
42    force_anti_alias: bool,
43}
44
45/// List of values to draw for a variable. It is an ordered list of values that should
46/// be drawn at the *start time* until the *start time* of the next value
47pub 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, // makes it easier to later access the actual Transaction object
87}
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    // we need to get the variable info here to get the correct info for aliases
125    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    // In order to insert a final draw command at the end of a trace,
133    // we need to know if this is the last timestamp to draw
134    let end_pixel = timestamps.iter().last().map(|t| t.0).unwrap_or_default();
135    // The first pixel we actually draw is the second pixel in the
136    // list, since we skip one pixel to have a previous value
137    let start_pixel = timestamps.get(1).map(|t| t.0).unwrap_or_default();
138
139    // Iterate over all the time stamps to draw on
140    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                &timestamp.to_bigint().unwrap(),
160                view_width,
161                &num_timestamps,
162            ),
163            // If we don't have a next timestamp, we don't need to recheck until the last time
164            // step
165            Ok(_) => timestamps.last().map(|t| t.0).unwrap_or_default(),
166            // If we get an error here, we'll let the next match block handle it, but we'll take
167            // note that we need to recheck every pixel until the end
168            _ => 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        // Check if the value remains unchanged between this pixel
184        // and the last
185        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            // If the value changed between this and the previous pixel, we want to
221            // draw a transition even if the translated value didn't change.  We
222            // only want to do this for root variables, because resolving when a
223            // sub-field change is tricky without more information from the
224            // translators
225            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            // This is not the value we drew last time
231            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        // Compute which timestamp to draw in each pixel. We'll draw from -extra_draw_width to
319        // width + extra_draw_width in order to draw initial transitions outside the screen
320        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            // Iterate over the variables, generating draw commands for all the
348            // subfields
349            .filter_map(|(id, displayed_variable)| {
350                variable_draw_commands(
351                    displayed_variable,
352                    id,
353                    &timestamps,
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                // find first visible transaction
474                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                    // stop drawing after last visible transaction
495                    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                    // skip transactions that are rendered completely in the previous pixel
519                    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    // Transform from screen coordinates taking timeline into account
573    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        // the draw commands have been invalidated, recompute
618        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            // If we have a single touch, we'll interpret that as a pan
660            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        // Handle cursor
675        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        // Draw background
687        painter.rect_filled(
688            response.rect,
689            CornerRadiusF32::ZERO,
690            self.user.config.theme.canvas_colors.background,
691        );
692
693        // Check for mouse gesture starting
694        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        // Check for measure drag starting
704        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                // As of 0.29 Egui draws things in the middle of pixels which means that if our
716                // line width is odd, we'll get very fuzzy lines. To compensate for this, we'll
717                // offset the coordinates by 0.5 if we have an odd line width.
718                // Relevant issues: https://github.com/emilk/egui/issues/1322
719                //                  https://github.com/emilk/egui/pull/4943
720                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        // We draw in absolute coords, but the variable offset in the y
732        // direction is also in absolute coordinates, so we need to
733        // compensate for that
734        let y_zero = to_screen.transform_pos(Pos2::ZERO).y;
735        for (vidx, drawing_info) in waves.drawing_infos.iter().enumerate() {
736            // Get background color
737            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            // We draw in absolute coords, but the variable offset in the y
878            // direction is also in absolute coordinates, so we need to
879            // compensate for that
880            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                        // Get background color and determine best text color
894                        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                        // Get background color and determine best text color
956                        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            // Draws the surrounding border of the stream
1034            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                        // Get background color and determine best text color
1134                        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        // Draws the relations of the focused transaction
1160        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                // For performance, it might be nice to draw both the background and line with this
1205                // call, but using convex_polygon on our polygons create artefacts on thin transitions.
1206                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    /// Draws a curvy arrow from `start` to `end`.
1325    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    /// Draws arrowheads for the vector going from `vec_start` to `vec_tip`.
1351    /// The `angle` has to be in degrees.
1352    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        // calculate the points of the new vector, which forms an angle of the given degrees with the given vector
1367        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        // scale the new vector to be head_length long
1371        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                    // At the moment we only support 255 markers, and the cursor is the 255th
1430                    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    /// Takes a pointer pos in the canvas and returns a position that is snapped to transitions
1446    /// if the cursor is close enough to any transition. If the cursor is on the canvas and no
1447    /// transitions are close enough for snapping, the raw point will be returned. If the cursor is
1448    /// off the canvas, `None` is returned
1449    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    /// Return the height and color with which to draw this value if it is a boolean
1534    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            // Seemingly a bit redundant to determine tx twice, but since the
1590            // alternative is to do it every frame for every transaction, this
1591            // is most likely still a better approach.
1592            // Feel free to use some Rust magic to only do it once though...
1593            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}