fatui 0.1.0

a silly little framework for terminal user interfaces
Documentation
use crate::{grid::Grid, input::Nop, pos::Size};
use owo_colors::Style;

use crate::{
	Event,
	input::FrameInput,
	output::StyledChar,
	pos::{X, Y},
};

use super::Frame;

#[test]
fn empty_splits_are_empty() {
	let (t, b) = Frame::nop(Size::ZERO).split_v(Y(0)).expect("couldn't split at y=0");
	assert!(t.is_empty());
	assert!(b.is_empty());
	let (l, r) = Frame::nop(Size::ZERO).split_h(X(0)).expect("couldn't split at x=0");
	assert!(l.is_empty());
	assert!(r.is_empty());
}

#[test]
fn oob_splits_are_none() {
	assert!(Frame::nop(Size::ZERO).split_v(Y(1)).is_none(), "could get 1st row of empty frame",);
	assert!(Frame::nop(Size::ZERO).split_h(X(1)).is_none(), "could get 1st col of empty frame",);
}

const BLANK_CHAR: StyledChar = StyledChar::from_owo(Style::new().red().on_green().style('_'));
const WRITTEN_CHAR: StyledChar = StyledChar::from_owo(Style::new().yellow().on_black().style('#'));

fn cg(x: usize, y: usize) -> Grid<StyledChar> {
	let mut res = Grid::<StyledChar>::new(Size::rnew(x, y));
	res.all_cells().fill(BLANK_CHAR);
	res
}

fn fill(name: &str, f: &mut Frame<impl FrameInput>) {
	println!("filling {name}: {f:?}");
	for y in f.rows() {
		let mut row = f.row(y);
		for (x, cell) in row.iter_mut().enumerate() {
			if *cell != BLANK_CHAR {
				panic!("{name}:{x},{y} was already written");
			}
			*cell = WRITTEN_CHAR;
		}
	}
	// then write back, so we can do multiple fill passes
	for y in f.rows() {
		f.row(y).fill(BLANK_CHAR);
	}
}

macro_rules! assert_frame {
	($frame:expr, ($x:literal, $y:literal; $w:literal, $h:literal)) => {
		let mut frame = $frame;
		assert_eq!(frame.area.bounds.tl, ($x, $y), concat!("'", stringify!($frame), "' pos is wrong"));
		assert_eq!(frame.size(), ($w, $h), concat!("'", stringify!($frame), "' size is wrong"));
		fill(stringify!($frame), &mut frame);
	};
}

macro_rules! test_splits {
	( $(
		$name:ident: ($w:literal x $h:literal).$mthd:ident($pos:expr) =>
		($x1:literal, $y1:literal, $w1:literal, $h1:literal),
		($x2:literal, $y2:literal, $w2:literal, $h2:literal)
	);* $(;)? ) => { $(
		#[test]
		fn $name() {
			let mut v = cg($w, $h);
			let frame = Frame::new(Nop, &mut v);
			println!("frame: {frame:?}");
			assert!(!frame.is_empty(), "can't test splits on empty frames");
			let (s1, s2) = frame.$mthd($pos).expect("split was unexpectedly out of bounds");
			println!("s1: {s1:?}");
			println!("s2: {s2:?}");
			assert_frame!(s1, ($x1, $y1; $w1, $h1));
			assert_frame!(s2, ($x2, $y2; $w2, $h2));
		}
	)* }
}

