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