1use 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#[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 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 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(); 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 .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 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}