generated from GarandPLG/rust-flake-template
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:
@@ -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
@@ -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(())
|
||||
|
||||
@@ -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
@@ -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
@@ -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."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 ]"),
|
||||
)
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user