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