rsnaker/graphics/menus/retro_parameter_table/
generic_logic.rs1use crate::controls::playing_input::has_no_modifiers;
2use crate::graphics::menus::retro_parameter_table::generic_style::{
3 DISPLAY_CELL_OUT_SPACE, ScrollBarCustomRetroStyle, TableCustomRetroStyle, get_formated_footer,
4};
5use crate::graphics::menus::utils_layout::{
6 calculate_max_column_widths, calculate_sum_inner_row_heights, constraint_length_from_widths,
7};
8use crossterm::event;
9use crossterm::event::{Event, KeyCode, KeyEventKind};
10use ratatui::widgets::FrameExt;
11use ratatui::{
12 DefaultTerminal, Frame,
13 layout::{Constraint, Layout},
14 widgets::Paragraph,
15};
16use unicode_segmentation::UnicodeSegmentation;
17
18pub trait ActionParameter {
19 fn apply_and_save(&mut self, rows: &[RowData], current_preset: Option<u16>);
20}
21#[derive(Clone)]
22pub struct FooterData {
23 pub symbol: String,
24 pub text: String,
25 pub value: Option<u16>,
26}
27
28pub struct ActionInputs<'a> {
29 pub key: Vec<KeyCode>,
30 pub action: Vec<TableParameterAction<'a>>,
31}
32#[allow(clippy::type_complexity)]
33pub enum TableParameterAction<'a> {
34 NextValue,
35 PreviousValue,
36 NextRow,
37 PreviousRow,
38 Quit,
40 ApplyAndSave(&'a mut dyn ActionParameter),
42 LoadPreset(
46 u16,
47 fn(u16) -> (Option<Vec<RowData>>, Option<Vec<FooterData>>),
48 ),
49 Randomize(fn(Option<u16>, &[RowData]) -> Vec<RowData>),
50}
51
52#[derive(Clone)]
54pub enum CellValue {
55 Text(String),
57 Options {
59 option_name: String,
60 values: Vec<String>,
61 index: usize,
62 index_ini: usize,
63 },
64}
65impl CellValue {
66 #[must_use]
67 pub fn new(text: String) -> Self {
68 Self::Text(text)
69 }
70 #[must_use]
71 pub fn new_with_options(option_name: String, values: Vec<String>, index: usize) -> Self {
72 Self::Options {
73 option_name,
74 values,
75 index,
76 index_ini: index,
77 }
78 }
79 fn next_value(&mut self) {
80 if let CellValue::Options { values, index, .. } = self {
81 *index = (*index + 1) % values.len();
82 }
83 }
84
85 fn previous_value(&mut self) {
86 if let CellValue::Options { values, index, .. } = self {
87 let max = values.len();
88 *index = (*index + max.saturating_sub(1)) % max;
89 }
90 }
91 fn width(&self) -> usize {
92 match self {
93 CellValue::Options { values, .. } => {
94 let max = values
95 .iter()
96 .map(|v| v.as_str().graphemes(true).count())
97 .max()
98 .unwrap_or(0);
99 max + DISPLAY_CELL_OUT_SPACE
102 }
103 CellValue::Text(v) => v.split('\n').map(|s| s.chars().count()).max().unwrap_or(0),
105 }
106 }
107 fn height(&self) -> usize {
108 match self {
109 CellValue::Options { values, .. } => values
110 .iter()
111 .map(|v| v.split('\n').count())
112 .max()
113 .unwrap_or(0),
114 CellValue::Text(v) => v.split('\n').count(),
116 }
117 }
118}
119
120#[derive(Clone)]
125pub struct RowData {
126 pub cells: Vec<CellValue>,
128}
129
130impl RowData {
131 #[must_use]
132 pub fn new(cells: Vec<CellValue>) -> Self {
133 Self { cells }
134 }
135 pub(crate) fn get_cell_widths(&self) -> Vec<usize> {
136 self.cells.iter().map(CellValue::width).collect()
137 }
138 pub(crate) fn get_cell_heights(&self) -> Vec<usize> {
139 self.cells.iter().map(CellValue::height).collect()
140 }
141 fn next_cell_value(&mut self) {
142 for c in &mut self.cells {
143 c.next_value();
144 }
145 }
146 fn previous_cell_value(&mut self) {
147 for c in &mut self.cells {
148 c.previous_value();
149 }
150 }
151}
152pub struct GenericMenu<'a> {
154 table_custom: TableCustomRetroStyle<'a>,
155 scrollbar: ScrollBarCustomRetroStyle<'a>,
156 selected_row: usize,
157 info_footer: Paragraph<'a>,
158 info_footer_data: Vec<FooterData>,
159 vertical_layout: Layout,
160 current_preset: Option<u16>,
161}
162
163impl<'a> GenericMenu<'a> {
164 #[must_use]
165 pub fn new(
166 rows: Vec<RowData>,
167 headers: &[String],
168 info_footer: Vec<FooterData>,
169 current_preset: Option<u16>,
170 ) -> Self {
171 let column_widths = calculate_max_column_widths(&rows, headers);
173 let constraints = constraint_length_from_widths(&column_widths);
174 let row_sum_height = calculate_sum_inner_row_heights(&rows);
175 let vertical_layout = Layout::vertical([
176 Constraint::Min(1),
177 Constraint::Length(
178 u16::try_from(headers.len()).expect("too much headers to store :p "),
179 ),
180 ]);
181 Self {
182 table_custom: TableCustomRetroStyle::new(headers, rows, 0, constraints),
183 scrollbar: ScrollBarCustomRetroStyle::new(row_sum_height),
184 selected_row: 0,
185 info_footer: get_formated_footer(&info_footer),
186 info_footer_data: info_footer,
187 vertical_layout,
188 current_preset,
189 }
190 }
191
192 pub fn next_row(&mut self) {
193 let i = match self.table_custom.state.selected() {
194 Some(i) => (i + 1) % self.table_custom.rows.len(),
195 None => 0,
196 };
197 self.table_custom.state.select(Some(i));
198 self.selected_row = i;
199 self.scrollbar.scroll_state =
200 self.scrollbar
201 .scroll_state
202 .position(calculate_sum_inner_row_heights(
203 &self.table_custom.rows[..i],
204 ));
205 }
206
207 pub fn previous_row(&mut self) {
208 let i = match self.table_custom.state.selected() {
209 Some(i) => (i + self.table_custom.rows.len() - 1) % self.table_custom.rows.len(),
210 None => 0,
211 };
212 self.table_custom.state.select(Some(i));
213 self.selected_row = i;
214 self.scrollbar.scroll_state =
215 self.scrollbar
216 .scroll_state
217 .position(calculate_sum_inner_row_heights(
218 &self.table_custom.rows[..i],
219 ));
220 }
221
222 pub fn next_parameter_value(&mut self) {
223 if let Some(row) = self.table_custom.rows.get_mut(self.selected_row) {
224 row.next_cell_value();
225 }
226 }
227
228 pub fn previous_parameter_value(&mut self) {
229 if let Some(row) = self.table_custom.rows.get_mut(self.selected_row) {
230 row.previous_cell_value();
231 }
232 }
233
234 pub fn run(
235 &mut self,
236 mut actions_inputs: Vec<ActionInputs<'a>>,
237 terminal: &mut DefaultTerminal,
238 ) {
239 loop {
240 terminal.draw(|frame| self.draw(frame)).unwrap();
241 if let Event::Key(key) = event::read().unwrap()
242 && key.kind == KeyEventKind::Press
243 && has_no_modifiers(&key)
244 {
245 for action_input in &mut actions_inputs {
246 for key_code in action_input.key.clone() {
247 if key_code == key.code {
248 for unitary_tp_action in &mut action_input.action {
249 match unitary_tp_action {
250 TableParameterAction::NextValue => {
251 self.next_parameter_value();
252 }
253 TableParameterAction::PreviousValue => {
254 self.previous_parameter_value();
255 }
256 TableParameterAction::NextRow => {
257 self.next_row();
258 }
259 TableParameterAction::PreviousRow => {
260 self.previous_row();
261 }
262 TableParameterAction::ApplyAndSave(action) => {
263 action.apply_and_save(
264 &self.table_custom.rows,
265 self.current_preset,
266 );
267 }
268 TableParameterAction::Quit => {
269 return;
270 }
271 TableParameterAction::LoadPreset(index, loader) => {
272 let (new_rows, new_footer) = loader(*index);
273 let new_rows =
274 new_rows.unwrap_or(self.table_custom.rows.clone());
275 let new_footer =
276 new_footer.unwrap_or(self.info_footer_data.clone());
277 *self = Self::new(
280 new_rows,
281 &self.table_custom.headers,
282 new_footer,
283 Some(*index),
284 );
285 }
286 TableParameterAction::Randomize(randomizer) => {
287 let new_rows = randomizer(
288 self.current_preset,
289 &self.table_custom.rows,
290 );
291 *self = Self::new(
294 new_rows,
295 &self.table_custom.headers,
296 self.info_footer_data.clone(),
297 self.current_preset,
298 );
299 }
300 } } } } } } } }
308
309 fn draw(&mut self, frame: &mut Frame) {
310 let rects = self.vertical_layout.split(frame.area());
311 self.table_custom
314 .update_table_color_background(self.selected_row);
315 self.table_custom.render(frame, rects[0]);
316 frame.render_stateful_widget(
320 self.scrollbar.widget.clone(),
321 rects[0].inner(self.scrollbar.margin),
322 &mut self.scrollbar.scroll_state,
323 );
324 frame.render_widget_ref(&self.info_footer, rects[1]);
325 }
326}
327
328#[must_use]
329pub fn get_default_action_input<'a>() -> Vec<ActionInputs<'a>> {
330 vec![
331 ActionInputs {
332 key: vec![KeyCode::Down],
333 action: vec![TableParameterAction::NextRow],
334 },
335 ActionInputs {
336 key: vec![KeyCode::Up],
337 action: vec![TableParameterAction::PreviousRow],
338 },
339 ActionInputs {
340 key: vec![KeyCode::Right],
341 action: vec![TableParameterAction::NextValue],
342 },
343 ActionInputs {
344 key: vec![KeyCode::Left],
345 action: vec![TableParameterAction::PreviousValue],
346 },
347 ActionInputs {
348 key: vec![KeyCode::Esc, KeyCode::Tab],
349 action: vec![TableParameterAction::Quit],
350 },
351 ]
352}