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