1use crate::SystemState;
3use crate::data_container::{DataContainer, VariableType as VarType};
4use crate::displayed_item_tree::VisibleItemIndex;
5use crate::message::Message;
6use crate::tooltips::{scope_tooltip_text, variable_tooltip_text};
7use crate::transaction_container::StreamScopeRef;
8use crate::transactions::{draw_transaction_root, draw_transaction_variable_list};
9use crate::variable_direction::get_direction_string;
10use crate::view::draw_true_name;
11use crate::wave_container::{
12 ScopeRef, ScopeRefExt, VariableMeta, VariableRef, VariableRefExt, WaveContainer,
13};
14use crate::wave_data::{ScopeType, WaveData};
15use derive_more::{Display, FromStr};
16use ecolor::Color32;
17use egui::text::LayoutJob;
18use egui::{CentralPanel, Frame, Layout, Panel, ScrollArea, TextStyle, Ui};
19use egui_remixicon::icons;
20use emath::Align;
21use enum_iterator::Sequence;
22use epaint::{
23 Margin,
24 text::{TextFormat, TextWrapMode},
25};
26use eyre::WrapErr as _;
27use itertools::Itertools;
28use num::BigUint;
29use serde::{Deserialize, Serialize};
30use std::ops::Range;
31use std::rc::Rc;
32use surfer_translation_types::translator::VariableNameInfo;
33use tracing::warn;
34#[derive(Clone, Copy, Debug, Deserialize, Display, FromStr, PartialEq, Eq, Serialize, Sequence)]
35pub enum HierarchyStyle {
36 Separate,
37 Tree,
38 Variables,
39}
40
41#[derive(Clone, Copy, Debug, Deserialize, Display, FromStr, PartialEq, Eq, Serialize, Sequence)]
42pub enum ParameterDisplayLocation {
43 Variables,
44 Scopes,
45 Tooltips,
46 None,
47}
48
49#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
50pub enum ScopeExpandType {
51 ExpandSpecific(ScopeRef),
52 ExpandAll,
53 CollapseAll,
54}
55
56use crate::variable_filter::VariableNameFilterType;
57use crate::wave_source::WaveSource;
58
59#[derive(Clone)]
60pub(crate) struct VariableListRow {
61 pub(crate) variable: VariableRef,
62 pub(crate) meta: Option<VariableMeta>,
63 pub(crate) name_info: Option<VariableNameInfo>,
64}
65
66#[derive(PartialEq)]
68pub(crate) struct AllVariableCacheKey {
69 cache_generation: u64,
70 translator_generation: u64,
71 wave_source: WaveSource,
72 filter_str: String,
73 filter_type: VariableNameFilterType,
74 case_insensitive: bool,
75 include_inputs: bool,
76 include_outputs: bool,
77 include_inouts: bool,
78 include_others: bool,
79 group_by_direction: bool,
80}
81
82impl SystemState {
83 pub fn separate(&mut self, ui: &mut Ui, msgs: &mut Vec<Message>) {
85 ui.visuals_mut().override_text_color =
86 Some(self.user.config.theme.primary_ui_color.foreground);
87
88 let total_space = ui.available_height();
89 Panel::top("scopes")
90 .resizable(true)
91 .default_size(total_space / 2.0)
92 .max_size(total_space - 64.0)
93 .frame(Frame::new().inner_margin(Margin::same(5)))
94 .show_inside(ui, |ui| {
95 ui.horizontal(|ui| {
96 ui.heading("Scopes")
97 .context_menu(|ui| self.hierarchy_menu(msgs, ui));
98 if self.user.waves.is_some() {
99 let default_padding = ui.spacing().button_padding;
100 ui.spacing_mut().button_padding = egui::vec2(0.0, default_padding.y);
101 ui.button(icons::MENU_UNFOLD_FILL)
102 .on_hover_text("Expand all scopes")
103 .clicked()
104 .then(|| msgs.push(Message::ExpandScope(ScopeExpandType::ExpandAll)));
105 ui.button(icons::MENU_FOLD_FILL)
106 .on_hover_text("Collapse all scopes")
107 .clicked()
108 .then(|| msgs.push(Message::ExpandScope(ScopeExpandType::CollapseAll)));
109 ui.spacing_mut().button_padding = default_padding;
110 }
111 });
112 ui.add_space(3.0);
113
114 ScrollArea::both()
115 .id_salt("scopes")
116 .auto_shrink([false; 2])
117 .show(ui, |ui| {
118 ui.style_mut().wrap_mode = Some(TextWrapMode::Extend);
119 if let Some(waves) = &self.user.waves {
120 self.draw_all_scopes(msgs, waves, false, ui);
121 }
122 });
123 });
124 CentralPanel::default()
125 .frame(Frame::new().inner_margin(Margin::same(5)))
126 .show_inside(ui, |ui| {
127 ui.with_layout(Layout::left_to_right(Align::TOP), |ui| {
128 ui.heading("Variables")
129 .context_menu(|ui| self.hierarchy_menu(msgs, ui));
130 ui.add_space(3.0);
131 self.draw_variable_filter_edit(ui, msgs, false);
132 });
133 ui.add_space(3.0);
134
135 self.draw_variables(msgs, ui);
136 });
137 *self.scope_ref_to_expand.borrow_mut() = None;
138 }
139
140 fn draw_variable_list_header(&self, ui: &mut Ui) {
141 let show_icons = self.show_hierarchy_icons();
142 let show_direction = self.show_variable_direction();
143
144 if !show_icons && !show_direction {
146 return;
147 }
148
149 ui.with_layout(
150 Layout::top_down(Align::LEFT).with_cross_justify(true),
151 |ui| {
152 let monospace_font = ui
153 .style()
154 .text_styles
155 .get(&TextStyle::Monospace)
156 .cloned()
157 .unwrap();
158
159 let text_format = TextFormat {
160 font_id: monospace_font,
161 color: self.user.config.theme.foreground,
162 ..Default::default()
163 };
164
165 let mut label = LayoutJob::default();
166 if show_icons {
168 label.append("T ", 0.0, text_format.clone());
169 }
170 if show_direction {
172 label.append("D ", 0.0, text_format.clone());
173 }
174 label.append("Name", 0.0, text_format);
176
177 ui.add(egui::Button::selectable(false, label));
178 },
179 );
180 ui.separator();
181 }
182
183 fn draw_variables(&mut self, msgs: &mut Vec<Message>, ui: &mut Ui) {
184 if let Some(waves) = &self.user.waves {
185 let empty_scope = if waves.inner.is_waves() {
186 ScopeType::WaveScope(ScopeRef::empty())
187 } else {
188 ScopeType::StreamScope(StreamScopeRef::Empty(String::default()))
189 };
190 let active_scope = waves.active_scope.as_ref().unwrap_or(&empty_scope);
191 match active_scope {
192 ScopeType::WaveScope(scope) => {
193 let Some(wave_container) = waves.inner.as_waves() else {
194 return;
195 };
196 let variables = self.filtered_variables_unsorted(
197 &wave_container.variables_in_scope(scope),
198 false,
199 );
200 let variable_rows = self.build_variable_rows(wave_container, &variables);
201 self.draw_variable_list_header(ui);
203 if self.parameter_display_location() == ParameterDisplayLocation::Variables {
205 let parameters = wave_container.parameters_in_scope(scope);
206 if !parameters.is_empty() {
207 ScrollArea::both()
208 .auto_shrink([false; 2])
209 .id_salt("variables")
210 .show(ui, |ui| {
211 ui.style_mut().wrap_mode = Some(TextWrapMode::Extend);
212 self.draw_parameters(msgs, wave_container, ¶meters, ui);
213 self.draw_variable_rows(
214 msgs,
215 wave_container,
216 ui,
217 &variable_rows,
218 None,
219 false,
220 );
221 });
222 return; }
224 }
225 let row_height = ui
227 .text_style_height(&TextStyle::Monospace)
228 .max(ui.text_style_height(&TextStyle::Body));
229 ScrollArea::both()
230 .auto_shrink([false; 2])
231 .id_salt("variables")
232 .show_rows(ui, row_height, variable_rows.len(), |ui, row_range| {
233 ui.style_mut().wrap_mode = Some(TextWrapMode::Extend);
234
235 self.draw_variable_rows(
236 msgs,
237 wave_container,
238 ui,
239 &variable_rows,
240 Some(&row_range),
241 false,
242 );
243 });
244 }
245 ScopeType::StreamScope(s) => {
246 ScrollArea::both()
247 .auto_shrink([false; 2])
248 .id_salt("variables")
249 .show(ui, |ui| {
250 ui.style_mut().wrap_mode = Some(TextWrapMode::Extend);
251
252 draw_transaction_variable_list(msgs, waves, ui, s);
253 });
254 }
255 }
256 }
257 }
258
259 fn draw_parameters(
260 &self,
261 msgs: &mut Vec<Message>,
262 wave_container: &WaveContainer,
263 parameters: &[VariableRef],
264 ui: &mut Ui,
265 ) {
266 egui::collapsing_header::CollapsingState::load_with_default_open(
267 ui.ctx(),
268 egui::Id::new(parameters),
269 self.expand_parameter_section,
270 )
271 .show_header(ui, |ui| {
272 ui.with_layout(
273 Layout::top_down(Align::LEFT).with_cross_justify(true),
274 |ui| {
275 ui.label("Parameters");
276 },
277 );
278 })
279 .body(|ui| {
280 self.filter_and_draw_variable_list(msgs, wave_container, ui, parameters, None);
281 });
282 }
283
284 pub fn tree(&mut self, ui: &mut Ui, msgs: &mut Vec<Message>) {
286 ui.visuals_mut().override_text_color =
287 Some(self.user.config.theme.primary_ui_color.foreground);
288
289 ui.with_layout(
290 Layout::top_down(Align::LEFT).with_cross_justify(true),
291 |ui| {
292 Frame::new().inner_margin(Margin::same(5)).show(ui, |ui| {
293 ui.with_layout(Layout::left_to_right(Align::TOP), |ui| {
294 ui.heading("Hierarchy")
295 .context_menu(|ui| self.hierarchy_menu(msgs, ui));
296 ui.add_space(3.0);
297 self.draw_variable_filter_edit(ui, msgs, false);
298 });
299 ui.add_space(3.0);
300
301 ScrollArea::both().id_salt("hierarchy").show(ui, |ui| {
302 ui.style_mut().wrap_mode = Some(TextWrapMode::Extend);
303 if let Some(waves) = &self.user.waves {
304 ui.style_mut().wrap_mode = Some(TextWrapMode::Extend);
305 self.draw_all_scopes(msgs, waves, true, ui);
306 }
307 });
308 });
309 },
310 );
311 }
312
313 pub fn variable_list(&mut self, ui: &mut Ui, msgs: &mut Vec<Message>) {
315 ui.visuals_mut().override_text_color =
316 Some(self.user.config.theme.primary_ui_color.foreground);
317
318 ui.with_layout(
319 Layout::top_down(Align::LEFT).with_cross_justify(true),
320 |ui| {
321 Frame::new().inner_margin(Margin::same(5)).show(ui, |ui| {
322 ui.with_layout(Layout::left_to_right(Align::TOP), |ui| {
323 ui.heading("Variables")
324 .context_menu(|ui| self.hierarchy_menu(msgs, ui));
325 ui.add_space(3.0);
326 self.draw_variable_filter_edit(ui, msgs, true);
327 });
328 ui.add_space(3.0);
329 self.draw_all_variables(msgs, ui);
330 });
331 },
332 );
333 }
334
335 fn make_all_variable_cache_key(
336 &self,
337 cache_generation: u64,
338 wave_source: &WaveSource,
339 ) -> AllVariableCacheKey {
340 let f = &self.user.variable_filter;
341 AllVariableCacheKey {
342 cache_generation,
343 translator_generation: self.translator_generation,
344 wave_source: wave_source.clone(),
345 filter_str: f.name_filter_str.clone(),
346 filter_type: f.name_filter_type.clone(),
347 case_insensitive: f.name_filter_case_insensitive,
348 include_inputs: f.include_inputs,
349 include_outputs: f.include_outputs,
350 include_inouts: f.include_inouts,
351 include_others: f.include_others,
352 group_by_direction: f.group_by_direction,
353 }
354 }
355
356 fn draw_all_variables(&mut self, msgs: &mut Vec<Message>, ui: &mut Ui) {
357 if let Some(waves) = &self.user.waves
360 && let DataContainer::Waves(wave_container) = &waves.inner
361 {
362 let key = self.make_all_variable_cache_key(waves.cache_generation, &waves.source);
363 let is_stale = self
364 .all_variable_rows_cache
365 .as_ref()
366 .is_none_or(|(k, _)| k != &key);
367 if is_stale {
368 let variables = self.filtered_variables_unsorted(&wave_container.variables(), true);
369 let rows = self.build_variable_rows(wave_container, &variables);
370 self.all_variable_rows_cache = Some((key, Rc::new(rows)));
371 }
372 }
373
374 if let Some(waves) = &self.user.waves {
376 match &waves.inner {
377 DataContainer::Waves(wave_container) => {
378 let variable_rows = self
380 .all_variable_rows_cache
381 .as_ref()
382 .map_or_else(|| Rc::new(Vec::new()), |(_, rows)| Rc::clone(rows));
383 let row_height = ui
384 .text_style_height(&TextStyle::Monospace)
385 .max(ui.text_style_height(&TextStyle::Body));
386 self.draw_variable_list_header(ui);
388 ScrollArea::both()
389 .auto_shrink([false; 2])
390 .id_salt("variables")
391 .show_rows(ui, row_height, variable_rows.len(), |ui, row_range| {
392 ui.style_mut().wrap_mode = Some(TextWrapMode::Extend);
393 self.draw_variable_rows(
394 msgs,
395 wave_container,
396 ui,
397 &variable_rows,
398 Some(&row_range),
399 true,
400 );
401 });
402 }
403 DataContainer::Transactions(_) => {
404 ui.with_layout(
406 Layout::top_down(Align::LEFT).with_cross_justify(true),
407 |ui| {
408 ui.label("Streams are not yet supported.");
409 ui.label("Select another view.");
410 },
411 );
412 }
413 DataContainer::Empty => {}
414 }
415 }
416 }
417
418 fn draw_all_scopes(
419 &self,
420 msgs: &mut Vec<Message>,
421 wave: &WaveData,
422 draw_variables: bool,
423 ui: &mut Ui,
424 ) {
425 for scope in wave.inner.root_scopes() {
426 match scope {
427 ScopeType::WaveScope(scope) => {
428 self.draw_selectable_child_or_orphan_scope(
429 msgs,
430 wave,
431 &scope,
432 draw_variables,
433 ui,
434 );
435 }
436 ScopeType::StreamScope(_) => {
437 draw_transaction_root(msgs, wave, ui);
438 }
439 }
440 }
441 if draw_variables && let Some(wave_container) = wave.inner.as_waves() {
442 let scope = ScopeRef::empty();
443 let variables = wave_container.variables_in_scope(&scope);
444 self.filter_and_draw_variable_list(msgs, wave_container, ui, &variables, None);
445 }
446 }
447
448 fn add_scope_selectable_label(
449 &self,
450 msgs: &mut Vec<Message>,
451 wave: &WaveData,
452 scope: &ScopeRef,
453 ui: &mut Ui,
454 scroll_to_label: bool,
455 ) {
456 let name = scope.name();
457 let is_selected = wave.active_scope == Some(ScopeType::WaveScope(scope.clone()));
458 let mut response = if self.show_hierarchy_icons() {
459 let scope_type = wave
460 .inner
461 .as_waves()
462 .and_then(|wc| wc.get_scope_type(scope));
463 let (icon, icon_color) = self.user.config.theme.scope_icons.get_icon(scope_type);
464
465 let body_font = ui
466 .style()
467 .text_styles
468 .get(&TextStyle::Body)
469 .cloned()
470 .unwrap_or_default();
471 let icon_format = TextFormat {
472 font_id: body_font.clone(),
473 color: icon_color,
474 ..Default::default()
475 };
476 let name_format = TextFormat {
477 font_id: body_font,
478 color: self.user.config.theme.foreground,
479 ..Default::default()
480 };
481 let mut label = LayoutJob::default();
482 label.append(icon, 0.0, icon_format);
483 label.append(" ", 0.0, name_format.clone());
484 label.append(&name, 0.0, name_format);
485
486 ui.add(egui::Button::selectable(is_selected, label))
487 } else {
488 ui.add(egui::Button::selectable(is_selected, name))
489 };
490 let _ = response.interact(egui::Sense::click_and_drag());
491 response.drag_started().then(|| {
492 msgs.push(Message::VariableDragStarted(VisibleItemIndex(
493 wave.display_item_ref_counter,
494 )));
495 });
496
497 if scroll_to_label {
498 response.scroll_to_me(Some(Align::Center));
499 }
500
501 response.drag_stopped().then(|| {
502 if ui.input(|i| i.pointer.hover_pos().unwrap_or_default().x)
503 > self.user.sidepanel_width.unwrap_or_default()
504 {
505 let scope_t = ScopeType::WaveScope(scope.clone());
506 let variables = wave
507 .inner
508 .variables_in_scope(&scope_t)
509 .iter()
510 .filter_map(|var| match var {
511 VarType::Variable(var) => Some(var.clone()),
512 VarType::Generator(_) => None,
513 })
514 .collect_vec();
515
516 msgs.push(Message::AddDraggedVariables(
517 self.filtered_variables(variables.as_slice(), false),
518 ));
519 }
520 });
521 if self.show_scope_tooltip() {
522 response = response.on_hover_ui(|ui| {
523 ui.set_max_width(ui.spacing().tooltip_width);
524 ui.add(egui::Label::new(scope_tooltip_text(
525 wave,
526 scope,
527 self.parameter_display_location() == ParameterDisplayLocation::Tooltips,
528 )));
529 });
530 }
531 response.context_menu(|ui| {
532 if ui.button("Add scope").clicked() {
533 msgs.push(Message::AddScope(scope.clone(), false));
534 }
535 if ui.button("Add scope recursively").clicked() {
536 msgs.push(Message::AddScope(scope.clone(), true));
537 }
538 if ui.button("Add scope as group").clicked() {
539 msgs.push(Message::AddScopeAsGroup(scope.clone(), false));
540 }
541 if ui.button("Add scope as group recursively").clicked() {
542 msgs.push(Message::AddScopeAsGroup(scope.clone(), true));
543 }
544 if wave
545 .inner
546 .as_waves()
547 .is_some_and(|wc| wc.scope_is_array(scope))
548 && ui.button("Show frame buffer").clicked()
549 {
550 msgs.push(Message::SetFrameBufferArray(scope.clone()));
551 }
552 });
553 response.clicked().then(|| {
554 msgs.push(Message::SetActiveScope(if is_selected {
555 None
556 } else {
557 Some(ScopeType::WaveScope(scope.clone()))
558 }));
559 });
560 }
561
562 fn draw_selectable_child_or_orphan_scope(
563 &self,
564 msgs: &mut Vec<Message>,
565 wave: &WaveData,
566 scope: &ScopeRef,
567 draw_variables: bool,
568 ui: &mut Ui,
569 ) {
570 let Some(wave_container) = wave.inner.as_waves() else {
572 return;
573 };
574
575 let Some(child_scopes) = wave_container
576 .child_scopes(scope)
577 .context("Failed to get child scopes")
578 .map_err(|e| warn!("{e:#?}"))
579 .ok()
580 else {
581 return;
582 };
583
584 let no_variables_in_scope = wave_container.no_variables_in_scope(scope);
585 if child_scopes.is_empty() && no_variables_in_scope && !self.show_empty_scopes() {
586 return;
587 }
588
589 if child_scopes.is_empty() && (!draw_variables || no_variables_in_scope) {
590 ui.horizontal(|ui| {
593 ui.add_space(ui.spacing().icon_width + ui.spacing().icon_spacing);
594 self.add_scope_selectable_label(msgs, wave, scope, ui, false);
595 });
596 } else {
597 let should_open_header = self.should_open_header_and_scroll_to(scope);
598 let mut collapsing_header =
599 egui::collapsing_header::CollapsingState::load_with_default_open(
600 ui.ctx(),
601 egui::Id::new(scope),
602 false,
603 );
604 if let Some((header_state, _)) = should_open_header {
605 collapsing_header.set_open(header_state);
606 }
607 collapsing_header
608 .show_header(ui, |ui| {
609 ui.with_layout(
610 Layout::top_down(Align::LEFT).with_cross_justify(true),
611 |ui| {
612 self.add_scope_selectable_label(
613 msgs,
614 wave,
615 scope,
616 ui,
617 should_open_header.is_some_and(|(_, scroll)| scroll),
618 );
619 },
620 );
621 })
622 .body(|ui| {
623 if (draw_variables
624 && !(matches!(
625 self.parameter_display_location(),
626 ParameterDisplayLocation::Tooltips | ParameterDisplayLocation::None
627 )))
628 || self.parameter_display_location() == ParameterDisplayLocation::Scopes
629 {
630 let parameters = wave_container.parameters_in_scope(scope);
631 if !parameters.is_empty() {
632 self.draw_parameters(msgs, wave_container, ¶meters, ui);
633 }
634 }
635 self.draw_root_scope_view(msgs, wave, scope, draw_variables, ui);
636 if draw_variables {
637 let variables = wave_container.variables_in_scope(scope);
638 self.filter_and_draw_variable_list(
639 msgs,
640 wave_container,
641 ui,
642 &variables,
643 None,
644 );
645 }
646 });
647 }
648 }
649
650 fn draw_root_scope_view(
651 &self,
652 msgs: &mut Vec<Message>,
653 wave: &WaveData,
654 root_scope: &ScopeRef,
655 draw_variables: bool,
656 ui: &mut Ui,
657 ) {
658 let Some(wave_container) = wave.inner.as_waves() else {
660 return;
661 };
662
663 wave_container
664 .child_scopes(root_scope)
665 .context("Failed to get child scopes")
666 .map_err(|e| warn!("{e:#?}"))
667 .ok()
668 .into_iter()
669 .flatten()
670 .sorted_by(|a, b| numeric_sort::cmp(&a.name(), &b.name()))
671 .for_each(|child_scope| {
672 self.draw_selectable_child_or_orphan_scope(
673 msgs,
674 wave,
675 &child_scope,
676 draw_variables,
677 ui,
678 );
679 });
680 }
681
682 fn filter_and_draw_variable_list(
683 &self,
684 msgs: &mut Vec<Message>,
685 wave_container: &WaveContainer,
686 ui: &mut Ui,
687 variables: &[VariableRef],
688 row_range: Option<&Range<usize>>,
689 ) {
690 let filtered_variables = self.filtered_variables_unsorted(variables, false);
691 self.draw_variable_list(
692 msgs,
693 wave_container,
694 ui,
695 &filtered_variables,
696 row_range,
697 false,
698 );
699 }
700
701 fn draw_variable_list(
702 &self,
703 msgs: &mut Vec<Message>,
704 wave_container: &WaveContainer,
705 ui: &mut Ui,
706 variables: &[VariableRef],
707 row_range: Option<&Range<usize>>,
708 display_full_path: bool,
709 ) {
710 let variable_rows = self.build_variable_rows(wave_container, variables);
711 self.draw_variable_rows(
712 msgs,
713 wave_container,
714 ui,
715 &variable_rows,
716 row_range,
717 display_full_path,
718 );
719 }
720
721 fn build_variable_rows(
722 &self,
723 wave_container: &WaveContainer,
724 variables: &[VariableRef],
725 ) -> Vec<VariableListRow> {
726 let mut rows = variables
727 .iter()
728 .map(|var| {
729 let meta = wave_container.variable_meta(var).ok();
730 let name_info = self.get_variable_name_info(var, meta.as_ref());
731 VariableListRow {
732 variable: var.clone(),
733 meta,
734 name_info,
735 }
736 })
737 .collect::<Vec<_>>();
738
739 rows.sort_by(|a, b| {
740 let a_priority = a
741 .name_info
742 .as_ref()
743 .and_then(|info| info.priority)
744 .unwrap_or_default();
745 let b_priority = b
746 .name_info
747 .as_ref()
748 .and_then(|info| info.priority)
749 .unwrap_or_default();
750 b_priority
751 .cmp(&a_priority)
752 .then_with(|| self.variable_cmp(&a.variable, &b.variable, Some(wave_container)))
753 });
754 rows
755 }
756
757 fn draw_variable_rows(
758 &self,
759 msgs: &mut Vec<Message>,
760 wave_container: &WaveContainer,
761 ui: &mut Ui,
762 variable_rows: &[VariableListRow],
763 row_range: Option<&Range<usize>>,
764 display_full_path: bool,
765 ) {
766 let variable_rows = if let Some(range) = row_range {
767 let start = range.start.min(variable_rows.len());
768 let end = range.end.min(variable_rows.len());
769 &variable_rows[start..end]
770 } else {
771 variable_rows
772 };
773
774 let monospace_font = ui
778 .style()
779 .text_styles
780 .get(&TextStyle::Monospace)
781 .cloned()
782 .unwrap();
783 let body_font = ui
784 .style()
785 .text_styles
786 .get(&TextStyle::Body)
787 .cloned()
788 .unwrap();
789 let char_width_mono = ui.fonts_mut(|fonts| {
790 fonts
791 .layout_no_wrap(" ".to_string(), monospace_font.clone(), Color32::BLACK)
792 .size()
793 .x
794 });
795 let available_space = ui.available_width() - ui.spacing().button_padding.x * 2.;
797
798 for row in variable_rows {
800 let variable = &row.variable;
801 let meta = row.meta.as_ref();
802 let name_info = row.name_info.clone();
803
804 let index = meta
806 .and_then(|meta| meta.index)
807 .map(|index| {
808 if self.show_variable_indices() {
809 format!(" {index}")
810 } else {
811 String::new()
812 }
813 })
814 .unwrap_or_default();
815
816 let (type_icon, icon_color) = if self.show_hierarchy_icons() {
818 let (icon, color) = self.user.config.theme.variable_icons.get_icon(meta);
819 (format!("{icon} "), color)
820 } else {
821 (String::new(), self.user.config.theme.foreground)
822 };
823
824 let direction = self
826 .show_variable_direction()
827 .then(|| get_direction_string(meta, name_info.as_ref()))
828 .flatten()
829 .unwrap_or_default();
830 let value = if meta.is_some_and(surfer_translation_types::VariableMeta::is_parameter) {
832 let res = wave_container.query_variable(variable, &BigUint::ZERO).ok();
833 res.and_then(|o| o.and_then(|q| q.current.map(|v| format!(": {}", v.1))))
834 .unwrap_or_else(|| ": Undefined".to_string())
835 } else {
836 String::new()
837 };
838
839 ui.with_layout(
840 Layout::top_down(Align::LEFT).with_cross_justify(true),
841 |ui| {
842 let mut label = LayoutJob::default();
843 let true_name = name_info.and_then(|info| info.true_name);
844
845 let font = if true_name.is_some() {
846 monospace_font.clone()
847 } else {
848 body_font.clone()
849 };
850 let icon_format = TextFormat {
851 font_id: font.clone(),
852 color: icon_color,
853 ..Default::default()
854 };
855 let text_format = TextFormat {
856 font_id: font,
857 color: self.user.config.theme.foreground,
858 ..Default::default()
859 };
860
861 if let Some(name) = true_name {
862 let type_icon_size = type_icon.chars().count();
863 let direction_size = direction.chars().count();
864 let index_size = index.chars().count();
865 let value_size = value.chars().count();
866 let used_space = (type_icon_size + direction_size + index_size + value_size)
867 as f32
868 * char_width_mono;
869 let space_for_name = available_space - used_space;
870
871 label.append(&type_icon, 0.0, icon_format);
872 label.append(&direction, 0.0, text_format.clone());
873
874 draw_true_name(
875 &name,
876 &mut label,
877 monospace_font.clone(),
878 self.user.config.theme.foreground,
879 char_width_mono,
880 space_for_name,
881 self.user.config.layout.waveforms_line_height,
882 );
883
884 label.append(&index, 0.0, text_format.clone());
885 label.append(&value, 0.0, text_format);
886 } else {
887 let name = if display_full_path {
888 variable.full_path_string()
889 } else {
890 variable.name.clone()
891 };
892 label.append(&type_icon, 0.0, icon_format);
893 label.append(&direction, 0.0, text_format.clone());
894 label.append(&name, 0.0, text_format.clone());
895 label.append(&index, 0.0, text_format.clone());
896 label.append(&value, 0.0, text_format);
897 }
898
899 let mut response = ui.add(egui::Button::selectable(false, label));
900
901 let _ = response.interact(egui::Sense::click_and_drag());
902
903 if self.show_tooltip() {
904 let tooltip_meta = meta;
907 let tooltip_var = variable.clone();
908 response = response.on_hover_ui(move |ui| {
909 ui.set_max_width(ui.spacing().tooltip_width);
910 ui.add(egui::Label::new(variable_tooltip_text(
911 tooltip_meta,
912 &tooltip_var,
913 )));
914 });
915 }
916 response.drag_started().then(|| {
917 msgs.push(Message::VariableDragStarted(VisibleItemIndex(
918 self.user.waves.as_ref().unwrap().display_item_ref_counter,
919 )));
920 });
921 response.drag_stopped().then(|| {
922 if ui.input(|i| i.pointer.hover_pos().unwrap_or_default().x)
923 > self.user.sidepanel_width.unwrap_or_default()
924 {
925 msgs.push(Message::AddDraggedVariables(vec![variable.clone()]));
926 }
927 });
928 response
929 .clicked()
930 .then(|| msgs.push(Message::AddVariables(vec![variable.clone()])));
931 },
932 );
933 }
934 }
935
936 fn should_open_header_and_scroll_to(&self, scope: &ScopeRef) -> Option<(bool, bool)> {
937 let mut scope_ref_cell = self.scope_ref_to_expand.borrow_mut();
938 if let Some(state) = scope_ref_cell.as_mut() {
939 match state {
940 ScopeExpandType::ExpandAll => return Some((true, false)),
941 ScopeExpandType::CollapseAll => return Some((false, false)),
942 ScopeExpandType::ExpandSpecific(state) => {
943 if state.strs.starts_with(&scope.strs) {
944 if (state.strs.len() - 1) == scope.strs.len() {
945 *scope_ref_cell = None;
947 }
948 return Some((true, true));
949 }
950 }
951 }
952 }
953 None
954 }
955}