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)]
131pub struct AnalogVarState {
132 pub settings: AnalogSettings,
133 #[serde(skip)]
134 pub cache: Option<Arc<AnalogCacheEntry>>,
135}
136
137impl std::fmt::Debug for AnalogVarState {
138 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139 f.write_str("AnalogVarState")
140 }
141}
142
143impl Clone for AnalogVarState {
146 fn clone(&self) -> Self {
147 Self {
148 settings: self.settings,
149 cache: None, }
151 }
152}
153
154impl PartialEq for AnalogVarState {
155 fn eq(&self, other: &Self) -> bool {
156 self.settings == other.settings
157 }
158}
159
160impl AnalogVarState {
161 #[must_use]
162 pub fn new(settings: AnalogSettings) -> Self {
163 Self {
164 settings,
165 cache: None,
166 }
167 }
168}
169
170#[derive(Serialize, Deserialize, Clone)]
171pub struct DisplayedVariable {
172 pub variable_ref: VariableRef,
173 #[serde(skip)]
174 pub info: VariableInfo,
175 pub color: Option<String>,
176 pub background_color: Option<String>,
177 pub display_name: String,
178 pub display_name_type: VariableNameType,
179 pub manual_name: Option<String>,
180 pub format: Option<String>,
181 pub field_formats: Vec<FieldFormat>,
182 pub height_scaling_factor: Option<f32>,
183 pub analog: Option<AnalogVarState>,
184}
185
186impl DisplayedVariable {
187 pub fn downgrade_type_limits_if_unsupported(
189 &mut self,
190 translator: &DynTranslator,
191 meta: &VariableMeta,
192 ) {
193 if let Some(ref mut analog) = self.analog
194 && translator.numeric_range(meta).is_none()
195 {
196 analog.settings.downgrade_type_limits();
197 }
198 }
199
200 #[must_use]
201 pub fn get_format(&self, field: &[String]) -> Option<&String> {
202 if field.is_empty() {
203 self.format.as_ref()
204 } else {
205 self.field_formats
206 .iter()
207 .find(|ff| ff.field == field)
208 .map(|ff| &ff.format)
209 }
210 }
211
212 #[must_use]
214 pub fn update(
215 &self,
216 new_waves: &WaveContainer,
217 keep_unavailable: bool,
218 ) -> Option<DisplayedItem> {
219 match new_waves.update_variable_ref(&self.variable_ref) {
220 None if keep_unavailable => {
222 Some(DisplayedItem::Placeholder(self.clone().into_placeholder()))
223 }
224 None => None,
225 Some(new_ref) => {
226 let mut res = self.clone();
227 res.variable_ref = new_ref;
228 Some(DisplayedItem::Variable(res))
229 }
230 }
231 }
232
233 #[must_use]
234 pub fn into_placeholder(mut self) -> DisplayedPlaceholder {
235 self.variable_ref.clear_id(); DisplayedPlaceholder {
237 variable_ref: self.variable_ref,
238 color: self.color,
239 background_color: self.background_color,
240 display_name: self.display_name,
241 display_name_type: self.display_name_type,
242 manual_name: self.manual_name,
243 format: self.format,
244 field_formats: self.field_formats,
245 height_scaling_factor: self.height_scaling_factor,
246 analog: self.analog,
247 }
248 }
249}
250
251#[derive(Serialize, Deserialize, Clone)]
252pub struct DisplayedDivider {
253 pub color: Option<String>,
254 pub background_color: Option<String>,
255 pub name: Option<String>,
256}
257
258#[derive(Serialize, Deserialize, Clone)]
259pub struct DisplayedMarker {
260 pub color: Option<String>,
261 pub background_color: Option<String>,
262 pub name: Option<String>,
263 pub idx: u8,
264}
265
266impl DisplayedMarker {
267 #[must_use]
268 pub fn marker_text(&self, color: Color32) -> WidgetText {
269 let style = Style::default();
270 let mut layout_job = LayoutJob::default();
271 self.rich_text(color, &style, &mut layout_job);
272 WidgetText::LayoutJob(layout_job.into())
273 }
274
275 pub fn rich_text(&self, color: Color32, style: &Style, layout_job: &mut LayoutJob) {
276 RichText::new(format!("{idx}: ", idx = self.idx))
277 .color(color)
278 .append_to(layout_job, style, FontSelection::Default, Align::Center);
279 RichText::new(self.marker_name())
280 .color(color)
281 .italics()
282 .append_to(layout_job, style, FontSelection::Default, Align::Center);
283 }
284
285 fn marker_name(&self) -> String {
286 self.name
287 .clone()
288 .unwrap_or_else(|| DEFAULT_MARKER_NAME.to_string())
289 }
290}
291
292#[derive(Serialize, Deserialize, Clone)]
293pub struct DisplayedTimeLine {
294 pub color: Option<String>,
295 pub background_color: Option<String>,
296 pub name: Option<String>,
297}
298
299#[derive(Serialize, Deserialize, Clone)]
300pub struct DisplayedPlaceholder {
301 pub variable_ref: VariableRef,
302 pub color: Option<String>,
303 pub background_color: Option<String>,
304 pub display_name: String,
305 pub display_name_type: VariableNameType,
306 pub manual_name: Option<String>,
307 pub format: Option<String>,
308 pub field_formats: Vec<FieldFormat>,
309 pub height_scaling_factor: Option<f32>,
310 pub analog: Option<AnalogVarState>,
311}
312
313impl DisplayedPlaceholder {
314 #[must_use]
315 pub fn into_variable(
316 self,
317 variable_info: VariableInfo,
318 updated_variable_ref: VariableRef,
319 ) -> DisplayedVariable {
320 DisplayedVariable {
321 variable_ref: updated_variable_ref,
322 info: variable_info,
323 color: self.color,
324 background_color: self.background_color,
325 display_name: self.display_name,
326 display_name_type: self.display_name_type,
327 manual_name: self.manual_name,
328 format: self.format,
329 field_formats: self.field_formats,
330 height_scaling_factor: self.height_scaling_factor,
331 analog: self.analog,
332 }
333 }
334
335 pub fn rich_text(&self, text_color: Color32, style: &Style, layout_job: &mut LayoutJob) {
336 let s = self.manual_name.as_ref().unwrap_or(&self.display_name);
337 RichText::new("Not available: ".to_owned() + s)
338 .color(text_color)
339 .italics()
340 .append_to(layout_job, style, FontSelection::Default, Align::Center);
341 }
342}
343
344#[derive(Serialize, Deserialize, Clone)]
345pub struct DisplayedStream {
346 pub transaction_stream_ref: TransactionStreamRef,
347 pub color: Option<String>,
348 pub background_color: Option<String>,
349 pub display_name: String,
350 pub manual_name: Option<String>,
351 pub rows: usize,
352}
353
354impl DisplayedStream {
355 pub fn rich_text(
356 &self,
357 text_color: Color32,
358 style: &Style,
359 config: &SurferConfig,
360 layout_job: &mut LayoutJob,
361 ) {
362 RichText::new(format!(
363 "{}{}",
364 self.manual_name.as_ref().unwrap_or(&self.display_name),
365 "\n".repeat(self.rows - 1)
366 ))
367 .color(text_color)
368 .line_height(Some(config.layout.transactions_line_height))
370 .append_to(layout_job, style, FontSelection::Default, Align::Center);
371 }
372}
373
374#[derive(Serialize, Deserialize, Clone)]
375pub struct DisplayedGroup {
376 pub name: String,
377 pub color: Option<String>,
378 pub background_color: Option<String>,
379 pub content: Vec<DisplayedItemRef>,
380 pub is_open: bool,
381}
382
383impl DisplayedGroup {
384 pub fn rich_text(&self, text_color: Color32, style: &Style, layout_job: &mut LayoutJob) {
385 RichText::new(self.name.clone())
386 .color(text_color)
387 .append_to(layout_job, style, FontSelection::Default, Align::Center);
388 }
389}
390
391impl DisplayedItem {
392 #[must_use]
393 pub fn color(&self) -> Option<&str> {
394 match self {
395 DisplayedItem::Variable(variable) => variable.color.as_deref(),
396 DisplayedItem::Divider(divider) => divider.color.as_deref(),
397 DisplayedItem::Marker(marker) => marker.color.as_deref(),
398 DisplayedItem::TimeLine(timeline) => timeline.color.as_deref(),
399 DisplayedItem::Placeholder(_) => None,
400 DisplayedItem::Stream(stream) => stream.color.as_deref(),
401 DisplayedItem::Group(group) => group.color.as_deref(),
402 }
403 }
404
405 pub fn set_color(&mut self, color_name: &Option<String>) {
406 match self {
407 DisplayedItem::Variable(variable) => variable.color.clone_from(color_name),
408 DisplayedItem::Divider(divider) => divider.color.clone_from(color_name),
409 DisplayedItem::Marker(marker) => marker.color.clone_from(color_name),
410 DisplayedItem::TimeLine(timeline) => timeline.color.clone_from(color_name),
411 DisplayedItem::Placeholder(placeholder) => placeholder.color.clone_from(color_name),
412 DisplayedItem::Stream(stream) => stream.color.clone_from(color_name),
413 DisplayedItem::Group(group) => group.color.clone_from(color_name),
414 }
415 }
416
417 #[must_use]
418 pub fn name(&self) -> String {
419 match self {
420 DisplayedItem::Variable(variable) => variable
421 .manual_name
422 .as_ref()
423 .unwrap_or(&variable.display_name)
424 .clone(),
425 DisplayedItem::Divider(divider) => divider
426 .name
427 .as_ref()
428 .unwrap_or(&DEFAULT_DIVIDER_NAME.to_string())
429 .clone(),
430 DisplayedItem::Marker(marker) => marker.marker_name(),
431 DisplayedItem::TimeLine(timeline) => timeline
432 .name
433 .as_ref()
434 .unwrap_or(&DEFAULT_TIMELINE_NAME.to_string())
435 .clone(),
436 DisplayedItem::Placeholder(placeholder) => placeholder
437 .manual_name
438 .as_ref()
439 .unwrap_or(&placeholder.display_name)
440 .clone(),
441 DisplayedItem::Stream(stream) => stream
442 .manual_name
443 .as_ref()
444 .unwrap_or(&stream.display_name)
445 .clone(),
446 DisplayedItem::Group(group) => group.name.clone(),
447 }
448 }
449
450 pub fn add_to_layout_job(
452 &self,
453 color: Color32,
454 style: &Style,
455 layout_job: &mut LayoutJob,
456 field: Option<&FieldRef>,
457 config: &SurferConfig,
458 ) {
459 match self {
460 DisplayedItem::Variable(_) => {
461 let name = field
462 .and_then(|f| f.field.last())
463 .cloned()
464 .unwrap_or_else(|| self.name());
465 RichText::new(name)
466 .color(color)
467 .line_height(Some(
468 config.layout.waveforms_line_height * self.height_scaling_factor(),
469 ))
470 .append_to(layout_job, style, FontSelection::Default, Align::Center);
471 }
472 DisplayedItem::TimeLine(_) | DisplayedItem::Divider(_) => {
473 RichText::new(self.name()).color(color).italics().append_to(
474 layout_job,
475 style,
476 FontSelection::Default,
477 Align::Center,
478 );
479 }
480 DisplayedItem::Marker(marker) => {
481 marker.rich_text(color, style, layout_job);
482 }
483 DisplayedItem::Placeholder(placeholder) => {
484 let s = placeholder
485 .manual_name
486 .as_ref()
487 .unwrap_or(&placeholder.display_name);
488 RichText::new("Not available: ".to_owned() + s)
489 .color(color)
490 .italics()
491 .append_to(layout_job, style, FontSelection::Default, Align::Center);
492 }
493 DisplayedItem::Stream(stream) => {
494 RichText::new(format!("{}{}", self.name(), "\n".repeat(stream.rows - 1)))
495 .color(color)
496 .line_height(Some(config.layout.transactions_line_height))
497 .append_to(layout_job, style, FontSelection::Default, Align::Center);
498 }
499 DisplayedItem::Group(group) => {
500 group.rich_text(color, style, layout_job);
501 }
502 }
503 }
504
505 pub fn set_name(&mut self, name: Option<String>) {
506 match self {
507 DisplayedItem::Variable(variable) => {
508 variable.manual_name = name;
509 }
510 DisplayedItem::Divider(divider) => {
511 divider.name = name;
512 }
513 DisplayedItem::Marker(marker) => {
514 marker.name = name;
515 }
516 DisplayedItem::TimeLine(timeline) => {
517 timeline.name = name;
518 }
519 DisplayedItem::Placeholder(placeholder) => {
520 placeholder.manual_name = name;
521 }
522 DisplayedItem::Stream(stream) => {
523 stream.manual_name = name;
524 }
525 DisplayedItem::Group(group) => {
526 group.name = name.unwrap_or_default();
527 }
528 }
529 }
530
531 #[must_use]
532 pub fn has_overwritten_name(&self) -> bool {
533 match self {
534 DisplayedItem::Variable(variable) => variable.manual_name.is_some(),
535 DisplayedItem::Placeholder(placeholder) => placeholder.manual_name.is_some(),
536 DisplayedItem::Stream(stream) => stream.manual_name.is_some(),
537 DisplayedItem::Divider(_)
538 | DisplayedItem::Marker(_)
539 | DisplayedItem::TimeLine(_)
540 | DisplayedItem::Group(_) => false,
541 }
542 }
543
544 #[must_use]
545 pub fn background_color(&self) -> Option<&str> {
546 match self {
547 DisplayedItem::Variable(variable) => variable.background_color.as_deref(),
548 DisplayedItem::Divider(divider) => divider.background_color.as_deref(),
549 DisplayedItem::Marker(marker) => marker.background_color.as_deref(),
550 DisplayedItem::TimeLine(timeline) => timeline.background_color.as_deref(),
551 DisplayedItem::Placeholder(_) => None,
552 DisplayedItem::Stream(stream) => stream.background_color.as_deref(),
553 DisplayedItem::Group(group) => group.background_color.as_deref(),
554 }
555 }
556
557 pub fn set_background_color(&mut self, color_name: &Option<String>) {
558 match self {
559 DisplayedItem::Variable(variable) => {
560 variable.background_color.clone_from(color_name);
561 }
562 DisplayedItem::Divider(divider) => {
563 divider.background_color.clone_from(color_name);
564 }
565 DisplayedItem::Marker(marker) => {
566 marker.background_color.clone_from(color_name);
567 }
568 DisplayedItem::TimeLine(timeline) => {
569 timeline.background_color.clone_from(color_name);
570 }
571 DisplayedItem::Placeholder(placeholder) => {
572 placeholder.background_color.clone_from(color_name);
573 }
574 DisplayedItem::Stream(stream) => {
575 stream.background_color.clone_from(color_name);
576 }
577 DisplayedItem::Group(group) => {
578 group.background_color.clone_from(color_name);
579 }
580 }
581 }
582
583 #[must_use]
584 pub fn height_scaling_factor(&self) -> f32 {
585 match self {
586 DisplayedItem::Variable(variable) => variable.height_scaling_factor,
587 DisplayedItem::Placeholder(placeholder) => placeholder.height_scaling_factor,
588 _ => None,
589 }
590 .unwrap_or(1.0)
591 }
592
593 pub fn set_height_scaling_factor(&mut self, scale: f32) {
594 match self {
595 DisplayedItem::Variable(variable) => variable.height_scaling_factor = Some(scale),
596 DisplayedItem::Placeholder(placeholder) => {
597 placeholder.height_scaling_factor = Some(scale);
598 }
599 _ => {}
600 }
601 }
602}