rsnaker/graphics/menus/
utils_layout.rs

1use crate::graphics::menus::retro_parameter_table::generic_logic::RowData;
2use crate::graphics::menus::retro_parameter_table::generic_style::DEFAULT_ITEM_HEIGHT;
3use ratatui::layout::{Constraint, Direction, Layout, Rect};
4use ratatui::prelude::{Color, Style};
5use ratatui::widgets::{Block, Paragraph};
6use ratatui::Frame;
7use unicode_segmentation::UnicodeSegmentation;
8
9/// Creates a vertically centered rectangle within the given area with the specified number of lines.
10#[must_use]
11pub fn frame_vertically_centered_rect(area: Rect, lines: usize) -> Rect {
12    // Auto center vertically, elsewhere could be manually calculated with y offset
13    // by taking height from frame.area
14    Layout::default()
15        .direction(Direction::Vertical)
16        .constraints([
17            Constraint::Fill(1),                                          // top space
18            Constraint::Length(u16::try_from(lines).unwrap_or(u16::MAX)), // content
19            Constraint::Fill(1),                                          // bottom space
20        ])
21        // take second rect to render content
22        .split(area)[1]
23    // if we want to do manual center horizontally also
24    /*Layout::default()
25    .direction(Direction::Horizontal)
26    .constraints([
27        Constraint::Fill(1),                                            // top space
28        Constraint::Length(lines.lines().last().unwrap().len() as u16), // content
29        Constraint::Fill(1),                                            // bottom space
30    ])
31    // take second rect to render content
32    .split(vertical_layout)[1]*/
33}
34/// Render a horizontally and vertically centered paragraph with text and color style
35/// With the game external border set to the same color as the text
36pub fn render_full_centered_paragraph(frame: &mut Frame, text: &'static str, color: Option<Color>) {
37    let area = frame_vertically_centered_rect(frame.area(), text.lines().count());
38    // ensure that all cells under the popup are cleared to avoid leaking content: no useful, and ugly
39    //frame.render_widget(Clear, area);
40    frame.render_widget(
41        Paragraph::new(text)
42            //.style(Style::default().fg(color))
43            .alignment(ratatui::layout::Alignment::Center),
44        area,
45    );
46    // Set all screen items in the same color as the menu.
47    // Applying style break position of emojis
48    // even by previously setting a full style to item to avoid them to inherit the overall one
49    // so keep global style to the final situations without the snake moving after
50    if let Some(color) = color {
51        frame.render_widget(
52            Block::default().style(Style::default().fg(color)),
53            frame.area(),
54        );
55    }
56}
57#[must_use]
58pub fn constraint_length_from_widths(column_widths: &[u16]) -> Vec<Constraint> {
59    let mut constraints = vec![];
60    // A little fun with iterator, and peekable to foresee the future
61    //peekable show the next element without advancing the iterator
62    let mut iter = column_widths.iter().peekable();
63    //for each element, add a constraint
64    //  to have some fun with a destructuring pattern and while let
65    while let Some(&size) = iter.next() {
66        // if there is still more elements after
67        if iter.peek().is_some() {
68            constraints.push(Constraint::Length(size));
69        } else {
70            //for the last element, add all the remaining space
71            constraints.push(Constraint::Length(size + 1));
72        }
73    }
74    constraints
75}
76#[allow(clippy::cast_possible_truncation)]
77#[must_use]
78pub fn calculate_max_column_widths(rows: &[RowData], headers: &[String]) -> Vec<u16> {
79    // Initialize with header widths, +1 for header separator
80    let mut column_widths: Vec<u16> = headers
81        .iter()
82        .map(|h| h.as_str().graphemes(true).count() as u16 + 1)
83        .collect();
84    let column_len = column_widths.len();
85    // Update with row data widths
86    for row in rows {
87        let cell_widths = row.get_cell_widths();
88        for (i, &width) in cell_widths.iter().enumerate() {
89            if width as u16 > column_widths[i] {
90                column_widths[i % column_len] = width as u16;
91            }
92        }
93    }
94
95    column_widths
96}
97#[must_use]
98pub fn calculate_sum_inner_row_heights(rows: &[RowData]) -> usize {
99    rows.iter()
100        .map(|r| {
101            // Get the maximum height of the cells in the current row.
102            // max() returns an Option<&usize> because it operates on an iterator of references.
103            // *The result must be dereferenced to get the usize value before falling back
104            // to a default or summing.*
105            r.get_cell_heights()
106                .iter()
107                .max()
108                .copied() // Dereference the &usize to usize
109                .unwrap_or(DEFAULT_ITEM_HEIGHT) // Fall back to DEFAULT_ITEM_HEIGHT if the row is empty
110        })
111        .sum::<usize>()
112}