Refactor to View enum and add keybindings

Replace the `default_window` string field with a `View` enum throughout
the
application. Introduce a new keybinding module providing `Action`,
`KeyBinding` structs and helper functions, and integrate a keybindings
widget
into the main menu. Update the CLI to accept a `View` value enum and
adjust
imports and event handling accordingly. Add a release profile in
`Cargo.toml` with LTO, single codegen unit, higher optimisation level
and
binary stripping for smaller, faster builds.
This commit is contained in:
2026-03-10 14:26:18 +01:00
parent 64eb906b5f
commit de42569a51
10 changed files with 147 additions and 43 deletions
+6
View File
@@ -3,6 +3,12 @@ name = "war_in_tunnels"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
[profile.release]
codegen-units = 1
lto = true
opt-level = 3
strip = true
[dependencies] [dependencies]
clap = { version = "4.5.53", features = ["derive"] } clap = { version = "4.5.53", features = ["derive"] }
ratatui = "0.30.0" ratatui = "0.30.0"
+21 -4
View File
@@ -1,6 +1,8 @@
use crate::app::{Action, event_to_action};
use clap::ValueEnum;
use ratatui::{ use ratatui::{
DefaultTerminal, Frame, DefaultTerminal, Frame,
crossterm::event::{self, KeyCode, KeyEvent, KeyEventKind}, crossterm::event::{self, KeyEvent},
}; };
use std::{ use std::{
io::Result, io::Result,
@@ -9,7 +11,7 @@ use std::{
pub struct App { pub struct App {
pub exit: bool, pub exit: bool,
pub default_window: String, pub window: View,
pub username: String, pub username: String,
pub game_mode: String, pub game_mode: String,
pub map_width: u8, pub map_width: u8,
@@ -22,6 +24,15 @@ pub struct App {
pub skill_points_limit: u16, pub skill_points_limit: u16,
} }
#[derive(Debug, Clone, ValueEnum)]
pub enum View {
MainMenu,
Skirmish,
PerkDecks,
SkillsConfig,
Settings,
}
pub enum Event { pub enum Event {
Input(KeyEvent), Input(KeyEvent),
} }
@@ -48,8 +59,14 @@ impl App {
} }
fn handle_key_event(&mut self, key_event: KeyEvent) -> Result<()> { fn handle_key_event(&mut self, key_event: KeyEvent) -> Result<()> {
if key_event.kind == KeyEventKind::Press && key_event.code == KeyCode::Char('q') { // if key_event.kind == KeyEventKind::Press && key_event.code == KeyCode::Char('q') {
self.exit = true; // self.exit = true;
// }
if let Some(action) = event_to_action(&key_event) {
match action {
Action::Quit => self.exit = true,
}
} }
Ok(()) Ok(())
+45
View File
@@ -0,0 +1,45 @@
use ratatui::crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
use crate::app::View;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Action {
Quit,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct KeyBinding {
pub action: Action,
pub code: KeyCode,
pub kind: KeyEventKind,
pub modifiers: KeyModifiers,
pub description: &'static str,
}
pub static KEYBINDINGS: &[KeyBinding] = &[KeyBinding {
action: Action::Quit,
code: KeyCode::Char('q'),
kind: KeyEventKind::Press,
modifiers: KeyModifiers::NONE,
description: "Quit",
}];
pub fn binding_for(action: Action) -> Option<&'static KeyBinding> {
KEYBINDINGS.iter().find(|b| b.action == action)
}
pub fn event_to_action(event: &KeyEvent) -> Option<Action> {
KEYBINDINGS
.iter()
.find(|b| b.code == event.code && b.kind == event.kind && b.modifiers == event.modifiers)
.map(|b| b.action)
}
pub fn binding_for_view(view: View) -> Vec<&'static KeyBinding> {
match view {
View::MainMenu => binding_for(Action::Quit)
.map(|b| vec![b])
.unwrap_or_else(|| vec![]),
_ => vec![],
}
}
+5 -1
View File
@@ -1,5 +1,9 @@
pub mod app; pub mod app;
pub mod keybindings;
pub mod widget; pub mod widget;
pub mod widgets; pub mod widgets;
pub use app::{App, Event, handle_input_events}; pub use app::{App, Event, View, handle_input_events};
pub use keybindings::{
Action, KEYBINDINGS, KeyBinding, binding_for, binding_for_view, event_to_action,
};
+4 -3
View File
@@ -1,14 +1,15 @@
use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget}; use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};
use crate::app::{App, widgets::main_menu_widget}; use crate::app::{App, View, widgets::main_menu_widget};
impl Widget for &App { impl Widget for &App {
fn render(self, area: Rect, buf: &mut Buffer) fn render(self, area: Rect, buf: &mut Buffer)
where where
Self: Sized, Self: Sized,
{ {
if self.default_window == "main_menu" { match self.window {
main_menu_widget(area, buf); View::MainMenu => main_menu_widget(area, buf),
_ => panic!("This window doesn't have widget."),
} }
} }
} }
+31
View File
@@ -0,0 +1,31 @@
use ratatui::{
crossterm::event::KeyCode,
layout::Alignment,
style::Stylize,
text::{Line, Span},
widgets::{Block, Borders, Paragraph},
};
use crate::app::{View, binding_for_view};
pub fn keybindings_widget(view: View) -> Paragraph<'static> {
let lines: Vec<Line> = binding_for_view(view)
.iter()
.map(|b| {
let key_span = match b.code {
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 ]"),
)
}
+8 -13
View File
@@ -1,17 +1,19 @@
use ratatui::{ use ratatui::{
buffer::Buffer, buffer::Buffer,
layout::{Alignment, Constraint, Layout, Rect}, layout::{Alignment, Constraint, Layout, Rect},
style::{Color, Stylize}, style::Color,
text::Line,
widgets::{Block, Borders, Paragraph, Widget}, widgets::{Block, Borders, Paragraph, Widget},
}; };
use crate::app::{View, widgets::keybindings_widget};
pub fn main_menu_widget(area: Rect, buf: &mut Buffer) { pub fn main_menu_widget(area: Rect, buf: &mut Buffer) {
let vertical_layout: Layout = let vertical_layout: Layout =
Layout::vertical([Constraint::Percentage(90), Constraint::Percentage(10)]); Layout::vertical([Constraint::Percentage(90), Constraint::Percentage(10)]);
let [main_menu_area, keybindings_area] = vertical_layout.areas(area); let [main_menu_area, keybindings_area] = vertical_layout.areas(area);
{
let title_text: String = vec![ let title_text: String = vec![
r" __ __ _ _____ _ ", r" __ __ _ _____ _ ",
r"/ / /\ \ \__ _ _ __ (_)_ __ /__ \_ _ _ __ _ __ ___| |___", r"/ / /\ \ \__ _ _ __ (_)_ __ /__ \_ _ _ __ _ __ ___| |___",
@@ -31,17 +33,10 @@ pub fn main_menu_widget(area: Rect, buf: &mut Buffer) {
); );
title.render(main_menu_area, buf); title.render(main_menu_area, buf);
}
let keybindings_text: Vec<Line<'_>> = {
vec![Line::from_iter(["q".bold().red(), "\t - Quit".into()])]; let keybindings: Paragraph<'_> = keybindings_widget(View::MainMenu);
let keybindings: Paragraph<'_> = Paragraph::new(keybindings_text)
.alignment(Alignment::Left)
.block(
Block::default()
.borders(Borders::ALL)
.title("[ Keybinding ]"),
);
keybindings.render(keybindings_area, buf); keybindings.render(keybindings_area, buf);
} }
}
+2
View File
@@ -1,3 +1,5 @@
pub mod keybindings;
pub mod main_menu; pub mod main_menu;
pub use keybindings::keybindings_widget;
pub use main_menu::main_menu_widget; pub use main_menu::main_menu_widget;
+6 -3
View File
@@ -1,15 +1,18 @@
use clap::Parser; use clap::Parser;
use crate::app::View;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(version, about = "War in Tunnels", long_about = "War in Tunnels")] #[command(version, about = "War in Tunnels", long_about = "War in Tunnels")]
pub struct Cli { pub struct Cli {
#[arg( #[arg(
long, long,
help = "Default window", help = "Default window",
value_name = "main_menu | skirmish | skills_config | perk_decks | settings", value_name = "...",
default_value = "main_menu" default_value_t = View::MainMenu,
value_enum
)] )]
pub default_window: String, pub window: View,
#[arg( #[arg(
long, long,
+1 -1
View File
@@ -14,7 +14,7 @@ fn main() -> Result<()> {
let mut terminal: Terminal<CrosstermBackend<Stdout>> = ratatui::init(); let mut terminal: Terminal<CrosstermBackend<Stdout>> = ratatui::init();
let mut app: App = App { let mut app: App = App {
exit: false, exit: false,
default_window: args.default_window, window: args.window,
username: args.username, username: args.username,
game_mode: args.game_mode, game_mode: args.game_mode,
map_width: args.map_width, map_width: args.map_width,