#![doc = include_str!("../readme.md")]
use crossbeam::channel::{SendError, Sender};
use rat_widget::event::{ConsumedEvent, Outcome};
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use std::cmp::Ordering;
use std::fmt::Debug;
use std::mem;
pub(crate) mod control_queue;
mod framework;
pub mod poll;
pub(crate) mod poll_queue;
mod run_config;
pub mod terminal;
mod threadpool;
pub mod timer;
use crate::control_queue::ControlQueue;
use crate::threadpool::ThreadPool;
use crate::timer::{TimeOut, TimerDef, TimerHandle, Timers};
use rat_widget::focus::Focus;
pub use framework::*;
pub use run_config::*;
pub use threadpool::Cancel;
#[derive(Debug, Clone, Copy)]
#[must_use]
pub enum Control<Message> {
Continue,
Unchanged,
Changed,
Message(Message),
Quit,
}
impl<Message> Eq for Control<Message> {}
impl<Message> PartialEq for Control<Message> {
fn eq(&self, other: &Self) -> bool {
mem::discriminant(self) == mem::discriminant(other)
}
}
impl<Message> Ord for Control<Message> {
fn cmp(&self, other: &Self) -> Ordering {
match self {
Control::Continue => match other {
Control::Continue => Ordering::Equal,
Control::Unchanged => Ordering::Less,
Control::Changed => Ordering::Less,
Control::Message(_) => Ordering::Less,
Control::Quit => Ordering::Less,
},
Control::Unchanged => match other {
Control::Continue => Ordering::Greater,
Control::Unchanged => Ordering::Equal,
Control::Changed => Ordering::Less,
Control::Message(_) => Ordering::Less,
Control::Quit => Ordering::Less,
},
Control::Changed => match other {
Control::Continue => Ordering::Greater,
Control::Unchanged => Ordering::Greater,
Control::Changed => Ordering::Equal,
Control::Message(_) => Ordering::Less,
Control::Quit => Ordering::Less,
},
Control::Message(_) => match other {
Control::Continue => Ordering::Greater,
Control::Unchanged => Ordering::Greater,
Control::Changed => Ordering::Greater,
Control::Message(_) => Ordering::Equal,
Control::Quit => Ordering::Less,
},
Control::Quit => match other {
Control::Continue => Ordering::Greater,
Control::Unchanged => Ordering::Greater,
Control::Changed => Ordering::Greater,
Control::Message(_) => Ordering::Greater,
Control::Quit => Ordering::Equal,
},
}
}
}
impl<Message> PartialOrd for Control<Message> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<Message> ConsumedEvent for Control<Message> {
fn is_consumed(&self) -> bool {
!matches!(self, Control::Continue)
}
}
impl<Message, T: Into<Outcome>> From<T> for Control<Message> {
fn from(value: T) -> Self {
let r = value.into();
match r {
Outcome::Continue => Control::Continue,
Outcome::Unchanged => Control::Unchanged,
Outcome::Changed => Control::Changed,
}
}
}
#[allow(unused_variables)]
pub trait AppWidget<Global, Message, Error>
where
Message: 'static + Send + Debug,
Error: 'static + Send + Debug,
{
type State: AppState<Global, Message, Error> + Debug;
fn render(
&self,
area: Rect,
buf: &mut Buffer,
state: &mut Self::State,
ctx: &mut RenderContext<'_, Global>,
) -> Result<(), Error>;
}
#[allow(unused_variables)]
pub trait AppState<Global, Message, Error>
where
Message: 'static + Send + Debug,
Error: 'static + Send + Debug,
{
fn init(&mut self, ctx: &mut AppContext<'_, Global, Message, Error>) -> Result<(), Error> {
Ok(())
}
fn timer(
&mut self,
event: &TimeOut,
ctx: &mut AppContext<'_, Global, Message, Error>,
) -> Result<Control<Message>, Error> {
Ok(Control::Continue)
}
fn crossterm(
&mut self,
event: &crossterm::event::Event,
ctx: &mut AppContext<'_, Global, Message, Error>,
) -> Result<Control<Message>, Error> {
Ok(Control::Continue)
}
fn message(
&mut self,
event: &mut Message,
ctx: &mut AppContext<'_, Global, Message, Error>,
) -> Result<Control<Message>, Error> {
Ok(Control::Continue)
}
fn error(
&self,
event: Error,
ctx: &mut AppContext<'_, Global, Message, Error>,
) -> Result<Control<Message>, Error> {
Ok(Control::Continue)
}
}
#[derive(Debug)]
pub struct AppContext<'a, Global, Message, Error>
where
Message: 'static + Send + Debug,
Error: 'static + Send + Debug,
{
pub g: &'a mut Global,
pub focus: Option<Focus>,
pub(crate) timers: &'a Timers,
pub(crate) tasks: &'a ThreadPool<Message, Error>,
pub(crate) queue: &'a ControlQueue<Message, Error>,
}
#[derive(Debug)]
pub struct RenderContext<'a, Global> {
pub g: &'a mut Global,
pub count: usize,
pub cursor: Option<(u16, u16)>,
}
impl<'a, Global, Message, Error> AppContext<'a, Global, Message, Error>
where
Message: 'static + Send + Debug,
Error: 'static + Send + Debug,
{
#[inline]
pub fn add_timer(&self, t: TimerDef) -> TimerHandle {
self.timers.add(t)
}
#[inline]
pub fn remove_timer(&self, tag: TimerHandle) {
self.timers.remove(tag);
}
#[inline]
pub fn replace_timer(&self, h: Option<TimerHandle>, t: TimerDef) -> TimerHandle {
if let Some(h) = h {
self.remove_timer(h);
}
self.add_timer(t)
}
#[inline]
pub fn spawn(
&self,
task: impl FnOnce(
Cancel,
&Sender<Result<Control<Message>, Error>>,
) -> Result<Control<Message>, Error>
+ Send
+ 'static,
) -> Result<Cancel, SendError<()>>
where
Message: 'static + Send + Debug,
Error: 'static + Send + Debug,
{
self.tasks.send(Box::new(task))
}
#[inline]
pub fn queue(&self, ctrl: impl Into<Control<Message>>) {
self.queue.push(Ok(ctrl.into()));
}
#[inline]
pub fn queue_err(&self, err: Error) {
self.queue.push(Err(err));
}
#[inline]
pub fn focus(&self) -> &Focus {
self.focus.as_ref().expect("focus")
}
#[inline]
pub fn focus_mut(&mut self) -> &mut Focus {
self.focus.as_mut().expect("focus")
}
}
impl<'a, Global> RenderContext<'a, Global> {
pub fn set_screen_cursor(&mut self, cursor: Option<(u16, u16)>) {
if let Some(c) = cursor {
self.cursor = Some(c);
}
}
}
pub mod event {
pub use rat_widget::event::{ct_event, try_flow};
}