use ratatui::layout::Rect;
use crate::view::tail;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Regions {
pub forest: Rect,
pub tail: Rect,
pub keys: Rect,
}
pub fn regions(area: Rect) -> Regions {
let mut rows = area.height;
let keys = if rows >= 2 { 1 } else { 0 };
rows -= keys;
let tail = (tail::LINES + 1).min(rows.saturating_sub(1) / 2);
let forest = rows - tail;
Regions {
forest: Rect {
height: forest,
..area
},
tail: Rect {
y: area.y + forest,
height: tail,
..area
},
keys: Rect {
y: area.y + forest + tail,
height: keys,
..area
},
}
}
pub fn line_at(forest: Rect, from: usize, lines: usize, row: u16) -> Option<usize> {
let within = row.checked_sub(forest.y)? as usize;
if within >= forest.height as usize {
return None;
}
let at = from + within;
(at < lines).then_some(at)
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
use crate::model::snapshot::ProviderState;
use crate::view::draw::tests::*;
use crate::view::{Action, Motion, Notch};
const A_NOTCH: usize = 3;
#[test]
fn a_full_screen_gives_the_forest_most_of_it_the_tail_a_look_and_the_keys_a_row() {
let bands = regions(Rect::new(0, 0, 80, 24));
assert_eq!(bands.forest, Rect::new(0, 0, 80, 16));
assert_eq!(
bands.tail,
Rect::new(0, 16, 80, 7),
"six lines of pane, under the rule that names it"
);
assert_eq!(bands.keys, Rect::new(0, 23, 80, 1));
}
#[test]
fn a_short_screen_takes_the_rows_from_the_tail_and_not_from_the_forest() {
let bands = regions(Rect::new(0, 0, 80, 10));
assert_eq!(bands.forest.height, 5);
assert_eq!(bands.tail.height, 4);
assert_eq!(bands.keys.height, 1);
}
#[test]
fn the_forest_keeps_a_row_however_little_room_there_is() {
for height in 1..=8 {
let bands = regions(Rect::new(0, 0, 80, height));
assert!(bands.forest.height >= 1, "{height} rows: {bands:?}");
}
}
#[test]
fn the_smallest_screens_spend_their_rows_on_the_forest_first() {
assert_eq!(
regions(Rect::new(0, 0, 80, 2)),
Regions {
forest: Rect::new(0, 0, 80, 1),
tail: Rect::new(0, 1, 80, 0),
keys: Rect::new(0, 1, 80, 1),
}
);
assert_eq!(
regions(Rect::new(0, 0, 80, 1)),
Regions {
forest: Rect::new(0, 0, 80, 1),
tail: Rect::new(0, 1, 80, 0),
keys: Rect::new(0, 1, 80, 0),
}
);
}
#[test]
fn the_three_bands_tile_the_screen_exactly() {
for height in 0..40 {
let area = Rect::new(3, 7, 80, height);
let bands = regions(area);
assert_eq!(bands.forest.y, area.y, "{height}");
assert_eq!(
bands.tail.y,
bands.forest.y + bands.forest.height,
"{height}"
);
assert_eq!(bands.keys.y, bands.tail.y + bands.tail.height, "{height}");
assert_eq!(
bands.forest.height + bands.tail.height + bands.keys.height,
area.height,
"{height}"
);
}
}
#[test]
fn every_row_of_the_forest_names_the_line_drawn_on_it() {
for wheeled in 0..3 {
let (width, height) = (60, 24);
let band = regions(Rect::new(0, 0, width, height)).forest;
let mut forest = opened(&snapshot(
vec![grove(40)],
Vec::new(),
ProviderState::Answering,
));
forest.fit(band.height as usize);
forest.apply(Action::Move(Motion::HalfScreenDown));
for _ in 0..wheeled {
forest.scrolled(Notch::Down, A_NOTCH);
}
let frame = frame_of(&forest, width, height).rows();
let lines = forest.lines().len();
for row in band.y..band.y + band.height {
let at =
line_at(band, forest.from(), lines, row).expect("the band is full of lines");
let shown = match at {
0 => "summit-works".to_string(),
1 => "lift the ground station".to_string(),
at => format!("bead number {}", at - 1),
};
assert!(
frame[row as usize].contains(&shown),
"after {wheeled} notches, row {row} shows {:?}, not line {at}",
frame[row as usize]
);
}
}
}
#[test]
fn a_row_past_the_last_line_names_none() {
let band = Rect::new(0, 0, 60, 16);
assert_eq!(line_at(band, 0, 3, 2), Some(2));
for row in 3..16 {
assert_eq!(line_at(band, 0, 3, row), None, "row {row}");
}
}
#[test]
fn a_row_outside_the_forest_band_names_none() {
let bands = regions(Rect::new(0, 0, 60, 24));
let (from, lines) = (0, 100);
for row in [bands.tail.y, bands.tail.y + 3, bands.keys.y] {
assert_eq!(line_at(bands.forest, from, lines, row), None, "row {row}");
}
}
#[test]
fn a_row_above_the_forest_band_names_none() {
let band = Rect::new(0, 4, 60, 8);
assert_eq!(line_at(band, 0, 100, 4), Some(0));
for row in 0..4 {
assert_eq!(line_at(band, 0, 100, row), None, "row {row}");
}
}
#[test]
fn a_scrolled_band_names_the_lines_it_is_showing() {
let band = Rect::new(0, 0, 60, 8);
assert_eq!(line_at(band, 12, 100, 0), Some(12));
assert_eq!(line_at(band, 12, 100, 7), Some(19));
assert_eq!(line_at(band, 12, 100, 8), None);
}
}