rsnaker/graphics/menus/
main_menu.rs

1use crate::graphics::menus::messages::{CONTROLS_TABLE, SNAKE_LOGO};
2use crate::graphics::menus::utils_layout::frame_vertically_centered_rect;
3use clap::ValueEnum;
4use ratatui::style::Stylize;
5use ratatui::style::{Color, Modifier, Style};
6use ratatui::text::{Line, Span, Text};
7use ratatui::widgets::{Block, BorderType, Paragraph};
8use ratatui::{DefaultTerminal, Frame};
9use std::cmp::PartialEq;
10use std::thread::sleep;
11use std::time::Duration;
12use unicode_segmentation::UnicodeSegmentation;
13
14/// Print the wanted welcome screen controls
15/// Show Fruit and Speed menus alongside
16/// Sadly `slow_blink` and `fast_blink` are not rendered anymore on modern terminal...
17/// # Panics
18/// Will panic if no suitable terminal for displaying ios provided
19pub fn display_main_menu(
20    terminal: &mut DefaultTerminal,
21    selected_switch_menu_for_display_bracket: &SwitchMenu,
22) {
23    //terminal.clear().expect("Clearing terminal fail ");
24    terminal
25        .draw(|frame| {
26            let area = frame.area();
27            big_snake_menu(frame, selected_switch_menu_for_display_bracket);
28            //buttons_menu(frame, &app);
29            //set a border all around the terminal
30            frame.render_widget(Block::bordered().border_type(BorderType::Double), area);
31        })
32        .expect("Unusable terminal render");
33    sleep(Duration::from_millis(100));
34}
35
36fn big_snake_menu(frame: &mut Frame, to_display_switch: &SwitchMenu) {
37    let mut lines = vec![];
38    // Add logo lines
39    for logo_line in SNAKE_LOGO.lines() {
40        lines.push(Line::from(logo_line));
41    }
42
43    // Add navigation buttons
44    lines.push(Line::from(get_button_span(to_display_switch)));
45
46    // Add the control table
47    for table_line in CONTROLS_TABLE.lines() {
48        lines.push(Line::from(table_line));
49    }
50
51    // Add a greeting message
52    lines.push(Line::from("Have a good 🐍 game ! 🎮".green()));
53    let nb_lines = lines.len();
54    frame.render_widget(
55        //centered horizontally
56        Paragraph::new(Text::from(lines)).centered(),
57        frame_vertically_centered_rect(frame.area(), nb_lines),
58    );
59}
60/// Represents a button in the menu interface
61pub struct Button {
62    pub name: &'static str,
63    pub selected: bool,
64}
65
66impl Button {
67    /// Creates a new button with the given name and hotkey
68    #[must_use]
69    pub const fn new(name: &'static str) -> Self {
70        Self {
71            name,
72            selected: false,
73        }
74    }
75
76    /// Sets the selected state of the button
77    pub fn selected(&mut self, selected: bool) {
78        self.selected = selected;
79    }
80
81    /// Converts the button to a vector of spans for rendering
82    #[must_use]
83    pub fn to_spans(&self) -> Vec<Span<'static>> {
84        if self.selected {
85            vec![
86                Span::styled(" [ ", Style::default().fg(Color::Red)).add_modifier(Modifier::BOLD),
87                Span::raw(self.name),
88                Span::styled(
89                    " ] ",
90                    Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),
91                ),
92            ]
93        } else {
94            let (first, rest) = match self.name.graphemes(true).next() {
95                Some(grapheme) => {
96                    // A grapheme is a &str, so we can get its byte length with .len()
97                    let rest = &self.name[grapheme.len()..];
98                    (grapheme, rest) // Both are &str
99                }
100                None => ("", ""), // Handle empty string
101            };
102            vec![
103                Span::raw("["),
104                // Span::styled can take a &str directly
105                Span::styled(first, Style::default().fg(Color::Yellow)),
106                Span::raw(format!("{rest}]")),
107            ]
108        }
109    }
110}
111
112/// Option that can be selected from the main menu, using a lateral switcher
113#[derive(PartialEq, ValueEnum, Clone)]
114pub enum SwitchMenu {
115    Highs,
116    Fruits,
117    Speed,
118    Run,
119    Parameters,
120    Doc,
121    Main,
122}
123const BUTTONS: [(SwitchMenu, Button); 6] = [
124    (SwitchMenu::Highs, Button::new("Highs💯")),
125    (SwitchMenu::Fruits, Button::new("Fruit")),
126    (SwitchMenu::Speed, Button::new("Speed")),
127    (SwitchMenu::Run, Button::new("Run")),
128    (SwitchMenu::Parameters, Button::new("Edit⚙️")),
129    (SwitchMenu::Doc, Button::new("Docℹ️")),
130];
131/// Returns a vector of spans representing the button navigation menu
132fn get_button_span(selected: &SwitchMenu) -> Vec<Span<'static>> {
133    let mut vec_line_button = vec![Span::raw("↔")];
134    // Add each button to the menu, marking the selected one
135    for (menu, mut button) in BUTTONS {
136        button.selected(selected == &menu);
137        vec_line_button.extend(button.to_spans());
138    }
139    vec_line_button
140}