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