Skip to main content

libsurfer/
drawing_canvas.rs

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