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