libsurfer/
view.rs

1use std::ops::Range;
2
3use crate::fzcmd::expand_command;
4use ecolor::Color32;
5#[cfg(not(target_arch = "wasm32"))]
6use egui::ViewportCommand;
7use egui::{
8    FontId, FontSelection, Frame, Layout, Painter, RichText, ScrollArea, Sense, TextFormat,
9    TextStyle, UiBuilder, WidgetText,
10};
11use egui_extras::{Column, TableBuilder};
12use egui_remixicon::icons;
13use emath::{Align, GuiRounding, Pos2, Rect, RectTransform, Vec2};
14use epaint::{
15    text::{LayoutJob, TextWrapMode},
16    CornerRadiusF32, Margin, Stroke,
17};
18use eyre::Context;
19use itertools::Itertools;
20use log::{info, warn};
21
22use num::BigUint;
23use surfer_translation_types::{
24    translator::{TrueName, VariableNameInfo},
25    SubFieldFlatTranslationResult, TranslatedValue, Translator, VariableInfo, VariableType,
26};
27
28#[cfg(feature = "performance_plot")]
29use crate::benchmark::NUM_PERF_SAMPLES;
30use crate::command_parser::get_parser;
31use crate::displayed_item::{
32    draw_rename_window, DisplayedFieldRef, DisplayedItem, DisplayedItemRef,
33};
34use crate::displayed_item_tree::{ItemIndex, VisibleItemIndex};
35use crate::help::{
36    draw_about_window, draw_control_help_window, draw_license_window, draw_quickstart_help_window,
37};
38use crate::time::time_string;
39use crate::transaction_container::{StreamScopeRef, TransactionStreamRef};
40use crate::translation::TranslationResultExt;
41use crate::util::uint_idx_to_alpha_idx;
42use crate::variable_direction::VariableDirectionExt;
43use crate::variable_filter::VariableFilter;
44use crate::wave_container::{
45    FieldRef, FieldRefExt, ScopeRef, ScopeRefExt, VariableRef, VariableRefExt, WaveContainer,
46};
47use crate::wave_data::ScopeType;
48use crate::{
49    command_prompt::show_command_prompt, hierarchy, hierarchy::HierarchyStyle, wave_data::WaveData,
50    Message, MoveDir, SystemState,
51};
52use crate::{config::SurferTheme, wave_container::VariableMeta};
53use crate::{data_container::VariableType as VarType, OUTSTANDING_TRANSACTIONS};
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_height: f32,
65    pub line_height: f32,
66    pub text_size: f32,
67    pub extra_draw_width: i32,
68}
69
70impl DrawConfig {
71    pub fn new(canvas_height: f32, line_height: f32, text_size: f32) -> Self {
72        Self {
73            canvas_height,
74            line_height,
75            text_size,
76            extra_draw_width: 6,
77        }
78    }
79}
80
81#[derive(Debug)]
82pub struct VariableDrawingInfo {
83    pub field_ref: FieldRef,
84    pub displayed_field_ref: DisplayedFieldRef,
85    pub item_list_idx: VisibleItemIndex,
86    pub top: f32,
87    pub bottom: f32,
88}
89
90#[derive(Debug)]
91pub struct DividerDrawingInfo {
92    pub item_list_idx: VisibleItemIndex,
93    pub top: f32,
94    pub bottom: f32,
95}
96
97#[derive(Debug)]
98pub struct MarkerDrawingInfo {
99    pub item_list_idx: VisibleItemIndex,
100    pub top: f32,
101    pub bottom: f32,
102    pub idx: u8,
103}
104
105#[derive(Debug)]
106pub struct TimeLineDrawingInfo {
107    pub item_list_idx: VisibleItemIndex,
108    pub top: f32,
109    pub bottom: f32,
110}
111
112#[derive(Debug)]
113pub struct StreamDrawingInfo {
114    pub transaction_stream_ref: TransactionStreamRef,
115    pub item_list_idx: VisibleItemIndex,
116    pub top: f32,
117    pub bottom: f32,
118}
119
120#[derive(Debug)]
121pub struct GroupDrawingInfo {
122    pub item_list_idx: VisibleItemIndex,
123    pub top: f32,
124    pub bottom: f32,
125}
126
127pub enum ItemDrawingInfo {
128    Variable(VariableDrawingInfo),
129    Divider(DividerDrawingInfo),
130    Marker(MarkerDrawingInfo),
131    TimeLine(TimeLineDrawingInfo),
132    Stream(StreamDrawingInfo),
133    Group(GroupDrawingInfo),
134}
135
136impl ItemDrawingInfo {
137    pub fn top(&self) -> f32 {
138        match self {
139            ItemDrawingInfo::Variable(drawing_info) => drawing_info.top,
140            ItemDrawingInfo::Divider(drawing_info) => drawing_info.top,
141            ItemDrawingInfo::Marker(drawing_info) => drawing_info.top,
142            ItemDrawingInfo::TimeLine(drawing_info) => drawing_info.top,
143            ItemDrawingInfo::Stream(drawing_info) => drawing_info.top,
144            ItemDrawingInfo::Group(drawing_info) => drawing_info.top,
145        }
146    }
147    pub fn bottom(&self) -> f32 {
148        match self {
149            ItemDrawingInfo::Variable(drawing_info) => drawing_info.bottom,
150            ItemDrawingInfo::Divider(drawing_info) => drawing_info.bottom,
151            ItemDrawingInfo::Marker(drawing_info) => drawing_info.bottom,
152            ItemDrawingInfo::TimeLine(drawing_info) => drawing_info.bottom,
153            ItemDrawingInfo::Stream(drawing_info) => drawing_info.bottom,
154            ItemDrawingInfo::Group(drawing_info) => drawing_info.bottom,
155        }
156    }
157    pub fn item_list_idx(&self) -> VisibleItemIndex {
158        match self {
159            ItemDrawingInfo::Variable(drawing_info) => drawing_info.item_list_idx,
160            ItemDrawingInfo::Divider(drawing_info) => drawing_info.item_list_idx,
161            ItemDrawingInfo::Marker(drawing_info) => drawing_info.item_list_idx,
162            ItemDrawingInfo::TimeLine(drawing_info) => drawing_info.item_list_idx,
163            ItemDrawingInfo::Stream(drawing_info) => drawing_info.item_list_idx,
164            ItemDrawingInfo::Group(drawing_info) => drawing_info.item_list_idx,
165        }
166    }
167}
168
169impl eframe::App for SystemState {
170    fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
171        #[cfg(feature = "performance_plot")]
172        self.timing.borrow_mut().start_frame();
173
174        if self.continuous_redraw {
175            self.invalidate_draw_commands();
176        }
177
178        let (fullscreen, window_size) = ctx.input(|i| {
179            (
180                i.viewport().fullscreen.unwrap_or_default(),
181                Some(i.screen_rect.size()),
182            )
183        });
184        #[cfg(target_arch = "wasm32")]
185        let _ = fullscreen;
186
187        #[cfg(feature = "performance_plot")]
188        self.timing.borrow_mut().start("draw");
189        let mut msgs = self.draw(ctx, window_size);
190        #[cfg(feature = "performance_plot")]
191        self.timing.borrow_mut().end("draw");
192
193        #[cfg(feature = "performance_plot")]
194        self.timing.borrow_mut().start("update");
195        let ui_zoom_factor = self.ui_zoom_factor();
196        if ctx.zoom_factor() != ui_zoom_factor {
197            ctx.set_zoom_factor(ui_zoom_factor);
198        }
199
200        self.items_to_expand.borrow_mut().clear();
201
202        while let Some(msg) = msgs.pop() {
203            #[cfg(not(target_arch = "wasm32"))]
204            if let Message::Exit = msg {
205                ctx.send_viewport_cmd(ViewportCommand::Close);
206            }
207            #[cfg(not(target_arch = "wasm32"))]
208            if let Message::ToggleFullscreen = msg {
209                ctx.send_viewport_cmd(ViewportCommand::Fullscreen(!fullscreen));
210            }
211            self.update(msg);
212        }
213        #[cfg(feature = "performance_plot")]
214        self.timing.borrow_mut().end("update");
215
216        #[cfg(feature = "performance_plot")]
217        self.timing.borrow_mut().start("handle_async_messages");
218        #[cfg(feature = "performance_plot")]
219        self.timing.borrow_mut().end("handle_async_messages");
220
221        self.handle_async_messages();
222        self.handle_batch_commands();
223        #[cfg(target_arch = "wasm32")]
224        self.handle_wasm_external_messages();
225
226        let viewport_is_moving = if let Some(waves) = &mut self.user.waves {
227            let mut is_moving = false;
228            for vp in &mut waves.viewports {
229                if vp.is_moving() {
230                    vp.move_viewport(ctx.input(|i| i.stable_dt));
231                    is_moving = true;
232                }
233            }
234            is_moving
235        } else {
236            false
237        };
238
239        if let Some(waves) = self.user.waves.as_ref().and_then(|w| w.inner.as_waves()) {
240            waves.tick()
241        }
242
243        if viewport_is_moving {
244            self.invalidate_draw_commands();
245            ctx.request_repaint();
246        }
247
248        #[cfg(feature = "performance_plot")]
249        self.timing.borrow_mut().start("handle_wcp_commands");
250        self.handle_wcp_commands();
251        #[cfg(feature = "performance_plot")]
252        self.timing.borrow_mut().end("handle_wcp_commands");
253
254        // We can save some user battery life by not redrawing unless needed. At the moment,
255        // we only need to continuously redraw to make surfer interactive during loading, otherwise
256        // we'll let egui manage repainting. In practice
257        if self.continuous_redraw
258            || self.progress_tracker.is_some()
259            || self.user.show_performance
260            || OUTSTANDING_TRANSACTIONS.load(std::sync::atomic::Ordering::SeqCst) != 0
261        {
262            ctx.request_repaint();
263        }
264
265        #[cfg(feature = "performance_plot")]
266        if let Some(prev_cpu) = frame.info().cpu_usage {
267            self.rendering_cpu_times.push_back(prev_cpu);
268            if self.rendering_cpu_times.len() > NUM_PERF_SAMPLES {
269                self.rendering_cpu_times.pop_front();
270            }
271        }
272
273        #[cfg(feature = "performance_plot")]
274        self.timing.borrow_mut().end_frame();
275    }
276}
277
278impl SystemState {
279    pub(crate) fn draw(&mut self, ctx: &egui::Context, window_size: Option<Vec2>) -> Vec<Message> {
280        let max_width = ctx.available_rect().width();
281        let max_height = ctx.available_rect().height();
282
283        let mut msgs = vec![];
284
285        if self.user.show_about {
286            draw_about_window(ctx, &mut msgs);
287        }
288
289        if self.user.show_license {
290            draw_license_window(ctx, &mut msgs);
291        }
292
293        if self.user.show_keys {
294            draw_control_help_window(ctx, &mut msgs);
295        }
296
297        if self.user.show_quick_start {
298            draw_quickstart_help_window(ctx, &mut msgs);
299        }
300
301        if self.user.show_gestures {
302            self.mouse_gesture_help(ctx, &mut msgs);
303        }
304
305        if self.user.show_logs {
306            self.draw_log_window(ctx, &mut msgs);
307        }
308
309        if let Some(dialog) = &self.user.show_reload_suggestion {
310            self.draw_reload_waveform_dialog(ctx, dialog, &mut msgs);
311        }
312
313        if let Some(dialog) = &self.user.show_open_sibling_state_file_suggestion {
314            self.draw_open_sibling_state_file_dialog(ctx, dialog, &mut msgs);
315        }
316
317        if self.user.show_performance {
318            #[cfg(feature = "performance_plot")]
319            self.draw_performance_graph(ctx, &mut msgs);
320        }
321
322        if self.user.show_cursor_window {
323            if let Some(waves) = &self.user.waves {
324                self.draw_marker_window(waves, ctx, &mut msgs);
325            }
326        }
327
328        if let Some(idx) = self.user.rename_target {
329            draw_rename_window(
330                ctx,
331                &mut msgs,
332                idx,
333                &mut self.item_renaming_string.borrow_mut(),
334            );
335        }
336
337        if self
338            .user
339            .show_menu
340            .unwrap_or_else(|| self.user.config.layout.show_menu())
341        {
342            self.add_menu_panel(ctx, &mut msgs);
343        }
344
345        if self.show_toolbar() {
346            self.add_toolbar_panel(ctx, &mut msgs);
347        }
348
349        if self.user.show_url_entry {
350            self.draw_load_url(ctx, &mut msgs);
351        }
352
353        if self.show_statusbar() {
354            self.add_statusbar_panel(ctx, &self.user.waves, &mut msgs);
355        }
356        if let Some(waves) = &self.user.waves {
357            if self.show_overview() && !waves.items_tree.is_empty() {
358                self.add_overview_panel(ctx, waves, &mut msgs);
359            }
360        }
361
362        if self.show_hierarchy() {
363            egui::SidePanel::left("variable select left panel")
364                .default_width(300.)
365                .width_range(100.0..=max_width)
366                .frame(Frame {
367                    fill: self.user.config.theme.primary_ui_color.background,
368                    ..Default::default()
369                })
370                .show(ctx, |ui| {
371                    self.user.sidepanel_width = Some(ui.clip_rect().width());
372                    match self.hierarchy_style() {
373                        HierarchyStyle::Separate => hierarchy::separate(self, ui, &mut msgs),
374                        HierarchyStyle::Tree => hierarchy::tree(self, ui, &mut msgs),
375                    }
376                });
377        }
378
379        if self.command_prompt.visible {
380            show_command_prompt(self, ctx, window_size, &mut msgs);
381            if let Some(new_idx) = self.command_prompt.new_selection {
382                self.command_prompt.selected = new_idx;
383                self.command_prompt.new_selection = None;
384            }
385        }
386
387        if self.user.waves.is_some() {
388            let scroll_offset = self.user.waves.as_ref().unwrap().scroll_offset;
389            if self.user.waves.as_ref().unwrap().any_displayed() {
390                let draw_focus_ids = self.command_prompt.visible
391                    && expand_command(&self.command_prompt_text.borrow(), get_parser(self))
392                        .expanded
393                        .starts_with("item_focus");
394                if draw_focus_ids {
395                    egui::SidePanel::left("focus id list")
396                        .default_width(40.)
397                        .width_range(40.0..=max_width)
398                        .show(ctx, |ui| {
399                            self.handle_pointer_in_ui(ui, &mut msgs);
400                            let response = ScrollArea::both()
401                                .vertical_scroll_offset(scroll_offset)
402                                .show(ui, |ui| {
403                                    self.draw_item_focus_list(ui);
404                                });
405                            self.user.waves.as_mut().unwrap().top_item_draw_offset =
406                                response.inner_rect.min.y;
407                            self.user.waves.as_mut().unwrap().total_height =
408                                response.inner_rect.height();
409                            if (scroll_offset - response.state.offset.y).abs() > 5. {
410                                msgs.push(Message::SetScrollOffset(response.state.offset.y));
411                            }
412                        });
413                }
414
415                egui::SidePanel::left("variable list")
416                    .default_width(100.)
417                    .width_range(100.0..=max_width)
418                    .show(ctx, |ui| {
419                        ui.style_mut().wrap_mode = Some(TextWrapMode::Extend);
420                        self.handle_pointer_in_ui(ui, &mut msgs);
421                        if self.show_default_timeline() {
422                            ui.label(RichText::new("Time").italics());
423                        }
424
425                        let response = ScrollArea::both()
426                            .auto_shrink([false; 2])
427                            .vertical_scroll_offset(scroll_offset)
428                            .show(ui, |ui| {
429                                self.draw_item_list(&mut msgs, ui, ctx);
430                            });
431                        self.user.waves.as_mut().unwrap().top_item_draw_offset =
432                            response.inner_rect.min.y;
433                        self.user.waves.as_mut().unwrap().total_height =
434                            response.inner_rect.height();
435                        if (scroll_offset - response.state.offset.y).abs() > 5. {
436                            msgs.push(Message::SetScrollOffset(response.state.offset.y));
437                        }
438                    });
439
440                if self
441                    .user
442                    .waves
443                    .as_ref()
444                    .unwrap()
445                    .focused_transaction
446                    .1
447                    .is_some()
448                {
449                    egui::SidePanel::right("Transaction Details")
450                        .default_width(330.)
451                        .width_range(10.0..=max_width)
452                        .show(ctx, |ui| {
453                            ui.style_mut().wrap_mode = Some(TextWrapMode::Extend);
454                            self.handle_pointer_in_ui(ui, &mut msgs);
455                            self.draw_focused_transaction_details(ui);
456                        });
457                }
458
459                egui::SidePanel::left("variable values")
460                    .frame(Frame {
461                        inner_margin: Margin::ZERO,
462                        outer_margin: Margin::ZERO,
463                        ..Default::default()
464                    })
465                    .default_width(100.)
466                    .width_range(10.0..=max_width)
467                    .show(ctx, |ui| {
468                        ui.style_mut().wrap_mode = Some(TextWrapMode::Extend);
469                        self.handle_pointer_in_ui(ui, &mut msgs);
470                        let response = ScrollArea::both()
471                            .vertical_scroll_offset(scroll_offset)
472                            .show(ui, |ui| self.draw_var_values(ui, &mut msgs));
473                        if (scroll_offset - response.state.offset.y).abs() > 5. {
474                            msgs.push(Message::SetScrollOffset(response.state.offset.y));
475                        }
476                    });
477                let std_stroke = ctx.style().visuals.widgets.noninteractive.bg_stroke;
478                ctx.style_mut(|style| {
479                    style.visuals.widgets.noninteractive.bg_stroke = Stroke {
480                        width: self.user.config.theme.viewport_separator.width,
481                        color: self.user.config.theme.viewport_separator.color,
482                    };
483                });
484                let number_of_viewports = self.user.waves.as_ref().unwrap().viewports.len();
485                if number_of_viewports > 1 {
486                    // Draw additional viewports
487                    let max_width = ctx.available_rect().width();
488                    let default_width = max_width / (number_of_viewports as f32);
489                    for viewport_idx in 1..number_of_viewports {
490                        egui::SidePanel::right(format! {"view port {viewport_idx}"})
491                            .default_width(default_width)
492                            .width_range(30.0..=max_width)
493                            .frame(Frame {
494                                inner_margin: Margin::ZERO,
495                                outer_margin: Margin::ZERO,
496                                ..Default::default()
497                            })
498                            .show(ctx, |ui| self.draw_items(ctx, &mut msgs, ui, viewport_idx));
499                    }
500                }
501
502                egui::CentralPanel::default()
503                    .frame(Frame {
504                        inner_margin: Margin::ZERO,
505                        outer_margin: Margin::ZERO,
506                        ..Default::default()
507                    })
508                    .show(ctx, |ui| {
509                        self.draw_items(ctx, &mut msgs, ui, 0);
510                    });
511                ctx.style_mut(|style| {
512                    style.visuals.widgets.noninteractive.bg_stroke = std_stroke;
513                });
514            }
515        };
516
517        if self.user.waves.is_none()
518            || self
519                .user
520                .waves
521                .as_ref()
522                .is_some_and(|waves| !waves.any_displayed())
523        {
524            egui::CentralPanel::default()
525                .frame(Frame::NONE.fill(self.user.config.theme.canvas_colors.background))
526                .show(ctx, |ui| {
527                    ui.add_space(max_height * 0.1);
528                    ui.vertical_centered(|ui| {
529                        ui.label(RichText::new("🏄 Surfer").monospace().size(24.));
530                        ui.add_space(20.);
531                        let layout = Layout::top_down(Align::LEFT);
532                        ui.allocate_ui_with_layout(
533                            Vec2 {
534                                x: max_width * 0.35,
535                                y: max_height * 0.5,
536                            },
537                            layout,
538                            |ui| self.help_message(ui),
539                        );
540                    });
541                });
542        }
543
544        ctx.input(|i| {
545            i.raw.dropped_files.iter().for_each(|file| {
546                info!("Got dropped file");
547                msgs.push(Message::FileDropped(file.clone()));
548            });
549        });
550
551        // If some dialogs are open, skip decoding keypresses
552        if !self.user.show_url_entry
553            && self.user.rename_target.is_none()
554            && self.user.show_reload_suggestion.is_none()
555        {
556            self.handle_pressed_keys(ctx, &mut msgs);
557        }
558        msgs
559    }
560
561    fn draw_load_url(&self, ctx: &egui::Context, msgs: &mut Vec<Message>) {
562        let mut open = true;
563        egui::Window::new("Load URL")
564            .open(&mut open)
565            .collapsible(false)
566            .resizable(true)
567            .show(ctx, |ui| {
568                ui.vertical_centered(|ui| {
569                    let url = &mut *self.url.borrow_mut();
570                    let response = ui.text_edit_singleline(url);
571                    ui.horizontal(|ui| {
572                        if ui.button("Load URL").clicked()
573                            || (response.lost_focus()
574                                && ui.input(|i| i.key_pressed(egui::Key::Enter)))
575                        {
576                            if let Some(callback) = &self.url_callback {
577                                msgs.push(callback(url.clone()));
578                            }
579                            msgs.push(Message::SetUrlEntryVisible(false, None));
580                        }
581                        if ui.button("Cancel").clicked() {
582                            msgs.push(Message::SetUrlEntryVisible(false, None));
583                        }
584                    });
585                });
586            });
587        if !open {
588            msgs.push(Message::SetUrlEntryVisible(false, None));
589        }
590    }
591
592    fn handle_pointer_in_ui(&self, ui: &mut egui::Ui, msgs: &mut Vec<Message>) {
593        if ui.ui_contains_pointer() {
594            let scroll_delta = ui.input(|i| i.smooth_scroll_delta);
595            if scroll_delta.y > 0.0 {
596                msgs.push(Message::InvalidateCount);
597                msgs.push(Message::VerticalScroll(MoveDir::Up, self.get_count()));
598            } else if scroll_delta.y < 0.0 {
599                msgs.push(Message::InvalidateCount);
600                msgs.push(Message::VerticalScroll(MoveDir::Down, self.get_count()));
601            }
602        }
603    }
604
605    pub fn draw_all_scopes(
606        &self,
607        msgs: &mut Vec<Message>,
608        wave: &WaveData,
609        draw_variables: bool,
610        ui: &mut egui::Ui,
611        filter: &VariableFilter,
612    ) {
613        for scope in wave.inner.root_scopes() {
614            match scope {
615                ScopeType::WaveScope(scope) => {
616                    self.draw_selectable_child_or_orphan_scope(
617                        msgs,
618                        wave,
619                        &scope,
620                        draw_variables,
621                        ui,
622                        filter,
623                    );
624                }
625                ScopeType::StreamScope(_) => {
626                    self.draw_transaction_root(msgs, wave, ui);
627                }
628            }
629        }
630        if draw_variables {
631            if let Some(wave_container) = wave.inner.as_waves() {
632                let scope = ScopeRef::empty();
633                let variables = wave_container.variables_in_scope(&scope);
634                self.draw_variable_list(msgs, wave_container, ui, &variables, None, filter);
635            }
636        }
637    }
638
639    fn add_scope_selectable_label(
640        &self,
641        msgs: &mut Vec<Message>,
642        wave: &WaveData,
643        scope: &ScopeRef,
644        ui: &mut egui::Ui,
645        scroll_to_label: bool,
646    ) {
647        let name = scope.name();
648        let mut response = ui.add(egui::Button::selectable(
649            wave.active_scope == Some(ScopeType::WaveScope(scope.clone())),
650            name,
651        ));
652        let _ = response.interact(egui::Sense::click_and_drag());
653        response.drag_started().then(|| {
654            msgs.push(Message::VariableDragStarted(VisibleItemIndex(
655                self.user.waves.as_ref().unwrap().display_item_ref_counter,
656            )))
657        });
658
659        if scroll_to_label {
660            response.scroll_to_me(Some(Align::Center));
661        }
662
663        response.drag_stopped().then(|| {
664            if ui.input(|i| i.pointer.hover_pos().unwrap_or_default().x)
665                > self.user.sidepanel_width.unwrap_or_default()
666            {
667                let scope_t = ScopeType::WaveScope(scope.clone());
668                let variables = self
669                    .user
670                    .waves
671                    .as_ref()
672                    .unwrap()
673                    .inner
674                    .variables_in_scope(&scope_t)
675                    .iter()
676                    .filter_map(|var| match var {
677                        VarType::Variable(var) => Some(var.clone()),
678                        _ => None,
679                    })
680                    .collect_vec();
681
682                msgs.push(Message::AddDraggedVariables(self.filtered_variables(
683                    variables.as_slice(),
684                    &self.user.variable_filter,
685                )));
686            }
687        });
688        if self.show_scope_tooltip() {
689            response = response.on_hover_ui(|ui| {
690                ui.set_max_width(ui.spacing().tooltip_width);
691                ui.add(egui::Label::new(scope_tooltip_text(wave, scope)));
692            });
693        }
694        response.context_menu(|ui| {
695            if ui.button("Add scope").clicked() {
696                msgs.push(Message::AddScope(scope.clone(), false));
697            }
698            if ui.button("Add scope recursively").clicked() {
699                msgs.push(Message::AddScope(scope.clone(), true));
700            }
701            if ui.button("Add scope as group").clicked() {
702                msgs.push(Message::AddScopeAsGroup(scope.clone(), false));
703            }
704            if ui.button("Add scope as group recursively").clicked() {
705                msgs.push(Message::AddScopeAsGroup(scope.clone(), true));
706            }
707        });
708        response
709            .clicked()
710            .then(|| msgs.push(Message::SetActiveScope(ScopeType::WaveScope(scope.clone()))));
711    }
712
713    fn draw_selectable_child_or_orphan_scope(
714        &self,
715        msgs: &mut Vec<Message>,
716        wave: &WaveData,
717        scope: &ScopeRef,
718        draw_variables: bool,
719        ui: &mut egui::Ui,
720        filter: &VariableFilter,
721    ) {
722        let Some(child_scopes) = wave
723            .inner
724            .as_waves()
725            .unwrap()
726            .child_scopes(scope)
727            .context("Failed to get child scopes")
728            .map_err(|e| warn!("{e:#?}"))
729            .ok()
730        else {
731            return;
732        };
733
734        let no_variables_in_scope = wave.inner.as_waves().unwrap().no_variables_in_scope(scope);
735        if child_scopes.is_empty() && no_variables_in_scope && !self.show_empty_scopes() {
736            return;
737        }
738        if child_scopes.is_empty() && (!draw_variables || no_variables_in_scope) {
739            self.add_scope_selectable_label(msgs, wave, scope, ui, false);
740        } else {
741            let should_open_header = self.should_open_header(scope);
742            let mut collapsing_header =
743                egui::collapsing_header::CollapsingState::load_with_default_open(
744                    ui.ctx(),
745                    egui::Id::new(scope),
746                    false,
747                );
748            if should_open_header {
749                collapsing_header.set_open(true);
750            }
751            collapsing_header
752                .show_header(ui, |ui| {
753                    ui.with_layout(
754                        Layout::top_down(Align::LEFT).with_cross_justify(true),
755                        |ui| {
756                            self.add_scope_selectable_label(
757                                msgs,
758                                wave,
759                                scope,
760                                ui,
761                                should_open_header,
762                            );
763                        },
764                    );
765                })
766                .body(|ui| {
767                    if draw_variables || self.show_parameters_in_scopes() {
768                        let wave_container = wave.inner.as_waves().unwrap();
769                        let parameters = wave_container.parameters_in_scope(scope);
770                        if !parameters.is_empty() {
771                            egui::collapsing_header::CollapsingState::load_with_default_open(
772                                ui.ctx(),
773                                egui::Id::new(&parameters),
774                                false,
775                            )
776                            .show_header(ui, |ui| {
777                                ui.with_layout(
778                                    Layout::top_down(Align::LEFT).with_cross_justify(true),
779                                    |ui| {
780                                        ui.label("Parameters");
781                                    },
782                                );
783                            })
784                            .body(|ui| {
785                                self.draw_variable_list(
786                                    msgs,
787                                    wave_container,
788                                    ui,
789                                    &parameters,
790                                    None,
791                                    filter,
792                                );
793                            });
794                        }
795                    }
796                    self.draw_root_scope_view(msgs, wave, scope, draw_variables, ui, filter);
797                    if draw_variables {
798                        let wave_container = wave.inner.as_waves().unwrap();
799                        let variables = wave_container.variables_in_scope(scope);
800                        self.draw_variable_list(msgs, wave_container, ui, &variables, None, filter);
801                    }
802                });
803        }
804    }
805
806    fn draw_root_scope_view(
807        &self,
808        msgs: &mut Vec<Message>,
809        wave: &WaveData,
810        root_scope: &ScopeRef,
811        draw_variables: bool,
812        ui: &mut egui::Ui,
813        filter: &VariableFilter,
814    ) {
815        let Some(child_scopes) = wave
816            .inner
817            .as_waves()
818            .unwrap()
819            .child_scopes(root_scope)
820            .context("Failed to get child scopes")
821            .map_err(|e| warn!("{e:#?}"))
822            .ok()
823        else {
824            return;
825        };
826
827        let child_scopes_sorted = child_scopes
828            .iter()
829            .sorted_by(|a, b| numeric_sort::cmp(&a.name(), &b.name()))
830            .collect_vec();
831
832        for child_scope in child_scopes_sorted {
833            self.draw_selectable_child_or_orphan_scope(
834                msgs,
835                wave,
836                child_scope,
837                draw_variables,
838                ui,
839                filter,
840            );
841        }
842    }
843
844    pub fn draw_variable_list(
845        &self,
846        msgs: &mut Vec<Message>,
847        wave_container: &WaveContainer,
848        ui: &mut egui::Ui,
849        all_variables: &[VariableRef],
850        row_range: Option<Range<usize>>,
851        filter: &VariableFilter,
852    ) {
853        let all_variables = self.filtered_variables(all_variables, filter);
854        self.draw_filtered_variable_list(msgs, wave_container, ui, &all_variables, row_range);
855    }
856
857    pub fn draw_filtered_variable_list(
858        &self,
859        msgs: &mut Vec<Message>,
860        wave_container: &WaveContainer,
861        ui: &mut egui::Ui,
862        all_variables: &[VariableRef],
863        row_range: Option<Range<usize>>,
864    ) {
865        let variables = all_variables
866            .iter()
867            .map(|var| {
868                let meta = wave_container.variable_meta(var).ok();
869                let name_info = self.get_variable_name_info(wave_container, var);
870                (var, meta, name_info)
871            })
872            .sorted_by_key(|(_, _, name_info)| {
873                -name_info
874                    .as_ref()
875                    .and_then(|info| info.priority)
876                    .unwrap_or_default()
877            })
878            .skip(row_range.as_ref().map(|r| r.start).unwrap_or(0))
879            .take(
880                row_range
881                    .as_ref()
882                    .map(|r| r.end - r.start)
883                    .unwrap_or(all_variables.len()),
884            );
885
886        for (variable, meta, name_info) in variables {
887            let index = meta
888                .as_ref()
889                .and_then(|meta| meta.index)
890                .map(|index| {
891                    if self.show_variable_indices() {
892                        format!(" {index}")
893                    } else {
894                        String::new()
895                    }
896                })
897                .unwrap_or_default();
898
899            let direction = if self.show_variable_direction() {
900                meta.as_ref()
901                    .and_then(|meta| meta.direction)
902                    .map(|direction| {
903                        format!(
904                            "{} ",
905                            // Icon based on direction
906                            direction.get_icon().unwrap_or_else(|| {
907                                if meta.as_ref().is_some_and(|meta| {
908                                    meta.variable_type == Some(VariableType::VCDParameter)
909                                }) {
910                                    // If parameter
911                                    icons::MAP_PIN_2_LINE
912                                } else {
913                                    // Align other items (can be improved)
914                                    // The padding depends on if we will render monospace or not
915                                    if name_info.is_some() {
916                                        "  "
917                                    } else {
918                                        "    "
919                                    }
920                                }
921                            })
922                        )
923                    })
924                    .unwrap_or_default()
925            } else {
926                String::new()
927            };
928
929            let value = if meta
930                .as_ref()
931                .is_some_and(|meta| meta.variable_type == Some(VariableType::VCDParameter))
932            {
933                let res = wave_container.query_variable(variable, &BigUint::ZERO).ok();
934                res.and_then(|o| o.and_then(|q| q.current.map(|v| format!(": {}", v.1))))
935                    .unwrap_or_else(|| ": Undefined".to_string())
936            } else {
937                String::new()
938            };
939
940            ui.with_layout(
941                Layout::top_down(Align::LEFT).with_cross_justify(true),
942                |ui| {
943                    let mut label = LayoutJob::default();
944
945                    match name_info.and_then(|info| info.true_name) {
946                        Some(name) => {
947                            // NOTE: Safe unwrap, we know that egui has its own built-in font
948                            let font = ui.style().text_styles.get(&TextStyle::Monospace).unwrap();
949                            let char_width = ui.fonts(|fonts| {
950                                fonts
951                                    .layout_no_wrap(
952                                        " ".to_string(),
953                                        font.clone(),
954                                        Color32::from_rgb(0, 0, 0),
955                                    )
956                                    .size()
957                                    .x
958                            });
959
960                            let direction_size = direction.chars().count();
961                            let index_size = index.chars().count();
962                            let value_size = value.chars().count();
963                            let used_space =
964                                (direction_size + index_size + value_size) as f32 * char_width;
965                            // The button padding is added by egui on selectable labels
966                            let available_space =
967                                ui.available_width() - ui.spacing().button_padding.x * 2.;
968                            let space_for_name = available_space - used_space;
969
970                            let text_format = TextFormat {
971                                font_id: font.clone(),
972                                color: self.user.config.theme.foreground,
973                                ..Default::default()
974                            };
975
976                            label.append(&direction, 0.0, text_format.clone());
977
978                            draw_true_name(
979                                &name,
980                                &mut label,
981                                font.clone(),
982                                self.user.config.theme.foreground,
983                                char_width,
984                                space_for_name,
985                            );
986
987                            label.append(&index, 0.0, text_format.clone());
988                            label.append(&value, 0.0, text_format.clone());
989                        }
990                        None => {
991                            let font = ui.style().text_styles.get(&TextStyle::Body).unwrap();
992                            let text_format = TextFormat {
993                                font_id: font.clone(),
994                                color: self.user.config.theme.foreground,
995                                ..Default::default()
996                            };
997                            label.append(&direction, 0.0, text_format.clone());
998                            label.append(&variable.name, 0.0, text_format.clone());
999                            label.append(&index, 0.0, text_format.clone());
1000                            label.append(&value, 0.0, text_format.clone());
1001                        }
1002                    }
1003
1004                    let mut response = ui.add(egui::Button::selectable(false, label));
1005
1006                    let _ = response.interact(egui::Sense::click_and_drag());
1007
1008                    if self.show_tooltip() {
1009                        // Should be possible to reuse the meta from above?
1010                        response = response.on_hover_ui(|ui| {
1011                            let meta = wave_container.variable_meta(variable).ok();
1012                            ui.set_max_width(ui.spacing().tooltip_width);
1013                            ui.add(egui::Label::new(variable_tooltip_text(&meta, variable)));
1014                        });
1015                    }
1016                    response.drag_started().then(|| {
1017                        msgs.push(Message::VariableDragStarted(VisibleItemIndex(
1018                            self.user.waves.as_ref().unwrap().display_item_ref_counter,
1019                        )))
1020                    });
1021                    response.drag_stopped().then(|| {
1022                        if ui.input(|i| i.pointer.hover_pos().unwrap_or_default().x)
1023                            > self.user.sidepanel_width.unwrap_or_default()
1024                        {
1025                            msgs.push(Message::AddDraggedVariables(vec![variable.clone()]));
1026                        }
1027                    });
1028                    response
1029                        .clicked()
1030                        .then(|| msgs.push(Message::AddVariables(vec![variable.clone()])));
1031                },
1032            );
1033        }
1034    }
1035
1036    fn draw_item_focus_list(&self, ui: &mut egui::Ui) {
1037        let alignment = self.get_name_alignment();
1038        ui.with_layout(
1039            Layout::top_down(alignment).with_cross_justify(false),
1040            |ui| {
1041                if self.show_default_timeline() {
1042                    ui.add_space(ui.text_style_height(&egui::TextStyle::Body) + 2.0);
1043                }
1044                for (vidx, _) in self
1045                    .user
1046                    .waves
1047                    .as_ref()
1048                    .unwrap()
1049                    .items_tree
1050                    .iter_visible()
1051                    .enumerate()
1052                {
1053                    let vidx = VisibleItemIndex(vidx);
1054                    ui.scope(|ui| {
1055                        ui.style_mut().visuals.selection.bg_fill =
1056                            self.user.config.theme.accent_warn.background;
1057                        ui.style_mut().visuals.override_text_color =
1058                            Some(self.user.config.theme.accent_warn.foreground);
1059                        let _ = ui.selectable_label(true, self.get_alpha_focus_id(vidx));
1060                    });
1061                }
1062            },
1063        );
1064    }
1065
1066    fn hierarchy_icon(
1067        &self,
1068        ui: &mut egui::Ui,
1069        has_children: bool,
1070        unfolded: bool,
1071        alignment: Align,
1072    ) -> egui::Response {
1073        let (rect, response) = ui.allocate_exact_size(
1074            Vec2::splat(self.user.config.layout.waveforms_text_size),
1075            Sense::click(),
1076        );
1077        if !has_children {
1078            return response;
1079        }
1080
1081        // fixme: use the much nicer remixicon arrow? do a layout here and paint the galley into the rect?
1082        // or alternatively: change how the tree iterator works and use the egui facilities (cross widget?)
1083        let icon_rect = Rect::from_center_size(
1084            rect.center(),
1085            emath::vec2(rect.width(), rect.height()) * 0.75,
1086        );
1087        let mut points = vec![
1088            icon_rect.left_top(),
1089            icon_rect.right_top(),
1090            icon_rect.center_bottom(),
1091        ];
1092        let rotation = emath::Rot2::from_angle(if unfolded {
1093            0.0
1094        } else if alignment == Align::LEFT {
1095            -std::f32::consts::TAU / 4.0
1096        } else {
1097            std::f32::consts::TAU / 4.0
1098        });
1099        for p in &mut points {
1100            *p = icon_rect.center() + rotation * (*p - icon_rect.center());
1101        }
1102
1103        let style = ui.style().interact(&response);
1104        ui.painter().add(egui::Shape::convex_polygon(
1105            points,
1106            style.fg_stroke.color,
1107            egui::Stroke::NONE,
1108        ));
1109        response
1110    }
1111
1112    fn draw_item_list(&mut self, msgs: &mut Vec<Message>, ui: &mut egui::Ui, ctx: &egui::Context) {
1113        let mut item_offsets = Vec::new();
1114
1115        let any_groups = self
1116            .user
1117            .waves
1118            .as_ref()
1119            .unwrap()
1120            .items_tree
1121            .iter()
1122            .any(|node| node.level > 0);
1123        let alignment = self.get_name_alignment();
1124        ui.with_layout(Layout::top_down(alignment).with_cross_justify(true), |ui| {
1125            let available_rect = ui.available_rect_before_wrap();
1126            for crate::displayed_item_tree::Info {
1127                node:
1128                    crate::displayed_item_tree::Node {
1129                        item_ref,
1130                        level,
1131                        unfolded,
1132                        ..
1133                    },
1134                vidx,
1135                has_children,
1136                last,
1137                ..
1138            } in self
1139                .user
1140                .waves
1141                .as_ref()
1142                .unwrap()
1143                .items_tree
1144                .iter_visible_extra()
1145            {
1146                let Some(displayed_item) = self
1147                    .user
1148                    .waves
1149                    .as_ref()
1150                    .unwrap()
1151                    .displayed_items
1152                    .get(item_ref)
1153                else {
1154                    continue;
1155                };
1156
1157                ui.with_layout(
1158                    if alignment == Align::LEFT {
1159                        Layout::left_to_right(Align::TOP)
1160                    } else {
1161                        Layout::right_to_left(Align::TOP)
1162                    },
1163                    |ui| {
1164                        ui.add_space(10.0 * *level as f32);
1165                        if any_groups {
1166                            let response =
1167                                self.hierarchy_icon(ui, has_children, *unfolded, alignment);
1168                            if response.clicked() {
1169                                if *unfolded {
1170                                    msgs.push(Message::GroupFold(Some(*item_ref)));
1171                                } else {
1172                                    msgs.push(Message::GroupUnfold(Some(*item_ref)));
1173                                }
1174                            }
1175                        }
1176
1177                        let item_rect = match displayed_item {
1178                            DisplayedItem::Variable(displayed_variable) => {
1179                                let levels_to_force_expand =
1180                                    self.items_to_expand.borrow().iter().find_map(
1181                                        |(id, levels)| {
1182                                            if item_ref == id {
1183                                                Some(*levels)
1184                                            } else {
1185                                                None
1186                                            }
1187                                        },
1188                                    );
1189
1190                                self.draw_variable(
1191                                    msgs,
1192                                    vidx,
1193                                    displayed_item,
1194                                    *item_ref,
1195                                    FieldRef::without_fields(
1196                                        displayed_variable.variable_ref.clone(),
1197                                    ),
1198                                    &mut item_offsets,
1199                                    &displayed_variable.info,
1200                                    ui,
1201                                    ctx,
1202                                    levels_to_force_expand,
1203                                    alignment,
1204                                )
1205                            }
1206                            DisplayedItem::Divider(_)
1207                            | DisplayedItem::Marker(_)
1208                            | DisplayedItem::Placeholder(_)
1209                            | DisplayedItem::TimeLine(_)
1210                            | DisplayedItem::Stream(_)
1211                            | DisplayedItem::Group(_) => {
1212                                ui.with_layout(
1213                                    ui.layout()
1214                                        .with_main_justify(true)
1215                                        .with_main_align(alignment),
1216                                    |ui| {
1217                                        self.draw_plain_item(
1218                                            msgs,
1219                                            vidx,
1220                                            *item_ref,
1221                                            displayed_item,
1222                                            &mut item_offsets,
1223                                            ui,
1224                                            ctx,
1225                                        )
1226                                    },
1227                                )
1228                                .inner
1229                            }
1230                        };
1231                        // expand to the left, but not over the icon size
1232                        let mut expanded_rect = item_rect;
1233                        expanded_rect.set_left(
1234                            available_rect.left()
1235                                + self.user.config.layout.waveforms_text_size
1236                                + ui.spacing().item_spacing.x,
1237                        );
1238                        expanded_rect.set_right(available_rect.right());
1239                        self.draw_drag_target(msgs, vidx, expanded_rect, available_rect, ui, last);
1240                    },
1241                );
1242            }
1243        });
1244
1245        self.user.waves.as_mut().unwrap().drawing_infos = item_offsets;
1246    }
1247
1248    fn draw_transaction_root(
1249        &self,
1250        msgs: &mut Vec<Message>,
1251        streams: &WaveData,
1252        ui: &mut egui::Ui,
1253    ) {
1254        egui::collapsing_header::CollapsingState::load_with_default_open(
1255            ui.ctx(),
1256            egui::Id::from("Streams"),
1257            false,
1258        )
1259        .show_header(ui, |ui| {
1260            ui.with_layout(
1261                Layout::top_down(Align::LEFT).with_cross_justify(true),
1262                |ui| {
1263                    let root_name = String::from("tr");
1264                    let response = ui.add(egui::Button::selectable(
1265                        streams.active_scope == Some(ScopeType::StreamScope(StreamScopeRef::Root)),
1266                        root_name,
1267                    ));
1268
1269                    response.clicked().then(|| {
1270                        msgs.push(Message::SetActiveScope(ScopeType::StreamScope(
1271                            StreamScopeRef::Root,
1272                        )));
1273                    });
1274                },
1275            );
1276        })
1277        .body(|ui| {
1278            for (id, stream) in &streams.inner.as_transactions().unwrap().inner.tx_streams {
1279                let name = stream.name.clone();
1280                let response = ui.add(egui::Button::selectable(
1281                    streams.active_scope.as_ref().is_some_and(|s| {
1282                        if let ScopeType::StreamScope(StreamScopeRef::Stream(scope_stream)) = s {
1283                            scope_stream.stream_id == *id
1284                        } else {
1285                            false
1286                        }
1287                    }),
1288                    name.clone(),
1289                ));
1290
1291                response.clicked().then(|| {
1292                    msgs.push(Message::SetActiveScope(ScopeType::StreamScope(
1293                        StreamScopeRef::Stream(TransactionStreamRef::new_stream(*id, name)),
1294                    )));
1295                });
1296            }
1297        });
1298    }
1299
1300    pub fn draw_transaction_variable_list(
1301        &self,
1302        msgs: &mut Vec<Message>,
1303        streams: &WaveData,
1304        ui: &mut egui::Ui,
1305        active_stream: &StreamScopeRef,
1306    ) {
1307        let inner = streams.inner.as_transactions().unwrap();
1308        match active_stream {
1309            StreamScopeRef::Root => {
1310                for stream in inner.get_streams() {
1311                    ui.with_layout(
1312                        Layout::top_down(Align::LEFT).with_cross_justify(true),
1313                        |ui| {
1314                            let response =
1315                                ui.add(egui::Button::selectable(false, stream.name.clone()));
1316
1317                            response.clicked().then(|| {
1318                                msgs.push(Message::AddStreamOrGenerator(
1319                                    TransactionStreamRef::new_stream(
1320                                        stream.id,
1321                                        stream.name.clone(),
1322                                    ),
1323                                ));
1324                            });
1325                        },
1326                    );
1327                }
1328            }
1329            StreamScopeRef::Stream(stream_ref) => {
1330                for gen_id in &inner.get_stream(stream_ref.stream_id).unwrap().generators {
1331                    let gen_name = inner.get_generator(*gen_id).unwrap().name.clone();
1332                    ui.with_layout(
1333                        Layout::top_down(Align::LEFT).with_cross_justify(true),
1334                        |ui| {
1335                            let response = ui.add(egui::Button::selectable(false, &gen_name));
1336
1337                            response.clicked().then(|| {
1338                                msgs.push(Message::AddStreamOrGenerator(
1339                                    TransactionStreamRef::new_gen(
1340                                        stream_ref.stream_id,
1341                                        *gen_id,
1342                                        gen_name,
1343                                    ),
1344                                ));
1345                            });
1346                        },
1347                    );
1348                }
1349            }
1350            StreamScopeRef::Empty(_) => {}
1351        }
1352    }
1353    fn draw_focused_transaction_details(&self, ui: &mut egui::Ui) {
1354        ui.with_layout(
1355            Layout::top_down(Align::LEFT).with_cross_justify(true),
1356            |ui| {
1357                ui.label("Focused Transaction Details");
1358                let column_width = ui.available_width() / 2.;
1359                TableBuilder::new(ui)
1360                    .column(Column::exact(column_width))
1361                    .column(Column::auto())
1362                    .header(20.0, |mut header| {
1363                        header.col(|ui| {
1364                            ui.heading("Properties");
1365                        });
1366                    })
1367                    .body(|mut body| {
1368                        let focused_transaction = self
1369                            .user
1370                            .waves
1371                            .as_ref()
1372                            .unwrap()
1373                            .focused_transaction
1374                            .1
1375                            .as_ref()
1376                            .unwrap();
1377                        let row_height = 15.;
1378                        body.row(row_height, |mut row| {
1379                            row.col(|ui| {
1380                                ui.label("Transaction ID");
1381                            });
1382                            row.col(|ui| {
1383                                ui.label(focused_transaction.get_tx_id().to_string());
1384                            });
1385                        });
1386                        body.row(row_height, |mut row| {
1387                            row.col(|ui| {
1388                                ui.label("Type");
1389                            });
1390                            row.col(|ui| {
1391                                let gen = self
1392                                    .user
1393                                    .waves
1394                                    .as_ref()
1395                                    .unwrap()
1396                                    .inner
1397                                    .as_transactions()
1398                                    .unwrap()
1399                                    .get_generator(focused_transaction.get_gen_id())
1400                                    .unwrap();
1401                                ui.label(gen.name.to_string());
1402                            });
1403                        });
1404                        body.row(row_height, |mut row| {
1405                            row.col(|ui| {
1406                                ui.label("Start Time");
1407                            });
1408                            row.col(|ui| {
1409                                ui.label(focused_transaction.get_start_time().to_string());
1410                            });
1411                        });
1412                        body.row(row_height, |mut row| {
1413                            row.col(|ui| {
1414                                ui.label("End Time");
1415                            });
1416                            row.col(|ui| {
1417                                ui.label(focused_transaction.get_end_time().to_string());
1418                            });
1419                        });
1420                        body.row(row_height + 5., |mut row| {
1421                            row.col(|ui| {
1422                                ui.heading("Attributes");
1423                            });
1424                        });
1425
1426                        body.row(row_height + 3., |mut row| {
1427                            row.col(|ui| {
1428                                ui.label(RichText::new("Name").size(15.));
1429                            });
1430                            row.col(|ui| {
1431                                ui.label(RichText::new("Value").size(15.));
1432                            });
1433                        });
1434
1435                        for attr in &focused_transaction.attributes {
1436                            body.row(row_height, |mut row| {
1437                                row.col(|ui| {
1438                                    ui.label(attr.name.to_string());
1439                                });
1440                                row.col(|ui| {
1441                                    ui.label(attr.value().to_string());
1442                                });
1443                            });
1444                        }
1445
1446                        if !focused_transaction.inc_relations.is_empty() {
1447                            body.row(row_height + 5., |mut row| {
1448                                row.col(|ui| {
1449                                    ui.heading("Incoming Relations");
1450                                });
1451                            });
1452
1453                            body.row(row_height + 3., |mut row| {
1454                                row.col(|ui| {
1455                                    ui.label(RichText::new("Source Tx").size(15.));
1456                                });
1457                                row.col(|ui| {
1458                                    ui.label(RichText::new("Sink Tx").size(15.));
1459                                });
1460                            });
1461
1462                            for rel in &focused_transaction.inc_relations {
1463                                body.row(row_height, |mut row| {
1464                                    row.col(|ui| {
1465                                        ui.label(rel.source_tx_id.to_string());
1466                                    });
1467                                    row.col(|ui| {
1468                                        ui.label(rel.sink_tx_id.to_string());
1469                                    });
1470                                });
1471                            }
1472                        }
1473
1474                        if !focused_transaction.out_relations.is_empty() {
1475                            body.row(row_height + 5., |mut row| {
1476                                row.col(|ui| {
1477                                    ui.heading("Outgoing Relations");
1478                                });
1479                            });
1480
1481                            body.row(row_height + 3., |mut row| {
1482                                row.col(|ui| {
1483                                    ui.label(RichText::new("Source Tx").size(15.));
1484                                });
1485                                row.col(|ui| {
1486                                    ui.label(RichText::new("Sink Tx").size(15.));
1487                                });
1488                            });
1489
1490                            for rel in &focused_transaction.out_relations {
1491                                body.row(row_height, |mut row| {
1492                                    row.col(|ui| {
1493                                        ui.label(rel.source_tx_id.to_string());
1494                                    });
1495                                    row.col(|ui| {
1496                                        ui.label(rel.sink_tx_id.to_string());
1497                                    });
1498                                });
1499                            }
1500                        }
1501                    });
1502            },
1503        );
1504    }
1505
1506    fn get_name_alignment(&self) -> Align {
1507        if self
1508            .user
1509            .align_names_right
1510            .unwrap_or_else(|| self.user.config.layout.align_names_right())
1511        {
1512            Align::RIGHT
1513        } else {
1514            Align::LEFT
1515        }
1516    }
1517
1518    fn draw_drag_source(
1519        &self,
1520        msgs: &mut Vec<Message>,
1521        vidx: VisibleItemIndex,
1522        item_response: &egui::Response,
1523        modifiers: egui::Modifiers,
1524    ) {
1525        if item_response.dragged_by(egui::PointerButton::Primary)
1526            && item_response.drag_delta().length() > self.user.config.theme.drag_threshold
1527        {
1528            if !modifiers.ctrl
1529                && !(self.user.waves.as_ref())
1530                    .and_then(|w| w.items_tree.get_visible(vidx))
1531                    .map(|i| i.selected)
1532                    .unwrap_or(false)
1533            {
1534                msgs.push(Message::FocusItem(vidx));
1535                msgs.push(Message::ItemSelectionClear);
1536            }
1537            msgs.push(Message::SetItemSelected(vidx, true));
1538            msgs.push(Message::VariableDragStarted(vidx));
1539        }
1540
1541        if item_response.drag_stopped()
1542            && self
1543                .user
1544                .drag_source_idx
1545                .is_some_and(|source_idx| source_idx == vidx)
1546        {
1547            msgs.push(Message::VariableDragFinished);
1548        }
1549    }
1550
1551    #[allow(clippy::too_many_arguments)]
1552    fn draw_variable_label(
1553        &self,
1554        vidx: VisibleItemIndex,
1555        displayed_item: &DisplayedItem,
1556        displayed_id: DisplayedItemRef,
1557        field: FieldRef,
1558        msgs: &mut Vec<Message>,
1559        ui: &mut egui::Ui,
1560        ctx: &egui::Context,
1561    ) -> egui::Response {
1562        let mut variable_label = self.draw_item_label(
1563            vidx,
1564            displayed_id,
1565            displayed_item,
1566            Some(&field),
1567            msgs,
1568            ui,
1569            ctx,
1570        );
1571
1572        if self.show_tooltip() {
1573            variable_label = variable_label.on_hover_ui(|ui| {
1574                let tooltip = if let Some(waves) = &self.user.waves {
1575                    if field.field.is_empty() {
1576                        let wave_container = waves.inner.as_waves().unwrap();
1577                        let meta = wave_container.variable_meta(&field.root).ok();
1578                        variable_tooltip_text(&meta, &field.root)
1579                    } else {
1580                        "From translator".to_string()
1581                    }
1582                } else {
1583                    "No VCD loaded".to_string()
1584                };
1585                ui.set_max_width(ui.spacing().tooltip_width);
1586                ui.add(egui::Label::new(tooltip));
1587            });
1588        }
1589
1590        variable_label
1591    }
1592
1593    #[allow(clippy::too_many_arguments)]
1594    fn draw_variable(
1595        &self,
1596        msgs: &mut Vec<Message>,
1597        vidx: VisibleItemIndex,
1598        displayed_item: &DisplayedItem,
1599        displayed_id: DisplayedItemRef,
1600        field: FieldRef,
1601        drawing_infos: &mut Vec<ItemDrawingInfo>,
1602        info: &VariableInfo,
1603        ui: &mut egui::Ui,
1604        ctx: &egui::Context,
1605        levels_to_force_expand: Option<usize>,
1606        alignment: Align,
1607    ) -> Rect {
1608        let displayed_field_ref = DisplayedFieldRef {
1609            item: displayed_id,
1610            field: field.field.clone(),
1611        };
1612        match info {
1613            VariableInfo::Compound { subfields } => {
1614                let mut header = egui::collapsing_header::CollapsingState::load_with_default_open(
1615                    ui.ctx(),
1616                    egui::Id::new(&field),
1617                    false,
1618                );
1619
1620                if let Some(level) = levels_to_force_expand {
1621                    header.set_open(level > 0);
1622                }
1623
1624                let response = ui
1625                    .with_layout(Layout::top_down(alignment).with_cross_justify(true), |ui| {
1626                        header
1627                            .show_header(ui, |ui| {
1628                                ui.with_layout(
1629                                    Layout::top_down(alignment).with_cross_justify(true),
1630                                    |ui| {
1631                                        self.draw_variable_label(
1632                                            vidx,
1633                                            displayed_item,
1634                                            displayed_id,
1635                                            field.clone(),
1636                                            msgs,
1637                                            ui,
1638                                            ctx,
1639                                        )
1640                                    },
1641                                );
1642                            })
1643                            .body(|ui| {
1644                                for (name, info) in subfields {
1645                                    let mut new_path = field.clone();
1646                                    new_path.field.push(name.clone());
1647                                    ui.with_layout(
1648                                        Layout::top_down(alignment).with_cross_justify(true),
1649                                        |ui| {
1650                                            self.draw_variable(
1651                                                msgs,
1652                                                vidx,
1653                                                displayed_item,
1654                                                displayed_id,
1655                                                new_path,
1656                                                drawing_infos,
1657                                                info,
1658                                                ui,
1659                                                ctx,
1660                                                levels_to_force_expand.map(|l| l.saturating_sub(1)),
1661                                                alignment,
1662                                            );
1663                                        },
1664                                    )
1665                                    .inner
1666                                }
1667                            })
1668                    })
1669                    .inner;
1670                drawing_infos.push(ItemDrawingInfo::Variable(VariableDrawingInfo {
1671                    displayed_field_ref,
1672                    field_ref: field.clone(),
1673                    item_list_idx: vidx,
1674                    top: response.0.rect.top(),
1675                    bottom: response.0.rect.bottom(),
1676                }));
1677                response.0.rect
1678            }
1679            VariableInfo::Bool
1680            | VariableInfo::Bits
1681            | VariableInfo::Clock
1682            | VariableInfo::String
1683            | VariableInfo::Real => {
1684                let label = ui
1685                    .with_layout(Layout::top_down(alignment).with_cross_justify(true), |ui| {
1686                        self.draw_variable_label(
1687                            vidx,
1688                            displayed_item,
1689                            displayed_id,
1690                            field.clone(),
1691                            msgs,
1692                            ui,
1693                            ctx,
1694                        )
1695                    })
1696                    .inner;
1697                self.draw_drag_source(msgs, vidx, &label, ctx.input(|e| e.modifiers));
1698                drawing_infos.push(ItemDrawingInfo::Variable(VariableDrawingInfo {
1699                    displayed_field_ref,
1700                    field_ref: field.clone(),
1701                    item_list_idx: vidx,
1702                    top: label.rect.top(),
1703                    bottom: label.rect.bottom(),
1704                }));
1705                label.rect
1706            }
1707        }
1708    }
1709
1710    fn draw_drag_target(
1711        &self,
1712        msgs: &mut Vec<Message>,
1713        vidx: VisibleItemIndex,
1714        expanded_rect: Rect,
1715        available_rect: Rect,
1716        ui: &mut egui::Ui,
1717        last: bool,
1718    ) {
1719        if !self.user.drag_started || self.user.drag_source_idx.is_none() {
1720            return;
1721        }
1722
1723        let waves = self
1724            .user
1725            .waves
1726            .as_ref()
1727            .expect("waves not available, but expected");
1728
1729        // expanded_rect is just for the label, leaving us with gaps between lines
1730        // expand to counter that
1731        let rect_with_margin = expanded_rect.expand2(ui.spacing().item_spacing / 2f32);
1732
1733        // collision check rect need to be
1734        // - limited to half the height of the item text
1735        // - extended to cover the empty space to the left
1736        // - for the last element, expanded till the bottom
1737        let before_rect = rect_with_margin
1738            .with_max_y(rect_with_margin.left_center().y)
1739            .with_min_x(available_rect.left())
1740            .round_to_pixels(ui.painter().pixels_per_point());
1741        let after_rect = if last {
1742            rect_with_margin.with_max_y(ui.max_rect().max.y)
1743        } else {
1744            rect_with_margin
1745        }
1746        .with_min_y(rect_with_margin.left_center().y)
1747        .with_min_x(available_rect.left())
1748        .round_to_pixels(ui.painter().pixels_per_point());
1749
1750        let (insert_vidx, line_y) = if ui.rect_contains_pointer(before_rect) {
1751            (vidx, rect_with_margin.top())
1752        } else if ui.rect_contains_pointer(after_rect) {
1753            (VisibleItemIndex(vidx.0 + 1), rect_with_margin.bottom())
1754        } else {
1755            return;
1756        };
1757
1758        let level_range = waves.items_tree.valid_levels_visible(insert_vidx, |node| {
1759            matches!(
1760                waves.displayed_items.get(&node.item_ref),
1761                Some(DisplayedItem::Group(..))
1762            )
1763        });
1764
1765        let left_x = |level: u8| -> f32 { rect_with_margin.left() + level as f32 * 10.0 };
1766        let Some(insert_level) = level_range.find_or_last(|&level| {
1767            let mut rect = expanded_rect.with_min_x(left_x(level));
1768            rect.set_width(10.0);
1769            if level == 0 {
1770                rect.set_left(available_rect.left());
1771            }
1772            ui.rect_contains_pointer(rect)
1773        }) else {
1774            return;
1775        };
1776
1777        ui.painter().line_segment(
1778            [
1779                Pos2::new(left_x(insert_level), line_y),
1780                Pos2::new(rect_with_margin.right(), line_y),
1781            ],
1782            Stroke::new(
1783                self.user.config.theme.linewidth,
1784                self.user.config.theme.drag_hint_color,
1785            ),
1786        );
1787        msgs.push(Message::VariableDragTargetChanged(
1788            crate::displayed_item_tree::TargetPosition {
1789                before: ItemIndex(
1790                    waves
1791                        .items_tree
1792                        .to_displayed(insert_vidx)
1793                        .map(|index| index.0)
1794                        .unwrap_or_else(|| waves.items_tree.len()),
1795                ),
1796                level: insert_level,
1797            },
1798        ));
1799    }
1800
1801    #[allow(clippy::too_many_arguments)]
1802    fn draw_item_label(
1803        &self,
1804        vidx: VisibleItemIndex,
1805        displayed_id: DisplayedItemRef,
1806        displayed_item: &DisplayedItem,
1807        field: Option<&FieldRef>,
1808        msgs: &mut Vec<Message>,
1809        ui: &mut egui::Ui,
1810        ctx: &egui::Context,
1811    ) -> egui::Response {
1812        let text_color = {
1813            let style = ui.style_mut();
1814            if self.item_is_focused(vidx) {
1815                style.visuals.selection.bg_fill = self.user.config.theme.accent_info.background;
1816                self.user.config.theme.accent_info.foreground
1817            } else if self.item_is_selected(displayed_id) {
1818                style.visuals.selection.bg_fill =
1819                    self.user.config.theme.selected_elements_colors.background;
1820                self.user.config.theme.selected_elements_colors.foreground
1821            } else if matches!(
1822                displayed_item,
1823                DisplayedItem::Variable(_) | DisplayedItem::Placeholder(_)
1824            ) {
1825                style.visuals.selection.bg_fill =
1826                    self.user.config.theme.primary_ui_color.background;
1827                self.user.config.theme.primary_ui_color.foreground
1828            } else {
1829                style.visuals.selection.bg_fill =
1830                    self.user.config.theme.primary_ui_color.background;
1831                *self.get_item_text_color(displayed_item)
1832            }
1833        };
1834
1835        let monospace_font = ui.style().text_styles.get(&TextStyle::Monospace).unwrap();
1836        let monospace_width = {
1837            ui.fonts(|fonts| {
1838                fonts
1839                    .layout_no_wrap(" ".to_string(), monospace_font.clone(), Color32::BLACK)
1840                    .size()
1841                    .x
1842            })
1843        };
1844        let available_space = ui.available_width();
1845
1846        let mut layout_job = LayoutJob::default();
1847        match displayed_item {
1848            DisplayedItem::Variable(var) if field.is_some() => {
1849                let field = field.unwrap();
1850                if field.field.is_empty() {
1851                    let wave_container =
1852                        self.user.waves.as_ref().unwrap().inner.as_waves().unwrap();
1853                    let name_info = self.get_variable_name_info(wave_container, &var.variable_ref);
1854
1855                    if let Some(true_name) = name_info.and_then(|info| info.true_name) {
1856                        draw_true_name(
1857                            &true_name,
1858                            &mut layout_job,
1859                            monospace_font.clone(),
1860                            text_color,
1861                            monospace_width,
1862                            available_space,
1863                        )
1864                    } else {
1865                        displayed_item.add_to_layout_job(
1866                            &text_color,
1867                            ui.style(),
1868                            &mut layout_job,
1869                            Some(field),
1870                            &self.user.config,
1871                        )
1872                    }
1873                } else {
1874                    RichText::new(field.field.last().unwrap().clone())
1875                        .color(text_color)
1876                        .line_height(Some(self.user.config.layout.waveforms_line_height))
1877                        .append_to(
1878                            &mut layout_job,
1879                            ui.style(),
1880                            FontSelection::Default,
1881                            Align::Center,
1882                        )
1883                }
1884            }
1885            _ => displayed_item.add_to_layout_job(
1886                &text_color,
1887                ui.style(),
1888                &mut layout_job,
1889                field,
1890                &self.user.config,
1891            ),
1892        }
1893
1894        let item_label = ui
1895            .selectable_label(
1896                self.item_is_selected(displayed_id) || self.item_is_focused(vidx),
1897                WidgetText::LayoutJob(layout_job.into()),
1898            )
1899            .interact(Sense::drag());
1900        item_label.context_menu(|ui| {
1901            self.item_context_menu(field, msgs, ui, vidx);
1902        });
1903
1904        if item_label.clicked() {
1905            let focused = self.user.waves.as_ref().and_then(|w| w.focused_item);
1906            let was_focused = focused == Some(vidx);
1907            if was_focused {
1908                msgs.push(Message::UnfocusItem);
1909            } else {
1910                let modifiers = ctx.input(|i| i.modifiers);
1911                if modifiers.ctrl {
1912                    msgs.push(Message::ToggleItemSelected(Some(vidx)));
1913                } else if modifiers.shift {
1914                    msgs.push(Message::Batch(vec![
1915                        Message::ItemSelectionClear,
1916                        Message::ItemSelectRange(vidx),
1917                    ]));
1918                } else {
1919                    msgs.push(Message::Batch(vec![
1920                        Message::ItemSelectionClear,
1921                        Message::FocusItem(vidx),
1922                    ]));
1923                }
1924            }
1925        }
1926
1927        item_label
1928    }
1929
1930    #[allow(clippy::too_many_arguments)]
1931    fn draw_plain_item(
1932        &self,
1933        msgs: &mut Vec<Message>,
1934        vidx: VisibleItemIndex,
1935        displayed_id: DisplayedItemRef,
1936        displayed_item: &DisplayedItem,
1937        drawing_infos: &mut Vec<ItemDrawingInfo>,
1938        ui: &mut egui::Ui,
1939        ctx: &egui::Context,
1940    ) -> Rect {
1941        let label = self.draw_item_label(vidx, displayed_id, displayed_item, None, msgs, ui, ctx);
1942
1943        self.draw_drag_source(msgs, vidx, &label, ui.ctx().input(|e| e.modifiers));
1944        match displayed_item {
1945            DisplayedItem::Divider(_) => {
1946                drawing_infos.push(ItemDrawingInfo::Divider(DividerDrawingInfo {
1947                    item_list_idx: vidx,
1948                    top: label.rect.top(),
1949                    bottom: label.rect.bottom(),
1950                }));
1951            }
1952            DisplayedItem::Marker(cursor) => {
1953                drawing_infos.push(ItemDrawingInfo::Marker(MarkerDrawingInfo {
1954                    item_list_idx: vidx,
1955                    top: label.rect.top(),
1956                    bottom: label.rect.bottom(),
1957                    idx: cursor.idx,
1958                }));
1959            }
1960            DisplayedItem::TimeLine(_) => {
1961                drawing_infos.push(ItemDrawingInfo::TimeLine(TimeLineDrawingInfo {
1962                    item_list_idx: vidx,
1963                    top: label.rect.top(),
1964                    bottom: label.rect.bottom(),
1965                }));
1966            }
1967            DisplayedItem::Stream(stream) => {
1968                drawing_infos.push(ItemDrawingInfo::Stream(StreamDrawingInfo {
1969                    transaction_stream_ref: stream.transaction_stream_ref.clone(),
1970                    item_list_idx: vidx,
1971                    top: label.rect.top(),
1972                    bottom: label.rect.bottom(),
1973                }));
1974            }
1975            DisplayedItem::Group(_) => {
1976                drawing_infos.push(ItemDrawingInfo::Group(GroupDrawingInfo {
1977                    item_list_idx: vidx,
1978                    top: label.rect.top(),
1979                    bottom: label.rect.bottom(),
1980                }));
1981            }
1982            &DisplayedItem::Variable(_) => {}
1983            &DisplayedItem::Placeholder(_) => {}
1984        }
1985        label.rect
1986    }
1987
1988    fn get_alpha_focus_id(&self, vidx: VisibleItemIndex) -> RichText {
1989        let alpha_id = uint_idx_to_alpha_idx(
1990            vidx,
1991            self.user
1992                .waves
1993                .as_ref()
1994                .map_or(0, |waves| waves.displayed_items.len()),
1995        );
1996
1997        RichText::new(alpha_id).monospace()
1998    }
1999
2000    fn item_is_focused(&self, vidx: VisibleItemIndex) -> bool {
2001        if let Some(waves) = &self.user.waves {
2002            waves.focused_item == Some(vidx)
2003        } else {
2004            false
2005        }
2006    }
2007
2008    fn item_is_selected(&self, id: DisplayedItemRef) -> bool {
2009        if let Some(waves) = &self.user.waves {
2010            waves
2011                .items_tree
2012                .iter_visible_selected()
2013                .any(|node| node.item_ref == id)
2014        } else {
2015            false
2016        }
2017    }
2018
2019    fn draw_var_values(&self, ui: &mut egui::Ui, msgs: &mut Vec<Message>) {
2020        let Some(waves) = &self.user.waves else {
2021            return;
2022        };
2023        let (response, mut painter) = ui.allocate_painter(ui.available_size(), Sense::click());
2024        let rect = response.rect;
2025        let container_rect = Rect::from_min_size(Pos2::ZERO, rect.size());
2026        let to_screen = RectTransform::from_to(container_rect, rect);
2027        let cfg = DrawConfig::new(
2028            rect.height(),
2029            self.user.config.layout.waveforms_line_height,
2030            self.user.config.layout.waveforms_text_size,
2031        );
2032        let frame_width = rect.width();
2033
2034        painter.rect_filled(
2035            rect,
2036            CornerRadiusF32::ZERO,
2037            self.user.config.theme.secondary_ui_color.background,
2038        );
2039        let ctx = DrawingContext {
2040            painter: &mut painter,
2041            cfg: &cfg,
2042            // This 0.5 is very odd, but it fixes the lines we draw being smushed out across two
2043            // pixels, resulting in dimmer colors https://github.com/emilk/egui/issues/1322
2044            to_screen: &|x, y| to_screen.transform_pos(Pos2::new(x, y) + Vec2::new(0.5, 0.5)),
2045            theme: &self.user.config.theme,
2046        };
2047
2048        let gap = ui.spacing().item_spacing.y * 0.5;
2049        let y_zero = to_screen.transform_pos(Pos2::ZERO).y;
2050        let ucursor = waves.cursor.as_ref().and_then(num::BigInt::to_biguint);
2051
2052        // Add default margin as it was removed when creating the frame
2053        let rect_with_margin = Rect {
2054            min: rect.min + ui.spacing().item_spacing,
2055            max: rect.max,
2056        };
2057
2058        let builder = UiBuilder::new().max_rect(rect_with_margin);
2059        ui.scope_builder(builder, |ui| {
2060            let text_style = TextStyle::Monospace;
2061            ui.style_mut().override_text_style = Some(text_style);
2062            for (vidx, drawing_info) in waves
2063                .drawing_infos
2064                .iter()
2065                .sorted_by_key(|o| o.top() as i32)
2066                .enumerate()
2067            {
2068                let vidx = VisibleItemIndex(vidx);
2069                let next_y = ui.cursor().top();
2070                // In order to align the text in this view with the variable tree,
2071                // we need to keep track of how far away from the expected offset we are,
2072                // and compensate for it
2073                if next_y < drawing_info.top() {
2074                    ui.add_space(drawing_info.top() - next_y);
2075                }
2076
2077                let backgroundcolor = &self.get_background_color(waves, drawing_info, vidx);
2078                self.draw_background(
2079                    drawing_info,
2080                    y_zero,
2081                    &ctx,
2082                    gap,
2083                    frame_width,
2084                    backgroundcolor,
2085                );
2086                match drawing_info {
2087                    ItemDrawingInfo::Variable(drawing_info) => {
2088                        if ucursor.as_ref().is_none() {
2089                            ui.label("");
2090                            continue;
2091                        }
2092
2093                        let v = self.get_variable_value(
2094                            waves,
2095                            &drawing_info.displayed_field_ref,
2096                            &ucursor,
2097                        );
2098                        if let Some(v) = v {
2099                            ui.label(RichText::new(v).color(
2100                                *self.user.config.theme.get_best_text_color(backgroundcolor),
2101                            ))
2102                            .context_menu(|ui| {
2103                                self.item_context_menu(
2104                                    Some(&FieldRef::without_fields(
2105                                        drawing_info.field_ref.root.clone(),
2106                                    )),
2107                                    msgs,
2108                                    ui,
2109                                    vidx,
2110                                );
2111                            });
2112                        }
2113                    }
2114
2115                    ItemDrawingInfo::Marker(numbered_cursor) => {
2116                        if let Some(cursor) = &waves.cursor {
2117                            let delta = time_string(
2118                                &(waves.numbered_marker_time(numbered_cursor.idx) - cursor),
2119                                &waves.inner.metadata().timescale,
2120                                &self.user.wanted_timeunit,
2121                                &self.get_time_format(),
2122                            );
2123
2124                            ui.label(RichText::new(format!("Δ: {delta}",)).color(
2125                                *self.user.config.theme.get_best_text_color(backgroundcolor),
2126                            ))
2127                            .context_menu(|ui| {
2128                                self.item_context_menu(None, msgs, ui, vidx);
2129                            });
2130                        } else {
2131                            ui.label("");
2132                        }
2133                    }
2134                    ItemDrawingInfo::Divider(_)
2135                    | ItemDrawingInfo::TimeLine(_)
2136                    | ItemDrawingInfo::Stream(_)
2137                    | ItemDrawingInfo::Group(_) => {
2138                        ui.label("");
2139                    }
2140                }
2141            }
2142        });
2143    }
2144
2145    pub fn get_variable_value(
2146        &self,
2147        waves: &WaveData,
2148        displayed_field_ref: &DisplayedFieldRef,
2149        ucursor: &Option<num::BigUint>,
2150    ) -> Option<String> {
2151        if let Some(ucursor) = ucursor {
2152            let Some(DisplayedItem::Variable(displayed_variable)) =
2153                waves.displayed_items.get(&displayed_field_ref.item)
2154            else {
2155                return None;
2156            };
2157            let variable = &displayed_variable.variable_ref;
2158            let translator =
2159                waves.variable_translator(&displayed_field_ref.without_field(), &self.translators);
2160            let meta = waves.inner.as_waves().unwrap().variable_meta(variable);
2161
2162            let translation_result = waves
2163                .inner
2164                .as_waves()
2165                .unwrap()
2166                .query_variable(variable, ucursor)
2167                .ok()
2168                .flatten()
2169                .and_then(|q| q.current)
2170                .map(|(_time, value)| meta.and_then(|meta| translator.translate(&meta, &value)));
2171
2172            if let Some(Ok(s)) = translation_result {
2173                let fields = s.format_flat(
2174                    &displayed_variable.format,
2175                    &displayed_variable.field_formats,
2176                    &self.translators,
2177                );
2178
2179                let subfield = fields
2180                    .iter()
2181                    .find(|res| res.names == displayed_field_ref.field);
2182
2183                if let Some(SubFieldFlatTranslationResult {
2184                    names: _,
2185                    value: Some(TranslatedValue { value: v, kind: _ }),
2186                }) = subfield
2187                {
2188                    Some(v.clone())
2189                } else {
2190                    Some("-".to_string())
2191                }
2192            } else {
2193                None
2194            }
2195        } else {
2196            None
2197        }
2198    }
2199
2200    pub fn get_variable_name_info(
2201        &self,
2202        wave_container: &WaveContainer,
2203        var: &VariableRef,
2204    ) -> Option<VariableNameInfo> {
2205        let meta = wave_container.variable_meta(var).ok();
2206
2207        let info = self
2208            .variable_name_info_cache
2209            .borrow_mut()
2210            .entry(var.clone())
2211            .or_insert_with(|| {
2212                meta.as_ref().and_then(|meta| {
2213                    let info = self
2214                        .translators
2215                        .all_translators()
2216                        .iter()
2217                        .find_map(|t| t.variable_name_info(meta));
2218                    info
2219                })
2220            })
2221            .clone();
2222
2223        info
2224    }
2225
2226    pub fn draw_background(
2227        &self,
2228        drawing_info: &ItemDrawingInfo,
2229        y_zero: f32,
2230        ctx: &DrawingContext<'_>,
2231        gap: f32,
2232        frame_width: f32,
2233        background_color: &Color32,
2234    ) {
2235        // Draw background
2236        let min = (ctx.to_screen)(0.0, drawing_info.top() - y_zero - gap);
2237        let max = (ctx.to_screen)(frame_width, drawing_info.bottom() - y_zero + gap);
2238        ctx.painter
2239            .rect_filled(Rect { min, max }, CornerRadiusF32::ZERO, *background_color);
2240    }
2241
2242    pub fn get_background_color(
2243        &self,
2244        waves: &WaveData,
2245        drawing_info: &ItemDrawingInfo,
2246        vidx: VisibleItemIndex,
2247    ) -> Color32 {
2248        if let Some(focused) = waves.focused_item {
2249            if self.highlight_focused() && focused == vidx {
2250                return self.user.config.theme.highlight_background;
2251            }
2252        }
2253        *waves
2254            .displayed_items
2255            .get(
2256                &waves
2257                    .items_tree
2258                    .get_visible(drawing_info.item_list_idx())
2259                    .unwrap()
2260                    .item_ref,
2261            )
2262            .and_then(super::displayed_item::DisplayedItem::background_color)
2263            .and_then(|color| self.user.config.theme.get_color(color))
2264            .unwrap_or_else(|| self.get_default_alternating_background_color(vidx))
2265    }
2266
2267    fn get_default_alternating_background_color(&self, vidx: VisibleItemIndex) -> &Color32 {
2268        // Set background color
2269        if self.user.config.theme.alt_frequency != 0
2270            && (vidx.0 / self.user.config.theme.alt_frequency) % 2 == 1
2271        {
2272            &self.user.config.theme.canvas_colors.alt_background
2273        } else {
2274            &Color32::TRANSPARENT
2275        }
2276    }
2277
2278    fn should_open_header(&self, scope: &ScopeRef) -> bool {
2279        let mut scope_ref_cell = self.scope_ref_to_expand.borrow_mut();
2280        if let Some(state) = scope_ref_cell.as_mut() {
2281            if state.strs.starts_with(&scope.strs) {
2282                if (state.strs.len() - 1) == scope.strs.len() {
2283                    // need to compare vs. parent of signal
2284                    *scope_ref_cell = None;
2285                }
2286                return true;
2287            }
2288        }
2289        false
2290    }
2291
2292    /// Draw the default timeline at the top of the canvas
2293    pub fn draw_default_timeline(
2294        &self,
2295        waves: &WaveData,
2296        ctx: &DrawingContext,
2297        viewport_idx: usize,
2298        frame_width: f32,
2299        cfg: &DrawConfig,
2300    ) {
2301        let ticks = waves.get_ticks(
2302            &waves.viewports[viewport_idx],
2303            &waves.inner.metadata().timescale,
2304            frame_width,
2305            cfg.text_size,
2306            &self.user.wanted_timeunit,
2307            &self.get_time_format(),
2308            &self.user.config,
2309        );
2310
2311        waves.draw_ticks(
2312            Some(&self.user.config.theme.foreground),
2313            &ticks,
2314            ctx,
2315            0.0,
2316            egui::Align2::CENTER_TOP,
2317            &self.user.config,
2318        );
2319    }
2320}
2321
2322fn variable_tooltip_text(meta: &Option<VariableMeta>, variable: &VariableRef) -> String {
2323    if let Some(meta) = meta {
2324        format!(
2325            "{}\nNum bits: {}\nType: {}\nDirection: {}",
2326            variable.full_path_string(),
2327            meta.num_bits
2328                .map_or_else(|| "unknown".to_string(), |bits| bits.to_string()),
2329            meta.variable_type_name
2330                .clone()
2331                .or_else(|| meta.variable_type.map(|t| t.to_string()))
2332                .unwrap_or_else(|| "unknown".to_string()),
2333            meta.direction
2334                .map_or_else(|| "unknown".to_string(), |direction| format!("{direction}"))
2335        )
2336    } else {
2337        variable.full_path_string()
2338    }
2339}
2340
2341fn scope_tooltip_text(wave: &WaveData, scope: &ScopeRef) -> String {
2342    let other = wave.inner.as_waves().unwrap().get_scope_tooltip_data(scope);
2343    if other.is_empty() {
2344        format!("{scope}")
2345    } else {
2346        format!("{scope}\n{other}")
2347    }
2348}
2349
2350pub fn draw_true_name(
2351    true_name: &TrueName,
2352    layout_job: &mut LayoutJob,
2353    font: FontId,
2354    foreground: Color32,
2355    char_width: f32,
2356    allowed_space: f32,
2357) {
2358    let char_budget = (allowed_space / char_width) as usize;
2359
2360    match true_name {
2361        TrueName::SourceCode {
2362            line_number,
2363            before,
2364            this,
2365            after,
2366        } => {
2367            let before_chars = before.chars().collect::<Vec<_>>();
2368            let this_chars = this.chars().collect::<Vec<_>>();
2369            let after_chars = after.chars().collect::<Vec<_>>();
2370            let line_num = format!("{line_number} ");
2371            let important_chars = line_num.len() + this_chars.len();
2372            let required_extra_chars = before_chars.len() + after_chars.len();
2373
2374            // If everything fits, things are very easy
2375            let (line_num, before, this, after) =
2376                if char_budget >= important_chars + required_extra_chars {
2377                    (line_num, before.clone(), this.clone(), after.clone())
2378                } else if char_budget > important_chars {
2379                    // How many extra chars we have available
2380                    let extra_chars = char_budget - important_chars;
2381
2382                    let max_from_before = (extra_chars as f32 / 2.).ceil() as usize;
2383                    let max_from_after = (extra_chars as f32 / 2.).floor() as usize;
2384
2385                    let (chars_from_before, chars_from_after) =
2386                        if max_from_before > before_chars.len() {
2387                            (before_chars.len(), extra_chars - before_chars.len())
2388                        } else if max_from_after > after_chars.len() {
2389                            (extra_chars - after_chars.len(), before_chars.len())
2390                        } else {
2391                            (max_from_before, max_from_after)
2392                        };
2393
2394                    let mut before = before_chars
2395                        .into_iter()
2396                        .rev()
2397                        .take(chars_from_before)
2398                        .rev()
2399                        .collect::<Vec<_>>();
2400                    if !before.is_empty() {
2401                        before[0] = '…'
2402                    }
2403                    let mut after = after_chars
2404                        .into_iter()
2405                        .take(chars_from_after)
2406                        .collect::<Vec<_>>();
2407                    if !after.is_empty() {
2408                        let last_elem = after.len() - 1;
2409                        after[last_elem] = '…'
2410                    }
2411
2412                    (
2413                        line_num,
2414                        before.into_iter().collect(),
2415                        this.clone(),
2416                        after.into_iter().collect(),
2417                    )
2418                } else {
2419                    // If we can't even fit the whole important part,
2420                    // we'll prefer the line number
2421                    let from_line_num = line_num.len();
2422                    let from_this = char_budget.saturating_sub(from_line_num);
2423                    let this = this
2424                        .chars()
2425                        .take(from_this)
2426                        .enumerate()
2427                        .map(|(i, c)| if i == from_this - 1 { '…' } else { c })
2428                        .collect();
2429                    (line_num, "".to_string(), this, "".to_string())
2430                };
2431
2432            layout_job.append(
2433                &line_num,
2434                0.0,
2435                TextFormat {
2436                    font_id: font.clone(),
2437                    color: foreground.gamma_multiply(0.75),
2438                    ..Default::default()
2439                },
2440            );
2441            layout_job.append(
2442                &before,
2443                0.0,
2444                TextFormat {
2445                    font_id: font.clone(),
2446                    color: foreground.gamma_multiply(0.5),
2447                    ..Default::default()
2448                },
2449            );
2450            layout_job.append(
2451                &this,
2452                0.0,
2453                TextFormat {
2454                    font_id: font.clone(),
2455                    color: foreground,
2456                    ..Default::default()
2457                },
2458            );
2459            layout_job.append(
2460                after.trim_end(),
2461                0.0,
2462                TextFormat {
2463                    font_id: font.clone(),
2464                    color: foreground.gamma_multiply(0.5),
2465                    ..Default::default()
2466                },
2467            )
2468        }
2469    }
2470}