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 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 Loading,
60 Ready {
62 viewport_min: f64,
64 viewport_max: f64,
65 global_min: f64,
67 global_max: f64,
68 type_limits: Option<NumericRange>,
70 values: Vec<AnalogDrawingCommand>,
72 min_valid_pixel: f32,
74 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}
97pub 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, }
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#[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#[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 let end_pixel = timestamps.iter().last().map(|t| t.0).unwrap_or_default();
232 let start_pixel = timestamps.get(1).map(|t| t.0).unwrap_or_default();
235
236 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 ×tamp.to_bigint().unwrap(),
253 view_width,
254 range,
255 ),
256 Ok(_) => timestamps.last().map(|t| t.0).unwrap_or_default(),
259 _ => 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 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 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 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 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 .filter_map(|(id, displayed_variable)| {
446 variable_draw_commands(
447 displayed_variable,
448 id,
449 ×tamps,
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 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 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 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 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 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 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 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 painter.rect_filled(
797 response.rect,
798 CornerRadius::ZERO,
799 self.user.config.theme.canvas_colors.background,
800 );
801
802 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 if let Some(focused_pos) = focused_transaction_start {
1384 let path_stroke = PathStroke::from(&ctx.theme.relation_arrow.style);
1385 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 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 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 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 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 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 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 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 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#[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 let bg_luminance = crate::config::get_luminance(background);
1838 let rgba = Rgba::from(color);
1839 let result = if bg_luminance < 0.5 {
1840 rgba * (1.0 - delta) + Rgba::WHITE * delta
1842 } else {
1843 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 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}