generated from GarandPLG/rust-flake-template
Add documentation
Extensive doc comments were added to keybindings, CLI options, logging, and widget modules. The `count_largest_group` function was introduced to compute the largest group size among actions. CLI validation for `skill_points_limit` was broadened to a 90‑690 range. Imports and signatures for `Display` were adjusted accordingly.
This commit is contained in:
@@ -2,49 +2,95 @@ use clap::ValueEnum;
|
|||||||
use ratatui::crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
|
use ratatui::crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
/// Represents the set of actions that the UI can perform.
|
||||||
|
///
|
||||||
|
/// Each variant corresponds to a concrete operation that can be triggered by a
|
||||||
|
/// key binding. The `WildCard` variant is a special case that matches any
|
||||||
|
/// character key and carries the actual character pressed.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub enum Action {
|
pub enum Action {
|
||||||
|
/// Request the application to quit.
|
||||||
Quit,
|
Quit,
|
||||||
|
/// Alternate quit action.
|
||||||
Quit2,
|
Quit2,
|
||||||
|
/// Move the selection cursor up.
|
||||||
Up,
|
Up,
|
||||||
|
/// Move the selection cursor down.
|
||||||
Down,
|
Down,
|
||||||
|
/// Move the selection cursor left.
|
||||||
Left,
|
Left,
|
||||||
|
/// Move the selection cursor right.
|
||||||
Right,
|
Right,
|
||||||
|
/// Scroll up without moving the cursor.
|
||||||
ScrollUp,
|
ScrollUp,
|
||||||
|
/// Scroll down without moving the cursor.
|
||||||
ScrollDown,
|
ScrollDown,
|
||||||
|
/// Scroll left without moving the cursor.
|
||||||
ScrollLeft,
|
ScrollLeft,
|
||||||
|
/// Scroll right without moving the cursor.
|
||||||
ScrollRight,
|
ScrollRight,
|
||||||
|
/// Select the current item.
|
||||||
Space,
|
Space,
|
||||||
|
/// Submit or confirm the current choice.
|
||||||
Enter,
|
Enter,
|
||||||
|
/// Return to the main menu or previous screen.
|
||||||
Esc,
|
Esc,
|
||||||
|
/// Delete the character before the cursor.
|
||||||
Backspace,
|
Backspace,
|
||||||
|
/// Zoom the view in.
|
||||||
ZoomIn,
|
ZoomIn,
|
||||||
|
/// Zoom the view out.
|
||||||
ZoomOut,
|
ZoomOut,
|
||||||
|
/// Matches any character key; the inner `char` is the actual key pressed.
|
||||||
WildCard(char),
|
WildCard(char),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Logical groups of key bindings used for organising help screens or
|
||||||
|
/// configuration sections.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, ValueEnum)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, ValueEnum)]
|
||||||
pub enum Group {
|
pub enum Group {
|
||||||
|
/// Bindings that combine `Ctrl` with movement keys.
|
||||||
CtrlMovement,
|
CtrlMovement,
|
||||||
|
/// Plain movement bindings.
|
||||||
Movement,
|
Movement,
|
||||||
|
/// Scroll bindings.
|
||||||
Scroll,
|
Scroll,
|
||||||
|
/// Selection bindings.
|
||||||
Select,
|
Select,
|
||||||
|
/// Text input related bindings.
|
||||||
Input,
|
Input,
|
||||||
|
/// Zoom related bindings.
|
||||||
Zoom,
|
Zoom,
|
||||||
|
/// Quit related bindings.
|
||||||
Quit,
|
Quit,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Description of a single key binding.
|
||||||
|
///
|
||||||
|
/// This struct stores the mapping from a physical key event to an `Action`,
|
||||||
|
/// together with metadata used for displaying help information.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub struct KeyBinding {
|
pub struct KeyBinding {
|
||||||
|
/// The logical action performed when this binding is triggered.
|
||||||
pub action: Action,
|
pub action: Action,
|
||||||
|
/// The raw `crossterm::event::KeyCode` that identifies the key.
|
||||||
pub code: KeyCode,
|
pub code: KeyCode,
|
||||||
|
/// The kind of key event (crossterm::event::KeyEventKind).
|
||||||
pub kind: KeyEventKind,
|
pub kind: KeyEventKind,
|
||||||
|
/// Any modifier keys that must be held (crossterm::event::KeyModifiers).
|
||||||
pub modifiers: KeyModifiers,
|
pub modifiers: KeyModifiers,
|
||||||
|
/// The group this binding belongs to, useful for categorising help.
|
||||||
pub group: Group,
|
pub group: Group,
|
||||||
|
/// Human‑readable symbol shown in UI/help screens.
|
||||||
pub symbol: &'static str,
|
pub symbol: &'static str,
|
||||||
|
/// Short description of what the binding does.
|
||||||
pub description: &'static str,
|
pub description: &'static str,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Complete list of all key bindings used by the application.
|
||||||
|
///
|
||||||
|
/// The slice is ordered primarily for readability; the runtime lookup scans
|
||||||
|
/// this slice linearly, which is acceptable given the modest size of the list.
|
||||||
pub static KEYBINDINGS: &[KeyBinding] = &[
|
pub static KEYBINDINGS: &[KeyBinding] = &[
|
||||||
KeyBinding {
|
KeyBinding {
|
||||||
action: Action::Quit,
|
action: Action::Quit,
|
||||||
@@ -201,10 +247,18 @@ pub static KEYBINDINGS: &[KeyBinding] = &[
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/// Returns the `KeyBinding` associated with the given `Action`, if one exists.
|
||||||
pub fn binding_for(action: Action) -> Option<&'static KeyBinding> {
|
pub fn binding_for(action: Action) -> Option<&'static KeyBinding> {
|
||||||
KEYBINDINGS.iter().find(|b| b.action == action)
|
KEYBINDINGS.iter().find(|b| b.action == action)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converts a raw `KeyEvent` into an `Action` if a matching binding is found.
|
||||||
|
///
|
||||||
|
/// The function first looks for an exact match on code, kind, and modifiers.
|
||||||
|
/// If no exact match exists but a wildcard binding is present for the same
|
||||||
|
/// event kind, the function returns `Action::WildCard` with the character that
|
||||||
|
/// was pressed. Returns `None` when the event does not correspond to any known
|
||||||
|
/// action.
|
||||||
pub fn event_to_action(event: &KeyEvent) -> Option<Action> {
|
pub fn event_to_action(event: &KeyEvent) -> Option<Action> {
|
||||||
if let Some(b) = KEYBINDINGS
|
if let Some(b) = KEYBINDINGS
|
||||||
.iter()
|
.iter()
|
||||||
@@ -225,6 +279,11 @@ pub fn event_to_action(event: &KeyEvent) -> Option<Action> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Determines the size of the largest group among a slice of actions.
|
||||||
|
///
|
||||||
|
/// It counts how many times each group appears in the supplied actions and
|
||||||
|
/// returns the highest count. This can be used, for example, to size UI
|
||||||
|
/// elements that display groups of bindings.
|
||||||
pub fn count_largest_group(actions: &Vec<Action>) -> u16 {
|
pub fn count_largest_group(actions: &Vec<Action>) -> u16 {
|
||||||
let mut group_counts: HashMap<Group, u16> = HashMap::new();
|
let mut group_counts: HashMap<Group, u16> = HashMap::new();
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use clap::ValueEnum;
|
use clap::ValueEnum;
|
||||||
use std::fmt::Display;
|
use std::fmt::{Display, Formatter, Result};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub struct PerkDecksState {
|
pub struct PerkDecksState {
|
||||||
@@ -16,7 +16,7 @@ pub enum PerkDecks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Display for PerkDecks {
|
impl Display for PerkDecks {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||||
match self {
|
match self {
|
||||||
PerkDecks::Silesian => write!(f, "Silesian"),
|
PerkDecks::Silesian => write!(f, "Silesian"),
|
||||||
PerkDecks::BogeyMan => write!(f, "Bogey Man"),
|
PerkDecks::BogeyMan => write!(f, "Bogey Man"),
|
||||||
|
|||||||
@@ -12,11 +12,31 @@ use ratatui::{
|
|||||||
};
|
};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
/// A widget that visualises the keybindings for a set of actions.
|
||||||
|
///
|
||||||
|
/// The widget stores a vector of [`Paragraph`] objects, each representing one
|
||||||
|
/// logical group of keybindings (e.g. navigation, editing, etc.). The paragraphs
|
||||||
|
/// are rendered side‑by‑side inside a bordered container.
|
||||||
pub struct KeybindingsWidget {
|
pub struct KeybindingsWidget {
|
||||||
|
/// Paragraphs, one per used `Group`, containing the formatted keybinding lines.
|
||||||
grouped: Vec<Paragraph<'static>>,
|
grouped: Vec<Paragraph<'static>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KeybindingsWidget {
|
impl KeybindingsWidget {
|
||||||
|
/// Create a new widget from a list of actions.
|
||||||
|
///
|
||||||
|
/// For each action, the corresponding static [`KeyBinding`] is looked up
|
||||||
|
/// using [`binding_for`]. Only groups that have at least one binding are
|
||||||
|
/// displayed. The resulting widget contains a paragraph for each group,
|
||||||
|
/// formatted with the key symbol (red & bold) followed by its description.
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
///
|
||||||
|
/// * `actions` – A vector of actions for which keybindings should be shown.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// A fully‑initialised `KeybindingsWidget` ready for rendering.
|
||||||
pub fn new(actions: Vec<Action>) -> Self {
|
pub fn new(actions: Vec<Action>) -> Self {
|
||||||
let keybindings: Vec<Option<&'static KeyBinding>> =
|
let keybindings: Vec<Option<&'static KeyBinding>> =
|
||||||
actions.iter().map(|a| binding_for(*a)).collect();
|
actions.iter().map(|a| binding_for(*a)).collect();
|
||||||
@@ -66,6 +86,14 @@ impl KeybindingsWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for KeybindingsWidget {
|
impl Widget for KeybindingsWidget {
|
||||||
|
/// Render the widget inside the given rectangular area.
|
||||||
|
///
|
||||||
|
/// The widget first draws a surrounding border with the title *Keybindings*.
|
||||||
|
/// It then divides the inner area into equal horizontal slices—one per
|
||||||
|
/// used group—and renders each group's paragraph into its slice.
|
||||||
|
///
|
||||||
|
/// If no groups are present, the function returns early without drawing
|
||||||
|
/// anything.
|
||||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
let count: u16 = self.grouped.len() as u16;
|
let count: u16 = self.grouped.len() as u16;
|
||||||
|
|
||||||
|
|||||||
+36
-2
@@ -5,9 +5,16 @@ use crate::app::{
|
|||||||
use clap::{Error, Parser, error::ErrorKind, value_parser};
|
use clap::{Error, Parser, error::ErrorKind, value_parser};
|
||||||
use std::num::ParseFloatError;
|
use std::num::ParseFloatError;
|
||||||
|
|
||||||
|
/// Holds all configurable options that can be passed via the command line.
|
||||||
|
///
|
||||||
|
/// Each field corresponds to a CLI flag (e.g. `--view`, `--username`, …).
|
||||||
|
/// The `clap` attributes describe the flag name, help text, default value,
|
||||||
|
/// and any validation constraints. The struct derives `Parser` so that
|
||||||
|
/// `Cli::parse()` can be called directly to obtain a populated instance.
|
||||||
#[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 {
|
||||||
|
/// The initial view/window to display.
|
||||||
#[arg(
|
#[arg(
|
||||||
long,
|
long,
|
||||||
help = "Default window",
|
help = "Default window",
|
||||||
@@ -17,6 +24,7 @@ pub struct Cli {
|
|||||||
)]
|
)]
|
||||||
pub view: View,
|
pub view: View,
|
||||||
|
|
||||||
|
/// Player name shown in the UI.
|
||||||
#[arg(
|
#[arg(
|
||||||
long,
|
long,
|
||||||
help = "Username",
|
help = "Username",
|
||||||
@@ -25,6 +33,7 @@ pub struct Cli {
|
|||||||
)]
|
)]
|
||||||
pub username: String,
|
pub username: String,
|
||||||
|
|
||||||
|
/// Game mode selection.
|
||||||
#[arg(
|
#[arg(
|
||||||
long,
|
long,
|
||||||
help = "Game mode",
|
help = "Game mode",
|
||||||
@@ -34,6 +43,7 @@ pub struct Cli {
|
|||||||
)]
|
)]
|
||||||
pub game_mode: GameMode,
|
pub game_mode: GameMode,
|
||||||
|
|
||||||
|
/// Width of the generated map (20–100 tiles).
|
||||||
#[arg(
|
#[arg(
|
||||||
long,
|
long,
|
||||||
help = "Map width",
|
help = "Map width",
|
||||||
@@ -43,6 +53,7 @@ pub struct Cli {
|
|||||||
)]
|
)]
|
||||||
pub map_width: u8,
|
pub map_width: u8,
|
||||||
|
|
||||||
|
/// Height of the generated map (25–50 tiles).
|
||||||
#[arg(
|
#[arg(
|
||||||
long,
|
long,
|
||||||
help = "Map height",
|
help = "Map height",
|
||||||
@@ -52,6 +63,7 @@ pub struct Cli {
|
|||||||
)]
|
)]
|
||||||
pub map_height: u8,
|
pub map_height: u8,
|
||||||
|
|
||||||
|
/// Zoom level used for the UI.
|
||||||
#[arg(
|
#[arg(
|
||||||
long,
|
long,
|
||||||
help = "Zoom level",
|
help = "Zoom level",
|
||||||
@@ -61,6 +73,7 @@ pub struct Cli {
|
|||||||
)]
|
)]
|
||||||
pub zoom_level: ZoomLevel,
|
pub zoom_level: ZoomLevel,
|
||||||
|
|
||||||
|
/// Which perk deck to use.
|
||||||
#[arg(
|
#[arg(
|
||||||
long,
|
long,
|
||||||
help = "Perk Deck",
|
help = "Perk Deck",
|
||||||
@@ -70,6 +83,7 @@ pub struct Cli {
|
|||||||
)]
|
)]
|
||||||
pub perk_deck: PerkDecks,
|
pub perk_deck: PerkDecks,
|
||||||
|
|
||||||
|
/// Starting amount of wood (1–150).
|
||||||
#[arg(
|
#[arg(
|
||||||
long,
|
long,
|
||||||
help = "Starting wood",
|
help = "Starting wood",
|
||||||
@@ -79,6 +93,7 @@ pub struct Cli {
|
|||||||
)]
|
)]
|
||||||
pub starting_wood: u16,
|
pub starting_wood: u16,
|
||||||
|
|
||||||
|
/// Starting amount of iron (1–150).
|
||||||
#[arg(
|
#[arg(
|
||||||
long,
|
long,
|
||||||
help = "Starting iron",
|
help = "Starting iron",
|
||||||
@@ -88,6 +103,7 @@ pub struct Cli {
|
|||||||
)]
|
)]
|
||||||
pub starting_iron: u16,
|
pub starting_iron: u16,
|
||||||
|
|
||||||
|
/// Maximum amount of units (49–149).
|
||||||
#[arg(
|
#[arg(
|
||||||
long,
|
long,
|
||||||
help = "Supply limit",
|
help = "Supply limit",
|
||||||
@@ -97,6 +113,7 @@ pub struct Cli {
|
|||||||
)]
|
)]
|
||||||
pub supply_limit: u8,
|
pub supply_limit: u8,
|
||||||
|
|
||||||
|
/// Modifier applied to experience points (0.5–2.0).
|
||||||
#[arg(
|
#[arg(
|
||||||
long,
|
long,
|
||||||
help = "XP modifier",
|
help = "XP modifier",
|
||||||
@@ -106,15 +123,17 @@ pub struct Cli {
|
|||||||
)]
|
)]
|
||||||
pub xp_modifier: f32,
|
pub xp_modifier: f32,
|
||||||
|
|
||||||
|
/// Upper bound for skill points that can be earned (90–690).
|
||||||
#[arg(
|
#[arg(
|
||||||
long,
|
long,
|
||||||
help = "Skill points limit",
|
help = "Skill points limit",
|
||||||
value_name = "Positive integer [120; 690]",
|
value_name = "Positive integer [90; 690]",
|
||||||
default_value = "120",
|
default_value = "120",
|
||||||
value_parser = value_parser!(u16).range(120..=690)
|
value_parser = value_parser!(u16).range(90..=690)
|
||||||
)]
|
)]
|
||||||
pub skill_points_limit: u16,
|
pub skill_points_limit: u16,
|
||||||
|
|
||||||
|
/// Enable logging to a file (default: disabled).
|
||||||
#[arg(
|
#[arg(
|
||||||
long,
|
long,
|
||||||
help = "Enable logging to file (default: disabled)",
|
help = "Enable logging to file (default: disabled)",
|
||||||
@@ -123,10 +142,25 @@ pub struct Cli {
|
|||||||
pub log: bool,
|
pub log: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parses the command line arguments and returns a fully populated `Cli`
|
||||||
|
/// instance. This function simply forwards to `Cli::parse()`, which
|
||||||
|
/// handles argument validation and displays helpful error messages if
|
||||||
|
/// the user supplies invalid input.
|
||||||
pub fn get_args() -> Cli {
|
pub fn get_args() -> Cli {
|
||||||
Cli::parse()
|
Cli::parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parses a string into a floating‑point XP modifier and validates that it
|
||||||
|
/// lies within the inclusive range `0.5..=2.0`.
|
||||||
|
///
|
||||||
|
/// The function is used as a custom `value_parser` for the `--xp-modifier`
|
||||||
|
/// flag. On failure it returns a `clap::Error` with `ErrorKind::InvalidValue`,
|
||||||
|
/// allowing `clap` to present a clear error message to the user.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// * Returns an error if the string cannot be parsed as `f32`.
|
||||||
|
/// * Returns an error if the parsed value is outside the allowed range.
|
||||||
fn parse_xp(s: &str) -> Result<f32, Error> {
|
fn parse_xp(s: &str) -> Result<f32, Error> {
|
||||||
let v: f32 = s
|
let v: f32 = s
|
||||||
.parse()
|
.parse()
|
||||||
|
|||||||
+26
@@ -1,6 +1,32 @@
|
|||||||
use simplelog::{Config, LevelFilter, WriteLogger};
|
use simplelog::{Config, LevelFilter, WriteLogger};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
|
||||||
|
/// Initializes the global logger for the application.
|
||||||
|
///
|
||||||
|
/// This function creates (or truncates) a file called `war_in_tunnels.log`
|
||||||
|
/// in the current working directory and configures a `simplelog::WriteLogger`
|
||||||
|
/// to write log messages with a minimum level of `Info`.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// - If the log file cannot be created (e.g., due to permission issues).
|
||||||
|
/// - If the logger cannot be initialized (which would indicate an internal
|
||||||
|
/// error in the `simplelog` crate).
|
||||||
|
///
|
||||||
|
/// Call this once, preferably at the very beginning of `main()`, before any
|
||||||
|
/// logging macros are used.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use war_in_tunnels::logs::init_logger;
|
||||||
|
/// use log::info;
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// init_logger();
|
||||||
|
/// info!("Application started");
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
pub fn init_logger() {
|
pub fn init_logger() {
|
||||||
WriteLogger::init(
|
WriteLogger::init(
|
||||||
LevelFilter::Info,
|
LevelFilter::Info,
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ use war_in_tunnels::{
|
|||||||
logs::init_logger,
|
logs::init_logger,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Starts the terminal UI application.
|
||||||
|
///
|
||||||
|
/// The function follows the steps outlined in the module‑level documentation.
|
||||||
|
/// It returns any `std::io::Error` encountered while initializing or restoring the
|
||||||
|
/// terminal, or while running the `App`.
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let args: Cli = get_args();
|
let args: Cli = get_args();
|
||||||
if args.log {
|
if args.log {
|
||||||
|
|||||||
Reference in New Issue
Block a user