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"
edition = "2024"
[profile.release]
codegen-units = 1
lto = true
opt-level = 3
strip = true
[dependencies]
clap = { version = "4.5.53", features = ["derive"] }
ratatui = "0.30.0"
+21 -4
View File
@@ -1,6 +1,8 @@
use crate::app::{Action, event_to_action};
use clap::ValueEnum;
use ratatui::{
DefaultTerminal, Frame,
crossterm::event::{self, KeyCode, KeyEvent, KeyEventKind},
crossterm::event::{self, KeyEvent},
};
use std::{
io::Result,
@@ -9,7 +11,7 @@ use std::{
pub struct App {
pub exit: bool,
pub default_window: String,
pub window: View,
pub username: String,
pub game_mode: String,
pub map_width: u8,
@@ -22,6 +24,15 @@ pub struct App {
pub skill_points_limit: u16,
}
#[derive(Debug, Clone, ValueEnum)]
pub enum View {
MainMenu,
Skirmish,
PerkDecks,
SkillsConfig,
Settings,
}
pub enum Event {
Input(KeyEvent),
}
@@ -48,8 +59,14 @@ impl App {
}
fn handle_key_event(&mut self, key_event: KeyEvent) -> Result<()> {
if key_event.kind == KeyEventKind::Press && key_event.code == KeyCode::Char('q') {
self.exit = true;
// if key_event.kind == KeyEventKind::Press && key_event.code == KeyCode::Char('q') {
// self.exit = true;
// }
if let Some(action) = event_to_action(&key_event) {
match action {
Action::Quit => self.exit = true,
}
}
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 keybindings;
pub mod widget;
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 crate::app::{App, widgets::main_menu_widget};
use crate::app::{App, View, widgets::main_menu_widget};
impl Widget for &App {
fn render(self, area: Rect, buf: &mut Buffer)
where
Self: Sized,
{
if self.default_window == "main_menu" {
main_menu_widget(area, buf);
match self.window {
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 ]"),
)
}
+26 -31
View File
@@ -1,47 +1,42 @@
use ratatui::{
buffer::Buffer,
layout::{Alignment, Constraint, Layout, Rect},
style::{Color, Stylize},
text::Line,
style::Color,
widgets::{Block, Borders, Paragraph, Widget},
};
use crate::app::{View, widgets::keybindings_widget};
pub fn main_menu_widget(area: Rect, buf: &mut Buffer) {
let vertical_layout: Layout =
Layout::vertical([Constraint::Percentage(90), Constraint::Percentage(10)]);
let [main_menu_area, keybindings_area] = vertical_layout.areas(area);
let title_text: String = vec![
r" __ __ _ _____ _ ",
r"/ / /\ \ \__ _ _ __ (_)_ __ /__ \_ _ _ __ _ __ ___| |___",
r"\ \/ \/ / _` | '__| | | '_ \ / /\/ | | | '_ \| '_ \ / _ \ / __|",
r" \ /\ / (_| | | | | | | | / / | |_| | | | | | | | __/ \__ \",
r" \/ \/ \__,_|_| |_|_| |_| \/ \__,_|_| |_|_| |_|\___|_|___/",
]
.join("\n");
{
let title_text: String = vec![
r" __ __ _ _____ _ ",
r"/ / /\ \ \__ _ _ __ (_)_ __ /__ \_ _ _ __ _ __ ___| |___",
r"\ \/ \/ / _` | '__| | | '_ \ / /\/ | | | '_ \| '_ \ / _ \ / __|",
r" \ /\ / (_| | | | | | | | / / | |_| | | | | | | | __/ \__ \",
r" \/ \/ \__,_|_| |_|_| |_| \/ \__,_|_| |_|_| |_|\___|_|___/",
]
.join("\n");
let title: Paragraph<'_> = Paragraph::new(title_text)
.alignment(Alignment::Center)
.style(Color::Yellow)
.block(
Block::default()
.borders(Borders::LEFT | Borders::RIGHT | Borders::TOP)
.style(Color::Gray),
);
let title: Paragraph<'_> = Paragraph::new(title_text)
.alignment(Alignment::Center)
.style(Color::Yellow)
.block(
Block::default()
.borders(Borders::LEFT | Borders::RIGHT | Borders::TOP)
.style(Color::Gray),
);
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<'_> = Paragraph::new(keybindings_text)
.alignment(Alignment::Left)
.block(
Block::default()
.borders(Borders::ALL)
.title("[ Keybinding ]"),
);
keybindings.render(keybindings_area, buf);
{
let keybindings: Paragraph<'_> = keybindings_widget(View::MainMenu);
keybindings.render(keybindings_area, buf);
}
}
+2
View File
@@ -1,3 +1,5 @@
pub mod keybindings;
pub mod main_menu;
pub use keybindings::keybindings_widget;
pub use main_menu::main_menu_widget;
+6 -3
View File
@@ -1,15 +1,18 @@
use clap::Parser;
use crate::app::View;
#[derive(Parser, Debug)]
#[command(version, about = "War in Tunnels", long_about = "War in Tunnels")]
pub struct Cli {
#[arg(
long,
help = "Default window",
value_name = "main_menu | skirmish | skills_config | perk_decks | settings",
default_value = "main_menu"
value_name = "...",
default_value_t = View::MainMenu,
value_enum
)]
pub default_window: String,
pub window: View,
#[arg(
long,
+1 -1
View File
@@ -14,7 +14,7 @@ fn main() -> Result<()> {
let mut terminal: Terminal<CrosstermBackend<Stdout>> = ratatui::init();
let mut app: App = App {
exit: false,
default_window: args.default_window,
window: args.window,
username: args.username,
game_mode: args.game_mode,
map_width: args.map_width,