Skip to main content

libsurfer/
drawing_canvas.rs

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