diff --git a/src/app/app.rs b/src/app/app.rs index 2ebb20f..9087601 100644 --- a/src/app/app.rs +++ b/src/app/app.rs @@ -22,22 +22,29 @@ pub struct App { pub exit: bool, pub view: View, pub window_area: Rect, - pub states: GameStates, + pub args: Cli, + pub states: Option, } impl App { pub fn new(args: Cli) -> Self { - let mut states: GameStates = GameStates::new(&args); - states.skirmish.init_board(); - Self { exit: false, view: args.view, window_area: Rect::default(), - states, + args: args, + states: None, } } + pub fn states(&self) -> Option<&GameStates> { + self.states.as_ref() + } + + pub fn states_mut(&mut self) -> Option<&mut GameStates> { + self.states.as_mut() + } + pub fn run(&mut self, terminal: &mut DefaultTerminal, rx: Receiver) -> Result<()> { while !self.exit { terminal.draw(|frame: &mut Frame<'_>| self.draw(frame))?; @@ -65,6 +72,9 @@ impl App { || window_area.height != self.window_area.height { self.window_area = window_area; + if self.states.is_none() { + self.states = Some(GameStates::new(&self.args, &self.window_area)) + } } frame.render_widget(self, window_area); diff --git a/src/app/helpers/cells_area.rs b/src/app/helpers/cells_area.rs new file mode 100644 index 0000000..c2f245b --- /dev/null +++ b/src/app/helpers/cells_area.rs @@ -0,0 +1,15 @@ +use crate::app::views::skirmish_layout; +use ratatui::{ + layout::{Margin, Rect}, + widgets::{Block, Borders}, +}; + +pub fn cells_area_helper(area: &Rect) -> Rect { + Block::new() + .borders(Borders::LEFT | Borders::TOP | Borders::RIGHT) + .inner(skirmish_layout(*area)[1]) + .inner(Margin { + horizontal: 3, + vertical: 1, + }) +} diff --git a/src/app/helpers/mod.rs b/src/app/helpers/mod.rs index 66e0930..931f3e3 100644 --- a/src/app/helpers/mod.rs +++ b/src/app/helpers/mod.rs @@ -1,7 +1,9 @@ pub mod block_title; +pub mod cells_area; pub mod main_menu_option; pub mod zoom_level; pub use block_title::{block_single_title_helper, block_title_helper}; +pub use cells_area::cells_area_helper; pub use main_menu_option::main_menu_option_helper; pub use zoom_level::{CellSizes, cell_size_helper}; diff --git a/src/app/helpers/zoom_level.rs b/src/app/helpers/zoom_level.rs index b848cf7..97c0927 100644 --- a/src/app/helpers/zoom_level.rs +++ b/src/app/helpers/zoom_level.rs @@ -5,7 +5,7 @@ pub enum CellSizes { Height, } -pub fn cell_size_helper(cell_size: CellSizes, zoom_level: ZoomLevel) -> u16 { +pub fn cell_size_helper(cell_size: CellSizes, zoom_level: ZoomLevel) -> usize { match (cell_size, zoom_level) { (CellSizes::Width, ZoomLevel::ZoomedIn) => 13, (CellSizes::Width, ZoomLevel::Default) => 9, diff --git a/src/app/keybindings/main_menu.rs b/src/app/keybindings/main_menu.rs index ce55e24..7f1c895 100644 --- a/src/app/keybindings/main_menu.rs +++ b/src/app/keybindings/main_menu.rs @@ -7,17 +7,22 @@ use ratatui::crossterm::event::KeyEvent; pub fn main_menu_keybindings(app: &mut App, event: &KeyEvent) { if let Some(action) = event_to_action(&event) { common_keybindings(app, action); + + let Some(states) = app.states_mut() else { + return; + }; + match action { Action::Up => { - app.states.main_menu.selected_view = - app.states.main_menu.selected_view.saturating_sub(1).max(1); + states.main_menu.selected_view = + states.main_menu.selected_view.saturating_sub(1).max(1); } Action::Down => { - app.states.main_menu.selected_view = - app.states.main_menu.selected_view.saturating_add(1).min(3); + states.main_menu.selected_view = + states.main_menu.selected_view.saturating_add(1).min(3); } Action::Space => { - let selected_view: usize = app.states.main_menu.selected_view; + let selected_view: usize = states.main_menu.selected_view; if selected_view == 1 { app.view = View::Skirmish; diff --git a/src/app/keybindings/skirmish.rs b/src/app/keybindings/skirmish.rs index 8ca4ca9..f99005f 100644 --- a/src/app/keybindings/skirmish.rs +++ b/src/app/keybindings/skirmish.rs @@ -8,48 +8,53 @@ use ratatui::crossterm::event::KeyEvent; pub fn skirmish_keybindings(app: &mut App, key_event: &KeyEvent) { if let Some(action) = event_to_action(&key_event) { common_keybindings(app, action); + + let Some(states) = app.states_mut() else { + return; + }; + match action { - Action::Up => app.states.skirmish.focused_cell.move_up(), - Action::Down => app.states.skirmish.focused_cell.move_down(), - Action::Left => app.states.skirmish.focused_cell.move_left(), - Action::Right => app.states.skirmish.focused_cell.move_right(), + Action::Up => states.skirmish.board.focused_cell.move_up(), + Action::Down => states.skirmish.board.focused_cell.move_down(), + Action::Left => states.skirmish.board.focused_cell.move_left(), + Action::Right => states.skirmish.board.focused_cell.move_right(), Action::ScrollUp => { - app.states.skirmish.vertical_offset.prev(); - app.states.skirmish.focused_cell.move_up(); + states.skirmish.board.vertical_offset.prev(); + states.skirmish.board.focused_cell.move_up(); } Action::ScrollDown => { - app.states.skirmish.vertical_offset.next(); - app.states.skirmish.focused_cell.move_down(); + states.skirmish.board.vertical_offset.next(); + states.skirmish.board.focused_cell.move_down(); } Action::ScrollLeft => { - app.states.skirmish.horizontal_offset.prev(); - app.states.skirmish.focused_cell.move_left(); + states.skirmish.board.horizontal_offset.prev(); + states.skirmish.board.focused_cell.move_left(); } Action::ScrollRight => { - app.states.skirmish.horizontal_offset.next(); - app.states.skirmish.focused_cell.move_right(); + states.skirmish.board.horizontal_offset.next(); + states.skirmish.board.focused_cell.move_right(); } - Action::ZoomIn => match app.states.skirmish.zoom_level { + Action::ZoomIn => match states.skirmish.board.zoom_level { ZoomLevel::ZoomedIn => {} ZoomLevel::Default => { - app.states.skirmish.zoom_level = ZoomLevel::ZoomedIn; - app.states.skirmish.vertical_offset.next(); + states.skirmish.board.zoom_change(ZoomLevel::ZoomedIn); + states.skirmish.board.vertical_offset.next(); } ZoomLevel::ZoomedOut => { - app.states.skirmish.zoom_level = ZoomLevel::Default; - app.states.skirmish.vertical_offset.next(); - app.states.skirmish.vertical_offset.next(); + states.skirmish.board.zoom_change(ZoomLevel::Default); + states.skirmish.board.vertical_offset.next(); + states.skirmish.board.vertical_offset.next(); } }, - Action::ZoomOut => match app.states.skirmish.zoom_level { + Action::ZoomOut => match states.skirmish.board.zoom_level { ZoomLevel::ZoomedIn => { - app.states.skirmish.zoom_level = ZoomLevel::Default; - app.states.skirmish.vertical_offset.prev(); + states.skirmish.board.zoom_change(ZoomLevel::Default); + states.skirmish.board.vertical_offset.prev(); } ZoomLevel::Default => { - app.states.skirmish.zoom_level = ZoomLevel::ZoomedOut; - app.states.skirmish.vertical_offset.prev(); - app.states.skirmish.vertical_offset.prev(); + states.skirmish.board.zoom_change(ZoomLevel::ZoomedOut); + states.skirmish.board.vertical_offset.prev(); + states.skirmish.board.vertical_offset.prev(); } ZoomLevel::ZoomedOut => {} }, diff --git a/src/app/state.rs b/src/app/state.rs index 0939d32..ae98cb8 100644 --- a/src/app/state.rs +++ b/src/app/state.rs @@ -1,10 +1,11 @@ use crate::{ app::states::{ - FocusedCell, MainMenuState, Offset, PerkDecksState, SettingsState, SkillsConfigState, - SkirmishState, + MainMenuState, PerkDecksState, SettingsState, SkillsConfigState, SkirmishState, + skirmish_states::BoardState, }, cli::Cli, }; +use ratatui::layout::Rect; #[derive(Debug, Clone, PartialEq)] pub struct GameStates { @@ -16,7 +17,7 @@ pub struct GameStates { } impl GameStates { - pub fn new(args: &Cli) -> Self { + pub fn new(args: &Cli, area: &Rect) -> Self { Self { main_menu: MainMenuState { id: 0, @@ -26,17 +27,11 @@ impl GameStates { skirmish: SkirmishState { id: 1, name: "Skirmish", - vertical_offset: Offset::new(), - horizontal_offset: Offset::new(), - map_width: args.map_width as usize, - map_height: args.map_height as usize, - board_cells: Vec::new(), - zoom_level: args.zoom_level, - focused_cell: FocusedCell::new( - (args.map_height / 2) as usize - 1, - 2, - args.map_height as usize, + board: BoardState::new( + area, args.map_width as usize, + args.map_height as usize, + args.zoom_level, ), }, perk_decks: PerkDecksState { diff --git a/src/app/states/skirmish.rs b/src/app/states/skirmish.rs index 012ddee..4df3814 100644 --- a/src/app/states/skirmish.rs +++ b/src/app/states/skirmish.rs @@ -1,34 +1,11 @@ -use crate::app::{ - states::{FocusedCell, Offset}, - widgets::CellWidget, -}; +use crate::app::states::skirmish_states::BoardState; use clap::ValueEnum; #[derive(Debug, Clone, PartialEq, Eq)] pub struct SkirmishState { pub id: usize, pub name: &'static str, - pub map_width: usize, - pub map_height: usize, - pub vertical_offset: Offset, - pub horizontal_offset: Offset, - pub board_cells: Vec, - pub zoom_level: ZoomLevel, - pub focused_cell: FocusedCell, -} - -impl SkirmishState { - pub fn init_board(&mut self) { - if !self.board_cells.is_empty() { - return; - } - - for row in 0..self.map_height { - for col in 0..self.map_width { - self.board_cells.push(CellWidget::new(row, col, false)); - } - } - } + pub board: BoardState, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, ValueEnum)] diff --git a/src/app/states/skirmish_states/board.rs b/src/app/states/skirmish_states/board.rs new file mode 100644 index 0000000..66de493 --- /dev/null +++ b/src/app/states/skirmish_states/board.rs @@ -0,0 +1,96 @@ +use crate::app::{ + helpers::{CellSizes, cell_size_helper, cells_area_helper}, + states::{FocusedCell, Offset, ZoomLevel}, + widgets::CellWidget, +}; +use ratatui::layout::Rect; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct BoardState { + pub cells_area: Rect, + pub cell_width: usize, + pub cell_height: usize, + pub map_width: usize, + pub map_height: usize, + pub cols: usize, + pub rows: usize, + pub vertical_offset: Offset, + pub horizontal_offset: Offset, + pub cells: Vec, + pub zoom_level: ZoomLevel, + pub focused_cell: FocusedCell, +} + +impl BoardState { + pub fn new(area: &Rect, map_width: usize, map_height: usize, zoom_level: ZoomLevel) -> Self { + let cells_area: Rect = cells_area_helper(area); + + let cell_width: usize = cell_size_helper(CellSizes::Width, zoom_level); + let cell_height: usize = cell_size_helper(CellSizes::Height, zoom_level); + + let cols: usize = (cells_area.width / cell_width as u16) as usize; + let rows: usize = (cells_area.height / cell_height as u16) as usize; + + let v_max_offset: usize = Self::max_offset(map_height, rows); + let h_max_offset: usize = Self::max_offset(map_width, cols); + + let vertical_offset: Offset = + Offset::new(Some((map_height - rows as usize) / 2), Some(v_max_offset)); + let horizontal_offset: Offset = Offset::new(None, Some(h_max_offset)); + + let focused_cell: FocusedCell = + FocusedCell::new((map_height / 2) - 1, 2, map_height, map_width); + + let mut cells: Vec = Vec::new(); + + for row in 0..map_height { + for col in 0..map_width { + cells.push(CellWidget::new(row, col, false)); + } + } + + Self { + cells_area, + cell_width, + cell_height, + map_width, + map_height, + cols, + rows, + vertical_offset, + horizontal_offset, + cells, + zoom_level, + focused_cell, + } + } + + pub fn zoom_change(&mut self, new_zoom_level: ZoomLevel) { + self.zoom_level = new_zoom_level; + + self.cell_width = cell_size_helper(CellSizes::Width, self.zoom_level); + self.cell_height = cell_size_helper(CellSizes::Height, self.zoom_level); + + self.cols = (self.cells_area.width / self.cell_width as u16) as usize; + self.rows = (self.cells_area.height / self.cell_height as u16) as usize; + + let v_max_offset: usize = Self::max_offset(self.map_height, self.rows); + let h_max_offset: usize = Self::max_offset(self.map_width, self.cols); + + self.vertical_offset = + Offset::new(Some(self.vertical_offset.get_value()), Some(v_max_offset)); + self.horizontal_offset = + Offset::new(Some(self.horizontal_offset.get_value()), Some(h_max_offset)); + + self.focused_cell = FocusedCell::new( + self.focused_cell.row, + self.focused_cell.col, + self.map_height, + self.map_width, + ); + } + + fn max_offset(map_size: usize, size: usize) -> usize { + if map_size > size { map_size - size } else { 0 } + } +} diff --git a/src/app/states/skirmish_states/mod.rs b/src/app/states/skirmish_states/mod.rs index 8c1e14a..0534388 100644 --- a/src/app/states/skirmish_states/mod.rs +++ b/src/app/states/skirmish_states/mod.rs @@ -1,5 +1,7 @@ +pub mod board; pub mod focused_cell; pub mod offset; +pub use board::BoardState; pub use focused_cell::FocusedCell; pub use offset::Offset; diff --git a/src/app/states/skirmish_states/offset.rs b/src/app/states/skirmish_states/offset.rs index b9c3c77..98a0f13 100644 --- a/src/app/states/skirmish_states/offset.rs +++ b/src/app/states/skirmish_states/offset.rs @@ -2,17 +2,13 @@ pub struct Offset { value: usize, max: usize, - value_initiated: bool, - max_initiated: bool, } impl Offset { - pub fn new() -> Self { + pub fn new(initial_value: Option, initial_max: Option) -> Self { Self { - value: 0, - max: 0, - value_initiated: false, - max_initiated: false, + value: initial_value.unwrap_or(0), + max: initial_max.unwrap_or(0), } } @@ -20,22 +16,12 @@ impl Offset { self.value } - pub fn set_initial_value(&mut self, value: usize) { - if self.value_initiated { - return; - } - + pub fn set_value(&mut self, value: usize) { self.value = value; - self.value_initiated = true; } pub fn set_max(&mut self, max: usize) { - if self.max_initiated { - return; - } - self.max = max; - self.max_initiated = true; } pub fn next(&mut self) { diff --git a/src/app/views/main_menu.rs b/src/app/views/main_menu.rs index bdf9879..537c09a 100644 --- a/src/app/views/main_menu.rs +++ b/src/app/views/main_menu.rs @@ -30,6 +30,8 @@ fn main_menu_layout(area: Rect) -> [Rect; 2] { } pub fn main_menu_view(app: &App, area: Rect, buf: &mut Buffer) { + let Some(states) = app.states() else { return }; + let [main_menu_area, keybindings_area] = main_menu_layout(area); Block::new() @@ -70,7 +72,7 @@ pub fn main_menu_view(app: &App, area: Rect, buf: &mut Buffer) { .map(|(i, view)| { let view_string: String = main_menu_option_helper(format!("{:?}", view)); - let styled: Line<'_> = if app.states.main_menu.selected_view == i { + let styled: Line<'_> = if states.main_menu.selected_view == i { Line::from_iter(["> ".cyan(), view_string.yellow()]).yellow() } else { Line::from(format!(" {view_string}")).white() diff --git a/src/app/views/mod.rs b/src/app/views/mod.rs index e5f3eb2..b98397b 100644 --- a/src/app/views/mod.rs +++ b/src/app/views/mod.rs @@ -4,4 +4,4 @@ pub mod skirmish; pub use default::default_view; pub use main_menu::main_menu_view; -pub use skirmish::skirmish_view; +pub use skirmish::{skirmish_layout, skirmish_view}; diff --git a/src/app/views/skirmish.rs b/src/app/views/skirmish.rs index 3b00ae9..a3149bd 100644 --- a/src/app/views/skirmish.rs +++ b/src/app/views/skirmish.rs @@ -28,7 +28,7 @@ const ACTIONS: &[Action] = &[ Action::Esc, ]; -fn skirmish_layout(area: Rect) -> [Rect; 3] { +pub fn skirmish_layout(area: Rect) -> [Rect; 3] { Layout::vertical([ Constraint::Length(4), Constraint::Fill(1), @@ -37,22 +37,21 @@ fn skirmish_layout(area: Rect) -> [Rect; 3] { .areas(area) } -pub fn skirmish_view(app: &mut App, area: Rect, buf: &mut Buffer) { +pub fn skirmish_view(app: &App, area: Rect, buf: &mut Buffer) { + let Some(states) = app.states() else { return }; + let [title_area, main_area, keybindings_area] = skirmish_layout(area); { let lines: Vec> = Vec::from_iter([ Line::raw("War in Tunnels").yellow(), Line::from_iter([ - format!("Wood: {} (+{}) | ", app.states.settings.starting_wood, 5), - format!("Iron: {} (+{}) | ", app.states.settings.starting_iron, 1), - format!( - "Supply Limit: {}/{} | ", - 10, app.states.settings.supply_limit - ), + format!("Wood: {} (+{}) | ", states.settings.starting_wood, 5), + format!("Iron: {} (+{}) | ", states.settings.starting_iron, 1), + format!("Supply Limit: {}/{} | ", 10, states.settings.supply_limit), format!( "Skills points: {} ({}/{}) | ", - 1, 20, app.states.settings.skill_points_limit + 1, 20, states.settings.skill_points_limit ), format!("Perk Deck: {}/9", 5), ]), @@ -91,7 +90,7 @@ pub fn skirmish_view(app: &mut App, area: Rect, buf: &mut Buffer) { vertical: 1, }); - BoardWidget::new(app, cells_area.width, cells_area.height).render(cells_area, buf); + BoardWidget::new(&states.skirmish.board).render(cells_area, buf); } { diff --git a/src/app/widgets/board.rs b/src/app/widgets/board.rs index ebfa5c9..687acdf 100644 --- a/src/app/widgets/board.rs +++ b/src/app/widgets/board.rs @@ -1,9 +1,4 @@ -use crate::app::{ - App, - helpers::{CellSizes, cell_size_helper}, - states::FocusedCell, - widgets::CellWidget, -}; +use crate::app::states::skirmish_states::BoardState; use ratatui::{ buffer::Buffer, layout::{Constraint, Layout, Rect}, @@ -12,87 +7,48 @@ use ratatui::{ use std::rc::Rc; pub struct BoardWidget<'a> { - map_width: usize, - cell_width: u16, - cell_height: u16, - cols: u16, - rows: u16, - h_offset: usize, - v_offset: usize, - cells: &'a mut Vec, - focused_cell: &'a FocusedCell, + state: &'a BoardState, } impl<'a> BoardWidget<'a> { - fn max_offset(map_size: u16, size: u16) -> usize { - if map_size > size { - (map_size - size) as usize - } else { - 0 - } - } - - pub fn new(app: &'a mut App, area_width: u16, area_height: u16) -> Self { - let cell_height: u16 = cell_size_helper(CellSizes::Height, app.states.skirmish.zoom_level); - let cell_width: u16 = cell_size_helper(CellSizes::Width, app.states.skirmish.zoom_level); - - let rows: u16 = area_height / cell_height; - let cols: u16 = area_width / cell_width; - - let v_max_offset: usize = Self::max_offset(app.states.skirmish.map_height as u16, rows); - let h_max_offset: usize = Self::max_offset(app.states.skirmish.map_width as u16, cols); - - app.states.skirmish.horizontal_offset.set_max(h_max_offset); - app.states.skirmish.vertical_offset.set_max(v_max_offset); - - if v_max_offset > 0 { - app.states - .skirmish - .vertical_offset - .set_initial_value((app.states.skirmish.map_height - rows as usize) / 2); - } - - Self { - map_width: app.states.skirmish.map_width as usize, - cell_width, - cell_height, - cols, - rows, - h_offset: app.states.skirmish.horizontal_offset.get_value(), - v_offset: app.states.skirmish.vertical_offset.get_value(), - cells: &mut app.states.skirmish.board_cells, - focused_cell: &app.states.skirmish.focused_cell, - } + pub fn new(state: &'a BoardState) -> Self { + Self { state } } } impl Widget for BoardWidget<'_> { fn render(self, area: Rect, buf: &mut Buffer) { - let horizontal: Rc<[Rect]> = Layout::horizontal(vec![ - Constraint::Length(self.cell_width); - self.cols as usize - ]) - .split(area); + let horizontal: Rc<[Rect]> = + Layout::horizontal(vec![ + Constraint::Length(self.state.cell_width as u16); + self.state.cols + ]) + .split(area); for (col_idx, col_area) in horizontal.iter().enumerate() { - let vertical: Rc<[Rect]> = Layout::vertical(vec![ - Constraint::Length(self.cell_height); - self.rows as usize - ]) - .split(*col_area); + let vertical: Rc<[Rect]> = + Layout::vertical(vec![ + Constraint::Length(self.state.cell_height as u16); + self.state.rows + ]) + .split(*col_area); for (row_idx, cell_area) in vertical.iter().enumerate() { - if let Some(cell) = self - .cells - .get_mut((row_idx + self.v_offset) * self.map_width + (col_idx + self.h_offset)) - { - if row_idx + self.v_offset == self.focused_cell.row - && col_idx + self.h_offset == self.focused_cell.col - { - cell.selected = true; - } else { - cell.selected = false; - } + if let Some(cell) = self.state.cells.get( + (row_idx + self.state.vertical_offset.get_value()) * self.state.map_width + + (col_idx + self.state.horizontal_offset.get_value()), + ) { + // FIXME: Fix showing selected cell + + // if row_idx + self.state.vertical_offset.get_value() + // == self.state.focused_cell.row + // && col_idx + self.state.horizontal_offset.get_value() + // == self.state.focused_cell.col + // { + // cell.selected = true; + // } else { + // cell.selected = false; + // } cell.render(*cell_area, buf); }