1use ahash::AHashSet;
3use ecolor::Color32;
4use egui::{FontSelection, RichText, Style, WidgetText};
5use emath::Align;
6use epaint::text::LayoutJob;
7use serde::{Deserialize, Serialize};
8use std::sync::Arc;
9
10use crate::analog_signal_cache::AnalogCacheEntry;
11use surfer_translation_types::VariableInfo;
12
13use crate::translation::DynTranslator;
14use crate::wave_container::VariableMeta;
15
16use crate::config::SurferConfig;
17use crate::transaction_container::TransactionStreamRef;
18use crate::wave_container::{FieldRef, VariableRef, VariableRefExt, WaveContainer};
19use crate::{
20 marker::DEFAULT_MARKER_NAME, time::DEFAULT_TIMELINE_NAME, variable_name_type::VariableNameType,
21};
22
23const DEFAULT_DIVIDER_NAME: &str = "";
24
25#[derive(Serialize, Deserialize, Debug, Copy, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
27#[cfg_attr(target_arch = "wasm32", wasm_bindgen::prelude::wasm_bindgen)]
28pub struct DisplayedItemRef(pub usize);
29
30impl From<usize> for DisplayedItemRef {
31 fn from(item: usize) -> Self {
32 DisplayedItemRef(item)
33 }
34}
35
36#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq, Hash)]
37pub struct DisplayedFieldRef {
38 pub item: DisplayedItemRef,
39 pub field: Vec<String>,
40}
41
42impl DisplayedFieldRef {
43 #[must_use]
44 pub fn without_field(&self) -> DisplayedFieldRef {
45 DisplayedFieldRef {
46 item: self.item,
47 field: vec![],
48 }
49 }
50}
51
52impl From<DisplayedItemRef> for DisplayedFieldRef {
53 fn from(item: DisplayedItemRef) -> Self {
54 DisplayedFieldRef {
55 item,
56 field: vec![],
57 }
58 }
59}
60
61#[derive(Serialize, Deserialize, Clone)]
62pub enum DisplayedItem {
63 Variable(DisplayedVariable),
64 Divider(DisplayedDivider),
65 Marker(DisplayedMarker),
66 TimeLine(DisplayedTimeLine),
67 Placeholder(DisplayedPlaceholder),
68 Stream(DisplayedStream),
69 Group(DisplayedGroup),
70}
71
72#[derive(Serialize, Deserialize, Clone)]
73pub struct FieldFormat {
74 pub field: Vec<String>,
75 pub format: String,
76}
77
78#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Default)]
79pub enum AnalogRenderStyle {
80 #[default]
81 Step,
82 Interpolated,
83}
84
85impl AnalogRenderStyle {
86 #[must_use]
87 pub const fn label(self) -> &'static str {
88 match self {
89 Self::Step => "Step",
90 Self::Interpolated => "Interpolated",
91 }
92 }
93}
94
95#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Default)]
96pub enum AnalogYAxisScale {
97 #[default]
98 Viewport,
99 Global,
100 TypeLimits,
101}
102
103impl AnalogYAxisScale {
104 #[must_use]
105 pub const fn label(self) -> &'static str {
106 match self {
107 Self::Viewport => "Viewport",
108 Self::Global => "Global",
109 Self::TypeLimits => "Type Limits",
110 }
111 }
112}
113
114#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Default)]
115pub struct AnalogSettings {
116 pub render_style: AnalogRenderStyle,
117 pub y_axis_scale: AnalogYAxisScale,
118}
119
120impl AnalogSettings {
121 pub fn downgrade_type_limits(&mut self) {
123 if self.y_axis_scale == AnalogYAxisScale::TypeLimits {
124 self.y_axis_scale = AnalogYAxisScale::Global;
125 }
126 }
127}
128
129#[derive(Serialize, Deserialize)]
134pub struct AnalogVarState {
135 pub settings: AnalogSettings,
136 #[serde(skip)]
137 pub cache: Option<Arc<AnalogCacheEntry>>,
138}
139
140impl std::fmt::Debug for AnalogVarState {
141 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
142 f.write_str("AnalogVarState")
143 }
144}
145
146impl Clone for AnalogVarState {
149 fn clone(&self) -> Self {
150 Self {
151 settings: self.settings,
152 cache: None, }
154 }
155}
156
157impl PartialEq for AnalogVarState {
158 fn eq(&self, other: &Self) -> bool {
159 self.settings == other.settings
160 }
161}
162
163impl AnalogVarState {
164 #[must_use]
165 pub fn new(settings: AnalogSettings) -> Self {
166 Self {
167 settings,
168 cache: None,
169 }
170 }
171}
172
173#[derive(Serialize, Deserialize, Clone)]
174pub struct DisplayedVariable {
175 pub variable_ref: VariableRef,
176 #[serde(skip)]
177 pub info: VariableInfo,
178 pub color: Option<String>,
179 pub background_color: Option<String>,
180 pub display_name: String,
181 pub display_name_type: VariableNameType,
182 pub manual_name: Option<String>,
183 pub format: Option<String>,
184 pub field_formats: Vec<FieldFormat>,
185 pub height_scaling_factor: Option<f32>,
186 pub analog: Option<AnalogVarState>,
187 #[serde(default)]
190 pub unfolded_fields: AHashSet<Vec<String>>,
191}
192
193impl DisplayedVariable {
194 pub fn toggle_field_fold(&mut self, field: Vec<String>) {
196 if !self.unfolded_fields.remove(&field) {
197 self.unfolded_fields.insert(field);
198 }
199 }
200
201 pub fn downgrade_type_limits_if_unsupported(
203 &mut self,
204 translator: &DynTranslator,
205 meta: &VariableMeta,
206 ) {
207 if let Some(ref mut analog) = self.analog
208 && translator.numeric_range(meta).is_none()
209 {
210 analog.settings.downgrade_type_limits();
211 }
212 }
213
214 #[must_use]
215 pub fn get_format(&self, field: &[String]) -> Option<&String> {
216 if field.is_empty() {
217 self.format.as_ref()
218 } else {
219 self.field_formats
220 .iter()
221 .find(|ff| ff.field == field)
222 .map(|ff| &ff.format)
223 }
224 }
225
226 #[must_use]
228 pub fn update(
229 &self,
230 new_waves: &WaveContainer,
231 keep_unavailable: bool,
232 ) -> Option<DisplayedItem> {
233 match new_waves.update_variable_ref(&self.variable_ref) {
234 None if keep_unavailable => {
236 Some(DisplayedItem::Placeholder(self.clone().into_placeholder()))
237 }
238 None => None,
239 Some(new_ref) => {
240 let mut res = self.clone();
241 res.variable_ref = new_ref;
242 Some(DisplayedItem::Variable(res))
243 }
244 }
245 }
246
247 #[must_use]
248 pub fn into_placeholder(mut self) -> DisplayedPlaceholder {
249 self.variable_ref.clear_id(); DisplayedPlaceholder {
251 variable_ref: self.variable_ref,
252 color: self.color,
253 background_color: self.background_color,
254 display_name: self.display_name,
255 display_name_type: self.display_name_type,
256 manual_name: self.manual_name,
257 format: self.format,
258 field_formats: self.field_formats,
259 height_scaling_factor: self.height_scaling_factor,
260 analog: self.analog,
261 }
262 }
263}
264
265#[derive(Serialize, Deserialize, Clone)]
266pub struct DisplayedDivider {
267 pub color: Option<String>,
268 pub background_color: Option<String>,
269 pub name: Option<String>,
270}
271
272#[derive(Serialize, Deserialize, Clone)]
273pub struct DisplayedMarker {
274 pub color: Option<String>,
275 pub background_color: Option<String>,
276 pub name: Option<String>,
277 pub idx: u8,
278}
279
280impl DisplayedMarker {
281 #[must_use]
282 pub fn marker_text(&self, color: Color32) -> WidgetText {
283 let style = Style::default();
284 let mut layout_job = LayoutJob::default();
285 self.rich_text(color, &style, &mut layout_job);
286 WidgetText::LayoutJob(layout_job.into())
287 }
288
289 pub fn rich_text(&self, color: Color32, style: &Style, layout_job: &mut LayoutJob) {
290 RichText::new(format!("{idx}: ", idx = self.idx))
291 .color(color)
292 .append_to(layout_job, style, FontSelection::Default, Align::Center);
293 RichText::new(self.marker_name())
294 .color(color)
295 .italics()
296 .append_to(layout_job, style, FontSelection::Default, Align::Center);
297 }
298
299 fn marker_name(&self) -> String {
300 self.name
301 .clone()
302 .unwrap_or_else(|| DEFAULT_MARKER_NAME.to_string())
303 }
304}
305
306#[derive(Serialize, Deserialize, Clone)]
307pub struct DisplayedTimeLine {
308 pub color: Option<String>,
309 pub background_color: Option<String>,
310 pub name: Option<String>,
311}
312
313#[derive(Serialize, Deserialize, Clone)]
314pub struct DisplayedPlaceholder {
315 pub variable_ref: VariableRef,
316 pub color: Option<String>,
317 pub background_color: Option<String>,
318 pub display_name: String,
319 pub display_name_type: VariableNameType,
320 pub manual_name: Option<String>,
321 pub format: Option<String>,
322 pub field_formats: Vec<FieldFormat>,
323 pub height_scaling_factor: Option<f32>,
324 pub analog: Option<AnalogVarState>,
325}
326
327impl DisplayedPlaceholder {
328 #[must_use]
329 pub fn into_variable(
330 self,
331 variable_info: VariableInfo,
332 updated_variable_ref: VariableRef,
333 ) -> DisplayedVariable {
334 DisplayedVariable {
335 variable_ref: updated_variable_ref,
336 info: variable_info,
337 color: self.color,
338 background_color: self.background_color,
339 display_name: self.display_name,
340 display_name_type: self.display_name_type,
341 manual_name: self.manual_name,
342 format: self.format,
343 field_formats: self.field_formats,
344 height_scaling_factor: self.height_scaling_factor,
345 analog: self.analog,
346 unfolded_fields: AHashSet::new(),
347 }
348 }
349
350 pub fn rich_text(&self, text_color: Color32, style: &Style, layout_job: &mut LayoutJob) {
351 let s = self.manual_name.as_ref().unwrap_or(&self.display_name);
352 RichText::new("Not available: ".to_owned() + s)
353 .color(text_color)
354 .italics()
355 .append_to(layout_job, style, FontSelection::Default, Align::Center);
356 }
357}
358
359#[derive(Serialize, Deserialize, Clone)]
360pub struct DisplayedStream {
361 pub transaction_stream_ref: TransactionStreamRef,
362 pub color: Option<String>,
363 pub background_color: Option<String>,
364 pub display_name: String,
365 pub manual_name: Option<String>,
366 pub rows: usize,
367}
368
369impl DisplayedStream {
370 pub fn rich_text(
371 &self,
372 text_color: Color32,
373 style: &Style,
374 config: &SurferConfig,
375 layout_job: &mut LayoutJob,
376 ) {
377 RichText::new(format!(
378 "{}{}",
379 self.manual_name.as_ref().unwrap_or(&self.display_name),
380 "\n".repeat(self.rows - 1)
381 ))
382 .color(text_color)
383 .line_height(Some(config.layout.transactions_line_height))
385 .append_to(layout_job, style, FontSelection::Default, Align::Center);
386 }
387}
388
389#[derive(Serialize, Deserialize, Clone)]
390pub struct DisplayedGroup {
391 pub name: String,
392 pub color: Option<String>,
393 pub background_color: Option<String>,
394 pub content: Vec<DisplayedItemRef>,
395 pub is_open: bool,
396}
397
398impl DisplayedGroup {
399 pub fn rich_text(&self, text_color: Color32, style: &Style, layout_job: &mut LayoutJob) {
400 RichText::new(self.name.clone())
401 .color(text_color)
402 .append_to(layout_job, style, FontSelection::Default, Align::Center);
403 }
404}
405
406impl DisplayedItem {
407 #[must_use]
408 pub fn color(&self) -> Option<&str> {
409 match self {
410 DisplayedItem::Variable(variable) => variable.color.as_deref(),
411 DisplayedItem::Divider(divider) => divider.color.as_deref(),
412 DisplayedItem::Marker(marker) => marker.color.as_deref(),
413 DisplayedItem::TimeLine(timeline) => timeline.color.as_deref(),
414 DisplayedItem::Placeholder(_) => None,
415 DisplayedItem::Stream(stream) => stream.color.as_deref(),
416 DisplayedItem::Group(group) => group.color.as_deref(),
417 }
418 }
419
420 pub fn set_color(&mut self, color_name: &Option<String>) {
421 match self {
422 DisplayedItem::Variable(variable) => variable.color.clone_from(color_name),
423 DisplayedItem::Divider(divider) => divider.color.clone_from(color_name),
424 DisplayedItem::Marker(marker) => marker.color.clone_from(color_name),
425 DisplayedItem::TimeLine(timeline) => timeline.color.clone_from(color_name),
426 DisplayedItem::Placeholder(placeholder) => placeholder.color.clone_from(color_name),
427 DisplayedItem::Stream(stream) => stream.color.clone_from(color_name),
428 DisplayedItem::Group(group) => group.color.clone_from(color_name),
429 }
430 }
431
432 #[must_use]
433 pub fn name(&self) -> String {
434 match self {
435 DisplayedItem::Variable(variable) => variable
436 .manual_name
437 .as_ref()
438 .unwrap_or(&variable.display_name)
439 .clone(),
440 DisplayedItem::Divider(divider) => divider
441 .name
442 .as_ref()
443 .unwrap_or(&DEFAULT_DIVIDER_NAME.to_string())
444 .clone(),
445 DisplayedItem::Marker(marker) => marker.marker_name(),
446 DisplayedItem::TimeLine(timeline) => timeline
447 .name
448 .as_ref()
449 .unwrap_or(&DEFAULT_TIMELINE_NAME.to_string())
450 .clone(),
451 DisplayedItem::Placeholder(placeholder) => placeholder
452 .manual_name
453 .as_ref()
454 .unwrap_or(&placeholder.display_name)
455 .clone(),
456 DisplayedItem::Stream(stream) => stream
457 .manual_name
458 .as_ref()
459 .unwrap_or(&stream.display_name)
460 .clone(),
461 DisplayedItem::Group(group) => group.name.clone(),
462 }
463 }
464
465 pub fn add_to_layout_job(
467 &self,
468 color: Color32,
469 style: &Style,
470 layout_job: &mut LayoutJob,
471 field: Option<&FieldRef>,
472 config: &SurferConfig,
473 ) {
474 match self {
475 DisplayedItem::Variable(_) => {
476 let name = field
477 .and_then(|f| f.field.last())
478 .cloned()
479 .unwrap_or_else(|| self.name());
480 RichText::new(name)
481 .color(color)
482 .line_height(Some(config.layout.waveforms_line_height))
483 .append_to(layout_job, style, FontSelection::Default, Align::Center);
484 }
485 DisplayedItem::TimeLine(_) | DisplayedItem::Divider(_) => {
486 RichText::new(self.name()).color(color).italics().append_to(
487 layout_job,
488 style,
489 FontSelection::Default,
490 Align::Center,
491 );
492 }
493 DisplayedItem::Marker(marker) => {
494 marker.rich_text(color, style, layout_job);
495 }
496 DisplayedItem::Placeholder(placeholder) => {
497 let s = placeholder
498 .manual_name
499 .as_ref()
500 .unwrap_or(&placeholder.display_name);
501 RichText::new("Not available: ".to_owned() + s)
502 .color(color)
503 .italics()
504 .append_to(layout_job, style, FontSelection::Default, Align::Center);
505 }
506 DisplayedItem::Stream(stream) => {
507 RichText::new(format!("{}{}", self.name(), "\n".repeat(stream.rows - 1)))
508 .color(color)
509 .line_height(Some(config.layout.transactions_line_height))
510 .append_to(layout_job, style, FontSelection::Default, Align::Center);
511 }
512 DisplayedItem::Group(group) => {
513 group.rich_text(color, style, layout_job);
514 }
515 }
516 }
517
518 pub fn set_name(&mut self, name: Option<String>) {
519 match self {
520 DisplayedItem::Variable(variable) => {
521 variable.manual_name = name;
522 }
523 DisplayedItem::Divider(divider) => {
524 divider.name = name;
525 }
526 DisplayedItem::Marker(marker) => {
527 marker.name = name;
528 }
529 DisplayedItem::TimeLine(timeline) => {
530 timeline.name = name;
531 }
532 DisplayedItem::Placeholder(placeholder) => {
533 placeholder.manual_name = name;
534 }
535 DisplayedItem::Stream(stream) => {
536 stream.manual_name = name;
537 }
538 DisplayedItem::Group(group) => {
539 group.name = name.unwrap_or_default();
540 }
541 }
542 }
543
544 #[must_use]
545 pub fn has_overwritten_name(&self) -> bool {
546 match self {
547 DisplayedItem::Variable(variable) => variable.manual_name.is_some(),
548 DisplayedItem::Placeholder(placeholder) => placeholder.manual_name.is_some(),
549 DisplayedItem::Stream(stream) => stream.manual_name.is_some(),
550 DisplayedItem::Divider(_)
551 | DisplayedItem::Marker(_)
552 | DisplayedItem::TimeLine(_)
553 | DisplayedItem::Group(_) => false,
554 }
555 }
556
557 #[must_use]
558 pub fn background_color(&self) -> Option<&str> {
559 match self {
560 DisplayedItem::Variable(variable) => variable.background_color.as_deref(),
561 DisplayedItem::Divider(divider) => divider.background_color.as_deref(),
562 DisplayedItem::Marker(marker) => marker.background_color.as_deref(),
563 DisplayedItem::TimeLine(timeline) => timeline.background_color.as_deref(),
564 DisplayedItem::Placeholder(_) => None,
565 DisplayedItem::Stream(stream) => stream.background_color.as_deref(),
566 DisplayedItem::Group(group) => group.background_color.as_deref(),
567 }
568 }
569
570 pub fn set_background_color(&mut self, color_name: &Option<String>) {
571 match self {
572 DisplayedItem::Variable(variable) => {
573 variable.background_color.clone_from(color_name);
574 }
575 DisplayedItem::Divider(divider) => {
576 divider.background_color.clone_from(color_name);
577 }
578 DisplayedItem::Marker(marker) => {
579 marker.background_color.clone_from(color_name);
580 }
581 DisplayedItem::TimeLine(timeline) => {
582 timeline.background_color.clone_from(color_name);
583 }
584 DisplayedItem::Placeholder(placeholder) => {
585 placeholder.background_color.clone_from(color_name);
586 }
587 DisplayedItem::Stream(stream) => {
588 stream.background_color.clone_from(color_name);
589 }
590 DisplayedItem::Group(group) => {
591 group.background_color.clone_from(color_name);
592 }
593 }
594 }
595
596 #[must_use]
597 pub fn height_scaling_factor(&self) -> f32 {
598 match self {
599 DisplayedItem::Variable(variable) => variable.height_scaling_factor,
600 DisplayedItem::Placeholder(placeholder) => placeholder.height_scaling_factor,
601 _ => None,
602 }
603 .unwrap_or(1.0)
604 }
605
606 pub fn set_height_scaling_factor(&mut self, scale: f32) {
607 match self {
608 DisplayedItem::Variable(variable) => variable.height_scaling_factor = Some(scale),
609 DisplayedItem::Placeholder(placeholder) => {
610 placeholder.height_scaling_factor = Some(scale);
611 }
612 _ => {}
613 }
614 }
615}