initial editor mode

This commit is contained in:
2026-06-15 22:02:15 -05:00
parent f2dc7861d9
commit d1d0824d37
7 changed files with 507 additions and 63 deletions
+62
View File
@@ -50,6 +50,68 @@ impl<'a> BoardWidget<'a> {
}
}
/// Maps a board cell `(bx, by)` to its screen position within `area`, using the
/// same centering/scrolling layout as [`BoardWidget`]. Returns `None` when the
/// cell is scrolled out of view.
///
/// The inverse of [`screen_to_board`]; both reuse [`BoardWidget::axis`] so they
/// stay consistent with how the board is actually drawn. (Unlike
/// `speech::board_screen_pos`, which clamps off-screen cells to the edge, this
/// reports off-screen cells as `None` — needed for exact cursor placement.)
pub fn board_to_screen(area: Rect, board: &Board, bx: usize, by: usize) -> Option<(u16, u16)> {
let (off_x, pad_x, cols) = BoardWidget::axis(board.width, area.width as usize, board.player.x);
let (off_y, pad_y, rows) = BoardWidget::axis(board.height, area.height as usize, board.player.y);
// Cell must fall within the visible window on both axes.
if bx < off_x || bx >= off_x + cols || by < off_y || by >= off_y + rows {
return None;
}
let sx = area.x + pad_x + (bx - off_x) as u16;
let sy = area.y + pad_y + (by - off_y) as u16;
Some((sx, sy))
}
/// Maps a screen position `(sx, sy)` to the board cell drawn there within `area`,
/// the inverse of [`board_to_screen`]. Returns `None` when the point lies outside
/// the drawn board (in the centering pad or past the visible cells).
pub fn screen_to_board(area: Rect, board: &Board, sx: u16, sy: u16) -> Option<(usize, usize)> {
let (off_x, pad_x, cols) = BoardWidget::axis(board.width, area.width as usize, board.player.x);
let (off_y, pad_y, rows) = BoardWidget::axis(board.height, area.height as usize, board.player.y);
// Screen-space offset from the first drawn cell; reject anything before it.
let col = (sx as i32) - (area.x as i32 + pad_x as i32);
let row = (sy as i32) - (area.y as i32 + pad_y as i32);
if col < 0 || row < 0 || col as usize >= cols || row as usize >= rows {
return None;
}
Some((off_x + col as usize, off_y + row as usize))
}
#[cfg(test)]
mod tests {
use super::BoardWidget;
#[test]
fn axis_centers_a_small_board() {
// 10-cell board in a 20-cell view: no scroll, 5 cells of pad each side.
let (off, pad, count) = BoardWidget::axis(10, 20, 3);
assert_eq!((off, pad, count), (0, 5, 10));
}
#[test]
fn axis_scrolls_a_large_board_to_follow_the_player() {
// 100-cell board in a 20-cell view, player at 50: centered, no pad.
let (off, pad, count) = BoardWidget::axis(100, 20, 50);
assert_eq!((off, pad, count), (40, 0, 20));
}
#[test]
fn axis_clamps_scroll_at_the_edges() {
// Player near the left edge can't scroll past 0.
assert_eq!(BoardWidget::axis(100, 20, 2), (0, 0, 20));
// Player near the right edge clamps to board_len - view.
assert_eq!(BoardWidget::axis(100, 20, 99), (80, 0, 20));
}
}
impl Widget for BoardWidget<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
let board = self.board;