fatui 0.1.0

a silly little framework for terminal user interfaces
Documentation
use std::{
	collections::HashSet,
	ops::{Deref, DerefMut},
};

use crossterm::event::{KeyCode, ModifierKeyCode, MouseButton};

use crate::pos::{Pos, Rect};

use super::{Event, FrameInput};

/// the overall state of the input
///
/// backends don't track this by default, to save the extra calculation.
/// you can mix the newest [`Event`] with the latest `InputState` to get
/// a [`StateEvent`] like so:
///
/// ```rs
/// # use fatui::{Backend, InputState, Event};
/// let mut backend = // ...
/// # ();
/// let mut state = InputState::default();
///
/// // then, inside your loop somewhere:
/// let frame = backend.step().with(&mut state);
/// // `frame` is now a `Frame<StateEvent>`! e.g.:
/// assert_eq!(frame.input().pos, None);
/// ```
#[derive(Clone, Debug, Default)]
pub struct InputState {
	/// the keyboard keys currently being held down
	pub keys: Keys,
	/// the mouse buttons currently being held down
	pub buttons: HashSet<MouseButton>,
	/// the last known position of the mouse, if it's inside the frame
	pub mouse: Option<Pos>,
}

impl InputState {
	/// update the state with an event
	pub fn update(&mut self, event: &Event) {
		match event {
			Event::MouseMove(pos) => self.mouse = Some(*pos),
			Event::MouseOutside => self.mouse = None,
			Event::MouseUp(b, pos) => {
				self.mouse = Some(*pos);
				self.buttons.remove(b);
			}
			Event::MouseDown(b, pos) => {
				self.mouse = Some(*pos);
				self.buttons.insert(*b);
			}
			Event::KeyPress(k) => {
				self.keys.insert(*k);
			}
			Event::KeyRelease(k) => {
				self.keys.remove(k);
			}
			Event::Redraw | Event::Close | Event::Paste(_) => (),
		}
	}
}

/// an [`Event`] and the associated up-to-date [`InputState`]
#[derive(Clone, Debug)]
pub struct StateEvent<'s> {
	pub(crate) event: Event,
	pub(crate) state: &'s InputState,
	pub(crate) rect: Rect,
}
crate::seal!({'s} StateEvent{'s});

impl StateEvent<'_> {
	/// the actual event occurring
	pub fn event(&self) -> &Event {
		&self.event
	}
	/// keyboard keys currently being held down
	///
	/// to check whether a key has just now been pressed,
	/// just look at the [`Self::event`]!
	pub fn keys(&self) -> &Keys {
		&self.state.keys
	}
	/// mouse buttons currently being held down
	///
	/// to check whether a button has just now been pressed,
	/// just look at the [`Self::event`]!
	pub fn buttons(&self) -> &HashSet<MouseButton> {
		&self.state.buttons
	}
	/// the mouse position, or `None` if it's outside the frame
	pub fn mouse(&self) -> Option<Pos> {
		self.rect.rel(self.state.mouse?)
	}
}

macro_rules! split {
	($fn:ident, $pos:ty) => {
		fn $fn(self, mid: $pos) -> (Self, Self) {
			let (e1, e2) = self.event.$fn(mid);
			let (r1, r2) = self.rect.$fn(mid);
			(
				Self { event: e1, state: self.state, rect: r1 },
				Self { event: e2, state: self.state, rect: r2 },
			)
		}
	};
}

impl FrameInput for StateEvent<'_> {
	split!(split_h, crate::pos::X);
	split!(split_v, crate::pos::Y);
}

/// helper to provide convenience methods around checking keycodes
#[derive(Clone, Debug, Default)]
pub struct Keys(HashSet<KeyCode>);
impl Deref for Keys {
	type Target = HashSet<KeyCode>;
	fn deref(&self) -> &Self::Target {
		&self.0
	}
}
impl DerefMut for Keys {
	fn deref_mut(&mut self) -> &mut Self::Target {
		&mut self.0
	}
}

macro_rules! mod_methods {
	($(
		$(#[$doc:meta])*
		$fn:ident: $($mcc:ident),*
	);* $(;)?) => {
		impl Keys { $(
			$(#[$doc])*
			pub fn $fn(&self) -> bool {
				$(
					self.contains(&KeyCode::Modifier(ModifierKeyCode::$mcc))
				)||*
			}
		)* }
	}
}
mod_methods! {
	/// whether either control (ctrl) key is pressed
	ctrl: LeftControl, RightControl;
	/// whether either alt (option) key is pressed
	alt: LeftAlt, RightAlt;
	/// whether either super (windows) key is pressed
	///
	/// (note: `super` is a reserved keyword in rust.)
	win: LeftSuper, RightSuper;
}