Refactor view handling and keybinding API

Move the `View` enum to a dedicated module and implement `Widget` for
`&App` there.
Rename `default_view_keybindings` to `default_keybindings` and adjust
its
signature. Redesign `KeybindingsWidget` to accept an explicit list of
bindings instead of a `View`. Update imports, function signatures, and
rendering
logic across the codebase to reflect these changes.
This commit is contained in:
2026-03-13 20:10:49 +01:00
parent d563bbc966
commit 61e9dedfe8
15 changed files with 109 additions and 115 deletions
+3 -11
View File
@@ -1,5 +1,5 @@
use crate::{ use crate::{
app::{GameStates, handle_keybindings}, app::{GameStates, handle_keybindings, view::View},
cli::Cli, cli::Cli,
}; };
use clap::ValueEnum; use clap::ValueEnum;
@@ -34,15 +34,6 @@ pub struct App {
pub skill_points_limit: u16, pub skill_points_limit: u16,
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, ValueEnum)]
pub enum View {
MainMenu,
Skirmish,
PerkDecks,
SkillsConfig,
Settings,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, ValueEnum)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, ValueEnum)]
pub enum GameMode { pub enum GameMode {
LastManStanding, LastManStanding,
@@ -100,7 +91,8 @@ impl App {
} }
fn handle_key_event(&mut self, key_event: KeyEvent) -> Result<()> { fn handle_key_event(&mut self, key_event: KeyEvent) -> Result<()> {
handle_keybindings(self, key_event) handle_keybindings(self, key_event);
Ok(())
} }
} }
+5 -7
View File
@@ -1,16 +1,14 @@
use crate::app::{ use crate::app::{
App, View, App,
keybindings::{default_view_keybindings, main_menu_keybindings, settings_keybindings}, keybindings::{default_keybindings, main_menu_keybindings, settings_keybindings},
view::View,
}; };
use ratatui::crossterm::event::KeyEvent; use ratatui::crossterm::event::KeyEvent;
use std::io::Result;
pub fn handle_keybindings(app: &mut App, key_event: KeyEvent) -> Result<()> { pub fn handle_keybindings(app: &mut App, key_event: KeyEvent) {
match app.view { match app.view {
View::MainMenu => main_menu_keybindings(app, &key_event), View::MainMenu => main_menu_keybindings(app, &key_event),
View::Settings => settings_keybindings(app, &key_event), View::Settings => settings_keybindings(app, &key_event),
_ => default_view_keybindings(app, &key_event), _ => default_keybindings(app, &key_event),
} }
Ok(())
} }
+1 -1
View File
@@ -4,7 +4,7 @@ use crate::app::{
}; };
use ratatui::crossterm::event::KeyEvent; use ratatui::crossterm::event::KeyEvent;
pub fn default_view_keybindings(app: &mut App, key_event: &KeyEvent) { pub fn default_keybindings(app: &mut App, key_event: &KeyEvent) {
if let Some(action) = event_to_action(&key_event) { if let Some(action) = event_to_action(&key_event) {
match action { match action {
Action::Quit => app.exit = true, Action::Quit => app.exit = true,
-50
View File
@@ -1,6 +1,4 @@
use crate::app::View;
use ratatui::crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers}; use ratatui::crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
use std::collections::HashSet;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Action { pub enum Action {
@@ -115,51 +113,3 @@ pub fn event_to_action(event: &KeyEvent) -> Option<Action> {
.find(|b| b.code == event.code && b.kind == event.kind && b.modifiers == event.modifiers) .find(|b| b.code == event.code && b.kind == event.kind && b.modifiers == event.modifiers)
.map(|b| b.action) .map(|b| b.action)
} }
fn used_groups_in_view(keybindings: &Vec<Option<&'static KeyBinding>>) -> HashSet<Group> {
keybindings
.iter()
.filter_map(|b| b.map(|binding| binding.group))
.collect()
}
pub fn binding_for_view(view: View) -> (HashSet<Group>, Vec<Option<&'static KeyBinding>>) {
let main_menu_keybindings: Vec<Option<&'static KeyBinding>> = vec![
binding_for(Action::Up),
binding_for(Action::Down),
binding_for(Action::Space),
binding_for(Action::Quit),
binding_for(Action::Quit2),
];
let settings_keybinding: Vec<Option<&'static KeyBinding>> = vec![
binding_for(Action::Up),
binding_for(Action::Down),
binding_for(Action::Space),
binding_for(Action::Enter),
binding_for(Action::Quit),
binding_for(Action::Quit2),
binding_for(Action::Esc),
];
let default_keybindings: Vec<Option<&'static KeyBinding>> = vec![
binding_for(Action::Quit),
binding_for(Action::Quit2),
binding_for(Action::Esc),
];
match view {
View::MainMenu => (
used_groups_in_view(&main_menu_keybindings),
main_menu_keybindings,
),
View::Settings => (
used_groups_in_view(&settings_keybinding),
settings_keybinding,
),
_ => (
used_groups_in_view(&default_keybindings),
default_keybindings,
),
}
}
+2 -4
View File
@@ -3,9 +3,7 @@ pub mod keybindings;
pub mod main_menu; pub mod main_menu;
pub mod settings; pub mod settings;
pub use default::default_view_keybindings; pub use default::default_keybindings;
pub use keybindings::{ pub use keybindings::{Action, Group, KEYBINDINGS, KeyBinding, binding_for, event_to_action};
Action, Group, KEYBINDINGS, KeyBinding, binding_for, binding_for_view, event_to_action,
};
pub use main_menu::main_menu_keybindings; pub use main_menu::main_menu_keybindings;
pub use settings::settings_keybindings; pub use settings::settings_keybindings;
+4 -2
View File
@@ -2,11 +2,13 @@ pub mod app;
pub mod game_states; pub mod game_states;
pub mod keybind; pub mod keybind;
pub mod keybindings; pub mod keybindings;
pub mod widget; pub mod view;
pub mod views;
pub mod widgets; pub mod widgets;
pub use app::{App, Event, GameMode, PerkDecks, View, handle_input_events}; pub use app::{App, Event, GameMode, PerkDecks, handle_input_events};
pub use game_states::{ pub use game_states::{
GameStates, MainMenuState, PerkDecksState, SettingsState, SkillsConfigState, SkirmishState, GameStates, MainMenuState, PerkDecksState, SettingsState, SkillsConfigState, SkirmishState,
}; };
pub use keybind::handle_keybindings; pub use keybind::handle_keybindings;
pub use view::View;
+28
View File
@@ -0,0 +1,28 @@
use crate::app::{
App,
views::{default_view, main_menu_view, settings_view},
};
use clap::ValueEnum;
use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, ValueEnum)]
pub enum View {
MainMenu,
Skirmish,
PerkDecks,
SkillsConfig,
Settings,
}
impl Widget for &App {
fn render(self, area: Rect, buf: &mut Buffer)
where
Self: Sized,
{
match self.view {
View::MainMenu => main_menu_view(self, area, buf),
View::Settings => settings_view(self, area, buf),
_ => default_view(area, buf),
}
}
}
@@ -1,4 +1,7 @@
use crate::app::{App, widgets::KeybindingsWidget}; use crate::app::{
keybindings::{Action, KeyBinding, binding_for},
widgets::KeybindingsWidget,
};
use ratatui::{ use ratatui::{
buffer::Buffer, buffer::Buffer,
layout::{Alignment, Constraint, Layout, Rect}, layout::{Alignment, Constraint, Layout, Rect},
@@ -6,7 +9,7 @@ use ratatui::{
widgets::{Block, Borders, Paragraph, Widget}, widgets::{Block, Borders, Paragraph, Widget},
}; };
pub fn default_view_widget(app: &App, area: Rect, buf: &mut Buffer) { pub fn default_view(area: Rect, buf: &mut Buffer) {
let vertical_layout: Layout = Layout::vertical([Constraint::Fill(1), Constraint::Length(4)]); let vertical_layout: Layout = Layout::vertical([Constraint::Fill(1), Constraint::Length(4)]);
let [main_area, keybindings_area] = vertical_layout.areas(area); let [main_area, keybindings_area] = vertical_layout.areas(area);
@@ -21,5 +24,13 @@ pub fn default_view_widget(app: &App, area: Rect, buf: &mut Buffer) {
) )
.render(main_area, buf); .render(main_area, buf);
KeybindingsWidget::new(app.view).render(keybindings_area, buf); {
let keybindings: Vec<Option<&'static KeyBinding>> = vec![
binding_for(Action::Quit),
binding_for(Action::Quit2),
binding_for(Action::Esc),
];
KeybindingsWidget::new(keybindings).render(keybindings_area, buf);
}
} }
@@ -1,4 +1,8 @@
use crate::app::{App, View, widgets::KeybindingsWidget}; use crate::app::{
App, View,
keybindings::{Action, KeyBinding, binding_for},
widgets::KeybindingsWidget,
};
use clap::ValueEnum; use clap::ValueEnum;
use ratatui::{ use ratatui::{
buffer::Buffer, buffer::Buffer,
@@ -26,7 +30,7 @@ fn view_options() -> Vec<(usize, String)> {
.collect() .collect()
} }
pub fn main_menu_widget(app: &App, area: Rect, buf: &mut Buffer) { pub fn main_menu_view(app: &App, area: Rect, buf: &mut Buffer) {
let vertical_layout: Layout = Layout::vertical([Constraint::Fill(1), Constraint::Length(4)]); let vertical_layout: Layout = Layout::vertical([Constraint::Fill(1), Constraint::Length(4)]);
let [main_menu_area, keybindings_area] = vertical_layout.areas(area); let [main_menu_area, keybindings_area] = vertical_layout.areas(area);
@@ -81,5 +85,15 @@ pub fn main_menu_widget(app: &App, area: Rect, buf: &mut Buffer) {
.render(options_area, buf); .render(options_area, buf);
} }
KeybindingsWidget::new(View::MainMenu).render(keybindings_area, buf); {
let keybindings: Vec<Option<&'static KeyBinding>> = vec![
binding_for(Action::Up),
binding_for(Action::Down),
binding_for(Action::Space),
binding_for(Action::Quit),
binding_for(Action::Quit2),
];
KeybindingsWidget::new(keybindings).render(keybindings_area, buf);
}
} }
+7
View File
@@ -0,0 +1,7 @@
pub mod default;
pub mod main_menu;
pub mod settings;
pub use default::default_view;
pub use main_menu::main_menu_view;
pub use settings::settings_view;
@@ -1,4 +1,8 @@
use crate::app::{App, widgets::KeybindingsWidget}; use crate::app::{
App,
keybindings::{Action, KeyBinding, binding_for},
widgets::KeybindingsWidget,
};
use ratatui::{ use ratatui::{
buffer::Buffer, buffer::Buffer,
layout::{Alignment, Constraint, Layout, Rect}, layout::{Alignment, Constraint, Layout, Rect},
@@ -6,7 +10,7 @@ use ratatui::{
widgets::{Block, Borders, Padding, Paragraph, Widget}, widgets::{Block, Borders, Padding, Paragraph, Widget},
}; };
pub fn settings_widget(app: &App, area: Rect, buf: &mut Buffer) { pub fn settings_view(app: &App, area: Rect, buf: &mut Buffer) {
let vertical_layout: Layout = Layout::vertical([ let vertical_layout: Layout = Layout::vertical([
Constraint::Length(10), Constraint::Length(10),
Constraint::Fill(1), Constraint::Fill(1),
@@ -37,5 +41,17 @@ pub fn settings_widget(app: &App, area: Rect, buf: &mut Buffer) {
) )
.render(main_area, buf); .render(main_area, buf);
KeybindingsWidget::new(app.view).render(keybindings_area, buf); {
let keybindings: Vec<Option<&'static KeyBinding>> = vec![
binding_for(Action::Up),
binding_for(Action::Down),
binding_for(Action::Space),
binding_for(Action::Enter),
binding_for(Action::Quit),
binding_for(Action::Quit2),
binding_for(Action::Esc),
];
KeybindingsWidget::new(keybindings).render(keybindings_area, buf);
}
} }
-18
View File
@@ -1,18 +0,0 @@
use crate::app::{
App, View,
widgets::{default_view_widget, main_menu_widget, settings_widget},
};
use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};
impl Widget for &App {
fn render(self, area: Rect, buf: &mut Buffer)
where
Self: Sized,
{
match self.view {
View::MainMenu => main_menu_widget(self, area, buf),
View::Settings => settings_widget(self, area, buf),
_ => default_view_widget(self, area, buf),
}
}
}
+8 -6
View File
@@ -1,7 +1,6 @@
use crate::app::{ use std::collections::HashSet;
View,
keybindings::{Group, binding_for_view}, use crate::app::keybindings::{Group, KeyBinding};
};
use ratatui::{ use ratatui::{
buffer::Buffer, buffer::Buffer,
layout::{Alignment, Constraint, Layout, Rect}, layout::{Alignment, Constraint, Layout, Rect},
@@ -15,8 +14,11 @@ pub struct KeybindingsWidget {
} }
impl KeybindingsWidget { impl KeybindingsWidget {
pub fn new(view: View) -> Self { pub fn new(keybindings: Vec<Option<&'static KeyBinding>>) -> Self {
let (used_groups, keybindings) = binding_for_view(view); let used_groups: HashSet<Group> = keybindings
.iter()
.filter_map(|b| b.map(|binding| binding.group))
.collect();
let mut grouped_keybindings: Vec<Paragraph<'static>> = Vec::new(); let mut grouped_keybindings: Vec<Paragraph<'static>> = Vec::new();
for (i, group) in Group::iter().enumerate() { for (i, group) in Group::iter().enumerate() {
-6
View File
@@ -1,9 +1,3 @@
pub mod default;
pub mod keybindings; pub mod keybindings;
pub mod main_menu;
pub mod settings;
pub use default::default_view_widget;
pub use keybindings::KeybindingsWidget; pub use keybindings::KeybindingsWidget;
pub use main_menu::main_menu_widget;
pub use settings::settings_widget;
+1 -1
View File
@@ -1,4 +1,4 @@
use crate::app::{GameMode, PerkDecks, View}; use crate::app::{GameMode, PerkDecks, view::View};
use clap::Parser; use clap::Parser;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]