1use ratatui::{
2 layout::Rect,
3 style::{Color, Modifier, Style},
4 text::{Line, Span},
5 widgets::{Block, Borders, Paragraph},
6};
7
8use crate::tui::app::App;
9use crate::tui::chart;
10
11pub struct BrailleChart<'a> {
13 pub app: &'a App,
14 pub metric: &'a str,
15 pub focused: bool,
16}
17
18impl<'a> BrailleChart<'a> {
19 pub fn render_to_paragraph(&self, area: Rect) -> Paragraph<'a> {
20 let border_style = if self.focused {
21 Style::default()
22 .fg(Color::Cyan)
23 .add_modifier(Modifier::BOLD)
24 } else {
25 Style::default().fg(Color::DarkGray)
26 };
27
28 let block = Block::default()
29 .title(format!(" {} ", self.metric))
30 .borders(Borders::ALL)
31 .border_style(border_style);
32
33 let inner_width = area.width.saturating_sub(2) as usize;
35 let inner_height = area.height.saturating_sub(2) as usize;
36
37 if inner_width < 10 || inner_height < 2 {
38 return Paragraph::new("(too small)").block(block);
39 }
40
41 let lines: Vec<Line<'a>> = if let Some(exp_name) = self.app.experiment_names.first() {
43 let data = self.app.metric_series(exp_name, self.metric);
44 let rendered = chart::plot_series(&data, inner_width, inner_height, self.metric);
45 rendered
46 .into_iter()
47 .map(|s| Line::from(Span::raw(s)))
48 .collect()
49 } else {
50 vec![Line::from("No experiments")]
51 };
52
53 Paragraph::new(lines).block(block)
54 }
55}
56
57pub fn info_panel<'a>(app: &App, focused: bool) -> Paragraph<'a> {
59 let border_style = if focused {
60 Style::default()
61 .fg(Color::Cyan)
62 .add_modifier(Modifier::BOLD)
63 } else {
64 Style::default().fg(Color::DarkGray)
65 };
66
67 let block = Block::default()
68 .title(" Info ")
69 .borders(Borders::ALL)
70 .border_style(border_style);
71
72 let mut lines: Vec<Line> = Vec::new();
73
74 for exp_name in &app.experiment_names {
75 lines.push(Line::from(Span::styled(
76 format!("Experiment: {exp_name}"),
77 Style::default()
78 .fg(Color::White)
79 .add_modifier(Modifier::BOLD),
80 )));
81
82 if let Some(step) = app.current_step(exp_name) {
83 lines.push(Line::from(format!(" Step: {step}")));
84 }
85
86 let (m1, m2) = &app.chart_metrics;
88 if let Some(best) = app.best_metric(exp_name, m1) {
89 lines.push(Line::from(format!(" Best {m1}: {best:.4}")));
90 }
91 if let Some(best) = app.best_metric(exp_name, m2) {
92 lines.push(Line::from(format!(" Best {m2}: {best:.4}")));
93 }
94
95 lines.push(Line::from(""));
96 }
97
98 if !app.alerts.is_empty() {
100 lines.push(Line::from(Span::styled(
101 "Alerts:",
102 Style::default().fg(Color::Yellow),
103 )));
104 for alert in app.alerts.iter().rev().take(5) {
105 lines.push(Line::from(Span::styled(
106 format!(" ⚠ {alert}"),
107 Style::default().fg(Color::Yellow),
108 )));
109 }
110 }
111
112 lines.push(Line::from(""));
114 lines.push(Line::from(Span::styled(
115 "q:quit Tab:focus [/]:zoom",
116 Style::default().fg(Color::DarkGray),
117 )));
118
119 Paragraph::new(lines).block(block)
120}