use std::collections::VecDeque;
use ratatui::crossterm::event::{self, Event};
use tokio::sync::mpsc;
use crate::{
navigation::{Action, Navigator},
screen::ScreenState,
};
pub struct App<T = ()> {
events: mpsc::UnboundedReceiver<Event>,
state: T,
}
impl App<()> {
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;
}
}
});
Self {
events: events_rx,
state: (),
}
}
}
impl<T> App<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;
}
}
});
Self {
events: events_rx,
state,
}
}
pub async fn run<S>(&mut self) -> std::io::Result<()>
where
S: ScreenState<T>,
{
let mut terminal = ratatui::init();
let mut screens = VecDeque::from([S::default()]);
let (events_tx, mut events_rx) = mpsc::unbounded_channel();
let navigator = Navigator::new(events_tx);
screens
.back_mut()
.unwrap()
.on_enter(navigator.clone(), &mut self.state)
.await;
let mut draw = true;
loop {
let screen = screens.back_mut().expect("No screen in the stack!");
if draw {
terminal
.draw(|frame| screen.draw(frame, &self.state))
.inspect_err(|_| {
ratatui::restore();
})?;
draw = false;
}
tokio::select! {
Some(event) = self.events.recv() => {
if let Event::Resize(_, _) = event {
draw = true;
}
screen.on_event(event, navigator.clone(), &mut self.state).await;
},
Some(action) = events_rx.recv() => {
match action {
Action::Push(id) => {
screen.on_pause(navigator.clone(), &mut self.state).await;
let mut screen = S::new(id);
screen.on_enter(navigator.clone(), &mut self.state).await;
screens.push_back(screen);
draw = true;
}
Action::Replace(id) => {
let mut old_screen = screens.pop_back().unwrap();
old_screen.on_exit(navigator.clone(), &mut self.state).await;
let mut new_screen = S::new(id);
new_screen.on_enter(navigator.clone(), &mut self.state).await;
screens.push_back(new_screen);
draw = true;
}
Action::Back => {
if screens.len() > 1 {
let mut old_screen = screens.pop_back().unwrap();
old_screen.on_exit(navigator.clone(), &mut self.state).await;
let current_screen = screens.back_mut().unwrap();
current_screen.on_resume(navigator.clone(), &mut self.state).await;
draw = true;
}
}
Action::Clear => {
let current_screen = screens.pop_back().unwrap();
while let Some(mut old_screen) = screens.pop_back() {
old_screen.on_exit(navigator.clone(), &mut self.state).await;
}
screens.push_back(current_screen);
}
Action::Restart => {
while let Some(mut old_screen) = screens.pop_back() {
old_screen.on_exit(navigator.clone(), &mut self.state).await;
}
let mut new_screen = S::default();
new_screen.on_enter(navigator.clone(), &mut self.state).await;
screens.push_back(new_screen);
draw = true;
}
Action::Exit => {
while let Some(mut old_screen) = screens.pop_back() {
old_screen.on_exit(navigator.clone(), &mut self.state).await;
}
break;
}
Action::Rerender => {
draw = true;
}
}
}
}
}
ratatui::restore();
Ok(())
}
}
impl<T> Default for App<T>
where
T: Default,
{
fn default() -> Self {
Self::with_state(T::default())
}
}