diff --git a/src/app/app.rs b/src/app/app.rs index 7a26e67..0ab27b0 100644 --- a/src/app/app.rs +++ b/src/app/app.rs @@ -27,7 +27,7 @@ pub struct App { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, ValueEnum)] pub enum GameMode { LastManStanding, - Frontlines, + FrontLines, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, ValueEnum)] diff --git a/src/app/keybindings/settings.rs b/src/app/keybindings/settings.rs index 61bfee8..3c98b23 100644 --- a/src/app/keybindings/settings.rs +++ b/src/app/keybindings/settings.rs @@ -1,18 +1,91 @@ use crate::app::{ App, View, keybindings::{Action, event_to_action}, + states::SettingsValue, }; use ratatui::crossterm::event::KeyEvent; +use std::any::type_name_of_val; pub fn settings_keybindings(app: &mut App, key_event: &KeyEvent) { if let Some(action) = event_to_action(&key_event) { match action { - // Action::Up, - // Action::Down, - Action::Quit => app.exit = true, - Action::Quit2 => app.exit = true, + Action::Up => { + app.states.settings.selected_setting = app + .states + .settings + .selected_setting + .saturating_sub(1) + .max(0) + } + Action::Down => { + app.states.settings.selected_setting = app + .states + .settings + .selected_setting + .saturating_add(1) + .min(9) + } Action::Esc => app.view = View::MainMenu, Action::Space => app.states.settings.show_popup = !app.states.settings.show_popup, + Action::Enter => { + // FIXME: No feedback + if app.states.settings.show_popup { + let option = + &app.states.settings.options[app.states.settings.selected_setting].value; + + let option_type: &'static str = type_name_of_val(option); + + let new_value: String = app.states.settings.selected_setting_new_value.clone(); + + match option_type { + "u8" => { + if let Ok(value) = new_value.parse::() { + app.states.settings.options[app.states.settings.selected_setting] + .value = SettingsValue::U8(value); + + app.states.settings.selected_setting_new_value = "".to_string(); + app.states.settings.error_message = "".to_string(); + app.states.settings.show_popup = false; + } else { + app.states.settings.error_message = "Invalid value".to_string(); + } + } + "f32" => { + if let Ok(value) = new_value.parse::() { + app.states.settings.options[app.states.settings.selected_setting] + .value = SettingsValue::F32(value); + + app.states.settings.selected_setting_new_value = "".to_string(); + app.states.settings.error_message = "".to_string(); + app.states.settings.show_popup = false; + } else { + app.states.settings.error_message = "Invalid value".to_string(); + } + } + "u16" => { + if let Ok(value) = new_value.parse::() { + app.states.settings.options[app.states.settings.selected_setting] + .value = SettingsValue::U16(value); + + app.states.settings.selected_setting_new_value = "".to_string(); + app.states.settings.error_message = "".to_string(); + app.states.settings.show_popup = false; + } else { + app.states.settings.error_message = "Invalid value".to_string(); + } + } + "std::string::String" => { + app.states.settings.options[app.states.settings.selected_setting] + .value = SettingsValue::Text(new_value); + + app.states.settings.selected_setting_new_value = "".to_string(); + app.states.settings.error_message = "".to_string(); + app.states.settings.show_popup = false; + } + _ => (), + } + } + } Action::Backspace => { if app.states.settings.show_popup { app.states.settings.selected_setting_new_value.pop(); @@ -23,7 +96,9 @@ pub fn settings_keybindings(app: &mut App, key_event: &KeyEvent) { app.states.settings.selected_setting_new_value.push(c); } } - _ => (), + Action::Quit => app.exit = true, + Action::Quit2 => app.exit = true, + // _ => (), } } } diff --git a/src/app/states.rs b/src/app/states.rs index 94e39e5..e97dd14 100644 --- a/src/app/states.rs +++ b/src/app/states.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + use crate::{ app::{GameMode, PerkDecks}, cli::Cli, @@ -39,28 +41,63 @@ pub struct SkillsConfigState { pub selected_skill: usize, } -#[derive(Debug, Clone, PartialEq)] -pub struct SettingsOptions { - pub username: String, - pub game_mode: GameMode, - pub map_width: u8, - pub map_height: u8, - pub perk_deck: PerkDecks, - pub starting_wood: u8, - pub starting_iron: u8, - pub supply_limit: u8, - pub xp_modifier: f32, - pub skill_points_limit: u16, -} - #[derive(Debug, Clone, PartialEq)] pub struct SettingsState { pub id: usize, pub name: &'static str, - pub options: SettingsOptions, pub selected_setting: usize, pub show_popup: bool, pub selected_setting_new_value: String, + pub error_message: String, + pub options: Vec, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum SettingsValue { + U8(u8), + F32(f32), + U16(u16), + Text(String), + GameMode(GameMode), + PerkDeck(PerkDecks), +} + +#[derive(Debug, Clone, PartialEq)] +pub struct SettingsOption { + pub name: &'static str, + pub value: SettingsValue, +} + +impl Display for SettingsValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + SettingsValue::U8(v) => write!(f, "{}", v), + SettingsValue::F32(v) => write!(f, "{}", v), + SettingsValue::U16(v) => write!(f, "{}", v), + SettingsValue::Text(v) => write!(f, "{}", v), + SettingsValue::GameMode(v) => write!(f, "{}", v), + SettingsValue::PerkDeck(v) => write!(f, "{}", v), + } + } +} + +impl Display for GameMode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + GameMode::FrontLines => write!(f, "Front Lines"), + GameMode::LastManStanding => write!(f, "Last Man Standing"), + } + } +} + +impl Display for PerkDecks { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + PerkDecks::Silesian => write!(f, "Silesian"), + PerkDecks::BogeyMan => write!(f, "Bogey Man"), + PerkDecks::Anteater => write!(f, "Anteater"), + } + } } impl GameStates { @@ -91,18 +128,49 @@ impl GameStates { selected_setting: 0, show_popup: false, selected_setting_new_value: String::new(), - options: SettingsOptions { - username: args.username, - game_mode: args.game_mode, - map_width: args.map_width, - map_height: args.map_height, - perk_deck: args.perk_deck, - starting_wood: args.starting_wood, - starting_iron: args.starting_iron, - supply_limit: args.supply_limit, - xp_modifier: args.xp_modifier, - skill_points_limit: args.skill_points_limit, - }, + error_message: String::new(), + options: vec![ + SettingsOption { + name: "Username", + value: SettingsValue::Text(args.username), + }, + SettingsOption { + name: "Game Mode", + value: SettingsValue::GameMode(args.game_mode), + }, + SettingsOption { + name: "Map Width", + value: SettingsValue::U8(args.map_width), + }, + SettingsOption { + name: "Map Height", + value: SettingsValue::U8(args.map_height), + }, + SettingsOption { + name: "Perk Deck", + value: SettingsValue::PerkDeck(args.perk_deck), + }, + SettingsOption { + name: "Starting Wood", + value: SettingsValue::U8(args.starting_wood), + }, + SettingsOption { + name: "Starting Iron", + value: SettingsValue::U8(args.starting_iron), + }, + SettingsOption { + name: "Supply Limit", + value: SettingsValue::U8(args.supply_limit), + }, + SettingsOption { + name: "XP Modifier", + value: SettingsValue::F32(args.xp_modifier), + }, + SettingsOption { + name: "Skill Points Limit", + value: SettingsValue::U16(args.skill_points_limit), + }, + ], }, } } diff --git a/src/app/views/main_menu.rs b/src/app/views/main_menu.rs index f437b50..010602a 100644 --- a/src/app/views/main_menu.rs +++ b/src/app/views/main_menu.rs @@ -66,9 +66,9 @@ pub fn main_menu_view(app: &App, area: Rect, buf: &mut Buffer) { let view_string: String = format_view_string(format!("{:?}", view)); let styled: Line<'_> = if app.states.main_menu.selected_view == i { - Line::from(format!("> {view_string}")).yellow() + Line::from_iter(["> ".cyan(), view_string.yellow()]).yellow() } else { - Line::from(view_string).white() + Line::from(format!(" {view_string}")).white() }; styled diff --git a/src/app/views/settings.rs b/src/app/views/settings.rs index 7af9912..20bd536 100644 --- a/src/app/views/settings.rs +++ b/src/app/views/settings.rs @@ -3,14 +3,15 @@ use ratatui::{ buffer::Buffer, layout::{Alignment, Constraint, Layout, Rect}, style::{Style, Stylize}, + text::Line, widgets::{Block, Borders, Padding, Paragraph, Widget, Wrap}, }; pub fn settings_view(app: &App, area: Rect, buf: &mut Buffer) { let vertical_layout: Layout = Layout::vertical([ - Constraint::Length(10), - Constraint::Fill(1), Constraint::Length(4), + Constraint::Fill(1), + Constraint::Length(5), ]); let [title_area, main_area, keybindings_area] = vertical_layout.areas(area); @@ -23,31 +24,68 @@ pub fn settings_view(app: &App, area: Rect, buf: &mut Buffer) { Block::new() .gray() .borders(Borders::LEFT | Borders::TOP | Borders::RIGHT) - .padding(Padding::new(1, 1, 1, 1)), + .padding(Padding::new(5, 1, 1, 1)), ) .render(title_area, buf); - Paragraph::new("Settings") - .alignment(Alignment::Center) - .yellow() - .block( - Block::new() - .gray() - .borders(Borders::LEFT | Borders::RIGHT) - .padding(Padding::new(1, 1, 1, 1)), - ) - .render(main_area, buf); + let lines: Vec> = app + .states + .settings + .options + .iter() + .enumerate() + .map(|(i, o)| { + if i == app.states.settings.selected_setting { + Line::from_iter([ + "> ".cyan(), + o.name.yellow(), + ": ".gray(), + o.value.to_string().light_green(), + ]) + } else { + Line::from(format!(" {}: {}", o.name, o.value)).white() + } + }) + .collect(); + + let settings_block: Block<'_> = Block::new() + .borders(Borders::LEFT | Borders::TOP | Borders::RIGHT) + .padding(Padding::new(1, 1, 1, 1)) + .title(Line::from_iter([ + "[ ".gray(), + "Settings".magenta(), + " ]".gray(), + ])) + .gray(); + + let settings_area: Rect = settings_block.inner(main_area); + + settings_block.render(main_area, buf); + + Paragraph::new(lines).alignment(Alignment::Center).render( + settings_area.centered_vertically(Constraint::Ratio(1, 2)), + buf, + ); + + let popup_text: Vec> = Vec::from_iter([ + app.states + .settings + .selected_setting_new_value + .clone() + .into(), + app.states.settings.error_message.clone().into(), + ]); if app.states.settings.show_popup { - Paragraph::new(app.states.settings.selected_setting_new_value.clone()) + Paragraph::new(popup_text) .wrap(Wrap { trim: true }) - .style(Style::default().yellow()) + .style(Style::default()) .block( Block::new() - .title("Insert value") - .title_style(Style::default().green()) + .title("Insert new value") + .title_style(Style::default().yellow()) .borders(Borders::ALL) - .border_style(Style::default().blue()) + .border_style(Style::default().gray()) .padding(Padding { left: 1, right: 1, @@ -57,10 +95,14 @@ pub fn settings_view(app: &App, area: Rect, buf: &mut Buffer) { ) .render( Rect { - x: main_area.width / 3, - y: main_area.height / 2, - width: main_area.width / 3, - height: main_area.height / 7, + x: 2, + y: main_area.height - 1, + width: main_area.width - 3, + height: if app.states.settings.error_message.is_empty() { + 5 + } else { + 6 + }, }, buf, ); diff --git a/src/app/widgets/keybindings.rs b/src/app/widgets/keybindings.rs index 0aed177..4ced29d 100644 --- a/src/app/widgets/keybindings.rs +++ b/src/app/widgets/keybindings.rs @@ -72,7 +72,11 @@ impl Widget for KeybindingsWidget { let block: Block<'_> = Block::default() .borders(Borders::ALL) - .title("[ Keybindings ]"); + .title(Line::from_iter([ + "[ ".gray(), + "Keybindings".magenta(), + " ]".gray(), + ])); let inner: Rect = block.inner(area);