fatui 0.1.0

a silly little framework for terminal user interfaces
Documentation
#![expect(dead_code)]

use std::{
	fmt,
	marker::PhantomData,
	ops::{Bound, RangeBounds},
	ptr::NonNull,
	slice,
};

use crate::{
	grid::Grid,
	output::StyledChar,
	pos::{Coord, Size, X, Y},
};

/// trimmed-down [`Grid<StyledChar>`] using a pointer for "simultaneous" `&mut`
///
/// (actually non-overlapping slices)
#[derive(Clone)]
pub struct CgPtr<'b> {
	/// the character grid buffer this is writing to
	///
	/// can't have multiple &mut to the same memory --
	/// so we have a *mut, and tie the lifetime with a PhantomData
	/// then slice out specific bits, so our &muts never overlap!
	///
	/// this can be `None` if the frame is fully shadowed,
	/// to let writes short-circuit.
	pub chars: NonNull<StyledChar>,
	/// the size of the associated [`Grid<StyledChar>`]
	pub size: Size,
	/// marker for the lifetime of the associated [`Grid<StyledChar>`]
	life: PhantomData<&'b mut ()>,
}

impl fmt::Debug for CgPtr<'_> {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		(..).fmt(f)
	}
}

impl<'b> CgPtr<'b> {
	pub fn new(cg: &'b mut Grid<StyledChar>) -> Self {
		// SAFETY: we're getting the pointer from a slice, which means it must be nonnull
		let ptr = unsafe { NonNull::new_unchecked(cg.all_cells().as_mut_ptr() as *mut _) };
		Self { chars: ptr, size: cg.size(), life: PhantomData }
	}

	/// get a reference to a single cell
	///
	/// panicks if `x` or `y` are out of bounds.
	///
	/// # safety
	///
	/// the caller must ensure that the same position isn't referenced anywhere else,
	/// including by being contained in a [`Self::slice`].
	pub unsafe fn cell(&mut self, x: X, y: Y) -> &mut StyledChar {
		assert!(x < self.size.width, "x={x} is out of bounds ({})", self.size.width);
		assert!(y < self.size.height, "y={y} is out of bounds ({})", self.size.height);
		// SAFETY:
		// - we've just ensured `x` and `y` are in bounds, so we're deferencing a valid location
		// - the caller promises we don't have aliased `&mut`s
		unsafe {
			let mut ptr = self.chars.add((y.0 * self.size.width.0) + x.0);
			ptr.as_mut()
		}
	}

	/// get a reference to multiple cells in a row
	///
	/// panicks if any part of `x` or `y` is out of bounds
	///
	/// # safety
	///
	/// the caller must ensure that the same position isn't referenced anywhere else,
	/// including by being returned as a [`Self::cell`].
	pub unsafe fn slice(&mut self, x: impl RangeBounds<Coord>, y: Y) -> &mut [StyledChar] {
		let start = match x.start_bound() {
			Bound::Included(s) => *s,
			Bound::Excluded(s) => s + 1,
			Bound::Unbounded => 0,
		};
		let end = match x.end_bound() {
			Bound::Included(s) => s + 1,
			Bound::Excluded(s) => *s,
			Bound::Unbounded => self.size.width.0,
		};
		assert!(start < self.size.width.0, "s={start} is out of bounds ({})", self.size.width);
		assert!(end <= self.size.width.0, "e={end} is out of bounds ({})", self.size.width);
		assert!(y < self.size.height, "y={y} is out of bounds ({})", self.size.height);
		// SAFETY:
		// - we've just ensured `start`/`end` and `y` are in bounds,
		//   so we're deferencing a valid location
		// - the caller promises we don't have aliased `&mut`s
		unsafe {
			let ptr = self.chars.add((y.0 * self.size.width.0) + start);
			let len = end - start;
			slice::from_raw_parts_mut(ptr.as_ptr(), len)
		}
	}
}