1use crate::{
2 config::{FocusHighlight, ThemeColorPair, TransitionValue},
3 dialog::{draw_open_sibling_state_file_dialog, draw_reload_waveform_dialog},
4 displayed_item::DisplayedVariable,
5 fzcmd::expand_command,
6 item_drawing_info::{
7 DividerDrawingInfo, GroupDrawingInfo, ItemDrawingInfo, MarkerDrawingInfo,
8 PlaceholderDrawingInfo, StreamDrawingInfo, TimeLineDrawingInfo, VariableDrawingInfo,
9 },
10 menus::generic_context_menu,
11 time::TimeFormatter,
12 tooltips::variable_tooltip_text,
13 wave_container::{ScopeId, VarId, VariableMeta},
14};
15use ahash::{AHashSet, AHasher};
16use ecolor::Color32;
17#[cfg(not(target_arch = "wasm32"))]
18use egui::ViewportCommand;
19use egui::{
20 CentralPanel, FontSelection, Frame, Layout, Painter, Panel, RichText, ScrollArea, Sense,
21 TextStyle, Ui, UiBuilder, WidgetText,
22};
23use emath::{Align, GuiRounding, Pos2, Rect, RectTransform, Vec2};
24use epaint::{
25 CornerRadius, Margin, Shape, Stroke,
26 text::{FontId, LayoutJob, TextFormat, TextWrapMode},
27};
28use itertools::Itertools;
29use num::{BigUint, One, Zero};
30use std::hash::{Hash, Hasher};
31use std::rc::Rc;
32use tracing::info;
33
34use surfer_translation_types::{
35 TranslatedValue, Translator, VariableInfo, VariableValue,
36 translator::{TrueName, VariableNameInfo},
37};
38
39use crate::OUTSTANDING_TRANSACTIONS;
40#[cfg(feature = "performance_plot")]
41use crate::benchmark::NUM_PERF_SAMPLES;
42use crate::command_parser::get_parser;
43use crate::config::SurferTheme;
44use crate::displayed_item::{DisplayedFieldRef, DisplayedItem, DisplayedItemRef};
45use crate::displayed_item_tree::{ItemIndex, VisibleItemIndex};
46use crate::help::{
47 draw_about_window, draw_control_help_window, draw_license_window, draw_quickstart_help_window,
48};
49use crate::time::time_string;
50use crate::translation::TranslationResultExt;
51use crate::util::get_alpha_focus_id;
52use crate::wave_container::{FieldRef, FieldRefExt, VariableRef};
53use crate::{
54 Message, MoveDir, SystemState, command_prompt::show_command_prompt, hierarchy::HierarchyStyle,
55 wave_data::WaveData,
56};
57
58pub struct DrawingContext<'a> {
59 pub painter: &'a mut Painter,
60 pub cfg: &'a DrawConfig,
61 pub to_screen: &'a dyn Fn(f32, f32) -> Pos2,
62 pub theme: &'a SurferTheme,
63}
64
65#[derive(Debug)]
66pub struct DrawConfig {
67 pub canvas_size: Vec2,
68 pub line_height: f32,
69 pub text_size: f32,
70 pub extra_draw_width: i32,
71}
72
73impl DrawConfig {
74 #[must_use]
75 pub fn new(canvas_size: Vec2, line_height: f32, text_size: f32) -> Self {
76 Self {
77 canvas_size,
78 line_height,
79 text_size,
80 extra_draw_width: 6,
81 }
82 }
83}
84
85impl eframe::App for SystemState {
86 fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
87 #[cfg(feature = "performance_plot")]
88 self.timing.borrow_mut().start_frame();
89
90 if self.continuous_redraw {
91 self.invalidate_draw_commands();
92 }
93
94 let (fullscreen, window_size) = ui.input(|i| {
95 (
96 i.viewport().fullscreen.unwrap_or_default(),
97 Some(i.viewport_rect().size()),
98 )
99 });
100 #[cfg(target_arch = "wasm32")]
101 let _ = fullscreen;
102
103 #[cfg(feature = "performance_plot")]
104 self.timing.borrow_mut().start("draw");
105 let mut msgs = self.draw(ui, window_size);
106 #[cfg(feature = "performance_plot")]
107 self.timing.borrow_mut().end("draw");
108
109 #[cfg(feature = "performance_plot")]
110 self.timing.borrow_mut().start("push_async_messages");
111 self.push_async_messages(&mut msgs);
112 #[cfg(feature = "performance_plot")]
113 self.timing.borrow_mut().end("push_async_messages");
114
115 #[cfg(feature = "performance_plot")]
116 self.timing.borrow_mut().start("update");
117 let ui_zoom_factor = self.ui_zoom_factor();
118 if ui.zoom_factor() != ui_zoom_factor {
119 ui.set_zoom_factor(ui_zoom_factor);
120 }
121
122 while let Some(msg) = msgs.pop() {
123 #[cfg(not(target_arch = "wasm32"))]
124 if let Message::Exit = msg {
125 ui.send_viewport_cmd(ViewportCommand::Close);
126 }
127 #[cfg(not(target_arch = "wasm32"))]
128 if let Message::ToggleFullscreen = msg {
129 ui.send_viewport_cmd(ViewportCommand::Fullscreen(!fullscreen));
130 }
131 self.update(msg);
132 }
133 #[cfg(feature = "performance_plot")]
134 self.timing.borrow_mut().end("update");
135
136 self.handle_batch_commands();
137 #[cfg(target_arch = "wasm32")]
138 self.handle_wasm_external_messages();
139
140 let viewport_is_moving = if let Some(waves) = &mut self.user.waves {
141 let mut is_moving = false;
142 for vp in &mut waves.viewports {
143 if vp.is_moving() {
144 vp.move_viewport(ui.input(|i| i.stable_dt));
145 is_moving = true;
146 }
147 }
148 is_moving
149 } else {
150 false
151 };
152
153 if let Some(waves) = self.user.waves.as_ref().and_then(|w| w.inner.as_waves()) {
154 waves.tick();
155 }
156
157 if viewport_is_moving {
158 self.invalidate_draw_commands();
159 ui.request_repaint();
160 }
161
162 #[cfg(feature = "performance_plot")]
163 self.timing.borrow_mut().start("handle_wcp_commands");
164 self.handle_wcp_commands();
165 #[cfg(feature = "performance_plot")]
166 self.timing.borrow_mut().end("handle_wcp_commands");
167
168 if self.continuous_redraw
172 || self.progress_tracker.is_some()
173 || self.user.show_performance
174 || OUTSTANDING_TRANSACTIONS.load(std::sync::atomic::Ordering::SeqCst) != 0
175 {
176 ui.request_repaint();
177 }
178
179 #[cfg(feature = "performance_plot")]
180 if let Some(prev_cpu) = frame.info().cpu_usage {
181 self.rendering_cpu_times.push_back(prev_cpu);
182 if self.rendering_cpu_times.len() > NUM_PERF_SAMPLES {
183 self.rendering_cpu_times.pop_front();
184 }
185 }
186
187 #[cfg(feature = "performance_plot")]
188 self.timing.borrow_mut().end_frame();
189
190 if self.toolbar_dragging_group.is_some() {
192 ui.ctx()
193 .output_mut(|o| o.cursor_icon = egui::CursorIcon::Grabbing);
194 }
195 }
196}
197
198pub(crate) struct VariableFieldRow {
201 field: FieldRef,
202 depth: u32,
203 is_compound: bool,
204 has_children: bool,
205 unfolded: bool,
206}
207
208impl SystemState {
209 pub(crate) fn draw(&mut self, ui: &mut Ui, window_size: Option<Vec2>) -> Vec<Message> {
210 if crate::logs::take_error_notification() {
211 self.user.show_logs = true;
212 }
213
214 let max_width = ui.available_size().x;
215 let max_height = ui.available_size().y;
216
217 let mut msgs = vec![];
218
219 if self.user.show_about {
220 draw_about_window(ui, &mut msgs);
221 }
222
223 if self.user.show_license {
224 draw_license_window(ui, &mut msgs);
225 }
226
227 if self.user.show_keys {
228 draw_control_help_window(ui, &mut msgs, &self.user.config.shortcuts);
229 }
230
231 if self.user.show_quick_start {
232 draw_quickstart_help_window(ui, &mut msgs, &self.user.config.shortcuts);
233 }
234
235 if self.user.show_gestures {
236 self.mouse_gesture_help(ui, &mut msgs);
237 }
238
239 if self.user.show_logs {
240 self.draw_log_window(ui, &mut msgs);
241 }
242
243 if self.frame_buffer_content.is_some() {
244 self.draw_frame_buffer_window(ui.ctx(), &mut msgs);
245 }
246
247 self.draw_memory_viewer_window(ui.ctx(), &mut msgs);
248
249 if let Some(dialog) = self.user.show_reload_suggestion {
250 draw_reload_waveform_dialog(ui, dialog, &mut msgs);
251 }
252
253 if let Some(dialog) = self.user.show_open_sibling_state_file_suggestion {
254 draw_open_sibling_state_file_dialog(ui, dialog, &mut msgs);
255 }
256
257 if self.user.show_performance {
258 #[cfg(feature = "performance_plot")]
259 self.draw_performance_graph(ui, &mut msgs);
260 }
261
262 if self.user.show_cursor_window
263 && let Some(waves) = &self.user.waves
264 {
265 self.draw_marker_window(waves, ui, &mut msgs);
266 }
267
268 if self
269 .user
270 .show_menu
271 .unwrap_or_else(|| self.user.config.layout.show_menu())
272 {
273 self.add_menu_panel(ui, &mut msgs);
274 }
275
276 if self.show_toolbar() {
277 self.add_toolbar_panel(ui, &mut msgs);
278 }
279
280 if self.user.show_url_entry {
281 self.draw_load_url(ui, &mut msgs);
282 }
283
284 if self.user.show_server_file_window {
285 self.draw_surver_file_window(ui, &mut msgs);
286 }
287
288 if self.show_statusbar() {
289 self.add_statusbar_panel(ui, self.user.waves.as_ref(), &mut msgs);
290 }
291
292 if let Some(waves) = &self.user.waves
293 && self.show_overview()
294 && !waves.items_tree.is_empty()
295 {
296 self.add_overview_panel(ui, waves, &mut msgs);
297 }
298
299 if self.show_hierarchy() {
300 Panel::left("variable select left panel")
301 .default_size(300.)
302 .size_range(100.0..=max_width)
303 .frame(Frame {
304 fill: self.user.config.theme.primary_ui_color.background,
305 ..Default::default()
306 })
307 .show(ui, |ui| {
308 self.user.sidepanel_width = Some(ui.clip_rect().width());
309 match self.hierarchy_style() {
310 HierarchyStyle::Separate => self.separate(ui, &mut msgs),
311 HierarchyStyle::Tree => self.tree(ui, &mut msgs),
312 HierarchyStyle::Variables => self.variable_list(ui, &mut msgs),
313 }
314 });
315 }
316
317 if self.command_prompt.visible {
318 show_command_prompt(self, ui, window_size, &mut msgs);
319 if let Some(new_idx) = self.command_prompt.new_selection {
320 self.command_prompt.selected = new_idx;
321 self.command_prompt.new_selection = None;
322 }
323 }
324
325 if let Some(user_waves) = &self.user.waves {
326 let scroll_offset = user_waves.scroll_offset;
327 if user_waves.any_displayed() {
328 let draw_focus_ids = self.command_prompt.visible
329 && expand_command(&self.command_prompt_text.borrow(), get_parser(self))
330 .expanded
331 .starts_with("item_focus");
332 if draw_focus_ids {
333 Panel::left("focus id list")
334 .default_size(40.)
335 .size_range(40.0..=max_width)
336 .show(ui, |ui| {
337 if self.show_default_timeline() {
338 ui.add_space(self.default_timeline_offset());
339 }
340 let response = ScrollArea::both()
341 .vertical_scroll_offset(scroll_offset)
342 .show(ui, |ui| {
343 self.draw_item_focus_list(ui);
344 });
345 self.user.waves.as_mut().unwrap().total_height =
346 response.inner_rect.height();
347 if (scroll_offset - response.state.offset.y).abs() > 5. {
348 msgs.push(Message::SetScrollOffset(response.state.offset.y));
349 }
350 });
351 }
352
353 Panel::left("variable list")
354 .frame(
355 Frame::default()
356 .inner_margin(0)
357 .outer_margin(0)
358 .fill(self.user.config.theme.secondary_ui_color.background)
359 .stroke(Stroke::NONE),
360 )
361 .default_size(100.)
362 .size_range(100.0..=max_width)
363 .show(ui, |ui| {
364 ui.style_mut().wrap_mode = Some(TextWrapMode::Extend);
365 let text_margin = Self::item_text_margin(ui);
366 ui.spacing_mut().item_spacing.y = 0.0;
370 if self.show_default_timeline() {
371 let header_top = ui.cursor().top();
372 ui.allocate_ui_with_layout(
373 Vec2::new(
374 ui.available_width(),
375 self.user.config.layout.waveforms_text_size,
376 ),
377 Layout::top_down(Align::LEFT),
378 |ui| {
379 ui.horizontal(|ui| {
380 ui.add_space(text_margin.x);
381 ui.label(RichText::new("Time").italics());
382 });
383 },
384 );
385 let target_bottom = header_top + self.default_timeline_offset();
389 let next_y = ui.cursor().top();
390 if next_y < target_bottom {
391 ui.add_space(target_bottom - next_y);
392 }
393 }
394
395 let response = ScrollArea::both()
396 .auto_shrink([false; 2])
397 .vertical_scroll_offset(scroll_offset)
398 .show(ui, |ui| {
399 self.draw_item_list(&mut msgs, ui);
400 });
401 self.user.waves.as_mut().unwrap().total_height =
402 response.inner_rect.height();
403 if (scroll_offset - response.state.offset.y).abs() > 5. {
404 msgs.push(Message::SetScrollOffset(response.state.offset.y));
405 }
406 });
407
408 self.draw_transaction_detail_panel(ui, max_width, &mut msgs);
410
411 Panel::left("variable values")
412 .frame(
413 Frame::default()
414 .inner_margin(0)
415 .outer_margin(0)
416 .fill(self.user.config.theme.secondary_ui_color.background)
417 .stroke(Stroke::NONE),
418 )
419 .default_size(100.)
420 .size_range(10.0..=max_width)
421 .show(ui, |ui| {
422 ui.style_mut().wrap_mode = Some(TextWrapMode::Extend);
423 if self.show_default_timeline() {
424 ui.add_space(self.default_timeline_offset());
425 }
426 let response = ScrollArea::both()
427 .auto_shrink([false; 2])
428 .vertical_scroll_offset(scroll_offset)
429 .show(ui, |ui| self.draw_var_values(ui, &mut msgs));
430 if (scroll_offset - response.state.offset.y).abs() > 5. {
431 msgs.push(Message::SetScrollOffset(response.state.offset.y));
432 }
433 });
434 let std_stroke = ui.style().visuals.widgets.noninteractive.bg_stroke;
435 ui.style_mut().visuals.widgets.noninteractive.bg_stroke =
436 Stroke::from(&self.user.config.theme.viewport_separator);
437
438 if self.user.show_annotation_list {
439 let Some(waves) = self.user.waves.as_ref() else {
440 return msgs;
441 };
442
443 let time_formatter = TimeFormatter::new(
444 &waves.inner.metadata().timescale,
445 &self.user.wanted_timeunit,
446 &self.get_time_format(),
447 );
448
449 let Some(waves) = self.user.waves.as_mut() else {
450 return msgs;
451 };
452 let annotation_groups = &mut waves.annotation_groups.clone();
453
454 Panel::right("Annotation list")
455 .default_size(290.)
456 .size_range(100.0..=max_width)
457 .show_separator_line(false)
458 .frame(
459 Frame::default()
460 .inner_margin(0)
461 .outer_margin(0)
462 .fill(self.user.config.theme.secondary_ui_color.background)
463 .stroke(std_stroke),
464 )
465 .show(ui, |ui| {
466 waves.draw_annotation_list(
467 ui,
468 &mut msgs,
469 &time_formatter,
470 annotation_groups,
471 );
472 });
473
474 waves.annotation_groups = annotation_groups.clone();
475 }
476
477 self.click_handled = false;
478
479 let number_of_viewports = self.user.waves.as_ref().unwrap().viewports.len();
480 if number_of_viewports > 1 {
481 let max_width = ui.available_width();
483 let default_size = max_width / (number_of_viewports as f32);
484 for viewport_idx in 1..number_of_viewports {
485 Panel::right(format!("view port {viewport_idx}"))
486 .default_size(default_size)
487 .size_range(30.0..=max_width)
488 .frame(Frame {
489 inner_margin: Margin::ZERO,
490 outer_margin: Margin::ZERO,
491 ..Default::default()
492 })
493 .show(ui, |ui| self.draw_items(ui, &mut msgs, viewport_idx));
494 }
495 }
496
497 CentralPanel::default()
498 .frame(Frame {
499 inner_margin: Margin::ZERO,
500 outer_margin: Margin::ZERO,
501 ..Default::default()
502 })
503 .show(ui, |ui| {
504 self.draw_items(ui, &mut msgs, 0);
505 if ui.input(|i| i.pointer.primary_clicked()) && !self.click_handled {
506 msgs.push(Message::AnnotationClicked(None, None, None, None, None));
507 }
508 });
509 ui.style_mut().visuals.widgets.noninteractive.bg_stroke = std_stroke;
510 }
511 }
512
513 if self.user.waves.is_none()
514 || self
515 .user
516 .waves
517 .as_ref()
518 .is_some_and(|waves| !waves.any_displayed())
519 {
520 CentralPanel::default()
521 .frame(Frame::NONE.fill(self.user.config.theme.canvas_colors.background))
522 .show(ui, |ui| {
523 ui.add_space(max_height * 0.1);
524 ui.vertical_centered(|ui| {
525 ui.label(RichText::new("π Surfer").monospace().size(24.));
526 ui.add_space(20.);
527 let layout = Layout::top_down(Align::LEFT);
528 ui.allocate_ui_with_layout(
529 Vec2 {
530 x: max_width * 0.35,
531 y: max_height * 0.5,
532 },
533 layout,
534 |ui| self.help_message(ui),
535 );
536 });
537 });
538 }
539
540 ui.input(|i| {
541 i.raw.dropped_files.iter().for_each(|file| {
542 info!("Got dropped file");
543 msgs.push(Message::FileDropped(file.clone()));
544 });
545 });
546
547 if !self.user.show_url_entry
554 && self.user.show_reload_suggestion.is_none()
555 && !ui.egui_wants_keyboard_input()
556 {
557 self.handle_pressed_keys(ui, &mut msgs);
558 }
559
560 msgs
561 }
562
563 fn draw_load_url(&self, ui: &mut Ui, msgs: &mut Vec<Message>) {
564 let mut open = true;
565 egui::Window::new("Load URL")
566 .open(&mut open)
567 .collapsible(false)
568 .resizable(true)
569 .show(ui, |ui| {
570 ui.vertical_centered(|ui| {
571 let url = &mut *self.url.borrow_mut();
572 let response = ui.text_edit_singleline(url);
573 ui.horizontal(|ui| {
574 if ui.button("Load URL").clicked()
575 || (response.lost_focus()
576 && ui.input(|i| i.key_pressed(egui::Key::Enter)))
577 {
578 if let Some(callback) = &self.url_callback {
579 msgs.push(callback(url.clone()));
580 }
581 msgs.push(Message::SetUrlEntryVisible(false, None));
582 }
583 if ui.button("Cancel").clicked() {
584 msgs.push(Message::SetUrlEntryVisible(false, None));
585 }
586 });
587 });
588 });
589 if !open {
590 msgs.push(Message::SetUrlEntryVisible(false, None));
591 }
592 }
593
594 pub fn handle_pointer_in_ui(&self, ui: &mut Ui, msgs: &mut Vec<Message>) {
595 if ui.ui_contains_pointer() {
596 let scroll_delta = ui.input(|i| i.smooth_scroll_delta);
597 if scroll_delta.y > 0.0 {
598 msgs.push(Message::InvalidateCount);
599 msgs.push(Message::VerticalScroll(MoveDir::Up, self.get_count()));
600 } else if scroll_delta.y < 0.0 {
601 msgs.push(Message::InvalidateCount);
602 msgs.push(Message::VerticalScroll(MoveDir::Down, self.get_count()));
603 }
604 }
605 }
606
607 fn add_padding_for_last_bottom(ui: &mut Ui, last_bottom: Option<f32>, line_height: f32) {
611 if let Some(bottom) = last_bottom {
612 let target_bottom = bottom + line_height;
613 let next_y = ui.cursor().top();
614 if next_y < target_bottom {
615 ui.add_space(target_bottom - next_y);
616 }
617 }
618 }
619
620 fn item_text_margin(ui: &Ui) -> Vec2 {
621 ui.spacing().item_spacing
622 }
623
624 fn enforce_stable_row_widget_expansion(ui: &mut Ui) {
625 let visuals = &mut ui.style_mut().visuals.widgets;
626 visuals.inactive.expansion = 0.0;
627 visuals.hovered.expansion = 0.0;
628 visuals.active.expansion = 0.0;
629 visuals.open.expansion = 0.0;
630 }
631
632 fn desired_item_row_height(&self, displayed_item: &DisplayedItem) -> f32 {
633 let base_row_height = self.user.config.layout.waveforms_line_height
634 + 2.0 * self.user.config.layout.waveforms_gap;
635 match displayed_item {
636 DisplayedItem::Variable(_) | DisplayedItem::Placeholder(_) => {
637 self.user.config.layout.waveforms_line_height
638 * displayed_item.height_scaling_factor()
639 + 2.0 * self.user.config.layout.waveforms_gap
640 }
641 DisplayedItem::Stream(stream) => {
642 self.user.config.layout.transactions_line_height * stream.rows as f32
643 }
644 DisplayedItem::Divider(_)
645 | DisplayedItem::Marker(_)
646 | DisplayedItem::TimeLine(_)
647 | DisplayedItem::Group(_) => base_row_height,
648 }
649 }
650
651 fn flatten_variable_rows(
656 unfolded_fields: &AHashSet<Vec<String>>,
657 field: &FieldRef,
658 info: &VariableInfo,
659 depth: u32,
660 out: &mut Vec<VariableFieldRow>,
661 ) {
662 match info {
663 VariableInfo::Compound { subfields } => {
664 let unfolded = unfolded_fields.contains(&field.field);
665 out.push(VariableFieldRow {
666 field: field.clone(),
667 depth,
668 is_compound: true,
669 has_children: !subfields.is_empty(),
670 unfolded,
671 });
672 if unfolded {
673 for (name, child_info) in subfields {
674 let mut child_field = field.clone();
675 child_field.field.push(name.clone());
676 Self::flatten_variable_rows(
677 unfolded_fields,
678 &child_field,
679 child_info,
680 depth + 1,
681 out,
682 );
683 }
684 }
685 }
686 VariableInfo::Bool
687 | VariableInfo::Bits
688 | VariableInfo::Clock
689 | VariableInfo::String
690 | VariableInfo::Event
691 | VariableInfo::Real => out.push(VariableFieldRow {
692 field: field.clone(),
693 depth,
694 is_compound: false,
695 has_children: false,
696 unfolded: false,
697 }),
698 }
699 }
700
701 pub(crate) fn expand_levels_to_unfolded_fields(
704 info: &VariableInfo,
705 levels: usize,
706 ) -> AHashSet<Vec<String>> {
707 let mut out = AHashSet::new();
708 Self::collect_unfolded_fields(info, &mut Vec::new(), levels, &mut out);
709 out
710 }
711
712 fn collect_unfolded_fields(
713 info: &VariableInfo,
714 field: &mut Vec<String>,
715 levels: usize,
716 out: &mut AHashSet<Vec<String>>,
717 ) {
718 if levels == 0 {
719 return;
720 }
721 if let VariableInfo::Compound { subfields } = info {
722 out.insert(field.clone());
723 for (name, child_info) in subfields {
724 field.push(name.clone());
725 Self::collect_unfolded_fields(child_info, field, levels - 1, out);
726 field.pop();
727 }
728 }
729 }
730
731 fn unfolded_fields_signature(unfolded_fields: &AHashSet<Vec<String>>) -> u64 {
732 let mut items: Vec<&Vec<String>> = unfolded_fields.iter().collect();
733 items.sort();
734 let mut hasher = AHasher::default();
735 items.hash(&mut hasher);
736 hasher.finish()
737 }
738
739 fn flattened_variable_rows(
742 &self,
743 item_ref: DisplayedItemRef,
744 field: &FieldRef,
745 info: &VariableInfo,
746 unfolded_fields: &AHashSet<Vec<String>>,
747 ) -> Rc<Vec<VariableFieldRow>> {
748 let signature = Self::unfolded_fields_signature(unfolded_fields);
749 if let Some((cached_signature, rows)) = self.flattened_rows_cache.borrow().get(&item_ref)
750 && *cached_signature == signature
751 {
752 return Rc::clone(rows);
753 }
754 let mut rows = Vec::new();
755 Self::flatten_variable_rows(unfolded_fields, field, info, 0, &mut rows);
756 let rows = Rc::new(rows);
757 self.flattened_rows_cache
758 .borrow_mut()
759 .insert(item_ref, (signature, Rc::clone(&rows)));
760 rows
761 }
762
763 fn compute_item_drawing_infos(&self, waves: &WaveData) -> Vec<ItemDrawingInfo> {
769 let mut out = Vec::new();
770 let mut y = 0.0f32;
771 for info in waves.items_tree.iter_visible_extra() {
772 let item_ref = info.node.item_ref;
773 let vidx = info.vidx;
774 let Some(displayed_item) = waves.displayed_items.get(&item_ref) else {
775 continue;
776 };
777 let row_height = self.desired_item_row_height(displayed_item);
778 match displayed_item {
779 DisplayedItem::Variable(displayed_variable) => {
780 let field = FieldRef::without_fields(displayed_variable.variable_ref.clone());
781 let rows = self.flattened_variable_rows(
782 item_ref,
783 &field,
784 &displayed_variable.info,
785 &displayed_variable.unfolded_fields,
786 );
787 for row in rows.iter() {
788 let top = y;
789 let bottom = y + row_height;
790 out.push(ItemDrawingInfo::Variable(VariableDrawingInfo {
791 displayed_field_ref: DisplayedFieldRef {
792 item: item_ref,
793 field: row.field.field.clone(),
794 },
795 field_ref: row.field.clone(),
796 vidx,
797 top,
798 bottom,
799 }));
800 y = bottom;
801 }
802 }
803 DisplayedItem::Divider(_) => {
804 out.push(ItemDrawingInfo::Divider(DividerDrawingInfo {
805 vidx,
806 top: y,
807 bottom: y + row_height,
808 }));
809 y += row_height;
810 }
811 DisplayedItem::Marker(cursor) => {
812 out.push(ItemDrawingInfo::Marker(MarkerDrawingInfo {
813 vidx,
814 top: y,
815 bottom: y + row_height,
816 idx: cursor.idx,
817 }));
818 y += row_height;
819 }
820 DisplayedItem::TimeLine(_) => {
821 out.push(ItemDrawingInfo::TimeLine(TimeLineDrawingInfo {
822 vidx,
823 top: y,
824 bottom: y + row_height,
825 }));
826 y += row_height;
827 }
828 DisplayedItem::Stream(stream) => {
829 out.push(ItemDrawingInfo::Stream(StreamDrawingInfo {
830 transaction_stream_ref: stream.transaction_stream_ref.clone(),
831 vidx,
832 top: y,
833 bottom: y + row_height,
834 }));
835 y += row_height;
836 }
837 DisplayedItem::Group(_) => {
838 out.push(ItemDrawingInfo::Group(GroupDrawingInfo {
839 vidx,
840 top: y,
841 bottom: y + row_height,
842 }));
843 y += row_height;
844 }
845 DisplayedItem::Placeholder(_) => {
846 out.push(ItemDrawingInfo::Placeholder(PlaceholderDrawingInfo {
847 vidx,
848 top: y,
849 bottom: y + row_height,
850 }));
851 y += row_height;
852 }
853 }
854 }
855 out
856 }
857
858 fn item_layout_signature(&self, waves: &WaveData) -> u64 {
861 let mut hasher = AHasher::default();
862 let layout = &self.user.config.layout;
863 layout.waveforms_line_height.to_bits().hash(&mut hasher);
864 layout.waveforms_gap.to_bits().hash(&mut hasher);
865 layout.transactions_line_height.to_bits().hash(&mut hasher);
866 for info in waves.items_tree.iter_visible_extra() {
867 info.node.item_ref.0.hash(&mut hasher);
868 info.node.level.hash(&mut hasher);
869 info.node.unfolded.hash(&mut hasher);
870 match waves.displayed_items.get(&info.node.item_ref) {
871 Some(DisplayedItem::Variable(v)) => {
872 v.height_scaling_factor.map(f32::to_bits).hash(&mut hasher);
873 Self::unfolded_fields_signature(&v.unfolded_fields).hash(&mut hasher);
874 }
875 Some(DisplayedItem::Placeholder(p)) => {
876 p.height_scaling_factor.map(f32::to_bits).hash(&mut hasher);
877 }
878 Some(DisplayedItem::Stream(s)) => {
879 s.rows.hash(&mut hasher);
880 }
881 _ => {}
882 }
883 }
884 hasher.finish()
885 }
886
887 fn ensure_drawing_infos_cached(&mut self) {
891 let Some(waves) = self.user.waves.as_ref() else {
892 return;
893 };
894 let signature = self.item_layout_signature(waves);
895 if waves.drawing_infos_signature == Some(signature) {
896 return;
897 }
898 let infos = self.compute_item_drawing_infos(waves);
899 let waves = self.user.waves.as_mut().unwrap();
900 waves.drawing_infos = infos;
901 waves.drawing_infos_signature = Some(signature);
902 }
903
904 fn draw_item_focus_list(&self, ui: &mut Ui) {
905 let Some(waves) = self.user.waves.as_ref() else {
906 return;
907 };
908 let alignment = self.get_name_alignment();
909 ui.with_layout(
910 Layout::top_down(alignment).with_cross_justify(false),
911 |ui| {
912 let start_y = ui.cursor().top();
913 let row_layout = if alignment == Align::LEFT {
914 Layout::left_to_right(Align::Center)
915 } else {
916 Layout::right_to_left(Align::Center)
917 };
918 let clip = ui.clip_rect();
919 let visible_top = clip.top() - start_y;
920 let visible_bottom = clip.bottom() - start_y;
921 for drawing_info in waves.visible_drawing_infos(visible_top, visible_bottom) {
923 let next_y = ui.cursor().top();
924 if next_y < drawing_info.top_at(start_y) {
926 ui.add_space(drawing_info.top_at(start_y) - next_y);
927 }
928 let vidx = drawing_info.vidx();
929 let row_rect = Rect::from_min_max(
930 Pos2::new(ui.max_rect().left(), drawing_info.top_at(start_y)),
931 Pos2::new(ui.max_rect().right(), drawing_info.bottom_at(start_y)),
932 );
933 let mut row_ui =
934 ui.new_child(UiBuilder::new().max_rect(row_rect).layout(row_layout));
935 Self::enforce_stable_row_widget_expansion(&mut row_ui);
936 row_ui.style_mut().visuals.selection.bg_fill =
937 self.user.config.theme.accent_warn.background;
938 row_ui.style_mut().visuals.override_text_color =
939 Some(self.user.config.theme.accent_warn.foreground);
940 let _ = row_ui.selectable_label(true, get_alpha_focus_id(vidx, waves));
941 }
942 Self::add_padding_for_last_bottom(
943 ui,
944 waves.drawing_bottom_at(start_y),
945 self.user.config.layout.waveforms_line_height,
946 );
947 },
948 );
949 }
950
951 fn hierarchy_icon(
952 &self,
953 ui: &mut Ui,
954 has_children: bool,
955 unfolded: bool,
956 alignment: Align,
957 ) -> egui::Response {
958 let (rect, response) = ui.allocate_exact_size(
959 Vec2::splat(self.user.config.layout.waveforms_text_size),
960 Sense::click(),
961 );
962 if !has_children {
963 return response;
964 }
965
966 let icon_rect = Rect::from_center_size(
969 rect.center(),
970 emath::vec2(rect.width(), rect.height()) * 0.75,
971 );
972 let mut points = vec![
973 icon_rect.left_top(),
974 icon_rect.right_top(),
975 icon_rect.center_bottom(),
976 ];
977 let rotation = emath::Rot2::from_angle(if unfolded {
978 0.0
979 } else if alignment == Align::LEFT {
980 -std::f32::consts::PI * 0.5
981 } else {
982 std::f32::consts::PI * 0.5
983 });
984 for p in &mut points {
985 *p = icon_rect.center() + rotation * (*p - icon_rect.center());
986 }
987
988 let style = ui.style().interact(&response);
989 ui.painter().add(Shape::convex_polygon(
990 points,
991 style.fg_stroke.color,
992 Stroke::NONE,
993 ));
994 response
995 }
996
997 fn draw_item_list(&mut self, msgs: &mut Vec<Message>, ui: &mut Ui) {
998 let (any_groups, alignment) = {
999 let Some(waves) = self.user.waves.as_ref() else {
1000 return;
1001 };
1002 (
1003 waves.items_tree.iter().any(|node| node.level > 0),
1004 self.get_name_alignment(),
1005 )
1006 };
1007 let text_margin = Self::item_text_margin(ui);
1008
1009 self.ensure_drawing_infos_cached();
1012
1013 ui.with_layout(Layout::top_down(alignment).with_cross_justify(true), |ui| {
1014 let background_rect = ui.max_rect();
1015 let painter = ui.painter().clone();
1016
1017 let rect_with_margin = Rect {
1020 min: background_rect.min + Vec2::new(text_margin.x, 0.0),
1021 max: background_rect.max + Vec2::new(0.0, 40.0),
1022 };
1023
1024 let builder = UiBuilder::new().max_rect(rect_with_margin);
1025 ui.scope_builder(builder, |ui| {
1026 ui.spacing_mut().item_spacing.y = 0.0;
1028 let content_rect = ui.available_rect_before_wrap();
1029 let start_y = ui.cursor().top();
1033 let clip = ui.clip_rect();
1034 let clip_top = clip.top() - start_y;
1035 let clip_bottom = clip.bottom() - start_y;
1036
1037 let Some(waves) = self.user.waves.as_ref() else {
1038 return;
1039 };
1040
1041 let total_bottom = waves.drawing_bottom().unwrap_or(0.0);
1042 let mut row_iter = waves.drawing_infos.iter().peekable();
1043
1044 for (item_count, info) in waves.items_tree.iter_visible_extra().enumerate() {
1045 let item_ref = info.node.item_ref;
1046 let vidx = info.vidx;
1047 let Some(displayed_item) = waves.displayed_items.get(&item_ref) else {
1048 continue;
1049 };
1050
1051 let mut item_rows: Vec<&ItemDrawingInfo> = Vec::new();
1053 while row_iter.peek().is_some_and(|r| r.vidx() == vidx) {
1054 item_rows.push(row_iter.next().unwrap());
1055 }
1056 let Some(&first_row) = item_rows.first() else {
1057 continue;
1058 };
1059 let last_row = item_rows.last().unwrap();
1060
1061 let background_color = self.get_background_color(waves, vidx, item_count);
1062
1063 let is_visible =
1064 last_row.bottom() >= clip_top && first_row.top() <= clip_bottom;
1065 if !is_visible {
1066 continue;
1068 }
1069
1070 let row_top = first_row.top_at(start_y);
1071 let row_bottom = last_row.bottom_at(start_y);
1072
1073 let min = Pos2::new(background_rect.left(), row_top);
1074 let max = Pos2::new(background_rect.right(), row_bottom);
1075 painter.rect_filled(Rect { min, max }, CornerRadius::ZERO, background_color);
1076
1077 let row_layout = if alignment == Align::LEFT {
1080 Layout::left_to_right(Align::Center)
1081 } else {
1082 Layout::right_to_left(Align::Center)
1083 };
1084 let row_rect = Rect {
1087 min: Pos2::new(content_rect.left(), row_top),
1088 max,
1089 };
1090 let mut row_ui =
1091 ui.new_child(UiBuilder::new().max_rect(row_rect).layout(row_layout));
1092 let row_ui = &mut row_ui;
1093
1094 row_ui.add_space(10.0 * f32::from(info.node.level));
1095 if any_groups {
1096 let response = self.hierarchy_icon(
1097 row_ui,
1098 info.has_children,
1099 info.node.unfolded,
1100 alignment,
1101 );
1102 if response.clicked() {
1103 if info.node.unfolded {
1104 msgs.push(Message::GroupFold(Some(item_ref)));
1105 } else {
1106 msgs.push(Message::GroupUnfold(Some(item_ref)));
1107 }
1108 }
1109 }
1110
1111 match displayed_item {
1112 DisplayedItem::Variable(displayed_variable) => self.draw_variable(
1113 msgs,
1114 vidx,
1115 displayed_item,
1116 item_ref,
1117 &FieldRef::without_fields(displayed_variable.variable_ref.clone()),
1118 &displayed_variable.info,
1119 &displayed_variable.unfolded_fields,
1120 &item_rows,
1121 start_y,
1122 row_ui,
1123 alignment,
1124 background_color,
1125 ),
1126 DisplayedItem::Divider(_)
1127 | DisplayedItem::Marker(_)
1128 | DisplayedItem::Placeholder(_)
1129 | DisplayedItem::TimeLine(_)
1130 | DisplayedItem::Stream(_)
1131 | DisplayedItem::Group(_) => {
1132 row_ui.with_layout(
1133 row_ui
1134 .layout()
1135 .with_main_justify(true)
1136 .with_main_align(alignment),
1137 |ui| {
1138 self.draw_plain_item(
1139 msgs,
1140 vidx,
1141 item_ref,
1142 displayed_item,
1143 ui,
1144 background_color,
1145 );
1146 },
1147 );
1148 }
1149 }
1150
1151 let mut expanded_rect = Rect::from_min_max(
1153 Pos2::new(row_rect.min.x, row_top),
1154 Pos2::new(row_rect.max.x, first_row.bottom_at(start_y)),
1155 );
1156 expanded_rect.set_left(
1157 content_rect.left()
1158 + self.user.config.layout.waveforms_text_size
1159 + text_margin.x,
1160 );
1161 expanded_rect.set_right(content_rect.right());
1162 self.draw_drag_target(
1163 msgs,
1164 vidx,
1165 expanded_rect,
1166 content_rect,
1167 row_ui,
1168 info.last,
1169 );
1170 }
1171
1172 let target_bottom =
1174 total_bottom + start_y + self.user.config.layout.waveforms_line_height;
1175 let next_y = ui.cursor().top();
1176 if next_y < target_bottom {
1177 ui.add_space(target_bottom - next_y);
1178 }
1179 });
1180 });
1181
1182 let response = ui.allocate_response(ui.available_size(), Sense::click());
1184 generic_context_menu(msgs, &response);
1185 }
1186
1187 fn get_name_alignment(&self) -> Align {
1188 if self.align_names_right() {
1189 Align::RIGHT
1190 } else {
1191 Align::LEFT
1192 }
1193 }
1194
1195 fn draw_drag_source(
1196 &self,
1197 msgs: &mut Vec<Message>,
1198 vidx: VisibleItemIndex,
1199 item_response: &egui::Response,
1200 modifiers: egui::Modifiers,
1201 ) {
1202 if item_response.dragged_by(egui::PointerButton::Primary)
1203 && item_response.drag_delta().length() > self.user.config.theme.drag_threshold
1204 {
1205 if !modifiers.ctrl
1206 && !(self.user.waves.as_ref())
1207 .and_then(|w| w.items_tree.get_visible(vidx))
1208 .is_some_and(|i| i.selected)
1209 {
1210 msgs.push(Message::FocusItem(vidx));
1211 msgs.push(Message::ItemSelectionClear);
1212 }
1213 msgs.push(Message::SetItemSelected(vidx, true));
1214 msgs.push(Message::VariableDragStarted(vidx));
1215 }
1216
1217 if item_response.drag_stopped()
1218 && self
1219 .user
1220 .drag_source_idx
1221 .is_some_and(|source_idx| source_idx == vidx)
1222 {
1223 msgs.push(Message::VariableDragFinished);
1224 }
1225 }
1226
1227 #[allow(clippy::too_many_arguments)]
1228 fn draw_variable_label(
1229 &self,
1230 vidx: VisibleItemIndex,
1231 displayed_item: &DisplayedItem,
1232 displayed_id: DisplayedItemRef,
1233 field: &FieldRef,
1234 msgs: &mut Vec<Message>,
1235 ui: &mut Ui,
1236 meta: Option<&VariableMeta>,
1237 background_color: Color32,
1238 ) -> egui::Response {
1239 let mut variable_label = self.draw_item_label(
1240 vidx,
1241 displayed_id,
1242 displayed_item,
1243 Some(field),
1244 msgs,
1245 ui,
1246 meta,
1247 background_color,
1248 );
1249
1250 if self.show_tooltip() {
1251 variable_label = variable_label.on_hover_ui(|ui| {
1252 let tooltip = if let Some(user_waves) = &self.user.waves {
1253 if field.field.is_empty() {
1254 if meta.is_some() {
1255 variable_tooltip_text(meta, &field.root)
1256 } else {
1257 let wave_container = user_waves.inner.as_waves().unwrap();
1258 let meta = wave_container.variable_meta(&field.root).ok();
1259 variable_tooltip_text(meta.as_ref(), &field.root)
1260 }
1261 } else {
1262 "From translator".to_string()
1263 }
1264 } else {
1265 "No waveform loaded".to_string()
1266 };
1267 ui.set_max_width(ui.spacing().tooltip_width);
1268 ui.add(egui::Label::new(tooltip));
1269 });
1270 }
1271
1272 variable_label
1273 }
1274
1275 #[allow(clippy::too_many_arguments)]
1276 fn draw_variable(
1277 &self,
1278 msgs: &mut Vec<Message>,
1279 vidx: VisibleItemIndex,
1280 displayed_item: &DisplayedItem,
1281 displayed_id: DisplayedItemRef,
1282 field: &FieldRef,
1283 info: &VariableInfo,
1284 unfolded_fields: &AHashSet<Vec<String>>,
1285 item_rows: &[&ItemDrawingInfo],
1286 start_y: f32,
1287 ui: &mut Ui,
1288 alignment: Align,
1289 background_color: Color32,
1290 ) {
1291 let wave_top_padding = self.user.config.layout.waveforms_gap;
1292 let rows = self.flattened_variable_rows(displayed_id, field, info, unfolded_fields);
1295 let clip = ui.clip_rect();
1298 let clip_top = clip.top() - start_y;
1299 let clip_bottom = clip.bottom() - start_y;
1300
1301 ui.with_layout(Layout::top_down(alignment).with_cross_justify(true), |ui| {
1302 for (row, position) in rows.iter().zip(item_rows.iter()) {
1303 if !position.overlaps(clip_top, clip_bottom) {
1306 continue;
1307 }
1308
1309 let row_top = position.top_at(start_y);
1310 let row_bottom = position.bottom_at(start_y);
1311
1312 let row_layout = if alignment == Align::LEFT {
1313 Layout::left_to_right(Align::Center)
1314 } else {
1315 Layout::right_to_left(Align::Center)
1316 };
1317 let row_rect = Rect::from_min_max(
1318 Pos2::new(ui.max_rect().left(), row_top),
1319 Pos2::new(ui.max_rect().right(), row_bottom),
1320 );
1321 let mut row_ui =
1322 ui.new_child(UiBuilder::new().max_rect(row_rect).layout(row_layout));
1323 Self::enforce_stable_row_widget_expansion(&mut row_ui);
1324 row_ui.add_space(10.0 * row.depth as f32);
1325
1326 if row.is_compound {
1327 let icon_response =
1328 self.hierarchy_icon(&mut row_ui, row.has_children, row.unfolded, alignment);
1329 if icon_response.clicked() {
1330 msgs.push(Message::ToggleVariableFieldFold(
1331 displayed_id,
1332 row.field.field.clone(),
1333 ));
1334 }
1335 }
1336
1337 let label_response = row_ui
1338 .with_layout(Layout::top_down(alignment).with_cross_justify(true), |ui| {
1339 ui.add_space(wave_top_padding);
1340 self.draw_variable_label(
1341 vidx,
1342 displayed_item,
1343 displayed_id,
1344 &row.field,
1345 msgs,
1346 ui,
1347 None,
1348 background_color,
1349 )
1350 })
1351 .inner;
1352
1353 if !row.is_compound {
1355 self.draw_drag_source(msgs, vidx, &label_response, ui.input(|e| e.modifiers));
1356 }
1357 }
1358 });
1359 }
1360
1361 fn draw_drag_target(
1362 &self,
1363 msgs: &mut Vec<Message>,
1364 vidx: VisibleItemIndex,
1365 expanded_rect: Rect,
1366 content_rect: Rect,
1367 ui: &mut Ui,
1368 last: bool,
1369 ) {
1370 if !self.user.drag_started || self.user.drag_source_idx.is_none() {
1371 return;
1372 }
1373
1374 let waves = self
1375 .user
1376 .waves
1377 .as_ref()
1378 .expect("waves not available, but expected");
1379
1380 let rect_with_margin = expanded_rect.expand2(ui.spacing().item_spacing / 2f32);
1383
1384 let before_rect = rect_with_margin
1389 .with_max_y(rect_with_margin.left_center().y)
1390 .with_min_x(content_rect.left())
1391 .round_to_pixels(ui.painter().pixels_per_point());
1392 let after_rect = if last {
1393 rect_with_margin.with_max_y(ui.max_rect().max.y)
1394 } else {
1395 rect_with_margin
1396 }
1397 .with_min_y(rect_with_margin.left_center().y)
1398 .with_min_x(content_rect.left())
1399 .round_to_pixels(ui.painter().pixels_per_point());
1400
1401 let (insert_vidx, line_y) = if ui.rect_contains_pointer(before_rect) {
1402 (vidx, rect_with_margin.top())
1403 } else if ui.rect_contains_pointer(after_rect) {
1404 (VisibleItemIndex(vidx.0 + 1), rect_with_margin.bottom())
1405 } else {
1406 return;
1407 };
1408
1409 let level_range = waves.items_tree.valid_levels_visible(insert_vidx, |node| {
1410 matches!(
1411 waves.displayed_items.get(&node.item_ref),
1412 Some(DisplayedItem::Group(..))
1413 )
1414 });
1415
1416 let left_x = |level: u8| -> f32 { rect_with_margin.left() + f32::from(level) * 10.0 };
1417 let Some(insert_level) = level_range.find_or_last(|&level| {
1418 let mut rect = expanded_rect.with_min_x(left_x(level));
1419 rect.set_width(10.0);
1420 if level == 0 {
1421 rect.set_left(content_rect.left());
1422 }
1423 ui.rect_contains_pointer(rect)
1424 }) else {
1425 return;
1426 };
1427
1428 ui.painter().line_segment(
1429 [
1430 Pos2::new(left_x(insert_level), line_y),
1431 Pos2::new(rect_with_margin.right(), line_y),
1432 ],
1433 Stroke::new(
1434 self.user.config.theme.linewidth,
1435 self.user.config.theme.drag_hint_color,
1436 ),
1437 );
1438 msgs.push(Message::VariableDragTargetChanged(
1439 crate::displayed_item_tree::TargetPosition {
1440 before: ItemIndex(
1441 waves
1442 .items_tree
1443 .to_displayed(insert_vidx)
1444 .map_or_else(|| waves.items_tree.len(), |index| index.0),
1445 ),
1446 level: insert_level,
1447 },
1448 ));
1449 }
1450
1451 #[allow(clippy::too_many_arguments)]
1452 fn draw_item_label(
1453 &self,
1454 vidx: VisibleItemIndex,
1455 displayed_id: DisplayedItemRef,
1456 displayed_item: &DisplayedItem,
1457 field: Option<&FieldRef>,
1458 msgs: &mut Vec<Message>,
1459 ui: &mut Ui,
1460 meta: Option<&VariableMeta>,
1461 background_color: Color32,
1462 ) -> egui::Response {
1463 let color_pair = {
1464 if self.item_is_focused(vidx) {
1465 &self.user.config.theme.accent_info
1466 } else if self.item_is_selected(displayed_id) {
1467 &self.user.config.theme.selected_elements_colors
1468 } else if matches!(
1469 displayed_item,
1470 DisplayedItem::Variable(_) | DisplayedItem::Placeholder(_)
1471 ) {
1472 &ThemeColorPair {
1473 background: background_color,
1474 foreground: self.user.config.theme.get_best_text_color(background_color),
1475 }
1476 } else {
1477 &ThemeColorPair {
1478 background: self.user.config.theme.primary_ui_color.background,
1479 foreground: self.get_item_text_color(displayed_item),
1480 }
1481 }
1482 };
1483 {
1484 let style = ui.style_mut();
1485 style.visuals.selection.bg_fill = color_pair.background;
1486 }
1487
1488 let mut layout_job = LayoutJob::default();
1489 match displayed_item {
1490 DisplayedItem::Variable(var) if field.is_some() => {
1491 let field = field.unwrap();
1492 let base_line_height = self.user.config.layout.waveforms_line_height;
1493 layout_job.first_row_min_height =
1494 base_line_height * displayed_item.height_scaling_factor();
1495 if field.field.is_empty() {
1496 let name_info = self.get_variable_name_info(&var.variable_ref, meta);
1497
1498 if let Some(true_name) = name_info.and_then(|info| info.true_name) {
1499 let monospace_font =
1500 ui.style().text_styles.get(&TextStyle::Monospace).unwrap();
1501 let monospace_width = {
1502 ui.fonts_mut(|fonts| {
1503 fonts
1504 .layout_no_wrap(
1505 " ".to_string(),
1506 monospace_font.clone(),
1507 Color32::BLACK,
1508 )
1509 .size()
1510 .x
1511 })
1512 };
1513 let available_width = ui.available_width();
1514
1515 draw_true_name(
1516 &true_name,
1517 &mut layout_job,
1518 monospace_font,
1519 color_pair.foreground,
1520 monospace_width,
1521 available_width,
1522 base_line_height,
1523 );
1524 } else {
1525 displayed_item.add_to_layout_job(
1526 color_pair.foreground,
1527 ui.style(),
1528 &mut layout_job,
1529 Some(field),
1530 &self.user.config,
1531 );
1532 }
1533 } else {
1534 RichText::new(field.field.last().unwrap().clone())
1535 .color(color_pair.foreground)
1536 .line_height(Some(base_line_height))
1537 .append_to(
1538 &mut layout_job,
1539 ui.style(),
1540 FontSelection::Default,
1541 Align::Center,
1542 );
1543 }
1544 }
1545 _ => displayed_item.add_to_layout_job(
1546 color_pair.foreground,
1547 ui.style(),
1548 &mut layout_job,
1549 field,
1550 &self.user.config,
1551 ),
1552 }
1553
1554 let item_label = ui
1555 .scope(|ui| {
1556 Self::enforce_stable_row_widget_expansion(ui);
1559 ui.selectable_label(
1560 self.item_is_selected(displayed_id) || self.item_is_focused(vidx),
1561 WidgetText::LayoutJob(layout_job.into()),
1562 )
1563 .interact(Sense::drag())
1564 })
1565 .inner;
1566
1567 if item_label.clicked() || item_label.secondary_clicked() {
1580 let focused_item = self.user.waves.as_ref().and_then(|w| w.focused_item);
1581 let is_focused = focused_item == Some(vidx);
1582 let is_selected = self.item_is_selected(displayed_id);
1583 let single_selected = self
1584 .user
1585 .waves
1586 .as_ref()
1587 .map(|w| {
1588 let it = w.items_tree.iter_visible_selected();
1590 it.count() == 1
1591 })
1592 .unwrap();
1593
1594 let modifiers = ui.input(|i| i.modifiers);
1595 tracing::trace!(focused_item=?focused_item, is_focused=?is_focused, is_selected=?is_selected, single_selected=?single_selected, modifiers=?modifiers);
1596
1597 if item_label.clicked() && is_selected && single_selected {
1599 msgs.push(Message::Batch(vec![
1600 Message::ItemSelectionClear,
1601 Message::UnfocusItem,
1602 ]));
1603 return item_label;
1604 }
1605
1606 match (item_label.clicked(), modifiers.command, modifiers.shift) {
1607 (false, false, false) if is_selected => {}
1608 (_, false, false) => {
1609 msgs.push(Message::Batch(vec![
1610 Message::ItemSelectionClear,
1611 Message::SetItemSelected(vidx, true),
1612 Message::FocusItem(vidx),
1613 ]));
1614 }
1615 (_, _, true) => msgs.push(Message::Batch(vec![
1616 Message::ItemSelectRange(vidx),
1617 Message::FocusItem(vidx),
1618 ])),
1619 (_, true, false) => {
1620 if !is_selected {
1621 msgs.push(Message::Batch(vec![
1622 Message::SetItemSelected(vidx, true),
1623 Message::FocusItem(vidx),
1624 ]));
1625 } else if item_label.clicked() {
1626 msgs.push(Message::Batch(vec![
1627 Message::SetItemSelected(vidx, false),
1628 Message::UnfocusItem,
1629 ]));
1630 }
1631 }
1632 }
1633 }
1634
1635 item_label.context_menu(|ui| {
1636 self.item_context_menu(
1637 field,
1638 msgs,
1639 ui,
1640 vidx,
1641 true,
1642 crate::message::MessageTarget::CurrentSelection,
1643 );
1644 });
1645
1646 item_label
1647 }
1648
1649 fn draw_plain_item(
1650 &self,
1651 msgs: &mut Vec<Message>,
1652 vidx: VisibleItemIndex,
1653 displayed_id: DisplayedItemRef,
1654 displayed_item: &DisplayedItem,
1655 ui: &mut Ui,
1656 background_color: Color32,
1657 ) {
1658 let wave_top_padding = self.user.config.layout.waveforms_gap;
1659 let row = ui.allocate_ui_with_layout(
1660 ui.available_size(),
1661 Layout::top_down(self.get_name_alignment()).with_cross_justify(true),
1662 |ui| {
1663 ui.add_space(wave_top_padding);
1664 self.draw_item_label(
1665 vidx,
1666 displayed_id,
1667 displayed_item,
1668 None,
1669 msgs,
1670 ui,
1671 None,
1672 background_color,
1673 )
1674 },
1675 );
1676
1677 self.draw_drag_source(msgs, vidx, &row.inner, ui.input(|e| e.modifiers));
1678 }
1679
1680 fn item_is_focused(&self, vidx: VisibleItemIndex) -> bool {
1681 if let Some(waves) = &self.user.waves {
1682 waves.focused_item == Some(vidx)
1683 } else {
1684 false
1685 }
1686 }
1687
1688 fn item_is_selected(&self, id: DisplayedItemRef) -> bool {
1689 if let Some(waves) = &self.user.waves {
1690 waves
1691 .items_tree
1692 .iter_visible_selected()
1693 .any(|node| node.item_ref == id)
1694 } else {
1695 false
1696 }
1697 }
1698
1699 fn draw_var_values(&self, ui: &mut Ui, msgs: &mut Vec<Message>) {
1700 let Some(waves) = &self.user.waves else {
1701 return;
1702 };
1703 let response = ui.allocate_response(ui.available_size(), Sense::click());
1704 generic_context_menu(msgs, &response);
1705
1706 let mut painter = ui.painter().clone();
1707 let rect = response.rect;
1708 let container_rect = Rect::from_min_size(Pos2::ZERO, rect.size());
1709 let to_screen = RectTransform::from_to(container_rect, rect);
1710 let cfg = DrawConfig::new(
1711 rect.size(),
1712 self.user.config.layout.waveforms_line_height,
1713 self.user.config.layout.waveforms_text_size,
1714 );
1715
1716 let ctx = DrawingContext {
1717 painter: &mut painter,
1718 cfg: &cfg,
1719 to_screen: &|x, y| to_screen.transform_pos(Pos2::new(x, y)),
1720 theme: &self.user.config.theme,
1721 };
1722
1723 let ucursor = waves.cursor.as_ref().and_then(num::BigInt::to_biguint);
1724
1725 let rect_with_margin = Rect {
1728 min: rect.min + Vec2::new(ui.spacing().item_spacing.x, 0.0),
1729 max: rect.max + Vec2::new(0.0, 40.0),
1730 };
1731
1732 let builder = UiBuilder::new().max_rect(rect_with_margin);
1733 ui.scope_builder(builder, |ui| {
1734 let text_style = TextStyle::Monospace;
1735 ui.style_mut().override_text_style = Some(text_style);
1736 ui.spacing_mut().item_spacing.y = 0.0;
1737 let start_y = ui.cursor().top();
1738 let clip = ui.clip_rect();
1739 let clip_top = clip.top() - start_y;
1740 let clip_bottom = clip.bottom() - start_y;
1741 for drawing_info in waves.visible_drawing_infos(clip_top, clip_bottom) {
1745 let next_y = ui.cursor().top();
1746 if next_y < drawing_info.top_at(start_y) {
1750 ui.add_space(drawing_info.top_at(start_y) - next_y);
1751 }
1752
1753 let backgroundcolor =
1754 self.get_background_color(waves, drawing_info.vidx(), drawing_info.vidx().0);
1755 self.draw_background(drawing_info, start_y, &ctx, backgroundcolor);
1756 match drawing_info {
1757 ItemDrawingInfo::Variable(variable_info) => {
1758 if ucursor.as_ref().is_none() {
1759 ui.label("");
1760 continue;
1761 }
1762
1763 let v = self.get_variable_value(
1764 waves,
1765 &variable_info.displayed_field_ref,
1766 ucursor.as_ref(),
1767 );
1768 if let Some(v) = v {
1769 let waveforms_gap = self.user.config.layout.waveforms_gap;
1770 let waveform_height =
1771 (drawing_info.height() - 2.0 * waveforms_gap).max(1.0);
1772
1773 ui.add_space(waveforms_gap);
1774 let mut value_layout_job = LayoutJob {
1777 first_row_min_height: waveform_height,
1778 ..Default::default()
1779 };
1780 RichText::new(v)
1781 .color(self.user.config.theme.get_best_text_color(backgroundcolor))
1782 .line_height(Some(self.user.config.layout.waveforms_line_height))
1783 .append_to(
1784 &mut value_layout_job,
1785 ui.style(),
1786 FontSelection::Default,
1787 Align::Center,
1788 );
1789 ui.label(WidgetText::LayoutJob(value_layout_job.into()))
1790 .context_menu(|ui| {
1791 self.item_context_menu(
1792 Some(&variable_info.field_ref),
1793 msgs,
1794 ui,
1795 variable_info.vidx,
1796 true,
1797 crate::message::MessageTarget::CurrentSelection,
1798 );
1799 });
1800 }
1801 }
1802
1803 ItemDrawingInfo::Marker(numbered_cursor) => {
1804 if let Some(cursor) = &waves.cursor {
1805 let delta = time_string(
1806 &(waves.numbered_marker_time(numbered_cursor.idx) - cursor),
1807 &waves.inner.metadata().timescale,
1808 &self.user.wanted_timeunit,
1809 &self.get_time_format(),
1810 );
1811
1812 ui.add_space(self.user.config.layout.waveforms_gap);
1813 ui.label(
1814 RichText::new(format!("Ξ: {delta}"))
1815 .color(
1816 self.user.config.theme.get_best_text_color(backgroundcolor),
1817 )
1818 .line_height(Some(
1819 self.user.config.layout.waveforms_line_height.max(1.0),
1820 )),
1821 )
1822 .context_menu(|ui| {
1823 self.item_context_menu(
1824 None,
1825 msgs,
1826 ui,
1827 drawing_info.vidx(),
1828 true,
1829 crate::message::MessageTarget::CurrentSelection,
1830 );
1831 });
1832 } else {
1833 ui.label("");
1834 }
1835 }
1836 ItemDrawingInfo::Divider(_)
1837 | ItemDrawingInfo::TimeLine(_)
1838 | ItemDrawingInfo::Stream(_)
1839 | ItemDrawingInfo::Group(_)
1840 | ItemDrawingInfo::Placeholder(_) => {
1841 ui.label("");
1842 }
1843 }
1844 }
1845 Self::add_padding_for_last_bottom(
1846 ui,
1847 waves.drawing_bottom_at(start_y),
1848 self.user.config.layout.waveforms_line_height
1849 + 2.0 * self.user.config.layout.waveforms_gap,
1850 );
1851 });
1852 }
1853
1854 pub fn get_variable_value(
1855 &self,
1856 waves: &WaveData,
1857 displayed_field_ref: &DisplayedFieldRef,
1858 ucursor: Option<&num::BigUint>,
1859 ) -> Option<String> {
1860 let ucursor = ucursor?;
1861
1862 let DisplayedItem::Variable(displayed_variable) =
1863 waves.displayed_items.get(&displayed_field_ref.item)?
1864 else {
1865 return None;
1866 };
1867
1868 let variable = &displayed_variable.variable_ref;
1869 let meta = waves
1870 .inner
1871 .as_waves()
1872 .unwrap()
1873 .variable_meta(variable)
1874 .ok()?;
1875 let translator = waves.variable_translator_with_meta(
1876 &displayed_field_ref.without_field(),
1877 &self.translators,
1878 &meta,
1879 );
1880
1881 let wave_container = waves.inner.as_waves().unwrap();
1882 let query_result = wave_container
1883 .query_variable(variable, ucursor)
1884 .ok()
1885 .flatten()?;
1886
1887 let (time, val) = query_result.current?;
1888 let curr = self.translate_query_result(
1889 displayed_field_ref,
1890 displayed_variable,
1891 translator,
1892 &meta,
1893 &val,
1894 );
1895
1896 if time != *ucursor
1899 || (*ucursor).is_zero()
1900 || self.transition_value() == TransitionValue::Next
1901 {
1902 return curr;
1903 }
1904
1905 let prev_query_result = wave_container
1907 .query_variable(variable, &(ucursor - BigUint::one()))
1908 .ok()
1909 .flatten()?;
1910
1911 let (_, prev_val) = prev_query_result.current?;
1912 let prev = self.translate_query_result(
1913 displayed_field_ref,
1914 displayed_variable,
1915 translator,
1916 &meta,
1917 &prev_val,
1918 );
1919
1920 match self.transition_value() {
1921 TransitionValue::Previous => Some(format!("β{}", prev.unwrap_or_default())),
1922 TransitionValue::Both => match (curr, prev) {
1923 (Some(curr_val), Some(prev_val)) => Some(format!("{prev_val} β {curr_val}")),
1924 (None, Some(prev_val)) => Some(format!("{prev_val} β")),
1925 (Some(curr_val), None) => Some(format!("β {curr_val}")),
1926 _ => None,
1927 },
1928 TransitionValue::Next => curr, }
1930 }
1931
1932 fn translate_query_result(
1933 &self,
1934 displayed_field_ref: &DisplayedFieldRef,
1935 displayed_variable: &DisplayedVariable,
1936 translator: &dyn Translator<VarId, ScopeId, Message>,
1937 meta: &VariableMeta,
1938 val: &VariableValue,
1939 ) -> Option<String> {
1940 let translated = translator.translate(meta, val).ok()?;
1941 let fields = translated.format_flat(
1942 &displayed_variable.format,
1943 &displayed_variable.field_formats,
1944 &self.translators,
1945 );
1946
1947 let subfield = fields
1948 .iter()
1949 .find(|res| res.names == displayed_field_ref.field)?;
1950
1951 match &subfield.value {
1952 Some(TranslatedValue { value, .. }) => Some(value.clone()),
1953 None => Some("-".to_string()),
1954 }
1955 }
1956
1957 pub fn get_variable_name_info(
1958 &self,
1959 var: &VariableRef,
1960 meta: Option<&VariableMeta>,
1961 ) -> Option<VariableNameInfo> {
1962 self.variable_name_info_cache
1963 .borrow_mut()
1964 .entry(var.clone())
1965 .or_insert_with(|| {
1966 meta.as_ref().and_then(|meta| {
1967 self.translators
1968 .all_translators()
1969 .iter()
1970 .find_map(|t| t.variable_name_info(meta))
1971 })
1972 })
1973 .clone()
1974 }
1975
1976 pub fn draw_background(
1977 &self,
1978 drawing_info: &ItemDrawingInfo,
1979 y_offset: f32,
1980 ctx: &DrawingContext<'_>,
1981 background_color: Color32,
1982 ) {
1983 let row_top = drawing_info.top_at(y_offset);
1984 let row_bottom = drawing_info.bottom_at(y_offset);
1985 let left = (ctx.to_screen)(0.0, 0.0).x;
1986 let right = (ctx.to_screen)(ctx.cfg.canvas_size.x, 0.0).x;
1987 let min = Pos2::new(left, row_top);
1988 let max = Pos2::new(right, row_bottom);
1989 ctx.painter
1990 .rect_filled(Rect { min, max }, CornerRadius::ZERO, background_color);
1991 }
1992
1993 pub fn get_background_color(
1994 &self,
1995 waves: &WaveData,
1996 vidx: VisibleItemIndex,
1997 item_count: usize,
1998 ) -> Color32 {
1999 if let Some(focused) = waves.focused_item
2000 && matches!(self.focus_highlight(), FocusHighlight::Background)
2001 && focused == vidx
2002 {
2003 return self.user.config.theme.highlight_background;
2004 }
2005 waves
2006 .items_tree
2007 .get_visible(vidx)
2008 .and_then(|visible| waves.displayed_items.get(&visible.item_ref))
2009 .and_then(super::displayed_item::DisplayedItem::background_color)
2010 .and_then(|color| self.user.config.theme.get_color(color))
2011 .unwrap_or_else(|| self.get_default_alternating_background_color(item_count))
2012 }
2013
2014 fn get_default_alternating_background_color(&self, item_count: usize) -> Color32 {
2015 if self.user.config.theme.alt_frequency != 0
2017 && (item_count / self.user.config.theme.alt_frequency) % 2 == 1
2018 {
2019 self.user.config.theme.canvas_colors.alt_background
2020 } else {
2021 Color32::TRANSPARENT
2022 }
2023 }
2024
2025 pub fn draw_default_timeline(
2027 &self,
2028 waves: &WaveData,
2029 ctx: &DrawingContext,
2030 viewport_idx: usize,
2031 ) {
2032 let ticks = self.get_ticks_for_viewport_idx(waves, viewport_idx, ctx.cfg);
2033 let wave_top_padding = self.user.config.layout.waveforms_gap;
2034
2035 waves.draw_ticks(
2036 self.user.config.theme.foreground,
2037 &ticks,
2038 ctx,
2039 wave_top_padding,
2040 emath::Align2::CENTER_TOP,
2041 );
2042 }
2043}
2044
2045pub fn draw_true_name(
2046 true_name: &TrueName,
2047 layout_job: &mut LayoutJob,
2048 font: &FontId,
2049 foreground: Color32,
2050 char_width: f32,
2051 allowed_space: f32,
2052 line_height: f32,
2053) {
2054 let char_budget = (allowed_space / char_width) as usize;
2055
2056 match true_name {
2057 TrueName::SourceCode {
2058 line_number,
2059 before,
2060 this,
2061 after,
2062 } => {
2063 let before_chars = before.chars().collect::<Vec<_>>();
2064 let this_chars = this.chars().collect::<Vec<_>>();
2065 let after_chars = after.chars().collect::<Vec<_>>();
2066 let line_num = format!("{line_number} ");
2067 let important_chars = line_num.len() + this_chars.len();
2068 let required_extra_chars = before_chars.len() + after_chars.len();
2069
2070 let (line_num, before, this, after) =
2072 if char_budget >= important_chars + required_extra_chars {
2073 (line_num, before.clone(), this.clone(), after.clone())
2074 } else if char_budget > important_chars {
2075 let extra_chars = char_budget - important_chars;
2077
2078 let max_from_before = (extra_chars as f32 * 0.5).ceil() as usize;
2079 let max_from_after = (extra_chars as f32 * 0.5).floor() as usize;
2080
2081 let (chars_from_before, chars_from_after) =
2082 if max_from_before > before_chars.len() {
2083 (before_chars.len(), extra_chars - before_chars.len())
2084 } else if max_from_after > after_chars.len() {
2085 (extra_chars - after_chars.len(), before_chars.len())
2086 } else {
2087 (max_from_before, max_from_after)
2088 };
2089
2090 let mut before = before_chars
2091 .into_iter()
2092 .rev()
2093 .take(chars_from_before)
2094 .rev()
2095 .collect::<Vec<_>>();
2096 if !before.is_empty() {
2097 before[0] = 'β¦';
2098 }
2099 let mut after = after_chars
2100 .into_iter()
2101 .take(chars_from_after)
2102 .collect::<Vec<_>>();
2103 if !after.is_empty() {
2104 let last_elem = after.len() - 1;
2105 after[last_elem] = 'β¦';
2106 }
2107
2108 (
2109 line_num,
2110 before.into_iter().collect(),
2111 this.clone(),
2112 after.into_iter().collect(),
2113 )
2114 } else {
2115 let from_line_num = line_num.len();
2118 let from_this = char_budget.saturating_sub(from_line_num);
2119 let this = this
2120 .chars()
2121 .take(from_this)
2122 .enumerate()
2123 .map(|(i, c)| if i == from_this - 1 { 'β¦' } else { c })
2124 .collect();
2125 (line_num, String::new(), this, String::new())
2126 };
2127
2128 layout_job.append(
2129 &line_num,
2130 0.0,
2131 TextFormat {
2132 font_id: font.clone(),
2133 color: foreground.gamma_multiply(0.75),
2134 line_height: Some(line_height),
2135 valign: Align::Center,
2136 ..Default::default()
2137 },
2138 );
2139 layout_job.append(
2140 &before,
2141 0.0,
2142 TextFormat {
2143 font_id: font.clone(),
2144 color: foreground.gamma_multiply(0.5),
2145 line_height: Some(line_height),
2146 valign: Align::Center,
2147 ..Default::default()
2148 },
2149 );
2150 layout_job.append(
2151 &this,
2152 0.0,
2153 TextFormat {
2154 font_id: font.clone(),
2155 color: foreground,
2156 line_height: Some(line_height),
2157 valign: Align::Center,
2158 ..Default::default()
2159 },
2160 );
2161 layout_job.append(
2162 after.trim_end(),
2163 0.0,
2164 TextFormat {
2165 font_id: font.clone(),
2166 color: foreground.gamma_multiply(0.5),
2167 line_height: Some(line_height),
2168 valign: Align::Center,
2169 ..Default::default()
2170 },
2171 );
2172 }
2173 }
2174}