Add scrolling actions and board widget

Introduce directional and scroll actions, update keybindings, add
scrollbar
state to SkirmishState, and render the board with a new BoardWidget.
Adjust CLI
defaults and layout heights accordingly.
This commit is contained in:
2026-03-26 11:05:43 +01:00
parent 0577697059
commit cc179cee03
9 changed files with 205 additions and 15 deletions
+61
View File
@@ -7,6 +7,12 @@ pub enum Action {
Quit2, Quit2,
Up, Up,
Down, Down,
Left,
Right,
ScrollUp,
ScrollDown,
ScrollLeft,
ScrollRight,
Space, Space,
Enter, Enter,
Esc, Esc,
@@ -17,6 +23,7 @@ pub enum Action {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, ValueEnum)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, ValueEnum)]
pub enum Group { pub enum Group {
Movement, Movement,
Scroll,
Select, Select,
Input, Input,
Quit, Quit,
@@ -70,6 +77,60 @@ pub static KEYBINDINGS: &[KeyBinding] = &[
symbol: "", symbol: "",
description: "Down", description: "Down",
}, },
KeyBinding {
action: Action::Left,
code: KeyCode::Left,
kind: KeyEventKind::Press,
modifiers: KeyModifiers::NONE,
group: Group::Movement,
symbol: "",
description: "Left",
},
KeyBinding {
action: Action::Right,
code: KeyCode::Right,
kind: KeyEventKind::Press,
modifiers: KeyModifiers::NONE,
group: Group::Movement,
symbol: "",
description: "Right",
},
KeyBinding {
action: Action::ScrollUp,
code: KeyCode::Up,
kind: KeyEventKind::Press,
modifiers: KeyModifiers::CONTROL,
group: Group::Movement,
symbol: "Ctrl + ↑",
description: "Scroll Up ",
},
KeyBinding {
action: Action::ScrollDown,
code: KeyCode::Down,
kind: KeyEventKind::Press,
modifiers: KeyModifiers::CONTROL,
group: Group::Movement,
symbol: "Ctrl + ↓",
description: "Scroll Down",
},
KeyBinding {
action: Action::ScrollLeft,
code: KeyCode::Left,
kind: KeyEventKind::Press,
modifiers: KeyModifiers::CONTROL,
group: Group::Movement,
symbol: "Ctrl + ←",
description: "Scroll Left",
},
KeyBinding {
action: Action::ScrollRight,
code: KeyCode::Right,
kind: KeyEventKind::Press,
modifiers: KeyModifiers::CONTROL,
group: Group::Movement,
symbol: "Ctrl + →",
description: "Scroll Right",
},
KeyBinding { KeyBinding {
action: Action::Space, action: Action::Space,
code: KeyCode::Char(' '), code: KeyCode::Char(' '),
+4
View File
@@ -7,6 +7,10 @@ use ratatui::crossterm::event::KeyEvent;
pub fn skirmish_keybindings(app: &mut App, key_event: &KeyEvent) { pub fn skirmish_keybindings(app: &mut App, key_event: &KeyEvent) {
if let Some(action) = event_to_action(&key_event) { if let Some(action) = event_to_action(&key_event) {
match action { match action {
Action::ScrollUp => app.states.skirmish.vertical_scrollbar.prev(),
Action::ScrollDown => app.states.skirmish.vertical_scrollbar.next(),
Action::ScrollLeft => app.states.skirmish.horizontal_scrollbar.prev(),
Action::ScrollRight => app.states.skirmish.horizontal_scrollbar.next(),
Action::Quit => app.exit = true, Action::Quit => app.exit = true,
Action::Quit2 => app.exit = true, Action::Quit2 => app.exit = true,
Action::Esc => app.view = View::MainMenu, Action::Esc => app.view = View::MainMenu,
+4
View File
@@ -1,3 +1,5 @@
use ratatui::widgets::ScrollbarState;
use crate::{ use crate::{
app::states::{MainMenuState, PerkDecksState, SettingsState, SkillsConfigState, SkirmishState}, app::states::{MainMenuState, PerkDecksState, SettingsState, SkillsConfigState, SkirmishState},
cli::Cli, cli::Cli,
@@ -23,6 +25,8 @@ impl GameStates {
skirmish: SkirmishState { skirmish: SkirmishState {
id: 1, id: 1,
name: "Skirmish", name: "Skirmish",
vertical_scrollbar: ScrollbarState::new(0),
horizontal_scrollbar: ScrollbarState::new(0),
}, },
perk_decks: PerkDecksState { perk_decks: PerkDecksState {
id: 2, id: 2,
+3
View File
@@ -1,9 +1,12 @@
use clap::ValueEnum; use clap::ValueEnum;
use ratatui::widgets::ScrollbarState;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SkirmishState { pub struct SkirmishState {
pub id: usize, pub id: usize,
pub name: &'static str, pub name: &'static str,
pub vertical_scrollbar: ScrollbarState,
pub horizontal_scrollbar: ScrollbarState,
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, ValueEnum)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, ValueEnum)]
+17 -13
View File
@@ -1,4 +1,8 @@
use crate::app::{App, keybindings::Action, widgets::KeybindingsWidget}; use crate::app::{
App,
keybindings::Action,
widgets::{BoardWidget, KeybindingsWidget},
};
use ratatui::{ use ratatui::{
buffer::Buffer, buffer::Buffer,
layout::{Alignment, Constraint, Layout, Margin, Rect}, layout::{Alignment, Constraint, Layout, Margin, Rect},
@@ -11,7 +15,7 @@ pub fn skirmish_view(app: &App, area: Rect, buf: &mut Buffer) {
let vertical_layout: Layout = Layout::vertical([ let vertical_layout: Layout = Layout::vertical([
Constraint::Length(4), Constraint::Length(4),
Constraint::Fill(1), Constraint::Fill(1),
Constraint::Length(4), Constraint::Length(6),
]); ]);
let [title_area, main_area, keybindings_area] = vertical_layout.areas(area); let [title_area, main_area, keybindings_area] = vertical_layout.areas(area);
@@ -65,24 +69,24 @@ pub fn skirmish_view(app: &App, area: Rect, buf: &mut Buffer) {
let board_area: Rect = board_block.inner(main_area); let board_area: Rect = board_block.inner(main_area);
board_block.render(main_area, buf); board_block.render(main_area, buf);
let inner_board_area: Rect = board_area.inner(Margin { let cells_area: Rect = board_area.inner(Margin {
horizontal: 1, horizontal: 1,
vertical: 1, vertical: 1,
}); });
let map_width: u16 = inner_board_area.width / (app.states.settings.map_width as u16); BoardWidget::new(app, cells_area.width, cells_area.height).render(cells_area, buf);
let map_height: u16 = inner_board_area.height / (app.states.settings.map_height as u16);
Paragraph::new(format!(
"x = {}, y = {}, mw = {}, mh = {}",
inner_board_area.width, inner_board_area.height, map_width, map_height
))
.block(Block::default())
.render(inner_board_area, buf);
} }
{ {
let actions: Vec<Action> = vec![Action::Quit, Action::Quit2, Action::Esc]; let actions: Vec<Action> = vec![
Action::ScrollUp,
Action::ScrollDown,
Action::ScrollLeft,
Action::ScrollRight,
Action::Quit,
Action::Quit2,
Action::Esc,
];
KeybindingsWidget::new(actions).render(keybindings_area, buf); KeybindingsWidget::new(actions).render(keybindings_area, buf);
} }
+75
View File
@@ -0,0 +1,75 @@
use crate::app::{App, widgets::CellWidget};
use ratatui::{
buffer::Buffer,
layout::{Constraint, Layout, Rect},
widgets::Widget,
};
use std::rc::Rc;
pub struct BoardWidget {
cell_width: u16,
cell_height: u16,
cols: u16,
rows: u16,
h_offset: usize,
v_offset: usize,
}
impl BoardWidget {
pub fn new(app: &App, area_width: u16, area_height: u16) -> Self {
let cell_width: u16 = 7;
let cell_height: u16 = 4;
let cols: u16 = area_width / cell_width;
let rows: u16 = area_height / cell_height;
let h_max_offset: u16 = if app.states.settings.map_height as u16 > rows {
app.states.settings.map_height as u16 - rows
} else {
0
};
let v_max_offset: u16 = if app.states.settings.map_width as u16 > cols {
app.states.settings.map_width as u16 - cols
} else {
0
};
// let h_offset: usize = (cell_height * rows as u16 / area_height) as usize;
// let v_offset: usize = (cell_width * cols as u16 / area_width) as usize;
Self {
cell_width,
cell_height,
cols,
rows,
h_offset: h_max_offset as usize,
v_offset: v_max_offset as usize,
}
}
}
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);
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);
for (row_idx, cell_area) in vertical.iter().enumerate() {
let cell: CellWidget =
CellWidget::new(row_idx + self.v_offset, col_idx + self.h_offset);
cell.render(*cell_area, buf);
}
}
}
}
+35
View File
@@ -0,0 +1,35 @@
use ratatui::{
buffer::Buffer,
layout::{Alignment, Rect},
style::Stylize,
widgets::{Block, Borders, Paragraph, Widget},
};
// pub enum CellTags {
// Player,
// Enemy,
// Dirt,
// Tunnel,
// Base,
// }
pub struct CellWidget {
pub row: usize,
pub col: usize,
// pub tags: Vec<CellTags>,
}
impl CellWidget {
pub fn new(row: usize, col: usize) -> Self {
Self { row, col }
}
}
impl Widget for CellWidget {
fn render(self, area: Rect, buf: &mut Buffer) {
Paragraph::new(format!("{}/{}", self.row, self.col))
.alignment(Alignment::Center)
.block(Block::default().borders(Borders::ALL).white())
.render(area, buf);
}
}
+4
View File
@@ -1,3 +1,7 @@
pub mod board;
pub mod cell;
pub mod keybindings; pub mod keybindings;
pub use board::BoardWidget;
pub use cell::CellWidget;
pub use keybindings::KeybindingsWidget; pub use keybindings::KeybindingsWidget;
+2 -2
View File
@@ -37,7 +37,7 @@ pub struct Cli {
long, long,
help = "Map width", help = "Map width",
value_name = "Positive integer [20; 100]", value_name = "Positive integer [20; 100]",
default_value = "50" default_value = "27"
)] )]
pub map_width: u8, pub map_width: u8,
@@ -45,7 +45,7 @@ pub struct Cli {
long, long,
help = "Map height", help = "Map height",
value_name = "Positive integer [11; 50]", value_name = "Positive integer [11; 50]",
default_value = "21" default_value = "11"
)] )]
pub map_height: u8, pub map_height: u8,