From c84d59a3086f88ad21e3d5911a37d1249ecc4a9c Mon Sep 17 00:00:00 2001 From: Abdulmujeeb Raji Date: Thu, 20 Feb 2025 10:05:10 +0000 Subject: [PATCH 1/3] Proposal: Enum Pattern for Game State in the Client --- client/src/main.rs | 141 ++++++++++++++++++++++++++++----------------- server/Cargo.lock | 7 +++ txcore/Cargo.lock | 7 +++ 3 files changed, 101 insertions(+), 54 deletions(-) create mode 100644 server/Cargo.lock create mode 100644 txcore/Cargo.lock diff --git a/client/src/main.rs b/client/src/main.rs index 219fdd0..af96326 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -30,16 +30,22 @@ const FOOTER_RIGHT_STYLE: Style = Style::new().fg(LIME.c400); fn main() -> Result<()> { color_eyre::install()?; let terminal = ratatui::init(); - let app_result = App::default().run(terminal); + let app_result = GameMode::default().run(terminal); ratatui::restore(); app_result } -struct App { - should_exit: bool, - menu: MenuList, +#[derive(Clone)] +enum GameMode { + Exit, + MainMenu(MenuList), + FortyL, + Blitz, + TxLadder, + Config, } +#[derive(Clone)] struct MenuList { items: Vec<&'static str>, state: ListState, @@ -53,16 +59,60 @@ impl FromIterator<&'static str> for MenuList { } } -impl Default for App { - fn default() -> Self { - Self { - should_exit: false, - menu: MenuList::from_iter(["40L", "Blitz", "txLadder", "Config"]), +impl MenuList { + fn handle_key(&self, current_mode: GameMode, key: KeyEvent) -> GameMode { + match key.code { + KeyCode::Char('q') | KeyCode::Esc => GameMode::Exit, + KeyCode::Char('j') | KeyCode::Down => self.select_next(), + KeyCode::Char('k') | KeyCode::Up => self.select_previous(), + KeyCode::Char('c') => { + if key.modifiers.contains(event::KeyModifiers::CONTROL) { + GameMode::Exit + } else { + current_mode + } + } + _ => current_mode } } + + fn select_next(&self) -> GameMode { + let mut menu_list = self.clone(); + menu_list.state.select_next(); + GameMode::MainMenu(menu_list) + } + + fn select_previous(&self) -> GameMode { + let mut menu_list = self.clone(); + menu_list.state.select_previous(); + GameMode::MainMenu(menu_list) + } + + fn render(&mut self, area: Rect, buf: &mut Buffer) { + let block = Block::new() + .title(Line::raw(" Game ").centered().style(GAME_HEADER_STYLE)) + .padding(Padding::symmetric(2, 1)) + .borders(Borders::ALL); + + let items: Vec<&'static str> = self.items.clone(); + + let list = List::new(items) + .block(block) + .highlight_style(SELECTED_STYLE) + .highlight_symbol(" ") + .highlight_spacing(HighlightSpacing::Always); + + StatefulWidget::render(list, area, buf, &mut self.state); + } } -impl App { +impl Default for GameMode { + fn default() -> Self { + GameMode::MainMenu(MenuList::from_iter(["40L", "Blitz", "txLadder", "Config"])) + } +} + +impl GameMode { fn render_header(&mut self, area: Rect, buf: &mut Buffer) { Block::new() .title(Line::raw(" txtris ").centered().style(HEADER_STYLE)) @@ -86,23 +136,6 @@ impl App { .render(area, buf); } - fn render_list(&mut self, area: Rect, buf: &mut Buffer) { - let block = Block::new() - .title(Line::raw(" Game ").centered().style(GAME_HEADER_STYLE)) - .padding(Padding::symmetric(2, 1)) - .borders(Borders::ALL); - - let items: Vec<&'static str> = self.menu.items.clone(); - - let list = List::new(items) - .block(block) - .highlight_style(SELECTED_STYLE) - .highlight_symbol(" ") - .highlight_spacing(HighlightSpacing::Always); - - StatefulWidget::render(list, area, buf, &mut self.menu.state); - } - fn render_profile(&mut self, area: Rect, buf: &mut Buffer) { let block = Block::new() .title( @@ -133,37 +166,31 @@ impl App { } fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> { - while !self.should_exit { - terminal.draw(|frame| frame.render_widget(&mut self, frame.area()))?; - if let Event::Key(key) = event::read()? { - self.handle_key(key); - }; + loop { + match self { + GameMode::Exit => { + break + } + _ => { + terminal.draw(|frame| frame.render_widget(&mut self, frame.area()))?; + if let Event::Key(key) = event::read()? { + self = self.handle_key(key); + }; + } + } } Ok(()) } - fn handle_key(&mut self, key: KeyEvent) { + fn handle_key(self, key: KeyEvent) -> GameMode { if key.kind != KeyEventKind::Press { - return; + return self } - match key.code { - KeyCode::Char('q') | KeyCode::Esc => self.should_exit = true, - KeyCode::Char('j') | KeyCode::Down => self.select_next(), - KeyCode::Char('k') | KeyCode::Up => self.select_previous(), - KeyCode::Char('c') => { - if key.modifiers.contains(event::KeyModifiers::CONTROL) { - self.should_exit = true; - } - } - _ => {} - } - } - fn select_next(&mut self) { - self.menu.state.select_next(); - } - fn select_previous(&mut self) { - self.menu.state.select_previous(); + match self { + GameMode::MainMenu(ref menu_list) => menu_list.handle_key(self.clone(), key), + _ => return self, + } } } @@ -175,7 +202,7 @@ fn center(area: Rect, horizontal: Constraint, vertical: Constraint) -> Rect { area } -impl Widget for &mut App { +impl Widget for &mut GameMode { fn render(self, area: Rect, buf: &mut Buffer) { let [header_area, main_area, footer_area] = Layout::vertical([ Constraint::Length(1), @@ -195,8 +222,14 @@ impl Widget for &mut App { .areas(center_area); self.render_header(header_area, buf); - self.render_profile(profile_area, buf); - self.render_list(list_outer_area, buf); + + match self { + GameMode::MainMenu(menu_list) => { + menu_list.render(list_outer_area, buf); + self.render_profile(profile_area, buf); + } + _ => {}, + } self.render_footer(footer_area, buf); } } diff --git a/server/Cargo.lock b/server/Cargo.lock new file mode 100644 index 0000000..f1b1e97 --- /dev/null +++ b/server/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "server" +version = "0.1.0" diff --git a/txcore/Cargo.lock b/txcore/Cargo.lock new file mode 100644 index 0000000..f1d6024 --- /dev/null +++ b/txcore/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "txcore" +version = "0.1.0" From 82a45eab0801f8d71f0616d98b2857226d935811 Mon Sep 17 00:00:00 2001 From: Abdulmujeeb Raji Date: Thu, 20 Feb 2025 10:41:29 +0000 Subject: [PATCH 2/3] split into modules --- client/src/game.rs | 96 +++++++++++++++++++ client/src/main.rs | 231 +-------------------------------------------- client/src/menu.rs | 143 ++++++++++++++++++++++++++++ 3 files changed, 243 insertions(+), 227 deletions(-) create mode 100644 client/src/game.rs create mode 100644 client/src/menu.rs diff --git a/client/src/game.rs b/client/src/game.rs new file mode 100644 index 0000000..708e411 --- /dev/null +++ b/client/src/game.rs @@ -0,0 +1,96 @@ +use color_eyre::Result; +use ratatui::{ + DefaultTerminal, + buffer::Buffer, + crossterm::event::{self, Event, KeyEvent, KeyEventKind}, + layout::{Constraint, Flex, Layout, Rect}, + widgets::Widget, +}; + +use crate::menu; + +#[derive(Clone)] +pub enum Mode { + Exit, + MainMenu(menu::OptionList), + FortyL, + Blitz, + TxLadder, + Config, +} + +impl Default for Mode { + fn default() -> Self { + Mode::MainMenu(menu::OptionList::from_iter(["40L", "Blitz", "txLadder", "Config"])) + } +} + +impl Mode { + pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> { + loop { + match self { + Mode::Exit => { + break + } + _ => { + terminal.draw(|frame| frame.render_widget(&mut self, frame.area()))?; + if let Event::Key(key) = event::read()? { + self = self.handle_key(key); + }; + } + } + } + Ok(()) + } + + fn handle_key(self, key: KeyEvent) -> Mode { + if key.kind != KeyEventKind::Press { + return self + } + + match self { + Mode::MainMenu(ref menu_list) => menu_list.handle_key(self.clone(), key), + _ => return self, + } + } +} + +fn center(area: Rect, horizontal: Constraint, vertical: Constraint) -> Rect { + let [area] = Layout::horizontal([horizontal]) + .flex(Flex::Center) + .areas(area); + let [area] = Layout::vertical([vertical]).flex(Flex::Center).areas(area); + area +} + +impl Widget for &mut Mode { + fn render(self, area: Rect, buf: &mut Buffer) { + let [header_area, main_area, footer_area] = Layout::vertical([ + Constraint::Length(1), + Constraint::Fill(1), + Constraint::Length(2), + ]) + .areas(area); + + let center_area = center( + main_area, + Constraint::Length(50), + Constraint::Percentage(60), + ); + + let [profile_area, list_outer_area] = + Layout::vertical([Constraint::Percentage(50), Constraint::Percentage(50)]) + .areas(center_area); + + menu::render_header(header_area, buf); + + match self { + Mode::MainMenu(menu_list) => { + menu_list.render(list_outer_area, buf); + menu::render_profile(profile_area, buf); + } + _ => {}, + } + menu::render_footer(footer_area, buf); + } +} diff --git a/client/src/main.rs b/client/src/main.rs index af96326..9b06c40 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -1,235 +1,12 @@ use color_eyre::Result; -use ratatui::{ - DefaultTerminal, - buffer::Buffer, - crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind}, - layout::{Constraint, Flex, Layout, Rect}, - style::{Modifier, Style, Stylize, palette::tailwind::*}, - text::{Line, Span}, - widgets::{ - Block, Borders, HighlightSpacing, List, ListState, Padding, Paragraph, StatefulWidget, - Widget, - }, -}; -const GAME_HEADER_STYLE: Style = Style::new() - .fg(ZINC.c100) - .bg(BLUE.c600) - .add_modifier(Modifier::BOLD); -const PROFILE_HEADER_STYLE: Style = Style::new() - .fg(ZINC.c100) - .bg(VIOLET.c600) - .add_modifier(Modifier::BOLD); - -const HEADER_STYLE: Style = Style::new().fg(ROSE.c500).add_modifier(Modifier::BOLD); -const SELECTED_STYLE: Style = Style::new().bg(ZINC.c700).add_modifier(Modifier::BOLD); - -const FOOTER_LEFT_STYLE: Style = Style::new().fg(PURPLE.c400).add_modifier(Modifier::BOLD); -const FOOTER_RIGHT_STYLE: Style = Style::new().fg(LIME.c400); +mod game; +mod menu; fn main() -> Result<()> { color_eyre::install()?; let terminal = ratatui::init(); - let app_result = GameMode::default().run(terminal); + let app_result = game::Mode::default().run(terminal); ratatui::restore(); app_result -} - -#[derive(Clone)] -enum GameMode { - Exit, - MainMenu(MenuList), - FortyL, - Blitz, - TxLadder, - Config, -} - -#[derive(Clone)] -struct MenuList { - items: Vec<&'static str>, - state: ListState, -} - -impl FromIterator<&'static str> for MenuList { - fn from_iter>(iter: I) -> Self { - let items = iter.into_iter().collect(); - let state = ListState::default(); - Self { items, state } - } -} - -impl MenuList { - fn handle_key(&self, current_mode: GameMode, key: KeyEvent) -> GameMode { - match key.code { - KeyCode::Char('q') | KeyCode::Esc => GameMode::Exit, - KeyCode::Char('j') | KeyCode::Down => self.select_next(), - KeyCode::Char('k') | KeyCode::Up => self.select_previous(), - KeyCode::Char('c') => { - if key.modifiers.contains(event::KeyModifiers::CONTROL) { - GameMode::Exit - } else { - current_mode - } - } - _ => current_mode - } - } - - fn select_next(&self) -> GameMode { - let mut menu_list = self.clone(); - menu_list.state.select_next(); - GameMode::MainMenu(menu_list) - } - - fn select_previous(&self) -> GameMode { - let mut menu_list = self.clone(); - menu_list.state.select_previous(); - GameMode::MainMenu(menu_list) - } - - fn render(&mut self, area: Rect, buf: &mut Buffer) { - let block = Block::new() - .title(Line::raw(" Game ").centered().style(GAME_HEADER_STYLE)) - .padding(Padding::symmetric(2, 1)) - .borders(Borders::ALL); - - let items: Vec<&'static str> = self.items.clone(); - - let list = List::new(items) - .block(block) - .highlight_style(SELECTED_STYLE) - .highlight_symbol(" ") - .highlight_spacing(HighlightSpacing::Always); - - StatefulWidget::render(list, area, buf, &mut self.state); - } -} - -impl Default for GameMode { - fn default() -> Self { - GameMode::MainMenu(MenuList::from_iter(["40L", "Blitz", "txLadder", "Config"])) - } -} - -impl GameMode { - fn render_header(&mut self, area: Rect, buf: &mut Buffer) { - Block::new() - .title(Line::raw(" txtris ").centered().style(HEADER_STYLE)) - .borders(Borders::TOP) - .render(area, buf); - } - - fn render_footer(&mut self, area: Rect, buf: &mut Buffer) { - Block::new() - .title( - Line::raw(" Atiran © 2025 ") - .left_aligned() - .style(FOOTER_LEFT_STYLE), - ) - .title( - Line::raw(format!("v{} ", env!("CARGO_PKG_VERSION"))) - .right_aligned() - .style(FOOTER_RIGHT_STYLE), - ) - .borders(Borders::BOTTOM) - .render(area, buf); - } - - fn render_profile(&mut self, area: Rect, buf: &mut Buffer) { - let block = Block::new() - .title( - Line::raw(" Profile ") - .centered() - .style(PROFILE_HEADER_STYLE), - ) - .padding(Padding::symmetric(2, 1)) - .borders(Borders::ALL); - - let text = vec![ - Line::from(Span::styled("", Style::new().bold().underlined())), - Line::from(vec![ - Span::styled("40L: ", Style::new().blue()), - Span::raw("N/A"), - ]), - Line::from(vec![ - Span::styled("Blitz: ", Style::new().green()), - Span::raw("N/A"), - ]), - Line::from(vec![ - Span::styled("txLadder: ", Style::new().magenta()), - Span::raw("N/A"), - ]), - ]; - - Paragraph::new(text).block(block).render(area, buf); - } - - fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> { - loop { - match self { - GameMode::Exit => { - break - } - _ => { - terminal.draw(|frame| frame.render_widget(&mut self, frame.area()))?; - if let Event::Key(key) = event::read()? { - self = self.handle_key(key); - }; - } - } - } - Ok(()) - } - - fn handle_key(self, key: KeyEvent) -> GameMode { - if key.kind != KeyEventKind::Press { - return self - } - - match self { - GameMode::MainMenu(ref menu_list) => menu_list.handle_key(self.clone(), key), - _ => return self, - } - } -} - -fn center(area: Rect, horizontal: Constraint, vertical: Constraint) -> Rect { - let [area] = Layout::horizontal([horizontal]) - .flex(Flex::Center) - .areas(area); - let [area] = Layout::vertical([vertical]).flex(Flex::Center).areas(area); - area -} - -impl Widget for &mut GameMode { - fn render(self, area: Rect, buf: &mut Buffer) { - let [header_area, main_area, footer_area] = Layout::vertical([ - Constraint::Length(1), - Constraint::Fill(1), - Constraint::Length(2), - ]) - .areas(area); - - let center_area = center( - main_area, - Constraint::Length(50), - Constraint::Percentage(60), - ); - - let [profile_area, list_outer_area] = - Layout::vertical([Constraint::Percentage(50), Constraint::Percentage(50)]) - .areas(center_area); - - self.render_header(header_area, buf); - - match self { - GameMode::MainMenu(menu_list) => { - menu_list.render(list_outer_area, buf); - self.render_profile(profile_area, buf); - } - _ => {}, - } - self.render_footer(footer_area, buf); - } -} +} \ No newline at end of file diff --git a/client/src/menu.rs b/client/src/menu.rs new file mode 100644 index 0000000..e885e44 --- /dev/null +++ b/client/src/menu.rs @@ -0,0 +1,143 @@ +// Main menu sections +use ratatui::{ + buffer::Buffer, + crossterm::event::{self, KeyCode, KeyEvent}, + layout::Rect, + text::{Line, Span}, + style::{Modifier, Style, Stylize, palette::tailwind::*}, + widgets::{ + Block, Borders, HighlightSpacing, List, + ListState, Padding, StatefulWidget, Paragraph + }, +}; +use ratatui::prelude::*; + +use crate::game; + +pub fn render_header(area: Rect, buf: &mut Buffer) { + Block::new() + .title(Line::raw(" txtris ").centered().style(HEADER_STYLE)) + .borders(Borders::TOP) + .render(area, buf); +} + +pub fn render_footer(area: Rect, buf: &mut Buffer) { + Block::new() + .title( + Line::raw(" Atiran © 2025 ") + .left_aligned() + .style(FOOTER_LEFT_STYLE), + ) + .title( + Line::raw(format!("v{} ", env!("CARGO_PKG_VERSION"))) + .right_aligned() + .style(FOOTER_RIGHT_STYLE), + ) + .borders(Borders::BOTTOM) + .render(area, buf); +} + +pub fn render_profile(area: Rect, buf: &mut Buffer) { + let block = Block::new() + .title( + Line::raw(" Profile ") + .centered() + .style(PROFILE_HEADER_STYLE), + ) + .padding(Padding::symmetric(2, 1)) + .borders(Borders::ALL); + + let text = vec![ + Line::from(Span::styled("", Style::new().bold().underlined())), + Line::from(vec![ + Span::styled("40L: ", Style::new().blue()), + Span::raw("N/A"), + ]), + Line::from(vec![ + Span::styled("Blitz: ", Style::new().green()), + Span::raw("N/A"), + ]), + Line::from(vec![ + Span::styled("txLadder: ", Style::new().magenta()), + Span::raw("N/A"), + ]), + ]; + + Paragraph::new(text).block(block).render(area, buf); +} + +#[derive(Clone)] +pub struct OptionList { + items: Vec<&'static str>, + state: ListState, +} + +const GAME_HEADER_STYLE: Style = Style::new() + .fg(ZINC.c100) + .bg(BLUE.c600) + .add_modifier(Modifier::BOLD); +const PROFILE_HEADER_STYLE: Style = Style::new() + .fg(ZINC.c100) + .bg(VIOLET.c600) + .add_modifier(Modifier::BOLD); + +const HEADER_STYLE: Style = Style::new().fg(ROSE.c500).add_modifier(Modifier::BOLD); +const SELECTED_STYLE: Style = Style::new().bg(ZINC.c700).add_modifier(Modifier::BOLD); + +const FOOTER_LEFT_STYLE: Style = Style::new().fg(PURPLE.c400).add_modifier(Modifier::BOLD); +const FOOTER_RIGHT_STYLE: Style = Style::new().fg(LIME.c400); + +impl FromIterator<&'static str> for OptionList { + fn from_iter>(iter: I) -> Self { + let items = iter.into_iter().collect(); + let state = ListState::default(); + Self { items, state } + } +} + +impl OptionList { + pub fn handle_key(&self, current_mode: game::Mode, key: KeyEvent) -> game::Mode { + match key.code { + KeyCode::Char('q') | KeyCode::Esc => game::Mode::Exit, + KeyCode::Char('j') | KeyCode::Down => self.select_next(), + KeyCode::Char('k') | KeyCode::Up => self.select_previous(), + KeyCode::Char('c') => { + if key.modifiers.contains(event::KeyModifiers::CONTROL) { + game::Mode::Exit + } else { + current_mode + } + } + _ => current_mode + } + } + + fn select_next(&self) -> game::Mode { + let mut menu_list = self.clone(); + menu_list.state.select_next(); + game::Mode::MainMenu(menu_list) + } + + fn select_previous(&self) -> game::Mode { + let mut menu_list = self.clone(); + menu_list.state.select_previous(); + game::Mode::MainMenu(menu_list) + } + + pub fn render(&mut self, area: Rect, buf: &mut Buffer) { + let block = Block::new() + .title(Line::raw(" Game ").centered().style(GAME_HEADER_STYLE)) + .padding(Padding::symmetric(2, 1)) + .borders(Borders::ALL); + + let items: Vec<&'static str> = self.items.clone(); + + let list = List::new(items) + .block(block) + .highlight_style(SELECTED_STYLE) + .highlight_symbol(" ") + .highlight_spacing(HighlightSpacing::Always); + + StatefulWidget::render(list, area, buf, &mut self.state); + } +} \ No newline at end of file From 996cb404cdee417fef16aa5584a19e542ca06fda Mon Sep 17 00:00:00 2001 From: Abdulmujeeb Raji Date: Fri, 21 Feb 2025 08:27:42 +0000 Subject: [PATCH 3/3] less cloning --- client/src/game.rs | 8 +++---- client/src/menu.rs | 58 ++++++++++++++++++++++++---------------------- 2 files changed, 34 insertions(+), 32 deletions(-) diff --git a/client/src/game.rs b/client/src/game.rs index 708e411..0ec3fb6 100644 --- a/client/src/game.rs +++ b/client/src/game.rs @@ -45,12 +45,12 @@ impl Mode { fn handle_key(self, key: KeyEvent) -> Mode { if key.kind != KeyEventKind::Press { - return self + return self } match self { - Mode::MainMenu(ref menu_list) => menu_list.handle_key(self.clone(), key), - _ => return self, + Mode::MainMenu(menu_list) => menu_list.handle_key(key), + _ => Mode::Exit, } } } @@ -86,8 +86,8 @@ impl Widget for &mut Mode { match self { Mode::MainMenu(menu_list) => { - menu_list.render(list_outer_area, buf); menu::render_profile(profile_area, buf); + menu_list.render(list_outer_area, buf); } _ => {}, } diff --git a/client/src/menu.rs b/client/src/menu.rs index e885e44..77c69c9 100644 --- a/client/src/menu.rs +++ b/client/src/menu.rs @@ -14,6 +14,21 @@ use ratatui::prelude::*; use crate::game; +const GAME_HEADER_STYLE: Style = Style::new() + .fg(ZINC.c100) + .bg(BLUE.c600) + .add_modifier(Modifier::BOLD); +const PROFILE_HEADER_STYLE: Style = Style::new() + .fg(ZINC.c100) + .bg(VIOLET.c600) + .add_modifier(Modifier::BOLD); + +const HEADER_STYLE: Style = Style::new().fg(ROSE.c500).add_modifier(Modifier::BOLD); +const SELECTED_STYLE: Style = Style::new().bg(ZINC.c700).add_modifier(Modifier::BOLD); + +const FOOTER_LEFT_STYLE: Style = Style::new().fg(PURPLE.c400).add_modifier(Modifier::BOLD); +const FOOTER_RIGHT_STYLE: Style = Style::new().fg(LIME.c400); + pub fn render_header(area: Rect, buf: &mut Buffer) { Block::new() .title(Line::raw(" txtris ").centered().style(HEADER_STYLE)) @@ -72,21 +87,6 @@ pub struct OptionList { state: ListState, } -const GAME_HEADER_STYLE: Style = Style::new() - .fg(ZINC.c100) - .bg(BLUE.c600) - .add_modifier(Modifier::BOLD); -const PROFILE_HEADER_STYLE: Style = Style::new() - .fg(ZINC.c100) - .bg(VIOLET.c600) - .add_modifier(Modifier::BOLD); - -const HEADER_STYLE: Style = Style::new().fg(ROSE.c500).add_modifier(Modifier::BOLD); -const SELECTED_STYLE: Style = Style::new().bg(ZINC.c700).add_modifier(Modifier::BOLD); - -const FOOTER_LEFT_STYLE: Style = Style::new().fg(PURPLE.c400).add_modifier(Modifier::BOLD); -const FOOTER_RIGHT_STYLE: Style = Style::new().fg(LIME.c400); - impl FromIterator<&'static str> for OptionList { fn from_iter>(iter: I) -> Self { let items = iter.into_iter().collect(); @@ -96,32 +96,34 @@ impl FromIterator<&'static str> for OptionList { } impl OptionList { - pub fn handle_key(&self, current_mode: game::Mode, key: KeyEvent) -> game::Mode { + pub fn handle_key(mut self, key: KeyEvent) -> game::Mode { match key.code { KeyCode::Char('q') | KeyCode::Esc => game::Mode::Exit, - KeyCode::Char('j') | KeyCode::Down => self.select_next(), - KeyCode::Char('k') | KeyCode::Up => self.select_previous(), + KeyCode::Char('j') | KeyCode::Down => { + self.select_next(); + game::Mode::MainMenu(self) + } + KeyCode::Char('k') | KeyCode::Up => { + self.select_previous(); + game::Mode::MainMenu(self) + } KeyCode::Char('c') => { if key.modifiers.contains(event::KeyModifiers::CONTROL) { game::Mode::Exit } else { - current_mode + game::Mode::MainMenu(self) } } - _ => current_mode + _ => game::Mode::MainMenu(self), } } - fn select_next(&self) -> game::Mode { - let mut menu_list = self.clone(); - menu_list.state.select_next(); - game::Mode::MainMenu(menu_list) + fn select_next(&mut self) { + self.state.select_next(); } - fn select_previous(&self) -> game::Mode { - let mut menu_list = self.clone(); - menu_list.state.select_previous(); - game::Mode::MainMenu(menu_list) + fn select_previous(&mut self) { + self.state.select_previous(); } pub fn render(&mut self, area: Rect, buf: &mut Buffer) {