rsnaker/graphics/menus/
main_menu.rs1use crate::graphics::menus::edge_snake::EdgeSnake;
2use crate::graphics::menus::messages::{CONTROLS_TABLE, SNAKE_LOGO};
3use crate::graphics::menus::utils_layout::frame_vertically_centered_rect;
4use clap::ValueEnum;
5use ratatui::style::Stylize;
6use ratatui::style::{Color, Modifier, Style};
7use ratatui::text::{Line, Span, Text};
8use ratatui::widgets::{Block, BorderType, Paragraph};
9use ratatui::{DefaultTerminal, Frame};
10use std::cmp::PartialEq;
11use std::sync::{Mutex, OnceLock};
12use unicode_segmentation::UnicodeSegmentation;
13
14static EDGE_SNAKE: OnceLock<Mutex<EdgeSnake>> = OnceLock::new();
18
19pub fn display_main_menu(
25 terminal: &mut DefaultTerminal,
26 selected_switch_menu_for_display_bracket: &SwitchMenu,
27) {
28 terminal
30 .draw(|frame| {
31 let area = frame.area();
32 big_snake_menu(frame, selected_switch_menu_for_display_bracket);
33 frame.render_widget(Block::bordered().border_type(BorderType::Double), area);
35
36 let mut edge_snake = EDGE_SNAKE
39 .get_or_init(|| Mutex::new(EdgeSnake::new()))
40 .lock()
41 .unwrap();
42 edge_snake.update(&area);
43 edge_snake.render(frame, &area);
44 })
45 .expect("Unusable terminal render");
46}
47
48fn big_snake_menu(frame: &mut Frame, to_display_switch: &SwitchMenu) {
49 let mut lines = vec![];
50 for logo_line in SNAKE_LOGO.lines() {
52 lines.push(Line::from(logo_line));
53 }
54
55 lines.push(Line::from(get_button_span(to_display_switch)));
57
58 for table_line in CONTROLS_TABLE.lines() {
60 lines.push(Line::from(table_line));
61 }
62
63 lines.push(Line::from("Have a good 🐍 game ! 🎮".green()));
65 let nb_lines = lines.len();
66
67 frame.render_widget(
68 Paragraph::new(Text::from(lines)).centered(),
69 frame_vertically_centered_rect(frame.area(), nb_lines),
70 );
71}
72pub struct Button {
74 pub name: &'static str,
75 pub selected: bool,
76}
77
78impl Button {
79 #[must_use]
81 pub const fn new(name: &'static str) -> Self {
82 Self {
83 name,
84 selected: false,
85 }
86 }
87
88 pub fn selected(&mut self, selected: bool) {
90 self.selected = selected;
91 }
92
93 #[must_use]
95 pub fn to_spans(&self) -> Vec<Span<'static>> {
96 if self.selected {
97 vec![
98 Span::styled(" [ ", Style::default().fg(Color::Red)).add_modifier(Modifier::BOLD),
99 Span::raw(self.name),
100 Span::styled(
101 " ] ",
102 Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),
103 ),
104 ]
105 } else {
106 let (first, rest) = match self.name.graphemes(true).next() {
107 Some(grapheme) => {
108 let rest = &self.name[grapheme.len()..];
110 (grapheme, rest) }
112 None => ("", ""), };
114 vec![
115 Span::raw("["),
116 Span::styled(first, Style::default().fg(Color::Yellow)),
118 Span::raw(format!("{rest}]")),
119 ]
120 }
121 }
122}
123
124#[derive(PartialEq, ValueEnum, Clone)]
126pub enum SwitchMenu {
127 Highs,
128 Fruits,
129 Speed,
130 Run,
131 Parameters,
132 Doc,
133 Main,
134}
135const BUTTONS: [(SwitchMenu, Button); 6] = [
136 (SwitchMenu::Highs, Button::new("Highs💯")),
137 (SwitchMenu::Fruits, Button::new("Fruit")),
138 (SwitchMenu::Speed, Button::new("Speed")),
139 (SwitchMenu::Run, Button::new("Run")),
140 (SwitchMenu::Parameters, Button::new("Edit⚙️")),
141 (SwitchMenu::Doc, Button::new("Docℹ️")),
142];
143fn get_button_span(selected: &SwitchMenu) -> Vec<Span<'static>> {
145 let mut vec_line_button = vec![Span::raw("↔")];
146 for (menu, mut button) in BUTTONS {
148 button.selected(selected == &menu);
149 vec_line_button.extend(button.to_spans());
150 }
151 vec_line_button
152}