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