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