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