libsurfer/
view.rs

1use std::ops::Range;
2
3use crate::fzcmd::expand_command;
4use color_eyre::eyre::Context;
5use ecolor::Color32;
6#[cfg(not(target_arch = "wasm32"))]
7use egui::ViewportCommand;
8use egui::{
9    FontId, FontSelection, Frame, Layout, Painter, RichText, ScrollArea, Sense, TextFormat,
10    TextStyle, UiBuilder, WidgetText,
11};
12use egui_extras::{Column, TableBuilder};
13use egui_remixicon::icons;
14use emath::{Align, GuiRounding, Pos2, Rect, RectTransform, Vec2};
15use epaint::{
16    text::{LayoutJob, TextWrapMode},
17    CornerRadiusF32, Margin, Stroke,
18};
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.user.config.layout.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    ) {
646        let name = scope.name();
647        let mut response = ui.add(egui::SelectableLabel::new(
648            wave.active_scope == Some(ScopeType::WaveScope(scope.clone())),
649            name,
650        ));
651        let _ = response.interact(egui::Sense::click_and_drag());
652        response.drag_started().then(|| {
653            msgs.push(Message::VariableDragStarted(VisibleItemIndex(
654                self.user.waves.as_ref().unwrap().display_item_ref_counter,
655            )))
656        });
657
658        response.drag_stopped().then(|| {
659            if ui.input(|i| i.pointer.hover_pos().unwrap_or_default().x)
660                > self.user.sidepanel_width.unwrap_or_default()
661            {
662                let scope_t = ScopeType::WaveScope(scope.clone());
663                let variables = self
664                    .user
665                    .waves
666                    .as_ref()
667                    .unwrap()
668                    .inner
669                    .variables_in_scope(&scope_t)
670                    .iter()
671                    .filter_map(|var| match var {
672                        VarType::Variable(var) => Some(var.clone()),
673                        _ => None,
674                    })
675                    .collect_vec();
676
677                msgs.push(Message::AddDraggedVariables(self.filtered_variables(
678                    variables.as_slice(),
679                    &self.user.variable_filter,
680                )));
681            }
682        });
683        if self.show_scope_tooltip() {
684            response = response.on_hover_ui(|ui| {
685                ui.set_max_width(ui.spacing().tooltip_width);
686                ui.add(egui::Label::new(scope_tooltip_text(wave, scope)));
687            });
688        }
689        response.context_menu(|ui| {
690            if ui.button("Add scope").clicked() {
691                msgs.push(Message::AddScope(scope.clone(), false));
692                ui.close_menu();
693            }
694            if ui.button("Add scope recursively").clicked() {
695                msgs.push(Message::AddScope(scope.clone(), true));
696                ui.close_menu();
697            }
698            if ui.button("Add scope as group").clicked() {
699                msgs.push(Message::AddScopeAsGroup(scope.clone(), false));
700                ui.close_menu();
701            }
702            if ui.button("Add scope as group recursively").clicked() {
703                msgs.push(Message::AddScopeAsGroup(scope.clone(), true));
704                ui.close_menu();
705            }
706        });
707        response
708            .clicked()
709            .then(|| msgs.push(Message::SetActiveScope(ScopeType::WaveScope(scope.clone()))));
710    }
711
712    fn draw_selectable_child_or_orphan_scope(
713        &self,
714        msgs: &mut Vec<Message>,
715        wave: &WaveData,
716        scope: &ScopeRef,
717        draw_variables: bool,
718        ui: &mut egui::Ui,
719        filter: &VariableFilter,
720    ) {
721        let Some(child_scopes) = wave
722            .inner
723            .as_waves()
724            .unwrap()
725            .child_scopes(scope)
726            .context("Failed to get child scopes")
727            .map_err(|e| warn!("{e:#?}"))
728            .ok()
729        else {
730            return;
731        };
732
733        let no_variables_in_scope = wave.inner.as_waves().unwrap().no_variables_in_scope(scope);
734        if child_scopes.is_empty() && no_variables_in_scope && !self.show_empty_scopes() {
735            return;
736        }
737        if child_scopes.is_empty() && (!draw_variables || no_variables_in_scope) {
738            self.add_scope_selectable_label(msgs, wave, scope, ui);
739        } else {
740            egui::collapsing_header::CollapsingState::load_with_default_open(
741                ui.ctx(),
742                egui::Id::new(scope),
743                false,
744            )
745            .show_header(ui, |ui| {
746                ui.with_layout(
747                    Layout::top_down(Align::LEFT).with_cross_justify(true),
748                    |ui| {
749                        self.add_scope_selectable_label(msgs, wave, scope, ui);
750                    },
751                );
752            })
753            .body(|ui| {
754                if draw_variables || self.show_parameters_in_scopes() {
755                    let wave_container = wave.inner.as_waves().unwrap();
756                    let parameters = wave_container.parameters_in_scope(scope);
757                    if !parameters.is_empty() {
758                        egui::collapsing_header::CollapsingState::load_with_default_open(
759                            ui.ctx(),
760                            egui::Id::new(&parameters),
761                            false,
762                        )
763                        .show_header(ui, |ui| {
764                            ui.with_layout(
765                                Layout::top_down(Align::LEFT).with_cross_justify(true),
766                                |ui| {
767                                    ui.label("Parameters");
768                                },
769                            );
770                        })
771                        .body(|ui| {
772                            self.draw_variable_list(
773                                msgs,
774                                wave_container,
775                                ui,
776                                &parameters,
777                                None,
778                                filter,
779                            );
780                        });
781                    }
782                }
783                self.draw_root_scope_view(msgs, wave, scope, draw_variables, ui, filter);
784                if draw_variables {
785                    let wave_container = wave.inner.as_waves().unwrap();
786                    let variables = wave_container.variables_in_scope(scope);
787                    self.draw_variable_list(msgs, wave_container, ui, &variables, None, filter);
788                }
789            });
790        }
791    }
792
793    fn draw_root_scope_view(
794        &self,
795        msgs: &mut Vec<Message>,
796        wave: &WaveData,
797        root_scope: &ScopeRef,
798        draw_variables: bool,
799        ui: &mut egui::Ui,
800        filter: &VariableFilter,
801    ) {
802        let Some(child_scopes) = wave
803            .inner
804            .as_waves()
805            .unwrap()
806            .child_scopes(root_scope)
807            .context("Failed to get child scopes")
808            .map_err(|e| warn!("{e:#?}"))
809            .ok()
810        else {
811            return;
812        };
813
814        let child_scopes_sorted = child_scopes
815            .iter()
816            .sorted_by(|a, b| numeric_sort::cmp(&a.name(), &b.name()))
817            .collect_vec();
818
819        for child_scope in child_scopes_sorted {
820            self.draw_selectable_child_or_orphan_scope(
821                msgs,
822                wave,
823                child_scope,
824                draw_variables,
825                ui,
826                filter,
827            );
828        }
829    }
830
831    pub fn draw_variable_list(
832        &self,
833        msgs: &mut Vec<Message>,
834        wave_container: &WaveContainer,
835        ui: &mut egui::Ui,
836        all_variables: &[VariableRef],
837        row_range: Option<Range<usize>>,
838        filter: &VariableFilter,
839    ) {
840        let all_variables = self.filtered_variables(all_variables, filter);
841        self.draw_filtered_variable_list(msgs, wave_container, ui, &all_variables, row_range);
842    }
843
844    pub fn draw_filtered_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    ) {
852        let variables = all_variables
853            .iter()
854            .map(|var| {
855                let meta = wave_container.variable_meta(var).ok();
856                let name_info = self.get_variable_name_info(wave_container, var);
857                (var, meta, name_info)
858            })
859            .sorted_by_key(|(_, _, name_info)| {
860                -name_info
861                    .as_ref()
862                    .and_then(|info| info.priority)
863                    .unwrap_or_default()
864            })
865            .skip(row_range.as_ref().map(|r| r.start).unwrap_or(0))
866            .take(
867                row_range
868                    .as_ref()
869                    .map(|r| r.end - r.start)
870                    .unwrap_or(all_variables.len()),
871            );
872
873        for (variable, meta, name_info) in variables {
874            let index = meta
875                .as_ref()
876                .and_then(|meta| meta.index)
877                .map(|index| {
878                    if self.show_variable_indices() {
879                        format!(" {index}")
880                    } else {
881                        String::new()
882                    }
883                })
884                .unwrap_or_default();
885
886            let direction = if self.show_variable_direction() {
887                meta.as_ref()
888                    .and_then(|meta| meta.direction)
889                    .map(|direction| {
890                        format!(
891                            "{} ",
892                            // Icon based on direction
893                            direction.get_icon().unwrap_or_else(|| {
894                                if meta.as_ref().is_some_and(|meta| {
895                                    meta.variable_type == Some(VariableType::VCDParameter)
896                                }) {
897                                    // If parameter
898                                    icons::MAP_PIN_2_LINE
899                                } else {
900                                    // Align other items (can be improved)
901                                    // The padding depends on if we will render monospace or not
902                                    if name_info.is_some() {
903                                        "  "
904                                    } else {
905                                        "    "
906                                    }
907                                }
908                            })
909                        )
910                    })
911                    .unwrap_or_default()
912            } else {
913                String::new()
914            };
915
916            let value = if meta
917                .as_ref()
918                .is_some_and(|meta| meta.variable_type == Some(VariableType::VCDParameter))
919            {
920                let res = wave_container.query_variable(variable, &BigUint::ZERO).ok();
921                res.and_then(|o| o.and_then(|q| q.current.map(|v| format!(": {}", v.1))))
922                    .unwrap_or_else(|| ": Undefined".to_string())
923            } else {
924                String::new()
925            };
926
927            ui.with_layout(
928                Layout::top_down(Align::LEFT).with_cross_justify(true),
929                |ui| {
930                    let mut label = LayoutJob::default();
931
932                    match name_info.and_then(|info| info.true_name) {
933                        Some(name) => {
934                            // NOTE: Safe unwrap, we know that egui has its own built-in font
935                            let font = ui.style().text_styles.get(&TextStyle::Monospace).unwrap();
936                            let char_width = ui.fonts(|fonts| {
937                                fonts
938                                    .layout_no_wrap(
939                                        " ".to_string(),
940                                        font.clone(),
941                                        Color32::from_rgb(0, 0, 0),
942                                    )
943                                    .size()
944                                    .x
945                            });
946
947                            let direction_size = direction.chars().count();
948                            let index_size = index.chars().count();
949                            let value_size = value.chars().count();
950                            let used_space =
951                                (direction_size + index_size + value_size) as f32 * char_width;
952                            // The button padding is added by egui on selectable labels
953                            let available_space =
954                                ui.available_width() - ui.spacing().button_padding.x * 2.;
955                            let space_for_name = available_space - used_space;
956
957                            let text_format = TextFormat {
958                                font_id: font.clone(),
959                                color: self.user.config.theme.foreground,
960                                ..Default::default()
961                            };
962
963                            label.append(&direction, 0.0, text_format.clone());
964
965                            draw_true_name(
966                                &name,
967                                &mut label,
968                                font.clone(),
969                                self.user.config.theme.foreground,
970                                char_width,
971                                space_for_name,
972                            );
973
974                            label.append(&index, 0.0, text_format.clone());
975                            label.append(&value, 0.0, text_format.clone());
976                        }
977                        None => {
978                            let font = ui.style().text_styles.get(&TextStyle::Body).unwrap();
979                            let text_format = TextFormat {
980                                font_id: font.clone(),
981                                color: self.user.config.theme.foreground,
982                                ..Default::default()
983                            };
984                            label.append(&direction, 0.0, text_format.clone());
985                            label.append(&variable.name, 0.0, text_format.clone());
986                            label.append(&index, 0.0, text_format.clone());
987                            label.append(&value, 0.0, text_format.clone());
988                        }
989                    }
990
991                    let mut response = ui.add(egui::SelectableLabel::new(false, label));
992
993                    let _ = response.interact(egui::Sense::click_and_drag());
994
995                    if self.show_tooltip() {
996                        // Should be possible to reuse the meta from above?
997                        response = response.on_hover_ui(|ui| {
998                            let meta = wave_container.variable_meta(variable).ok();
999                            ui.set_max_width(ui.spacing().tooltip_width);
1000                            ui.add(egui::Label::new(variable_tooltip_text(&meta, variable)));
1001                        });
1002                    }
1003                    response.drag_started().then(|| {
1004                        msgs.push(Message::VariableDragStarted(VisibleItemIndex(
1005                            self.user.waves.as_ref().unwrap().display_item_ref_counter,
1006                        )))
1007                    });
1008                    response.drag_stopped().then(|| {
1009                        if ui.input(|i| i.pointer.hover_pos().unwrap_or_default().x)
1010                            > self.user.sidepanel_width.unwrap_or_default()
1011                        {
1012                            msgs.push(Message::AddDraggedVariables(vec![variable.clone()]));
1013                        }
1014                    });
1015                    response
1016                        .clicked()
1017                        .then(|| msgs.push(Message::AddVariables(vec![variable.clone()])));
1018                },
1019            );
1020        }
1021    }
1022
1023    fn draw_item_focus_list(&self, ui: &mut egui::Ui) {
1024        let alignment = self.get_name_alignment();
1025        ui.with_layout(
1026            Layout::top_down(alignment).with_cross_justify(false),
1027            |ui| {
1028                if self.show_default_timeline() {
1029                    ui.add_space(ui.text_style_height(&egui::TextStyle::Body) + 2.0);
1030                }
1031                for (vidx, _) in self
1032                    .user
1033                    .waves
1034                    .as_ref()
1035                    .unwrap()
1036                    .items_tree
1037                    .iter_visible()
1038                    .enumerate()
1039                {
1040                    let vidx = VisibleItemIndex(vidx);
1041                    ui.scope(|ui| {
1042                        ui.style_mut().visuals.selection.bg_fill =
1043                            self.user.config.theme.accent_warn.background;
1044                        ui.style_mut().visuals.override_text_color =
1045                            Some(self.user.config.theme.accent_warn.foreground);
1046                        let _ = ui.selectable_label(true, self.get_alpha_focus_id(vidx));
1047                    });
1048                }
1049            },
1050        );
1051    }
1052
1053    fn hierarchy_icon(
1054        &self,
1055        ui: &mut egui::Ui,
1056        has_children: bool,
1057        unfolded: bool,
1058        alignment: Align,
1059    ) -> egui::Response {
1060        let (rect, response) = ui.allocate_exact_size(
1061            Vec2::splat(self.user.config.layout.waveforms_text_size),
1062            Sense::click(),
1063        );
1064        if !has_children {
1065            return response;
1066        }
1067
1068        // fixme: use the much nicer remixicon arrow? do a layout here and paint the galley into the rect?
1069        // or alternatively: change how the tree iterator works and use the egui facilities (cross widget?)
1070        let icon_rect = Rect::from_center_size(
1071            rect.center(),
1072            emath::vec2(rect.width(), rect.height()) * 0.75,
1073        );
1074        let mut points = vec![
1075            icon_rect.left_top(),
1076            icon_rect.right_top(),
1077            icon_rect.center_bottom(),
1078        ];
1079        let rotation = emath::Rot2::from_angle(if unfolded {
1080            0.0
1081        } else if alignment == Align::LEFT {
1082            -std::f32::consts::TAU / 4.0
1083        } else {
1084            std::f32::consts::TAU / 4.0
1085        });
1086        for p in &mut points {
1087            *p = icon_rect.center() + rotation * (*p - icon_rect.center());
1088        }
1089
1090        let style = ui.style().interact(&response);
1091        ui.painter().add(egui::Shape::convex_polygon(
1092            points,
1093            style.fg_stroke.color,
1094            egui::Stroke::NONE,
1095        ));
1096        response
1097    }
1098
1099    fn draw_item_list(&mut self, msgs: &mut Vec<Message>, ui: &mut egui::Ui, ctx: &egui::Context) {
1100        let mut item_offsets = Vec::new();
1101
1102        let any_groups = self
1103            .user
1104            .waves
1105            .as_ref()
1106            .unwrap()
1107            .items_tree
1108            .iter()
1109            .any(|node| node.level > 0);
1110        let alignment = self.get_name_alignment();
1111        ui.with_layout(Layout::top_down(alignment).with_cross_justify(true), |ui| {
1112            let available_rect = ui.available_rect_before_wrap();
1113            for crate::displayed_item_tree::Info {
1114                node:
1115                    crate::displayed_item_tree::Node {
1116                        item_ref,
1117                        level,
1118                        unfolded,
1119                        ..
1120                    },
1121                vidx,
1122                has_children,
1123                last,
1124                ..
1125            } in self
1126                .user
1127                .waves
1128                .as_ref()
1129                .unwrap()
1130                .items_tree
1131                .iter_visible_extra()
1132            {
1133                let Some(displayed_item) = self
1134                    .user
1135                    .waves
1136                    .as_ref()
1137                    .unwrap()
1138                    .displayed_items
1139                    .get(item_ref)
1140                else {
1141                    continue;
1142                };
1143
1144                ui.with_layout(
1145                    if alignment == Align::LEFT {
1146                        Layout::left_to_right(Align::TOP)
1147                    } else {
1148                        Layout::right_to_left(Align::TOP)
1149                    },
1150                    |ui| {
1151                        ui.add_space(10.0 * *level as f32);
1152                        if any_groups {
1153                            let response =
1154                                self.hierarchy_icon(ui, has_children, *unfolded, alignment);
1155                            if response.clicked() {
1156                                if *unfolded {
1157                                    msgs.push(Message::GroupFold(Some(*item_ref)));
1158                                } else {
1159                                    msgs.push(Message::GroupUnfold(Some(*item_ref)));
1160                                }
1161                            }
1162                        }
1163
1164                        let item_rect = match displayed_item {
1165                            DisplayedItem::Variable(displayed_variable) => {
1166                                let levels_to_force_expand =
1167                                    self.items_to_expand.borrow().iter().find_map(
1168                                        |(id, levels)| {
1169                                            if item_ref == id {
1170                                                Some(*levels)
1171                                            } else {
1172                                                None
1173                                            }
1174                                        },
1175                                    );
1176
1177                                self.draw_variable(
1178                                    msgs,
1179                                    vidx,
1180                                    displayed_item,
1181                                    *item_ref,
1182                                    FieldRef::without_fields(
1183                                        displayed_variable.variable_ref.clone(),
1184                                    ),
1185                                    &mut item_offsets,
1186                                    &displayed_variable.info,
1187                                    ui,
1188                                    ctx,
1189                                    levels_to_force_expand,
1190                                    alignment,
1191                                )
1192                            }
1193                            DisplayedItem::Divider(_)
1194                            | DisplayedItem::Marker(_)
1195                            | DisplayedItem::Placeholder(_)
1196                            | DisplayedItem::TimeLine(_)
1197                            | DisplayedItem::Stream(_)
1198                            | DisplayedItem::Group(_) => {
1199                                ui.with_layout(
1200                                    ui.layout()
1201                                        .with_main_justify(true)
1202                                        .with_main_align(alignment),
1203                                    |ui| {
1204                                        self.draw_plain_item(
1205                                            msgs,
1206                                            vidx,
1207                                            *item_ref,
1208                                            displayed_item,
1209                                            &mut item_offsets,
1210                                            ui,
1211                                        )
1212                                    },
1213                                )
1214                                .inner
1215                            }
1216                        };
1217                        // expand to the left, but not over the icon size
1218                        let mut expanded_rect = item_rect;
1219                        expanded_rect.set_left(
1220                            available_rect.left()
1221                                + self.user.config.layout.waveforms_text_size
1222                                + ui.spacing().item_spacing.x,
1223                        );
1224                        expanded_rect.set_right(available_rect.right());
1225                        self.draw_drag_target(msgs, vidx, expanded_rect, available_rect, ui, last);
1226                    },
1227                );
1228            }
1229        });
1230
1231        self.user.waves.as_mut().unwrap().drawing_infos = item_offsets;
1232    }
1233
1234    fn draw_transaction_root(
1235        &self,
1236        msgs: &mut Vec<Message>,
1237        streams: &WaveData,
1238        ui: &mut egui::Ui,
1239    ) {
1240        egui::collapsing_header::CollapsingState::load_with_default_open(
1241            ui.ctx(),
1242            egui::Id::from("Streams"),
1243            false,
1244        )
1245        .show_header(ui, |ui| {
1246            ui.with_layout(
1247                Layout::top_down(Align::LEFT).with_cross_justify(true),
1248                |ui| {
1249                    let root_name = String::from("tr");
1250                    let response = ui.add(egui::SelectableLabel::new(
1251                        streams.active_scope == Some(ScopeType::StreamScope(StreamScopeRef::Root)),
1252                        root_name,
1253                    ));
1254
1255                    response.clicked().then(|| {
1256                        msgs.push(Message::SetActiveScope(ScopeType::StreamScope(
1257                            StreamScopeRef::Root,
1258                        )));
1259                    });
1260                },
1261            );
1262        })
1263        .body(|ui| {
1264            for (id, stream) in &streams.inner.as_transactions().unwrap().inner.tx_streams {
1265                let name = stream.name.clone();
1266                let response = ui.add(egui::SelectableLabel::new(
1267                    streams.active_scope.as_ref().is_some_and(|s| {
1268                        if let ScopeType::StreamScope(StreamScopeRef::Stream(scope_stream)) = s {
1269                            scope_stream.stream_id == *id
1270                        } else {
1271                            false
1272                        }
1273                    }),
1274                    name.clone(),
1275                ));
1276
1277                response.clicked().then(|| {
1278                    msgs.push(Message::SetActiveScope(ScopeType::StreamScope(
1279                        StreamScopeRef::Stream(TransactionStreamRef::new_stream(*id, name)),
1280                    )));
1281                });
1282            }
1283        });
1284    }
1285
1286    pub fn draw_transaction_variable_list(
1287        &self,
1288        msgs: &mut Vec<Message>,
1289        streams: &WaveData,
1290        ui: &mut egui::Ui,
1291        active_stream: &StreamScopeRef,
1292    ) {
1293        let inner = streams.inner.as_transactions().unwrap();
1294        match active_stream {
1295            StreamScopeRef::Root => {
1296                for stream in inner.get_streams() {
1297                    ui.with_layout(
1298                        Layout::top_down(Align::LEFT).with_cross_justify(true),
1299                        |ui| {
1300                            let response =
1301                                ui.add(egui::SelectableLabel::new(false, stream.name.clone()));
1302
1303                            response.clicked().then(|| {
1304                                msgs.push(Message::AddStreamOrGenerator(
1305                                    TransactionStreamRef::new_stream(
1306                                        stream.id,
1307                                        stream.name.clone(),
1308                                    ),
1309                                ));
1310                            });
1311                        },
1312                    );
1313                }
1314            }
1315            StreamScopeRef::Stream(stream_ref) => {
1316                for gen_id in &inner.get_stream(stream_ref.stream_id).unwrap().generators {
1317                    let gen_name = inner.get_generator(*gen_id).unwrap().name.clone();
1318                    ui.with_layout(
1319                        Layout::top_down(Align::LEFT).with_cross_justify(true),
1320                        |ui| {
1321                            let response = ui.add(egui::SelectableLabel::new(false, &gen_name));
1322
1323                            response.clicked().then(|| {
1324                                msgs.push(Message::AddStreamOrGenerator(
1325                                    TransactionStreamRef::new_gen(
1326                                        stream_ref.stream_id,
1327                                        *gen_id,
1328                                        gen_name,
1329                                    ),
1330                                ));
1331                            });
1332                        },
1333                    );
1334                }
1335            }
1336            StreamScopeRef::Empty(_) => {}
1337        }
1338    }
1339    fn draw_focused_transaction_details(&self, ui: &mut egui::Ui) {
1340        ui.with_layout(
1341            Layout::top_down(Align::LEFT).with_cross_justify(true),
1342            |ui| {
1343                ui.label("Focused Transaction Details");
1344                let column_width = ui.available_width() / 2.;
1345                TableBuilder::new(ui)
1346                    .column(Column::exact(column_width))
1347                    .column(Column::auto())
1348                    .header(20.0, |mut header| {
1349                        header.col(|ui| {
1350                            ui.heading("Properties");
1351                        });
1352                    })
1353                    .body(|mut body| {
1354                        let focused_transaction = self
1355                            .user
1356                            .waves
1357                            .as_ref()
1358                            .unwrap()
1359                            .focused_transaction
1360                            .1
1361                            .as_ref()
1362                            .unwrap();
1363                        let row_height = 15.;
1364                        body.row(row_height, |mut row| {
1365                            row.col(|ui| {
1366                                ui.label("Transaction ID");
1367                            });
1368                            row.col(|ui| {
1369                                ui.label(focused_transaction.get_tx_id().to_string());
1370                            });
1371                        });
1372                        body.row(row_height, |mut row| {
1373                            row.col(|ui| {
1374                                ui.label("Type");
1375                            });
1376                            row.col(|ui| {
1377                                let gen = self
1378                                    .user
1379                                    .waves
1380                                    .as_ref()
1381                                    .unwrap()
1382                                    .inner
1383                                    .as_transactions()
1384                                    .unwrap()
1385                                    .get_generator(focused_transaction.get_gen_id())
1386                                    .unwrap();
1387                                ui.label(gen.name.to_string());
1388                            });
1389                        });
1390                        body.row(row_height, |mut row| {
1391                            row.col(|ui| {
1392                                ui.label("Start Time");
1393                            });
1394                            row.col(|ui| {
1395                                ui.label(focused_transaction.get_start_time().to_string());
1396                            });
1397                        });
1398                        body.row(row_height, |mut row| {
1399                            row.col(|ui| {
1400                                ui.label("End Time");
1401                            });
1402                            row.col(|ui| {
1403                                ui.label(focused_transaction.get_end_time().to_string());
1404                            });
1405                        });
1406                        body.row(row_height + 5., |mut row| {
1407                            row.col(|ui| {
1408                                ui.heading("Attributes");
1409                            });
1410                        });
1411
1412                        body.row(row_height + 3., |mut row| {
1413                            row.col(|ui| {
1414                                ui.label(RichText::new("Name").size(15.));
1415                            });
1416                            row.col(|ui| {
1417                                ui.label(RichText::new("Value").size(15.));
1418                            });
1419                        });
1420
1421                        for attr in &focused_transaction.attributes {
1422                            body.row(row_height, |mut row| {
1423                                row.col(|ui| {
1424                                    ui.label(attr.name.to_string());
1425                                });
1426                                row.col(|ui| {
1427                                    ui.label(attr.value().to_string());
1428                                });
1429                            });
1430                        }
1431
1432                        if !focused_transaction.inc_relations.is_empty() {
1433                            body.row(row_height + 5., |mut row| {
1434                                row.col(|ui| {
1435                                    ui.heading("Incoming Relations");
1436                                });
1437                            });
1438
1439                            body.row(row_height + 3., |mut row| {
1440                                row.col(|ui| {
1441                                    ui.label(RichText::new("Source Tx").size(15.));
1442                                });
1443                                row.col(|ui| {
1444                                    ui.label(RichText::new("Sink Tx").size(15.));
1445                                });
1446                            });
1447
1448                            for rel in &focused_transaction.inc_relations {
1449                                body.row(row_height, |mut row| {
1450                                    row.col(|ui| {
1451                                        ui.label(rel.source_tx_id.to_string());
1452                                    });
1453                                    row.col(|ui| {
1454                                        ui.label(rel.sink_tx_id.to_string());
1455                                    });
1456                                });
1457                            }
1458                        }
1459
1460                        if !focused_transaction.out_relations.is_empty() {
1461                            body.row(row_height + 5., |mut row| {
1462                                row.col(|ui| {
1463                                    ui.heading("Outgoing Relations");
1464                                });
1465                            });
1466
1467                            body.row(row_height + 3., |mut row| {
1468                                row.col(|ui| {
1469                                    ui.label(RichText::new("Source Tx").size(15.));
1470                                });
1471                                row.col(|ui| {
1472                                    ui.label(RichText::new("Sink Tx").size(15.));
1473                                });
1474                            });
1475
1476                            for rel in &focused_transaction.out_relations {
1477                                body.row(row_height, |mut row| {
1478                                    row.col(|ui| {
1479                                        ui.label(rel.source_tx_id.to_string());
1480                                    });
1481                                    row.col(|ui| {
1482                                        ui.label(rel.sink_tx_id.to_string());
1483                                    });
1484                                });
1485                            }
1486                        }
1487                    });
1488            },
1489        );
1490    }
1491
1492    fn get_name_alignment(&self) -> Align {
1493        if self
1494            .user
1495            .align_names_right
1496            .unwrap_or_else(|| self.user.config.layout.align_names_right())
1497        {
1498            Align::RIGHT
1499        } else {
1500            Align::LEFT
1501        }
1502    }
1503
1504    fn draw_drag_source(
1505        &self,
1506        msgs: &mut Vec<Message>,
1507        vidx: VisibleItemIndex,
1508        item_response: &egui::Response,
1509        modifiers: egui::Modifiers,
1510    ) {
1511        if item_response.dragged_by(egui::PointerButton::Primary)
1512            && item_response.drag_delta().length() > self.user.config.theme.drag_threshold
1513        {
1514            if !modifiers.ctrl {
1515                msgs.push(Message::FocusItem(vidx));
1516                msgs.push(Message::ItemSelectionClear);
1517            }
1518            msgs.push(Message::SetItemSelected(vidx, true));
1519            msgs.push(Message::VariableDragStarted(vidx));
1520        }
1521
1522        if item_response.drag_stopped()
1523            && self
1524                .user
1525                .drag_source_idx
1526                .is_some_and(|source_idx| source_idx == vidx)
1527        {
1528            msgs.push(Message::VariableDragFinished);
1529        }
1530    }
1531
1532    #[allow(clippy::too_many_arguments)]
1533    fn draw_variable_label(
1534        &self,
1535        vidx: VisibleItemIndex,
1536        displayed_item: &DisplayedItem,
1537        displayed_id: DisplayedItemRef,
1538        field: FieldRef,
1539        msgs: &mut Vec<Message>,
1540        ui: &mut egui::Ui,
1541        ctx: &egui::Context,
1542    ) -> egui::Response {
1543        let wave_container = self.user.waves.as_ref().unwrap().inner.as_waves().unwrap();
1544
1545        let text_color: Color32;
1546        {
1547            let style = ui.style_mut();
1548            if self.item_is_focused(vidx) {
1549                style.visuals.selection.bg_fill = self.user.config.theme.accent_info.background;
1550                text_color = self.user.config.theme.accent_info.foreground;
1551            } else if self.item_is_selected(displayed_id) {
1552                style.visuals.selection.bg_fill =
1553                    self.user.config.theme.selected_elements_colors.background;
1554                text_color = self.user.config.theme.selected_elements_colors.foreground;
1555            } else {
1556                style.visuals.selection.bg_fill =
1557                    self.user.config.theme.primary_ui_color.background;
1558                text_color = self.user.config.theme.primary_ui_color.foreground;
1559            }
1560        }
1561        let style = ui.style();
1562
1563        // For rendering source code, we want to strategically hide parts of the code,
1564        // and to do so we need to compute the width of a monospace character
1565        let monospace_font = ui.style().text_styles.get(&TextStyle::Monospace).unwrap();
1566        let monospace_width = {
1567            ui.fonts(|fonts| {
1568                fonts
1569                    .layout_no_wrap(
1570                        " ".to_string(),
1571                        monospace_font.clone(),
1572                        Color32::from_rgb(0, 0, 0),
1573                    )
1574                    .size()
1575                    .x
1576            })
1577        };
1578        let available_space = ui.available_width();
1579
1580        let mut layout_job = LayoutJob::default();
1581        match displayed_item {
1582            DisplayedItem::Variable(var) => {
1583                if field.field.is_empty() {
1584                    let name_info = self.get_variable_name_info(wave_container, &var.variable_ref);
1585
1586                    if let Some(true_name) = name_info.and_then(|info| info.true_name) {
1587                        draw_true_name(
1588                            &true_name,
1589                            &mut layout_job,
1590                            monospace_font.clone(),
1591                            text_color,
1592                            monospace_width,
1593                            available_space,
1594                        )
1595                    } else {
1596                        displayed_item.add_to_layout_job(
1597                            &text_color,
1598                            style,
1599                            &mut layout_job,
1600                            Some(&field),
1601                            &self.user.config,
1602                        )
1603                    }
1604                } else {
1605                    // NOTE: Safe unwrap, we've checked that the field exists and is non-empty
1606                    RichText::new(field.field.last().unwrap().clone())
1607                        .color(text_color)
1608                        .line_height(Some(self.user.config.layout.waveforms_line_height))
1609                        .append_to(
1610                            &mut layout_job,
1611                            style,
1612                            FontSelection::Default,
1613                            Align::Center,
1614                        );
1615                };
1616            }
1617            _ => displayed_item.add_to_layout_job(
1618                &text_color,
1619                style,
1620                &mut layout_job,
1621                None,
1622                &self.user.config,
1623            ),
1624        }
1625
1626        let mut variable_label = ui
1627            .selectable_label(
1628                self.item_is_selected(displayed_id) || self.item_is_focused(vidx),
1629                WidgetText::LayoutJob(layout_job),
1630            )
1631            .interact(Sense::drag());
1632
1633        variable_label.context_menu(|ui| {
1634            self.item_context_menu(Some(&field), msgs, ui, vidx);
1635        });
1636
1637        if self.show_tooltip() {
1638            variable_label = variable_label.on_hover_ui(|ui| {
1639                let tooltip = if let Some(waves) = &self.user.waves {
1640                    if field.field.is_empty() {
1641                        let wave_container = waves.inner.as_waves().unwrap();
1642                        let meta = wave_container.variable_meta(&field.root).ok();
1643                        variable_tooltip_text(&meta, &field.root)
1644                    } else {
1645                        "From translator".to_string()
1646                    }
1647                } else {
1648                    "No VCD loaded".to_string()
1649                };
1650                ui.set_max_width(ui.spacing().tooltip_width);
1651                ui.add(egui::Label::new(tooltip));
1652            });
1653        }
1654
1655        if variable_label.clicked() {
1656            if self
1657                .user
1658                .waves
1659                .as_ref()
1660                .is_some_and(|w| w.focused_item.is_some_and(|f| f == vidx))
1661            {
1662                msgs.push(Message::UnfocusItem);
1663            } else {
1664                let modifiers = ctx.input(|i| i.modifiers);
1665                if modifiers.ctrl {
1666                    msgs.push(Message::ToggleItemSelected(Some(vidx)));
1667                } else if modifiers.shift {
1668                    msgs.push(Message::Batch(vec![
1669                        Message::ItemSelectionClear,
1670                        Message::ItemSelectRange(vidx),
1671                    ]));
1672                } else {
1673                    msgs.push(Message::Batch(vec![
1674                        Message::ItemSelectionClear,
1675                        Message::FocusItem(vidx),
1676                    ]));
1677                }
1678            }
1679        }
1680
1681        variable_label
1682    }
1683
1684    #[allow(clippy::too_many_arguments)]
1685    fn draw_variable(
1686        &self,
1687        msgs: &mut Vec<Message>,
1688        vidx: VisibleItemIndex,
1689        displayed_item: &DisplayedItem,
1690        displayed_id: DisplayedItemRef,
1691        field: FieldRef,
1692        drawing_infos: &mut Vec<ItemDrawingInfo>,
1693        info: &VariableInfo,
1694        ui: &mut egui::Ui,
1695        ctx: &egui::Context,
1696        levels_to_force_expand: Option<usize>,
1697        alignment: Align,
1698    ) -> Rect {
1699        let displayed_field_ref = DisplayedFieldRef {
1700            item: displayed_id,
1701            field: field.field.clone(),
1702        };
1703        match info {
1704            VariableInfo::Compound { subfields } => {
1705                let mut header = egui::collapsing_header::CollapsingState::load_with_default_open(
1706                    ui.ctx(),
1707                    egui::Id::new(&field),
1708                    false,
1709                );
1710
1711                if let Some(level) = levels_to_force_expand {
1712                    header.set_open(level > 0);
1713                }
1714
1715                let response = ui
1716                    .with_layout(Layout::top_down(alignment).with_cross_justify(true), |ui| {
1717                        header
1718                            .show_header(ui, |ui| {
1719                                ui.with_layout(
1720                                    Layout::top_down(alignment).with_cross_justify(true),
1721                                    |ui| {
1722                                        self.draw_variable_label(
1723                                            vidx,
1724                                            displayed_item,
1725                                            displayed_id,
1726                                            field.clone(),
1727                                            msgs,
1728                                            ui,
1729                                            ctx,
1730                                        )
1731                                    },
1732                                );
1733                            })
1734                            .body(|ui| {
1735                                for (name, info) in subfields {
1736                                    let mut new_path = field.clone();
1737                                    new_path.field.push(name.clone());
1738                                    ui.with_layout(
1739                                        Layout::top_down(alignment).with_cross_justify(true),
1740                                        |ui| {
1741                                            self.draw_variable(
1742                                                msgs,
1743                                                vidx,
1744                                                displayed_item,
1745                                                displayed_id,
1746                                                new_path,
1747                                                drawing_infos,
1748                                                info,
1749                                                ui,
1750                                                ctx,
1751                                                levels_to_force_expand.map(|l| l.saturating_sub(1)),
1752                                                alignment,
1753                                            );
1754                                        },
1755                                    )
1756                                    .inner
1757                                }
1758                            })
1759                    })
1760                    .inner;
1761                drawing_infos.push(ItemDrawingInfo::Variable(VariableDrawingInfo {
1762                    displayed_field_ref,
1763                    field_ref: field.clone(),
1764                    item_list_idx: vidx,
1765                    top: response.0.rect.top(),
1766                    bottom: response.0.rect.bottom(),
1767                }));
1768                response.0.rect
1769            }
1770            VariableInfo::Bool
1771            | VariableInfo::Bits
1772            | VariableInfo::Clock
1773            | VariableInfo::String
1774            | VariableInfo::Real => {
1775                let label = ui
1776                    .with_layout(Layout::top_down(alignment).with_cross_justify(true), |ui| {
1777                        self.draw_variable_label(
1778                            vidx,
1779                            displayed_item,
1780                            displayed_id,
1781                            field.clone(),
1782                            msgs,
1783                            ui,
1784                            ctx,
1785                        )
1786                    })
1787                    .inner;
1788                self.draw_drag_source(msgs, vidx, &label, ctx.input(|e| e.modifiers));
1789                drawing_infos.push(ItemDrawingInfo::Variable(VariableDrawingInfo {
1790                    displayed_field_ref,
1791                    field_ref: field.clone(),
1792                    item_list_idx: vidx,
1793                    top: label.rect.top(),
1794                    bottom: label.rect.bottom(),
1795                }));
1796                label.rect
1797            }
1798        }
1799    }
1800
1801    fn draw_drag_target(
1802        &self,
1803        msgs: &mut Vec<Message>,
1804        vidx: VisibleItemIndex,
1805        expanded_rect: Rect,
1806        available_rect: Rect,
1807        ui: &mut egui::Ui,
1808        last: bool,
1809    ) {
1810        if !self.user.drag_started || self.user.drag_source_idx.is_none() {
1811            return;
1812        }
1813
1814        let waves = self
1815            .user
1816            .waves
1817            .as_ref()
1818            .expect("waves not available, but expected");
1819
1820        // expanded_rect is just for the label, leaving us with gaps between lines
1821        // expand to counter that
1822        let rect_with_margin = expanded_rect.expand2(ui.spacing().item_spacing / 2f32);
1823
1824        // collision check rect need to be
1825        // - limited to half the height of the item text
1826        // - extended to cover the empty space to the left
1827        // - for the last element, expanded till the bottom
1828        let before_rect = rect_with_margin
1829            .with_max_y(rect_with_margin.left_center().y)
1830            .with_min_x(available_rect.left())
1831            .round_to_pixels(ui.painter().pixels_per_point());
1832        let after_rect = if last {
1833            rect_with_margin.with_max_y(ui.max_rect().max.y)
1834        } else {
1835            rect_with_margin
1836        }
1837        .with_min_y(rect_with_margin.left_center().y)
1838        .with_min_x(available_rect.left())
1839        .round_to_pixels(ui.painter().pixels_per_point());
1840
1841        let (insert_vidx, line_y) = if ui.rect_contains_pointer(before_rect) {
1842            (vidx, rect_with_margin.top())
1843        } else if ui.rect_contains_pointer(after_rect) {
1844            (VisibleItemIndex(vidx.0 + 1), rect_with_margin.bottom())
1845        } else {
1846            return;
1847        };
1848
1849        let level_range = waves.items_tree.valid_levels_visible(insert_vidx, |node| {
1850            matches!(
1851                waves.displayed_items.get(&node.item_ref),
1852                Some(DisplayedItem::Group(..))
1853            )
1854        });
1855
1856        let left_x = |level: u8| -> f32 { rect_with_margin.left() + level as f32 * 10.0 };
1857        let Some(insert_level) = level_range.find_or_last(|&level| {
1858            let mut rect = expanded_rect.with_min_x(left_x(level));
1859            rect.set_width(10.0);
1860            if level == 0 {
1861                rect.set_left(available_rect.left());
1862            }
1863            ui.rect_contains_pointer(rect)
1864        }) else {
1865            return;
1866        };
1867
1868        ui.painter().line_segment(
1869            [
1870                Pos2::new(left_x(insert_level), line_y),
1871                Pos2::new(rect_with_margin.right(), line_y),
1872            ],
1873            Stroke::new(
1874                self.user.config.theme.linewidth,
1875                self.user.config.theme.drag_hint_color,
1876            ),
1877        );
1878        msgs.push(Message::VariableDragTargetChanged(
1879            crate::displayed_item_tree::TargetPosition {
1880                before: ItemIndex(
1881                    waves
1882                        .items_tree
1883                        .to_displayed(insert_vidx)
1884                        .map(|index| index.0)
1885                        .unwrap_or_else(|| waves.items_tree.len()),
1886                ),
1887                level: insert_level,
1888            },
1889        ));
1890    }
1891
1892    fn draw_plain_item(
1893        &self,
1894        msgs: &mut Vec<Message>,
1895        vidx: VisibleItemIndex,
1896        displayed_id: DisplayedItemRef,
1897        displayed_item: &DisplayedItem,
1898        drawing_infos: &mut Vec<ItemDrawingInfo>,
1899        ui: &mut egui::Ui,
1900    ) -> Rect {
1901        let mut draw_label = |ui: &mut egui::Ui| {
1902            let style = ui.style_mut();
1903            let mut layout_job = LayoutJob::default();
1904            let text_color: Color32;
1905
1906            if self.item_is_focused(vidx) {
1907                style.visuals.selection.bg_fill = self.user.config.theme.accent_info.background;
1908                text_color = self.user.config.theme.accent_info.foreground;
1909            } else if self.item_is_selected(displayed_id) {
1910                style.visuals.selection.bg_fill =
1911                    self.user.config.theme.selected_elements_colors.background;
1912                text_color = self.user.config.theme.selected_elements_colors.foreground;
1913            } else {
1914                style.visuals.selection.bg_fill =
1915                    self.user.config.theme.primary_ui_color.background;
1916                text_color = *self.get_item_text_color(displayed_item);
1917            }
1918
1919            displayed_item.add_to_layout_job(
1920                &text_color,
1921                style,
1922                &mut layout_job,
1923                None,
1924                &self.user.config,
1925            );
1926
1927            let item_label = ui
1928                .selectable_label(
1929                    self.item_is_selected(displayed_id) || self.item_is_focused(vidx),
1930                    WidgetText::LayoutJob(layout_job),
1931                )
1932                .interact(Sense::drag());
1933            item_label.context_menu(|ui| {
1934                self.item_context_menu(None, msgs, ui, vidx);
1935            });
1936            if item_label.clicked() {
1937                msgs.push(Message::FocusItem(vidx));
1938            }
1939            item_label
1940        };
1941
1942        let label = draw_label(ui);
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.allocate_new_ui(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    /// Draw the default timeline at the top of the canvas
2279    pub fn draw_default_timeline(
2280        &self,
2281        waves: &WaveData,
2282        ctx: &DrawingContext,
2283        viewport_idx: usize,
2284        frame_width: f32,
2285        cfg: &DrawConfig,
2286    ) {
2287        let ticks = waves.get_ticks(
2288            &waves.viewports[viewport_idx],
2289            &waves.inner.metadata().timescale,
2290            frame_width,
2291            cfg.text_size,
2292            &self.user.wanted_timeunit,
2293            &self.get_time_format(),
2294            &self.user.config,
2295        );
2296
2297        waves.draw_ticks(
2298            Some(&self.user.config.theme.foreground),
2299            &ticks,
2300            ctx,
2301            0.0,
2302            egui::Align2::CENTER_TOP,
2303            &self.user.config,
2304        );
2305    }
2306}
2307
2308fn variable_tooltip_text(meta: &Option<VariableMeta>, variable: &VariableRef) -> String {
2309    if let Some(meta) = meta {
2310        format!(
2311            "{}\nNum bits: {}\nType: {}\nDirection: {}",
2312            variable.full_path_string(),
2313            meta.num_bits
2314                .map_or_else(|| "unknown".to_string(), |bits| bits.to_string()),
2315            meta.variable_type_name
2316                .clone()
2317                .or_else(|| meta.variable_type.map(|t| t.to_string()))
2318                .unwrap_or_else(|| "unknown".to_string()),
2319            meta.direction
2320                .map_or_else(|| "unknown".to_string(), |direction| format!("{direction}"))
2321        )
2322    } else {
2323        variable.full_path_string()
2324    }
2325}
2326
2327fn scope_tooltip_text(wave: &WaveData, scope: &ScopeRef) -> String {
2328    let other = wave.inner.as_waves().unwrap().get_scope_tooltip_data(scope);
2329    if other.is_empty() {
2330        format!("{scope}")
2331    } else {
2332        format!("{scope}\n{other}")
2333    }
2334}
2335
2336pub fn draw_true_name(
2337    true_name: &TrueName,
2338    layout_job: &mut LayoutJob,
2339    font: FontId,
2340    foreground: Color32,
2341    char_width: f32,
2342    allowed_space: f32,
2343) {
2344    let char_budget = (allowed_space / char_width) as usize;
2345
2346    match true_name {
2347        TrueName::SourceCode {
2348            line_number,
2349            before,
2350            this,
2351            after,
2352        } => {
2353            let before_chars = before.chars().collect::<Vec<_>>();
2354            let this_chars = this.chars().collect::<Vec<_>>();
2355            let after_chars = after.chars().collect::<Vec<_>>();
2356            let line_num = format!("{line_number} ");
2357            let important_chars = line_num.len() + this_chars.len();
2358            let required_extra_chars = before_chars.len() + after_chars.len();
2359
2360            // If everything fits, things are very easy
2361            let (line_num, before, this, after) =
2362                if char_budget >= important_chars + required_extra_chars {
2363                    (line_num, before.clone(), this.clone(), after.clone())
2364                } else if char_budget > important_chars {
2365                    // How many extra chars we have available
2366                    let extra_chars = char_budget - important_chars;
2367
2368                    let max_from_before = (extra_chars as f32 / 2.).ceil() as usize;
2369                    let max_from_after = (extra_chars as f32 / 2.).floor() as usize;
2370
2371                    let (chars_from_before, chars_from_after) =
2372                        if max_from_before > before_chars.len() {
2373                            (before_chars.len(), extra_chars - before_chars.len())
2374                        } else if max_from_after > after_chars.len() {
2375                            (extra_chars - after_chars.len(), before_chars.len())
2376                        } else {
2377                            (max_from_before, max_from_after)
2378                        };
2379
2380                    let mut before = before_chars
2381                        .into_iter()
2382                        .rev()
2383                        .take(chars_from_before)
2384                        .rev()
2385                        .collect::<Vec<_>>();
2386                    if !before.is_empty() {
2387                        before[0] = '…'
2388                    }
2389                    let mut after = after_chars
2390                        .into_iter()
2391                        .take(chars_from_after)
2392                        .collect::<Vec<_>>();
2393                    if !after.is_empty() {
2394                        let last_elem = after.len() - 1;
2395                        after[last_elem] = '…'
2396                    }
2397
2398                    (
2399                        line_num,
2400                        before.into_iter().collect(),
2401                        this.clone(),
2402                        after.into_iter().collect(),
2403                    )
2404                } else {
2405                    // If we can't even fit the whole important part,
2406                    // we'll prefer the line number
2407                    let from_line_num = line_num.len();
2408                    let from_this = char_budget.saturating_sub(from_line_num);
2409                    let this = this
2410                        .chars()
2411                        .take(from_this)
2412                        .enumerate()
2413                        .map(|(i, c)| if i == from_this - 1 { '…' } else { c })
2414                        .collect();
2415                    (line_num, "".to_string(), this, "".to_string())
2416                };
2417
2418            layout_job.append(
2419                &line_num,
2420                0.0,
2421                TextFormat {
2422                    font_id: font.clone(),
2423                    color: foreground.gamma_multiply(0.75),
2424                    ..Default::default()
2425                },
2426            );
2427            layout_job.append(
2428                &before,
2429                0.0,
2430                TextFormat {
2431                    font_id: font.clone(),
2432                    color: foreground.gamma_multiply(0.5),
2433                    ..Default::default()
2434                },
2435            );
2436            layout_job.append(
2437                &this,
2438                0.0,
2439                TextFormat {
2440                    font_id: font.clone(),
2441                    color: foreground,
2442                    ..Default::default()
2443                },
2444            );
2445            layout_job.append(
2446                after.trim_end(),
2447                0.0,
2448                TextFormat {
2449                    font_id: font.clone(),
2450                    color: foreground.gamma_multiply(0.5),
2451                    ..Default::default()
2452                },
2453            )
2454        }
2455    }
2456}