Skip to main content

libsurfer/
view.rs

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