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