#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Region {
pub col: u16,
pub row: u16,
pub width: u16,
pub height: u16,
}
impl Region {
pub fn new(col: u16, row: u16, width: u16, height: u16) -> Self {
Self {
col,
row,
width,
height,
}
}
pub fn full(cols: u16, rows: u16) -> Self {
Self::new(0, 0, cols, rows)
}
pub fn top_row(cols: u16) -> Self {
Self::new(0, 0, cols, 1)
}
pub fn footer(cols: u16, rows: u16) -> Self {
Self::new(0, rows.saturating_sub(1), cols, 1)
}
pub fn top_left(cols: u16, rows: u16) -> Self {
Self::new(0, 0, cols / 2, rows / 2)
}
pub fn top_right(cols: u16, rows: u16) -> Self {
let half_cols = cols / 2;
Self::new(half_cols, 0, cols - half_cols, rows / 2)
}
pub fn left_panel(cols: u16, rows: u16) -> Self {
Self::new(0, 0, cols / 2, rows)
}
pub fn right_panel(cols: u16, rows: u16) -> Self {
let half_cols = cols / 2;
Self::new(half_cols, 0, cols - half_cols, rows)
}
pub fn percent(
col_pct: u16,
row_pct: u16,
width_pct: u16,
height_pct: u16,
cols: u16,
rows: u16,
) -> Self {
let col = u16::try_from(u32::from(col_pct) * u32::from(cols) / 100).unwrap_or(u16::MAX);
let row = u16::try_from(u32::from(row_pct) * u32::from(rows) / 100).unwrap_or(u16::MAX);
let width = u16::try_from(u32::from(width_pct) * u32::from(cols) / 100).unwrap_or(u16::MAX);
let height =
u16::try_from(u32::from(height_pct) * u32::from(rows) / 100).unwrap_or(u16::MAX);
Self::new(
col.min(cols),
row.min(rows),
width.min(cols.saturating_sub(col)),
height.min(rows.saturating_sub(row)),
)
}
pub fn contains(&self, col: u16, row: u16) -> bool {
col >= self.col
&& col < self.col.saturating_add(self.width)
&& row >= self.row
&& row < self.row.saturating_add(self.height)
}
pub fn encloses(&self, other: &Region) -> bool {
other.col >= self.col
&& other.row >= self.row
&& other.col.saturating_add(other.width) <= self.col.saturating_add(self.width)
&& other.row.saturating_add(other.height) <= self.row.saturating_add(self.height)
}
pub fn right(&self) -> u16 {
self.col.saturating_add(self.width)
}
pub fn bottom(&self) -> u16 {
self.row.saturating_add(self.height)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn full_region_covers_entire_grid() {
let cols = 80;
let rows = 24;
let region = Region::full(cols, rows);
assert_eq!(region, Region::new(0, 0, 80, 24));
}
#[test]
fn top_row_spans_full_width_one_row() {
let region = Region::top_row(120);
assert_eq!(region.height, 1);
assert_eq!(region.width, 120);
assert_eq!(region.row, 0);
}
#[test]
fn footer_is_last_row() {
let region = Region::footer(80, 24);
assert_eq!(region.row, 23);
assert_eq!(region.height, 1);
}
#[test]
fn contains_checks_bounds() {
let region = Region::new(10, 5, 20, 10);
assert!(region.contains(10, 5));
assert!(region.contains(29, 14));
assert!(!region.contains(9, 5));
assert!(!region.contains(30, 5));
assert!(!region.contains(10, 15));
}
#[test]
fn encloses_checks_full_containment() {
let outer = Region::new(0, 0, 80, 24);
let inner = Region::new(10, 5, 20, 10);
let outside = Region::new(70, 20, 20, 10);
assert!(outer.encloses(&inner));
assert!(!outer.encloses(&outside));
assert!(!inner.encloses(&outer));
}
#[test]
fn percent_region_computes_correctly() {
let region = Region::percent(50, 0, 50, 100, 80, 24);
assert_eq!(region.col, 40);
assert_eq!(region.row, 0);
assert_eq!(region.width, 40);
assert_eq!(region.height, 24);
}
#[test]
fn top_right_covers_right_half() {
let region = Region::top_right(80, 24);
assert_eq!(region.col, 40);
assert_eq!(region.row, 0);
assert_eq!(region.width, 40);
assert_eq!(region.height, 12);
}
}