diff --git a/src/app/app.rs b/src/app/app.rs index 3414fa4..2824de1 100644 --- a/src/app/app.rs +++ b/src/app/app.rs @@ -103,6 +103,7 @@ impl App { if let Some(action) = event_to_action(&key_event) { match action { Action::Quit => self.exit = true, + Action::Esc => self.view = View::MainMenu, _ => (), } } diff --git a/src/app/keybindings/keybindings.rs b/src/app/keybindings/keybindings.rs index ab80c3c..f23fdfe 100644 --- a/src/app/keybindings/keybindings.rs +++ b/src/app/keybindings/keybindings.rs @@ -4,17 +4,39 @@ use ratatui::crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Action { Quit, - ScrollUp, - ScrollDown, + Up, + Down, + Space, + Esc, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Group { + Quit, + Movement, Select, } +impl Group { + pub fn iter() -> impl Iterator { + [Group::Quit, Group::Movement, Group::Select] + .iter() + .copied() + } + + pub fn len() -> usize { + Self::iter().count() + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct KeyBinding { pub action: Action, pub code: KeyCode, pub kind: KeyEventKind, pub modifiers: KeyModifiers, + pub group: Group, + pub symbol: &'static str, pub description: &'static str, } @@ -24,29 +46,46 @@ pub static KEYBINDINGS: &[KeyBinding] = &[ code: KeyCode::Char('q'), kind: KeyEventKind::Press, modifiers: KeyModifiers::NONE, + group: Group::Quit, + symbol: "q", description: "Quit", }, KeyBinding { - action: Action::ScrollUp, + action: Action::Up, code: KeyCode::Up, kind: KeyEventKind::Press, modifiers: KeyModifiers::NONE, + group: Group::Movement, + symbol: "↑", description: "Up", }, KeyBinding { - action: Action::ScrollDown, + action: Action::Down, code: KeyCode::Down, kind: KeyEventKind::Press, modifiers: KeyModifiers::NONE, + group: Group::Movement, + symbol: "↓", description: "Down", }, KeyBinding { - action: Action::Select, + action: Action::Space, code: KeyCode::Char(' '), kind: KeyEventKind::Press, modifiers: KeyModifiers::NONE, + group: Group::Select, + symbol: "Space", description: "Select", }, + KeyBinding { + action: Action::Esc, + code: KeyCode::Esc, + kind: KeyEventKind::Press, + modifiers: KeyModifiers::NONE, + group: Group::Movement, + symbol: "Esc", + description: "Go back", + }, ]; pub fn binding_for(action: Action) -> Option<&'static KeyBinding> { @@ -63,11 +102,14 @@ pub fn event_to_action(event: &KeyEvent) -> Option { pub fn binding_for_view(view: View) -> Vec<&'static KeyBinding> { match view { View::MainMenu => vec![ - binding_for(Action::ScrollUp).unwrap(), - binding_for(Action::ScrollDown).unwrap(), - binding_for(Action::Select).unwrap(), + binding_for(Action::Up).unwrap(), + binding_for(Action::Down).unwrap(), + binding_for(Action::Space).unwrap(), binding_for(Action::Quit).unwrap(), ], - _ => vec![], + _ => vec![ + binding_for(Action::Quit).unwrap(), + binding_for(Action::Esc).unwrap(), + ], } } diff --git a/src/app/keybindings/main_menu.rs b/src/app/keybindings/main_menu.rs index 4f0586c..3fd8a8f 100644 --- a/src/app/keybindings/main_menu.rs +++ b/src/app/keybindings/main_menu.rs @@ -8,7 +8,7 @@ pub fn main_menu_keybindings(app: &mut App, event: &KeyEvent) { if let Some(action) = event_to_action(&event) { match action { Action::Quit => app.exit = true, - Action::ScrollUp => { + Action::Up => { app.game_states.main_menu_state.selected_view = app .game_states .main_menu_state @@ -16,7 +16,7 @@ pub fn main_menu_keybindings(app: &mut App, event: &KeyEvent) { .saturating_sub(1) .max(1); } - Action::ScrollDown => { + Action::Down => { app.game_states.main_menu_state.selected_view = app .game_states .main_menu_state @@ -24,7 +24,7 @@ pub fn main_menu_keybindings(app: &mut App, event: &KeyEvent) { .saturating_add(1) .min(4); } - Action::Select => { + Action::Space => { let selected_view: usize = app.game_states.main_menu_state.selected_view; if selected_view == 1 { @@ -36,7 +36,8 @@ pub fn main_menu_keybindings(app: &mut App, event: &KeyEvent) { } else if selected_view == 4 { app.view = View::Settings; } - } // _ => (), + } + _ => (), } } } diff --git a/src/app/keybindings/mod.rs b/src/app/keybindings/mod.rs index 7e8c674..473b04c 100644 --- a/src/app/keybindings/mod.rs +++ b/src/app/keybindings/mod.rs @@ -2,6 +2,6 @@ pub mod keybindings; pub mod main_menu; pub use keybindings::{ - Action, KEYBINDINGS, KeyBinding, binding_for, binding_for_view, event_to_action, + Action, Group, KEYBINDINGS, KeyBinding, binding_for, binding_for_view, event_to_action, }; pub use main_menu::main_menu_keybindings; diff --git a/src/app/widgets/keybindings.rs b/src/app/widgets/keybindings.rs index 7080c06..71bdd8d 100644 --- a/src/app/widgets/keybindings.rs +++ b/src/app/widgets/keybindings.rs @@ -1,33 +1,72 @@ -use crate::app::{View, keybindings::binding_for_view}; +use crate::app::{ + View, + keybindings::{Group, KeyBinding, binding_for_view}, +}; use ratatui::{ - crossterm::event::KeyCode, - layout::Alignment, + buffer::Buffer, + layout::{Alignment, Constraint, Layout, Rect}, style::Stylize, - text::{Line, Span}, - widgets::{Block, Borders, Paragraph}, + text::Line, + widgets::{Block, Borders, Padding, Paragraph, Widget}, }; -pub fn keybindings_widget(view: View) -> Paragraph<'static> { - let lines: Vec = binding_for_view(view) - .iter() - .map(|b| { - let key_span = match b.code { - KeyCode::Up => Span::raw("↑".to_string()), - KeyCode::Down => Span::raw("↓".to_string()), - KeyCode::Char(' ') => Span::raw("Space".to_string()), - KeyCode::Char(c) => Span::raw(c.to_string()), - other => Span::raw(format!("{:?}", other)), - } - .bold() - .red(); - - Line::from_iter([key_span, "\t - ".into(), b.description.into()]) - }) - .collect(); - - Paragraph::new(lines).alignment(Alignment::Left).block( - Block::default() - .borders(Borders::ALL) - .title("[ Keybindings ]"), - ) +pub struct KeybindingsWidget { + grouped: Vec>, +} + +impl Widget for KeybindingsWidget { + fn render(self, area: Rect, buf: &mut Buffer) { + let count: u16 = self.grouped.len() as u16; + + if count == 0 { + return; + } + + let block: Block<'_> = Block::default() + .borders(Borders::ALL) + .title("[ Keybindings ]"); + + let inner: Rect = block.inner(area); + + block.render(area, buf); + + let base: u16 = if count == 0 { 0 } else { 10 }; + + let constraints: Vec = vec![Constraint::Percentage(base); count as usize]; + + let chunks: Vec = Layout::horizontal(constraints).split(inner).to_vec(); + + for (paragraph, chunk) in self.grouped.into_iter().zip(chunks.into_iter()) { + paragraph.render(chunk, buf); + } + } +} + +pub fn keybindings_widget(view: View) -> KeybindingsWidget { + let keybindings: Vec<&'static KeyBinding> = binding_for_view(view); + + let mut grouped_keybindings: Vec> = Vec::new(); + for (i, group) in Group::iter().enumerate() { + let grouped_lines: Vec> = keybindings + .iter() + .filter(|b| b.group == group) + .map(|b| Line::from_iter([b.symbol.red().bold(), " - ".into(), b.description.into()])) + .collect(); + + let mut block: Block<'_> = Block::default().padding(Padding::new(1, 1, 0, 0)); + + if i != Group::len() - 1 { + block = block.borders(Borders::RIGHT); + } + + grouped_keybindings.push( + Paragraph::new(grouped_lines) + .alignment(Alignment::Center) + .block(block), + ); + } + + KeybindingsWidget { + grouped: grouped_keybindings, + } } diff --git a/src/app/widgets/main_menu.rs b/src/app/widgets/main_menu.rs index 69669f1..8be5c26 100644 --- a/src/app/widgets/main_menu.rs +++ b/src/app/widgets/main_menu.rs @@ -28,7 +28,7 @@ fn view_options() -> Vec<(usize, String)> { pub fn main_menu_widget(app: &App, area: Rect, buf: &mut Buffer) { let vertical_layout: Layout = - Layout::vertical([Constraint::Percentage(88), Constraint::Percentage(12)]); + Layout::vertical([Constraint::Percentage(92), Constraint::Percentage(8)]); let [main_menu_area, keybindings_area] = vertical_layout.areas(area);