test_splits! {
	// simple horizontal case, splitting up a row
	one_row_v0: (10 x 1).split_v(Y(0)) => (0, 0, 10, 0), (0, 0, 10, 1);
	one_row_v1: (10 x 1).split_v(Y(1)) => (0, 0, 10, 1), (0, 0, 10, 0);
	one_row_h0: (10 x 1).split_h(X(0)) => (0, 0, 0, 1), (0, 0, 10, 1);
	one_row_h1: (10 x 1).split_h(X(1)) => (0, 0, 1, 1), (1, 0, 9, 1);
	// simple vertical case, splitting up a column
	one_col_v0: (1 x 10).split_v(Y(0)) => (0, 0, 1, 0), (0, 0, 1, 10);
	one_col_v1: (1 x 10).split_v(Y(1)) => (0, 0, 1, 1), (0, 1, 1, 9);
	one_col_h0: (1 x 10).split_h(X(0)) => (0, 0, 0, 10), (0, 0, 1, 10);
	one_col_h1: (1 x 10).split_h(X(1)) => (0, 0, 1, 10), (0, 0, 0, 10);
	// larger areas
	split_2x2_v0: (2 x 2).split_v(Y(0)) => (0, 0, 2, 0), (0, 0, 2, 2);
	split_2x2_v1: (2 x 2).split_v(Y(1)) => (0, 0, 2, 1), (0, 1, 2, 1);
	split_2x2_v2: (2 x 2).split_v(Y(2)) => (0, 0, 2, 2), (0, 0, 2, 0);
	split_2x2_h0: (2 x 2).split_h(X(0)) => (0, 0, 0, 2), (0, 0, 2, 2);
	split_2x2_h1: (2 x 2).split_h(X(1)) => (0, 0, 1, 2), (1, 0, 1, 2);
	split_2x2_h2: (2 x 2).split_h(X(2)) => (0, 0, 2, 2), (0, 0, 0, 2);
}

#[test]
fn split_treely() {
	let mut v = cg(10, 10);
	let frame = Frame::new(Event::default(), &mut v);
	assert_eq!(frame.size(), (10, 10));
	// split in half once
	let (mut t, mut b) = frame.split_v(Y(3)).unwrap();
	assert_frame!(&mut t, (0, 0; 10, 3));
	assert_frame!(&mut b, (0, 3; 10, 7));

	// split in halves again
	let (mut tl, mut tr) = t.split_h(X(4)).unwrap();
	assert_frame!(&mut tl, (0, 0; 4, 3));
	assert_frame!(&mut tr, (4, 0; 6, 3));
	let (mut bl, mut br) = b.split_h(X(8)).unwrap();
	assert_frame!(&mut bl, (0, 3; 8, 7));
	assert_frame!(&mut br, (8, 3; 2, 7));

	// split a couple of the bigger ones a couple more times
	let (mut tll, mut tlr) = tl.split_h(X(3)).unwrap();
	assert_frame!(&mut tll, (0, 0; 3, 3));
	assert_frame!(&mut tlr, (3, 0; 1, 3));
	let (mut brt, mut brb) = br.split_v(Y(5)).unwrap();
	assert_frame!(&mut brt, (8, 3; 2, 5));
	assert_frame!(&mut brb, (8, 8; 2, 2));
}

#[test]
fn split_lots() {
	let mut v = cg(10, 10);
	let rest = Frame::new(Event::default(), &mut v);
	let (h, mut rest) = rest.split_v(Y(1)).unwrap();
	assert_frame!(h, (0, 0; 10, 1));
	assert_frame!(&mut rest, (0, 1; 10, 9));
	let (h, mut rest) = rest.split_v(Y(2)).unwrap();
	assert_frame!(h, (0, 1; 10, 2));
	assert_frame!(&mut rest, (0, 3; 10, 7));
	let (h, mut rest) = rest.split_h(X(1)).unwrap();
	assert_frame!(h, (0, 3; 1, 7));
	assert_frame!(&mut rest, (1, 3; 9, 7));
	let (h, mut rest) = rest.split_v(Y(1)).unwrap();
	assert_frame!(h, (1, 3; 9, 1));
	assert_frame!(&mut rest, (1, 4; 9, 6));
	let (h, mut rest) = rest.split_h(X(2)).unwrap();
	assert_frame!(h, (1, 4; 2, 6));
	assert_frame!(&mut rest, (3, 4; 7, 6));
	let (h, mut rest) = rest.split_h(X(1)).unwrap();
	assert_frame!(h, (3, 4; 1, 6));
	assert_frame!(&mut rest, (4, 4; 6, 6));
	assert!(!rest.is_empty());
}