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 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 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 !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(¶meters),
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 ¶meters,
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 direction.get_icon().unwrap_or_else(|| {
894 if meta.as_ref().is_some_and(|meta| {
895 meta.variable_type == Some(VariableType::VCDParameter)
896 }) {
897 icons::MAP_PIN_2_LINE
899 } else {
900 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 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 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 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 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 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 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 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 let rect_with_margin = expanded_rect.expand2(ui.spacing().item_spacing / 2f32);
1823
1824 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 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 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 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 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 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 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 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 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 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}