fatui 0.1.0

a silly little framework for terminal user interfaces
Documentation
use std::ops::Add;

use fatui::{
	Event, InputState, KeyCode,
	grid::Grid,
	pos::{Size, X, Y},
};
use owo_colors::{AnsiColors, Style};
use rand::Rng;

fn main() {
	let mut backend = fatui::open().expect("couldn't open a default backend");
	let mut rng = rand::rng();
	let mut _is = InputState::default();
	let mut sparkles: Grid<usize> = Grid::new(Size::rnew(0, 0));
	loop {
		let mut frame = backend.step().unwrap().with(&mut _is);
		sparkles.resize(frame.size());
		match frame.input().event() {
			Event::Close => break,
			Event::KeyPress(KeyCode::Esc) => break,
			Event::KeyPress(KeyCode::Char('c')) if frame.input().keys().ctrl() => break,
			Event::Redraw => {
				for y in frame.rows() {
					for x in 0..frame.size().width.0 {
						let x = X(x);
						let val = sparkles.row(y).unwrap()[x];
						if val > 0 {
							sparkles.row_mut(y).unwrap()[x] -= 1;
							frame.row(y)[x].char = rng.random_range(' '..'~');
							frame.row(y)[x].style = Style::new().color(match rng.random_range(0..8) {
								0 => AnsiColors::BrightBlack,
								1 => AnsiColors::BrightRed,
								2 => AnsiColors::BrightGreen,
								3 => AnsiColors::BrightYellow,
								4 => AnsiColors::BrightBlue,
								5 => AnsiColors::BrightMagenta,
								6 => AnsiColors::BrightCyan,
								7 => AnsiColors::BrightWhite,
								_ => unreachable!(),
							});
						} else {
							frame.row(y)[x] = Default::default();
						}
					}
				}
			}
			&Event::MouseMove(pos) => {
				// calculate the smaller of width/height as the radius,
				// accounting for characters being 2:1 tall:wide (ish)
				let xr = 2.max(frame.size().width.0 / 5);
				let yr = 1.max(frame.size().height.0 / 10);
				let radius = xr.min(yr) * 2;
				let r2 = radius * radius;
				let xmin = pos.x.0.saturating_sub(radius * 2);
				let xmax = pos.x.0.add(radius * 2).min(frame.size().width.0);
				let ymin = pos.y.0.saturating_sub(yr);
				let ymax = pos.y.0.add(yr).min(frame.size().height.0);
				for y in ymin..ymax {
					for x in xmin..xmax {
						let dist = (x.abs_diff(pos.x.0) / 2).pow(2) + y.abs_diff(pos.y.0).pow(2) + 1;
						if dist > r2 {
							continue;
						}
						let max = 20 * radius / dist;
						let strength = rng.random_range(0..max);
						if !rng.random_ratio(1, dist as u32 * dist as u32) {
							continue;
						}
						sparkles.row_mut(Y(y)).unwrap()[X(x)] += strength;
					}
				}
			}
			_ => (),
		}
	}
}