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