use ratatui::crossterm::event::{self, Event};
use tokio::sync::{mpsc, watch};
use crate::{navigation::Navigator, screen::ScreenState};
pub struct App<S, T = ()>
where
S: ScreenState<T>,
{
renders: (watch::Sender<()>, watch::Receiver<()>),
events: mpsc::UnboundedReceiver<Event>,
screen: S,
state: T,
}
impl<S> App<S, ()>
where
S: ScreenState<()>,
{
pub fn new() -> Self {
let (events_tx, events_rx) = mpsc::unbounded_channel();
tokio::task::spawn_blocking(move || {
loop {
if let Ok(event) = event::read()
&& events_tx.send(event).is_err()
{
break;
}
}
});
let (renders_tx, renders_rx) = watch::channel(());
Self {
renders: (renders_tx, renders_rx),
events: events_rx,
screen: S::default(),
state: (),
}
}
}
impl<S, T> App<S, T>
where
S: ScreenState<T>,
{
pub fn with_state(state: T) -> Self {
let (events_tx, events_rx) = mpsc::unbounded_channel();
tokio::task::spawn_blocking(move || {
loop {
if let Ok(event) = event::read()
&& events_tx.send(event).is_err()
{
break;
}
}
});
let (renders_tx, renders_rx) = watch::channel(());
Self {
renders: (renders_tx, renders_rx),
events: events_rx,
screen: S::default(),
state,
}
}
pub async fn run(&mut self) -> std::io::Result<()> {
let mut terminal = ratatui::init();
loop {
terminal
.draw(|frame| self.screen.draw(frame, &mut self.state))
.inspect_err(|_| {
ratatui::restore();
})?;
tokio::select! {
Ok(_) = self.renders.1.changed() => {},
Some(event) = self.events.recv() => {
let navigator = Navigator::new(self.renders.0.clone());
self.screen.on_event(event, &navigator, &mut self.state).await;
let lock = navigator.inner.lock().await;
if lock.should_exit {
self.screen.on_exit(&mut self.state).await;
break;
}
if let Some(id) = lock.next_screen {
self.screen.on_exit(&mut self.state).await;
self.screen.navigate(&id);
self.screen.on_enter(&mut self.state).await;
}
},
_ = self.screen.rerender(&mut self.state) => {}
}
}
ratatui::restore();
Ok(())
}
}
impl<S, T> Default for App<S, T>
where
S: ScreenState<T>,
T: Default,
{
fn default() -> Self {
Self::with_state(T::default())
}
}