Files
war-in-tunnels/src/app/keybindings/skirmish.rs
T
GarandPLG 110d4c411a
Nix package / build (push) Successful in 7m59s
Linux package builds (test) / linux (push) Successful in 2m29s
Linux package builds (test) / aur (push) Successful in 7m18s
Rust checks / check (push) Successful in 3m21s
Add tab navigation for building and recruitment UI
- Add selection indices and option lists to SkirmishState.
- Implement `cycle_popup_option` and expose Left/Right actions in
  keybindings.
- Introduce `cancel_focused_tasks` in BoardState for Delete handling.
- CellWidget now displays recruitment progress on Base structures.
- Update README and documentation to describe new tabs and arrow
  navigation.
- Adjust test counts and add extensive task‑cancellation tests.
2026-10-23 17:53:46 +02:00

238 lines
8.9 KiB
Rust

use crate::app::{
App,
keybindings::{Action, common_keybindings, event_to_action},
states::{
SkirmishState,
skirmish_states::{
BoardState, FocusedCell, MoveFocusedCell, Players, ZoomLevel,
structures::{Structure, Structures},
units::{Unit, Units},
},
},
};
use ratatui::{crossterm::event::KeyEvent, layout::Rect};
pub fn available_skirmish_actions(state: &SkirmishState) -> Vec<Action> {
if state.recruitment_view {
let mut actions: Vec<Action> = vec![
Action::Recruitment,
Action::Left,
Action::Right,
Action::Quit2,
];
if state.can_recruit_miner(state.board.get_focused_cell().get_coords(), Players::Player) {
actions.push(Action::Enter);
}
return actions;
}
if state.building_view {
let mut actions: Vec<Action> =
vec![Action::Building, Action::Left, Action::Right, Action::Quit2];
let coords: (usize, usize) = state.board.get_focused_cell().get_coords();
if let Structures::MineConstruction(site) =
state.board.get_ref_cell(coords.0, coords.1).get_structure()
&& site.get_owner() == Players::Player
{
actions.push(Action::Delete);
} else if state.can_start_mine_construction(coords, Players::Player) {
actions.push(Action::Enter);
}
return actions;
}
let mut actions: Vec<Action> = vec![Action::Up, Action::Down, Action::Left, Action::Right];
if state.board.marked_cells.marking_cells {
actions.extend([
Action::Space,
Action::Backspace,
Action::Delete,
Action::Esc,
Action::Tab,
Action::Quit2,
]);
return actions;
}
actions.extend([
Action::ScrollUp,
Action::ScrollDown,
Action::ScrollLeft,
Action::ScrollRight,
Action::Tab,
Action::Building,
Action::Mute,
Action::Quit,
Action::Esc,
]);
if state.board.zoom_level != ZoomLevel::ZoomedIn {
actions.push(Action::ZoomIn);
}
if state.board.zoom_level != ZoomLevel::ZoomedOut {
actions.push(Action::ZoomOut);
}
let cell: &FocusedCell = state.board.get_focused_cell();
let (row, col): (usize, usize) = cell.get_coords();
if matches!(state.board.get_ref_cell(row, col).get_structure(), Structures::Base(base) if base.get_owner() == Players::Player)
{
actions.push(Action::Recruitment);
}
let unit: &Option<Units> = state.board.get_ref_cell(row, col).get_ref_option_unit();
if unit.get_owner() == Players::Player && unit.get_can_dig() {
actions.push(Action::Space);
}
if state.side_panel && unit.get_owner() == Players::Player && !unit.get_ref_tasks().is_empty() {
actions.extend([Action::Backspace, Action::Delete]);
}
actions
}
pub fn skirmish_keybindings(app: &mut App, key_event: &KeyEvent) -> () {
if let Some(action) = event_to_action(key_event) {
let Some(states) = app.states_mut() else {
common_keybindings(app, action);
return;
};
if states.skirmish.recruitment_view {
match action {
Action::Recruitment => states.skirmish.recruitment_view = false,
Action::Left => states.skirmish.cycle_popup_option(false),
Action::Right => states.skirmish.cycle_popup_option(true),
Action::Esc => (),
Action::Enter => {
let coords: (usize, usize) =
states.skirmish.board.get_focused_cell().get_coords();
states.skirmish.recruit_miner(coords, Players::Player);
}
_ => common_keybindings(app, action),
}
return;
}
if states.skirmish.building_view {
match action {
Action::Building => states.skirmish.building_view = false,
Action::Left => states.skirmish.cycle_popup_option(false),
Action::Right => states.skirmish.cycle_popup_option(true),
Action::Esc => (),
Action::Enter => {
let coords: (usize, usize) =
states.skirmish.board.get_focused_cell().get_coords();
states
.skirmish
.start_mine_construction(coords, Players::Player);
}
Action::Delete => {
let coords: (usize, usize) =
states.skirmish.board.get_focused_cell().get_coords();
states
.skirmish
.cancel_mine_construction(coords, Players::Player);
}
_ => common_keybindings(app, action),
}
return;
}
if action == Action::Esc && states.skirmish.board.marked_cells.marking_cells {
states.skirmish.board.marked_cells.marking_cells = false;
states.skirmish.board.end_marking_cells(true);
return;
}
let available: bool = available_skirmish_actions(&states.skirmish).contains(&action);
common_keybindings(app, action);
if !available {
return;
}
let window_area: Rect = app.window_area;
let Some(states) = app.states_mut() else {
return;
};
let board: &mut BoardState = &mut states.skirmish.board;
match action {
Action::Up => {
board.change_focused_cell(MoveFocusedCell::Up);
if !board.is_focused_cell_visible() {
board.vertical_offset.prev();
}
}
Action::Down => {
board.change_focused_cell(MoveFocusedCell::Down);
if !board.is_focused_cell_visible() {
board.vertical_offset.next();
}
}
Action::Left => {
board.change_focused_cell(MoveFocusedCell::Left);
if !board.is_focused_cell_visible() {
board.horizontal_offset.prev();
}
}
Action::Right => {
board.change_focused_cell(MoveFocusedCell::Right);
if !board.is_focused_cell_visible() {
board.horizontal_offset.next();
}
}
Action::ScrollUp => {
board.vertical_offset.prev();
board.change_focused_cell(MoveFocusedCell::Up);
}
Action::ScrollDown => {
board.vertical_offset.next();
board.change_focused_cell(MoveFocusedCell::Down);
}
Action::ScrollLeft => {
board.horizontal_offset.prev();
board.change_focused_cell(MoveFocusedCell::Left);
}
Action::ScrollRight => {
board.horizontal_offset.next();
board.change_focused_cell(MoveFocusedCell::Right);
}
Action::ZoomIn => match board.zoom_level {
ZoomLevel::ZoomedIn => {}
ZoomLevel::Default => {
board.change_zoom(ZoomLevel::ZoomedIn);
board.vertical_offset.next();
}
ZoomLevel::ZoomedOut => {
board.change_zoom(ZoomLevel::Default);
board.vertical_offset.next();
board.vertical_offset.next();
}
},
Action::ZoomOut => match board.zoom_level {
ZoomLevel::ZoomedIn => {
board.change_zoom(ZoomLevel::Default);
board.vertical_offset.prev();
}
ZoomLevel::Default => {
board.change_zoom(ZoomLevel::ZoomedOut);
board.vertical_offset.prev();
board.vertical_offset.prev();
}
ZoomLevel::ZoomedOut => {}
},
Action::Space => board.toggle_marking(),
Action::Building => states.skirmish.building_view = true,
Action::Recruitment => states.skirmish.recruitment_view = true,
Action::Backspace if board.marked_cells.marking_cells => board.pop_marked_cell(),
Action::Backspace => {
board.cancel_focused_tasks(false);
}
Action::Delete if !board.marked_cells.marking_cells => {
board.cancel_focused_tasks(true);
}
Action::Delete => {
board.marked_cells.marking_cells = false;
board.end_marking_cells(true)
}
Action::Tab => {
states.skirmish.side_panel = !states.skirmish.side_panel;
board.change_resize(&window_area, states.skirmish.side_panel);
}
_ => (),
}
}
}