rsnaker/graphics/menus/retro_parameter_table/
generic_style.rs1use crate::graphics::menus::retro_parameter_table::generic_logic::{
2 CellValue, FooterData, RowData,
3};
4use ratatui::layout::{Constraint, Margin, Rect};
5use ratatui::prelude::{Color, Line, Modifier, Span, Style, Text};
6use ratatui::widgets::{
7 Block, BorderType, Borders, Cell, FrameExt, HighlightSpacing, Paragraph, Row, Scrollbar,
8 ScrollbarOrientation, ScrollbarState, Table, TableState,
9};
10use ratatui::Frame;
11use std::fmt;
12
13pub const DEFAULT_ITEM_HEIGHT: usize = 1;
15
16const RETRO_PURPLE: Color = Color::Rgb(50, 50, 150); const RETRO_GREY: Color = Color::Rgb(128, 128, 128);
19const RETRO_YELLOW: Color = Color::Rgb(255, 255, 0);
20const RETRO_DARK_BLUE: Color = Color::Rgb(20, 20, 40);
21const RETRO_ORANGE: Color = Color::Rgb(255, 165, 0);
22const RETRO_BLUE: Color = Color::Rgb(0, 191, 255);
23const RETRO_GOLD: Color = Color::Rgb(212, 175, 55);
24const HIGHLIGHT_SYMBOL_LEFT: &str = "► ";
26const HIGHLIGHT_SYMBOL_RIGHT: &str = " ◄";
27pub const DISPLAY_CELL_OUT_SPACE: usize = 6;
28
29impl fmt::Display for CellValue {
30 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 match self {
32 CellValue::Text(text) => write!(f, "{text}"),
33 CellValue::Options {
34 values,
35 index,
36 index_ini,
37 ..
38 } => {
39 let fallback = "Bad index".to_string();
40 let content = values.get(*index).unwrap_or(&fallback);
41 if index == index_ini {
42 write!(f, "[ {content} ]")
43 } else {
44 write!(f, "『 {content} 』")
45 }
46 }
47 }
48 }
49}
50pub(crate) struct ScrollBarCustomRetroStyle<'a> {
51 pub scroll_state: ScrollbarState,
52 pub margin: Margin,
53 pub widget: Scrollbar<'a>,
54}
55impl ScrollBarCustomRetroStyle<'_> {
56 pub fn new(row_sum_height: usize) -> Self {
57 Self {
58 scroll_state: ScrollbarState::new(row_sum_height),
59 margin: Margin {
60 vertical: 1,
61 horizontal: 1,
62 },
63 widget: Scrollbar::default()
64 .orientation(ScrollbarOrientation::VerticalRight)
65 .begin_symbol(Some("▲"))
66 .end_symbol(Some("▼"))
67 .track_symbol(Some("│"))
68 .thumb_symbol("█")
69 .thumb_style(Style::default().fg(Color::DarkGray))
70 .track_style(Style::default().fg(Color::Gray))
71 .begin_style(Style::default().fg(Color::Red))
72 .end_style(Style::default().fg(Color::Red)),
73 }
74 }
75}
76pub(crate) struct TableCustomRetroStyle<'a> {
77 pub(crate) state: TableState,
78 table: Table<'a>,
79 pub(crate) rows: Vec<RowData>,
80 pub(crate) headers: Vec<String>,
81}
82impl<'a> TableCustomRetroStyle<'a> {
83 pub fn new(
84 headers: &[String],
85 rows: Vec<RowData>,
86 selected_row: usize,
87 constraints: Vec<Constraint>,
88 ) -> Self {
89 let headers_vec = headers.to_vec();
90 let header = Row::new(headers.iter().map(|h| Cell::from(h.clone())))
92 .style(Style::default().fg(Color::Black).bg(RETRO_GREY))
93 .height(1);
94
95 let mut highlight_symbols = vec![HIGHLIGHT_SYMBOL_LEFT.into()];
97 for _ in 1..headers.len() - 1 {
100 highlight_symbols.push("".into());
101 }
102 highlight_symbols.push(HIGHLIGHT_SYMBOL_RIGHT.into());
103
104 Self {
106 state: TableState::default().with_selected(0),
107 table: Table::new(
108 TableCustomRetroStyle::get_rows_color(&rows, selected_row),
109 constraints,
110 )
111 .header(header)
112 .row_highlight_style(Style::default().bg(RETRO_PURPLE).fg(Color::White))
113 .highlight_symbol(Text::from(highlight_symbols))
114 .highlight_spacing(HighlightSpacing::Always)
115 .block(
116 Block::default()
117 .borders(Borders::ALL)
118 .border_type(BorderType::Double)
119 .border_style(Style::default().fg(Color::DarkGray)),
120 ),
121 rows,
122 headers: headers_vec,
123 }
124 }
125 pub fn render(&mut self, frame: &mut Frame, area: Rect) {
126 frame.render_stateful_widget_ref(&self.table, area, &mut self.state);
127 }
128 fn get_rows_color(rows: &[RowData], selected_row: usize) -> impl IntoIterator<Item = Row<'a>> {
129 rows.iter()
131 .enumerate()
132 .map(|(index_row, row_data)| {
133 let row_style = if index_row % 2 == 0 {
134 Style::default().bg(Color::Black)
135 } else {
136 Style::default().bg(RETRO_DARK_BLUE)
137 };
138 let mut max_lines_for_this_row: u16 = 1;
139 let cells = row_data.cells.iter().map(|cell| {
140 let content = cell.to_string();
141 let cell_style = if selected_row == index_row {
142 Style::default()
143 .fg(RETRO_YELLOW)
144 .add_modifier(Modifier::BOLD)
145 } else if let CellValue::Options {
146 index, index_ini, ..
147 } = cell
148 {
149 if index == index_ini {
150 Style::default().fg(RETRO_ORANGE)
151 } else {
152 Style::default().fg(RETRO_YELLOW)
153 }
154 } else {
155 Style::default().fg(RETRO_BLUE)
156 };
157 #[allow(clippy::cast_possible_truncation)]
159 let max = content.lines().count() as u16;
160 if max > max_lines_for_this_row {
161 max_lines_for_this_row = max;
162 }
163 Cell::from(Text::from(content)).style(cell_style)
164 });
165
166 Row::new(cells)
167 .style(row_style)
168 .height(max_lines_for_this_row)
169 })
170 .collect::<Vec<Row<'a>>>()
171 }
172 pub fn update_table_color_background(&mut self, selected_row: usize) {
173 let new_rows = TableCustomRetroStyle::get_rows_color(&self.rows, selected_row);
174 self.table = self.table.clone().rows(new_rows);
176 }
177}
178#[must_use]
179pub fn get_formated_footer<'a>(data: &[FooterData]) -> Paragraph<'a> {
180 let mut info_spans = Vec::with_capacity(data.len() + 1);
182
183 let mut iter = data.iter().peekable();
185 while let Some(d) = iter.next() {
186 info_spans.push(Span::styled(
187 format!("({})", d.symbol),
188 Style::default().fg(RETRO_GOLD).add_modifier(Modifier::BOLD),
189 ));
190 info_spans.push(Span::styled(
191 format!(" {} ", d.text),
192 Style::default().fg(Color::White),
193 ));
194 if let Some(value) = d.value {
195 let red_style = Style::default().fg(Color::Red); info_spans.push(Span::styled("[".to_string(), red_style));
197 info_spans.push(Span::styled(
198 value.to_string(),
199 Style::default().fg(Color::White), ));
201 info_spans.push(Span::styled("]".to_string(), red_style));
202 }
203 if iter.peek().is_some() {
205 info_spans.push(Span::styled(
206 " | ".to_string(),
207 Style::default().fg(Color::White),
208 ));
209 }
210 }
211 Paragraph::new(Line::from(info_spans)).centered()
212}