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