#![feature(trait_upcasting)]
use ratatui::prelude::{Buffer, Rect};
use std::{
any::Any,
fmt,
hash::{Hash, Hasher},
mem::replace,
num::NonZeroU64,
};
use twox_hash::XxHash64;
mod jobs;
pub use jobs::*;
mod compositor;
pub use compositor::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct LayerId(pub i16);
impl LayerId {
pub const BACKGROUND: Self = Self(-1_000);
pub const MIDDLE: Self = Self(0);
pub const FOREGROUND: Self = Self(1_000);
pub const POPUP: Self = Self(2_000);
pub const OVERLAY: Self = Self(5_000);
pub const TOPMOST: Self = Self(10_000);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Id(NonZeroU64);
impl Id {
pub fn new(source: impl Hash) -> Self {
let mut hasher = XxHash64::default();
source.hash(&mut hasher);
NonZeroU64::new(hasher.finish()).map(Self).expect("id is 0")
}
pub fn with(&self, more: impl Hash) -> Self {
let mut hasher = XxHash64::default();
self.0.hash(&mut hasher);
more.hash(&mut hasher);
NonZeroU64::new(hasher.finish()).map(Self).expect("id is 0")
}
}
#[non_exhaustive]
pub enum Event<E> {
User(E),
Terminal(crossterm::event::Event),
Tick,
Exit,
None,
}
impl<T> Event<T> {
#[inline]
pub fn is_user(&self) -> bool {
matches!(self, Self::User(_))
}
#[inline]
pub fn as_user(&self) -> Option<&T> {
match self {
Event::User(e) => Some(e),
_ => None,
}
}
#[inline]
pub fn as_mut_user(&mut self) -> Option<&mut T> {
match self {
Event::User(e) => Some(e),
_ => None,
}
}
#[inline]
pub fn into_user(self) -> Result<T, Self> {
match self {
Event::User(e) => Ok(e),
_ => Err(self),
}
}
#[inline]
pub fn is_terminal(&self) -> bool {
matches!(self, Self::Terminal(_))
}
#[inline]
pub fn as_terminal(&self) -> Option<&crossterm::event::Event> {
match self {
Event::Terminal(e) => Some(e),
_ => None,
}
}
#[inline]
pub fn as_mut_terminal(&mut self) -> Option<&mut crossterm::event::Event> {
match self {
Event::Terminal(e) => Some(e),
_ => None,
}
}
#[inline]
pub fn into_terminal(self) -> Result<crossterm::event::Event, Self> {
match self {
Event::Terminal(e) => Ok(e),
_ => Err(self),
}
}
}
impl<T: Clone> Clone for Event<T> {
fn clone(&self) -> Self {
match self {
Event::Terminal(e) => Self::Terminal(e.clone()),
Event::User(e) => Self::User(e.clone()),
Event::Tick => Self::Tick,
Event::Exit => Self::Exit,
Event::None => Self::None,
}
}
}
impl<T: fmt::Debug> fmt::Debug for Event<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Event::Terminal(e) => f.debug_tuple("Crossterm").field(e).finish(),
Event::User(e) => f.debug_tuple("User").field(e).finish(),
Event::Tick => write!(f, "Tick"),
Event::Exit => write!(f, "Exit"),
Event::None => write!(f, "None"),
}
}
}
pub struct EventAccess<E = ()> {
event: Event<E>,
}
impl<E> EventAccess<E> {
#[inline]
pub fn peek(&self) -> &Event<E> {
&self.event
}
#[inline]
pub fn consume(&mut self) -> Event<E> {
replace(&mut self.event, Event::None)
}
#[inline]
pub fn replace(&mut self, event: Event<E>) -> Event<E> {
replace(&mut self.event, event)
}
#[inline]
pub fn is_consumed(&self) -> bool {
matches!(self.event, Event::None)
}
}
impl<E: Clone> EventAccess<E> {
#[inline]
pub fn cloned(&self) -> Event<E> {
self.event.clone()
}
}
pub trait Component<S = (), E = ()>: Any {
fn id(&self) -> Id;
fn view(&self, area: Rect, buf: &mut Buffer, state: &S);
fn handle_event(&mut self, _event: &mut EventAccess<E>, _cx: &mut Context<S, E>) {}
}
#[macro_export]
macro_rules! forward_handle_event {
(@ret $($tail:tt)*) => {
if $crate::forward_handle_event!($($tail)*) {
return;
}
};
($event:expr, $cx:expr, $($comp:expr),*) => {
'forward: {
$(
$comp.handle_event($event, $cx);
if $event.is_consumed() {
break 'forward true;
}
)*
false
}
};
}
#[macro_export]
macro_rules! forward_view {
($area:expr, $buf:expr, $state:expr, $($comp:expr),*) => {
{
let mut any = false;
$(
if $comp.should_update($state) {
$comp.view($area, $buf, $state);
any = true;
}
)*
any
}
};
}
#[macro_export]
macro_rules! id {
($self:expr) => {
<_ as $crate::Component>::id($self)
};
($state:tt, $self:expr) => {
<_ as $crate::Component<$state>>::id($self)
};
($state:tt, $event:tt, $self:expr) => {
<_ as $crate::Component<$state, $event>>::id($self)
};
}