libsurfer/
displayed_item.rs

1//! The items that are drawn in the main wave form view: waves, dividers, etc.
2use ecolor::Color32;
3use egui::{Context, FontSelection, Key, RichText, Style, WidgetText, Window};
4use emath::Align;
5use epaint::text::LayoutJob;
6use serde::{Deserialize, Serialize};
7use surfer_translation_types::VariableInfo;
8
9use crate::config::SurferConfig;
10use crate::displayed_item_tree::VisibleItemIndex;
11use crate::transaction_container::TransactionStreamRef;
12use crate::wave_container::{FieldRef, VariableRef, VariableRefExt, WaveContainer};
13use crate::{
14    marker::DEFAULT_MARKER_NAME, message::Message, time::DEFAULT_TIMELINE_NAME,
15    variable_name_type::VariableNameType,
16};
17
18const DEFAULT_DIVIDER_NAME: &str = "";
19
20/// Key for the [`crate::wave_data::WaveData::displayed_items`] hash map
21#[derive(Serialize, Deserialize, Debug, Copy, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
22#[cfg_attr(target_arch = "wasm32", wasm_bindgen::prelude::wasm_bindgen)]
23pub struct DisplayedItemRef(pub usize);
24
25impl From<usize> for DisplayedItemRef {
26    fn from(item: usize) -> Self {
27        DisplayedItemRef(item)
28    }
29}
30
31#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq, Hash)]
32pub struct DisplayedFieldRef {
33    pub item: DisplayedItemRef,
34    pub field: Vec<String>,
35}
36
37impl DisplayedFieldRef {
38    pub fn without_field(&self) -> DisplayedFieldRef {
39        DisplayedFieldRef {
40            item: self.item,
41            field: vec![],
42        }
43    }
44}
45
46impl From<DisplayedItemRef> for DisplayedFieldRef {
47    fn from(item: DisplayedItemRef) -> Self {
48        DisplayedFieldRef {
49            item,
50            field: vec![],
51        }
52    }
53}
54
55#[derive(Serialize, Deserialize, Clone)]
56pub enum DisplayedItem {
57    Variable(DisplayedVariable),
58    Divider(DisplayedDivider),
59    Marker(DisplayedMarker),
60    TimeLine(DisplayedTimeLine),
61    Placeholder(DisplayedPlaceholder),
62    Stream(DisplayedStream),
63    Group(DisplayedGroup),
64}
65
66#[derive(Serialize, Deserialize, Clone)]
67pub struct FieldFormat {
68    pub field: Vec<String>,
69    pub format: String,
70}
71
72#[derive(Serialize, Deserialize, Clone)]
73pub struct DisplayedVariable {
74    pub variable_ref: VariableRef,
75    #[serde(skip)]
76    pub info: VariableInfo,
77    pub color: Option<String>,
78    pub background_color: Option<String>,
79    pub display_name: String,
80    pub display_name_type: VariableNameType,
81    pub manual_name: Option<String>,
82    pub format: Option<String>,
83    pub field_formats: Vec<FieldFormat>,
84    pub height_scaling_factor: Option<f32>,
85}
86
87impl DisplayedVariable {
88    pub fn get_format(&self, field: &[String]) -> Option<&String> {
89        if field.is_empty() {
90            self.format.as_ref()
91        } else {
92            self.field_formats
93                .iter()
94                .find(|ff| ff.field == field)
95                .map(|ff| &ff.format)
96        }
97    }
98
99    /// Updates the variable after a new waveform has been loaded.
100    pub fn update(
101        &self,
102        new_waves: &WaveContainer,
103        keep_unavailable: bool,
104    ) -> Option<DisplayedItem> {
105        match new_waves.update_variable_ref(&self.variable_ref) {
106            // variable is not available in the new waveform
107            None if keep_unavailable => {
108                Some(DisplayedItem::Placeholder(self.clone().into_placeholder()))
109            }
110            None => None,
111            Some(new_ref) => {
112                let mut res = self.clone();
113                res.variable_ref = new_ref;
114                Some(DisplayedItem::Variable(res))
115            }
116        }
117    }
118
119    pub fn into_placeholder(mut self) -> DisplayedPlaceholder {
120        self.variable_ref.clear_id(); // placeholders do not refer to currently loaded variables
121        DisplayedPlaceholder {
122            variable_ref: self.variable_ref,
123            color: self.color,
124            background_color: self.background_color,
125            display_name: self.display_name,
126            display_name_type: self.display_name_type,
127            manual_name: self.manual_name,
128            format: self.format,
129            field_formats: self.field_formats,
130            height_scaling_factor: self.height_scaling_factor,
131        }
132    }
133}
134
135#[derive(Serialize, Deserialize, Clone)]
136pub struct DisplayedDivider {
137    pub color: Option<String>,
138    pub background_color: Option<String>,
139    pub name: Option<String>,
140}
141
142#[derive(Serialize, Deserialize, Clone)]
143pub struct DisplayedMarker {
144    pub color: Option<String>,
145    pub background_color: Option<String>,
146    pub name: Option<String>,
147    pub idx: u8,
148}
149
150impl DisplayedMarker {
151    pub fn marker_text(&self, color: &Color32) -> WidgetText {
152        let style = Style::default();
153        let mut layout_job = LayoutJob::default();
154        self.rich_text(color, &style, &mut layout_job);
155        WidgetText::LayoutJob(layout_job)
156    }
157
158    pub fn rich_text(&self, color: &Color32, style: &Style, layout_job: &mut LayoutJob) {
159        RichText::new(format!("{idx}: ", idx = self.idx))
160            .color(*color)
161            .append_to(layout_job, style, FontSelection::Default, Align::Center);
162        RichText::new(self.marker_name())
163            .color(*color)
164            .italics()
165            .append_to(layout_job, style, FontSelection::Default, Align::Center);
166    }
167
168    fn marker_name(&self) -> String {
169        self.name
170            .as_ref()
171            .cloned()
172            .unwrap_or_else(|| DEFAULT_MARKER_NAME.to_string())
173    }
174}
175
176#[derive(Serialize, Deserialize, Clone)]
177pub struct DisplayedTimeLine {
178    pub color: Option<String>,
179    pub background_color: Option<String>,
180    pub name: Option<String>,
181}
182
183#[derive(Serialize, Deserialize, Clone)]
184pub struct DisplayedPlaceholder {
185    pub variable_ref: VariableRef,
186    pub color: Option<String>,
187    pub background_color: Option<String>,
188    pub display_name: String,
189    pub display_name_type: VariableNameType,
190    pub manual_name: Option<String>,
191    pub format: Option<String>,
192    pub field_formats: Vec<FieldFormat>,
193    pub height_scaling_factor: Option<f32>,
194}
195
196impl DisplayedPlaceholder {
197    pub fn into_variable(
198        self,
199        variable_info: VariableInfo,
200        updated_variable_ref: VariableRef,
201    ) -> DisplayedVariable {
202        DisplayedVariable {
203            variable_ref: updated_variable_ref,
204            info: variable_info,
205            color: self.color,
206            background_color: self.background_color,
207            display_name: self.display_name,
208            display_name_type: self.display_name_type,
209            manual_name: self.manual_name,
210            format: self.format,
211            field_formats: self.field_formats,
212            height_scaling_factor: self.height_scaling_factor,
213        }
214    }
215
216    pub fn rich_text(&self, text_color: Color32, style: &Style, layout_job: &mut LayoutJob) {
217        let s = self.manual_name.as_ref().unwrap_or(&self.display_name);
218        RichText::new("Not available: ".to_owned() + s)
219            .color(text_color)
220            .italics()
221            .append_to(layout_job, style, FontSelection::Default, Align::Center);
222    }
223}
224
225#[derive(Serialize, Deserialize, Clone)]
226pub struct DisplayedStream {
227    pub transaction_stream_ref: TransactionStreamRef,
228    pub color: Option<String>,
229    pub background_color: Option<String>,
230    pub display_name: String,
231    pub manual_name: Option<String>,
232    pub rows: usize,
233}
234
235impl DisplayedStream {
236    pub fn rich_text(
237        &self,
238        text_color: Color32,
239        style: &Style,
240        config: &SurferConfig,
241        layout_job: &mut LayoutJob,
242    ) {
243        RichText::new(format!(
244            "{}{}",
245            self.manual_name.as_ref().unwrap_or(&self.display_name),
246            "\n".repeat(self.rows - 1)
247        ))
248        .color(text_color)
249        // TODO: What does setting this do? Is it for the multi-line transactions?
250        .line_height(Some(config.layout.transactions_line_height))
251        .append_to(layout_job, style, FontSelection::Default, Align::Center);
252    }
253}
254
255#[derive(Serialize, Deserialize, Clone)]
256pub struct DisplayedGroup {
257    pub name: String,
258    pub color: Option<String>,
259    pub background_color: Option<String>,
260    pub content: Vec<DisplayedItemRef>,
261    pub is_open: bool,
262}
263
264impl DisplayedGroup {
265    pub fn rich_text(&self, text_color: Color32, style: &Style, layout_job: &mut LayoutJob) {
266        RichText::new(self.name.clone())
267            .color(text_color)
268            .append_to(layout_job, style, FontSelection::Default, Align::Center);
269    }
270}
271
272impl DisplayedItem {
273    pub fn color(&self) -> Option<&str> {
274        match self {
275            DisplayedItem::Variable(variable) => variable.color.as_deref(),
276            DisplayedItem::Divider(divider) => divider.color.as_deref(),
277            DisplayedItem::Marker(marker) => marker.color.as_deref(),
278            DisplayedItem::TimeLine(timeline) => timeline.color.as_deref(),
279            DisplayedItem::Placeholder(_) => None,
280            DisplayedItem::Stream(stream) => stream.color.as_deref(),
281            DisplayedItem::Group(group) => group.color.as_deref(),
282        }
283    }
284
285    pub fn set_color(&mut self, color_name: Option<String>) {
286        match self {
287            DisplayedItem::Variable(variable) => variable.color.clone_from(&color_name),
288            DisplayedItem::Divider(divider) => divider.color.clone_from(&color_name),
289            DisplayedItem::Marker(marker) => marker.color.clone_from(&color_name),
290            DisplayedItem::TimeLine(timeline) => timeline.color.clone_from(&color_name),
291            DisplayedItem::Placeholder(placeholder) => placeholder.color.clone_from(&color_name),
292            DisplayedItem::Stream(stream) => stream.color.clone_from(&color_name),
293            DisplayedItem::Group(group) => group.color.clone_from(&color_name),
294        }
295    }
296
297    pub fn name(&self) -> String {
298        match self {
299            DisplayedItem::Variable(variable) => variable
300                .manual_name
301                .as_ref()
302                .unwrap_or(&variable.display_name)
303                .clone(),
304            DisplayedItem::Divider(divider) => divider
305                .name
306                .as_ref()
307                .unwrap_or(&DEFAULT_DIVIDER_NAME.to_string())
308                .clone(),
309            DisplayedItem::Marker(marker) => marker.marker_name(),
310            DisplayedItem::TimeLine(timeline) => timeline
311                .name
312                .as_ref()
313                .unwrap_or(&DEFAULT_TIMELINE_NAME.to_string())
314                .clone(),
315            DisplayedItem::Placeholder(placeholder) => placeholder
316                .manual_name
317                .as_ref()
318                .unwrap_or(&placeholder.display_name)
319                .clone(),
320            DisplayedItem::Stream(stream) => stream
321                .manual_name
322                .as_ref()
323                .unwrap_or(&stream.display_name)
324                .clone(),
325            DisplayedItem::Group(group) => group.name.clone(),
326        }
327    }
328
329    /// Widget displayed in variable list for the wave form, may include additional info compared to name()
330    pub fn add_to_layout_job(
331        &self,
332        color: &Color32,
333        style: &Style,
334        layout_job: &mut LayoutJob,
335        field: Option<&FieldRef>,
336        config: &SurferConfig,
337    ) {
338        match self {
339            DisplayedItem::Variable(_) => {
340                let name = if let Some(field) = field {
341                    if let Some(last) = field.field.last() {
342                        last.clone()
343                    } else {
344                        self.name()
345                    }
346                } else {
347                    self.name()
348                };
349                RichText::new(name)
350                    .color(*color)
351                    .line_height(Some(
352                        config.layout.waveforms_line_height * self.height_scaling_factor(),
353                    ))
354                    .append_to(layout_job, style, FontSelection::Default, Align::Center);
355            }
356            DisplayedItem::TimeLine(_) | DisplayedItem::Divider(_) => {
357                RichText::new(self.name())
358                    .color(*color)
359                    .italics()
360                    .append_to(layout_job, style, FontSelection::Default, Align::Center);
361            }
362            DisplayedItem::Marker(marker) => {
363                marker.rich_text(color, style, layout_job);
364            }
365            DisplayedItem::Placeholder(placeholder) => {
366                let s = placeholder
367                    .manual_name
368                    .as_ref()
369                    .unwrap_or(&placeholder.display_name);
370                RichText::new("Not available: ".to_owned() + s)
371                    .color(*color)
372                    .italics()
373                    .append_to(layout_job, style, FontSelection::Default, Align::Center);
374            }
375            DisplayedItem::Stream(stream) => {
376                RichText::new(format!("{}{}", self.name(), "\n".repeat(stream.rows - 1)))
377                    .color(*color)
378                    .line_height(Some(config.layout.transactions_line_height))
379                    .append_to(layout_job, style, FontSelection::Default, Align::Center);
380            }
381            DisplayedItem::Group(group) => {
382                group.rich_text(*color, style, layout_job);
383            }
384        }
385    }
386
387    pub fn set_name(&mut self, name: Option<String>) {
388        match self {
389            DisplayedItem::Variable(variable) => {
390                variable.manual_name = name;
391            }
392            DisplayedItem::Divider(divider) => {
393                divider.name = name;
394            }
395            DisplayedItem::Marker(marker) => {
396                marker.name = name;
397            }
398            DisplayedItem::TimeLine(timeline) => {
399                timeline.name = name;
400            }
401            DisplayedItem::Placeholder(placeholder) => {
402                placeholder.manual_name = name;
403            }
404            DisplayedItem::Stream(stream) => {
405                stream.manual_name = name;
406            }
407            DisplayedItem::Group(group) => {
408                group.name = name.unwrap_or_default();
409            }
410        }
411    }
412
413    pub fn background_color(&self) -> Option<&str> {
414        match self {
415            DisplayedItem::Variable(variable) => variable.background_color.as_deref(),
416            DisplayedItem::Divider(divider) => divider.background_color.as_deref(),
417            DisplayedItem::Marker(marker) => marker.background_color.as_deref(),
418            DisplayedItem::TimeLine(timeline) => timeline.background_color.as_deref(),
419            DisplayedItem::Placeholder(_) => None,
420            DisplayedItem::Stream(stream) => stream.background_color.as_deref(),
421            DisplayedItem::Group(group) => group.background_color.as_deref(),
422        }
423    }
424
425    pub fn set_background_color(&mut self, color_name: Option<String>) {
426        match self {
427            DisplayedItem::Variable(variable) => {
428                variable.background_color.clone_from(&color_name);
429            }
430            DisplayedItem::Divider(divider) => {
431                divider.background_color.clone_from(&color_name);
432            }
433            DisplayedItem::Marker(marker) => {
434                marker.background_color.clone_from(&color_name);
435            }
436            DisplayedItem::TimeLine(timeline) => {
437                timeline.background_color.clone_from(&color_name);
438            }
439            DisplayedItem::Placeholder(placeholder) => {
440                placeholder.background_color.clone_from(&color_name);
441            }
442            DisplayedItem::Stream(stream) => {
443                stream.background_color.clone_from(&color_name);
444            }
445            DisplayedItem::Group(group) => {
446                group.background_color.clone_from(&color_name);
447            }
448        }
449    }
450
451    pub fn height_scaling_factor(&self) -> f32 {
452        match self {
453            DisplayedItem::Variable(variable) => variable.height_scaling_factor,
454            DisplayedItem::Placeholder(placeholder) => placeholder.height_scaling_factor,
455            _ => None,
456        }
457        .unwrap_or(1.0)
458    }
459
460    pub fn set_height_scaling_factor(&mut self, scale: f32) {
461        match self {
462            DisplayedItem::Variable(variable) => variable.height_scaling_factor = Some(scale),
463            DisplayedItem::Placeholder(placeholder) => {
464                placeholder.height_scaling_factor = Some(scale)
465            }
466            _ => {}
467        }
468    }
469}
470
471pub fn draw_rename_window(
472    ctx: &Context,
473    msgs: &mut Vec<Message>,
474    vidx: VisibleItemIndex,
475    name: &mut String,
476) {
477    let mut open = true;
478    Window::new("Rename item")
479        .open(&mut open)
480        .collapsible(false)
481        .resizable(true)
482        .show(ctx, |ui| {
483            ui.vertical_centered(|ui| {
484                let response = ui.text_edit_singleline(name);
485                if response.lost_focus() && ui.input(|i| i.key_pressed(Key::Enter)) {
486                    msgs.push(Message::ItemNameChange(Some(vidx), Some(name.clone())));
487                    msgs.push(Message::SetRenameItemVisible(false));
488                }
489                response.request_focus();
490                ui.horizontal(|ui| {
491                    if ui.button("Rename").clicked() {
492                        msgs.push(Message::ItemNameChange(Some(vidx), Some(name.clone())));
493                        msgs.push(Message::SetRenameItemVisible(false));
494                    }
495                    if ui.button("Default").clicked() {
496                        msgs.push(Message::ItemNameChange(Some(vidx), None));
497                        msgs.push(Message::SetRenameItemVisible(false));
498                    }
499                    if ui.button("Cancel").clicked() {
500                        msgs.push(Message::SetRenameItemVisible(false));
501                    }
502                });
503            });
504        });
505    if !open {
506        msgs.push(Message::SetRenameItemVisible(false));
507    }
508}