Add marking mode; rename resize and zoom methods

Introduce a marking mode that lets users toggle cells with Space and
clear
them with Backspace. BoardState now stores `marking_cells` and a list of
`marked_cells` and provides methods to start, clear, and update marks.
CellWidget tracks a `marked` flag and renders marked cells in
LightMagenta.

Keybindings are updated: Space now reads “Marking cells”, Backspace
reads
“Cancel marking”, and the original Enter binding is commented out.

The former `resize_change` and `zoom_change` functions are renamed to
`change_resize` and `change_zoom`; all call sites are updated
accordingly.
This commit is contained in:
2026-04-10 19:06:47 +02:00
parent b45c300bc3
commit 869ba0bb7f
6 changed files with 128 additions and 50 deletions
+1 -1
View File
@@ -60,7 +60,7 @@ impl App {
let Some(state) = self.states_mut() else {
panic!("State issue")
};
state.skirmish.board.resize_change(&window_area);
state.skirmish.board.change_resize(&window_area);
}
}
}
+12 -12
View File
@@ -197,17 +197,17 @@ pub static KEYBINDINGS: &[KeyBinding] = &[
modifiers: KeyModifiers::NONE,
group: Group::Select,
symbol: "Space",
description: "Select",
},
KeyBinding {
action: Action::Enter,
code: KeyCode::Enter,
kind: KeyEventKind::Press,
modifiers: KeyModifiers::NONE,
group: Group::Select,
symbol: "Enter",
description: "Submit",
description: "Marking cells",
},
// KeyBinding {
// action: Action::Enter,
// code: KeyCode::Enter,
// kind: KeyEventKind::Press,
// modifiers: KeyModifiers::NONE,
// group: Group::Select,
// symbol: "Enter",
// description: "Submit",
// },
KeyBinding {
action: Action::Esc,
code: KeyCode::Esc,
@@ -222,9 +222,9 @@ pub static KEYBINDINGS: &[KeyBinding] = &[
code: KeyCode::Backspace,
kind: KeyEventKind::Press,
modifiers: KeyModifiers::NONE,
group: Group::Input,
group: Group::Select,
symbol: "Backspace",
description: "Delete character",
description: "Cancel marking",
},
KeyBinding {
action: Action::ZoomIn,
+17 -4
View File
@@ -62,27 +62,40 @@ pub fn skirmish_keybindings(app: &mut App, key_event: &KeyEvent) {
Action::ZoomIn => match board.zoom_level {
ZoomLevel::ZoomedIn => {}
ZoomLevel::Default => {
board.zoom_change(ZoomLevel::ZoomedIn);
board.change_zoom(ZoomLevel::ZoomedIn);
board.vertical_offset.next();
}
ZoomLevel::ZoomedOut => {
board.zoom_change(ZoomLevel::Default);
board.change_zoom(ZoomLevel::Default);
board.vertical_offset.next();
board.vertical_offset.next();
}
},
Action::ZoomOut => match board.zoom_level {
ZoomLevel::ZoomedIn => {
board.zoom_change(ZoomLevel::Default);
board.change_zoom(ZoomLevel::Default);
board.vertical_offset.prev();
}
ZoomLevel::Default => {
board.zoom_change(ZoomLevel::ZoomedOut);
board.change_zoom(ZoomLevel::ZoomedOut);
board.vertical_offset.prev();
board.vertical_offset.prev();
}
ZoomLevel::ZoomedOut => {}
},
Action::Space => {
if board.marking_cells {
board.marking_cells = false;
board.clear_marked_cells();
} else {
board.marking_cells = true;
board.start_marking_cells();
}
}
Action::Backspace => {
board.marking_cells = false;
board.clear_marked_cells()
}
_ => (),
}
}
+76 -21
View File
@@ -21,6 +21,8 @@ pub struct BoardState {
focused_cell: FocusedCell,
player_base_coords: (usize, usize),
enemy_base_coords: (usize, usize),
pub marking_cells: bool,
marked_cells: Vec<(usize, usize)>,
}
impl BoardState {
@@ -70,6 +72,9 @@ impl BoardState {
cells.push(rows);
}
let marking_cells: bool = false;
let marked_cells: Vec<(usize, usize)> = Vec::new();
Self {
cells_area,
cell_width,
@@ -85,6 +90,8 @@ impl BoardState {
focused_cell,
player_base_coords,
enemy_base_coords,
marking_cells,
marked_cells,
}
}
@@ -92,11 +99,76 @@ impl BoardState {
&mut self.cells[row][col]
}
pub fn get_marked_cells(&self) -> Vec<&CellWidget> {
self.marked_cells
.iter()
.map(move |&(row, col)| &self.cells[row][col])
.collect()
}
fn get_last_marked_cell(&self) -> Option<(usize, usize)> {
self.marked_cells.last().cloned()
}
pub fn set_marked_cell(&mut self, row: usize, col: usize, old_row: usize, old_col: usize) {
let last_cell: Option<(usize, usize)> = self.get_last_marked_cell();
let cell: &mut CellWidget = self.get_mut_cell(row, col);
if cell.get_marked() {
if last_cell == Some((old_row, old_col)) {
self.get_mut_cell(old_row, old_col).set_marked(false);
self.marked_cells.pop();
}
} else {
cell.set_marked(true);
self.marked_cells.push((row, col));
}
}
pub fn start_marking_cells(&mut self) {
let row: usize = self.focused_cell.get_row();
let col: usize = self.focused_cell.get_col();
let cell: &mut CellWidget = self.get_mut_cell(row, col);
cell.set_marked(true);
self.marked_cells.push((row, col));
}
pub fn clear_marked_cells(&mut self) {
for (row, col) in self.marked_cells.clone() {
self.get_mut_cell(row, col).set_marked(false);
}
self.marked_cells.clear();
}
fn max_offset(map_size: usize, size: usize) -> usize {
if map_size > size { map_size - size } else { 0 }
}
pub fn resize_change(&mut self, area: &Rect) {
pub fn is_focused_cell_visible(&self) -> bool {
let vertical_offset: usize = self.vertical_offset.get_value();
let horizontal_offset: usize = self.horizontal_offset.get_value();
let row: usize = self.focused_cell.get_row();
let col: usize = self.focused_cell.get_col();
if 0usize.saturating_add(vertical_offset) > row
|| row >= self.rows.saturating_add(vertical_offset)
{
return false;
}
if 0usize.saturating_add(horizontal_offset) > col
|| col >= self.cols.saturating_add(horizontal_offset)
{
return false;
}
true
}
pub fn change_resize(&mut self, area: &Rect) {
self.cells_area = cells_area_helper(area);
self.cols = (self.cells_area.width / self.cell_width as u16) as usize;
@@ -111,7 +183,7 @@ impl BoardState {
Offset::new(Some(self.vertical_offset.get_value()), Some(v_max_offset));
}
pub fn zoom_change(&mut self, new_zoom_level: ZoomLevel) {
pub fn change_zoom(&mut self, new_zoom_level: ZoomLevel) {
self.zoom_level = new_zoom_level;
self.cell_width = cell_size_helper(CellSizes::Width, self.zoom_level);
@@ -149,26 +221,9 @@ impl BoardState {
self.get_mut_cell(old_row, old_col).set_selected(false);
self.get_mut_cell(new_row, new_col).set_selected(true);
}
pub fn is_focused_cell_visible(&self) -> bool {
let vertical_offset: usize = self.vertical_offset.get_value();
let horizontal_offset: usize = self.horizontal_offset.get_value();
let row: usize = self.focused_cell.get_row();
let col: usize = self.focused_cell.get_col();
if 0usize.saturating_add(vertical_offset) > row
|| row >= self.rows.saturating_add(vertical_offset)
{
return false;
if self.marking_cells {
self.set_marked_cell(new_row, new_col, old_row, old_col);
}
if 0usize.saturating_add(horizontal_offset) > col
|| col >= self.cols.saturating_add(horizontal_offset)
{
return false;
}
true
}
}
+11 -12
View File
@@ -25,6 +25,8 @@ const ACTIONS: &[Action] = &[
Action::ZoomOut,
Action::VolumeUp,
Action::VolumeDown,
Action::Space,
Action::Backspace,
Action::Mute,
Action::Quit,
Action::Quit2,
@@ -92,19 +94,16 @@ pub fn skirmish_view(app: &App, area: Rect, buf: &mut Buffer) {
horizontal: 1,
vertical: 1,
});
// .centered(
// Constraint::Length(
// (states.skirmish.board.cell_width * states.skirmish.board.cols) as u16,
// ),
// Constraint::Length(
// (states.skirmish.board.cell_height * states.skirmish.board.rows) as u16,
// ),
// );
// TODO: add actual (effective) board sizes as Constraints
BoardWidget::new(&states.skirmish.board).render(
cells_area.centered(
Constraint::Length(
(states.skirmish.board.cell_width * states.skirmish.board.cols) as u16,
),
Constraint::Length(
(states.skirmish.board.cell_height * states.skirmish.board.rows) as u16,
),
),
buf,
);
BoardWidget::new(&states.skirmish.board).render(cells_area, buf);
}
{
+11
View File
@@ -27,6 +27,7 @@ pub struct CellWidget {
selected: bool,
zoom_level: ZoomLevel,
tag: CellTag,
marked: bool,
}
impl CellWidget {
@@ -43,6 +44,7 @@ impl CellWidget {
selected,
zoom_level,
tag,
marked: false,
}
}
@@ -58,6 +60,14 @@ impl CellWidget {
self.tag = tag;
}
pub fn get_marked(&self) -> bool {
self.marked
}
pub fn set_marked(&mut self, marked: bool) {
self.marked = marked
}
fn col_to_letters(&self) -> String {
let mut col: usize = self.col + 1;
let mut letters: Vec<char> = Vec::new();
@@ -80,6 +90,7 @@ impl CellWidget {
fn fg_color(&self) -> Color {
match self.tag {
_ if self.marked => Color::LightMagenta,
_ if self.selected => Color::LightYellow,
CellTag::Base(Players::Player) if !self.selected => Color::LightBlue,
CellTag::Base(Players::Enemy) if !self.selected => Color::LightRed,