use crate::on_drop::OnDrop;
use crate::{Component, Events, Render, on_resize};
use crossterm::event::{Event, EventStream};
use futures_util::stream::StreamExt;
use ratatui::Terminal;
use ratatui::prelude::Backend;
use std::cell::RefCell;
use std::io::Error;
use std::mem;
use std::rc::Rc;
use sycamore_reactive::{
RootHandle, Signal, batch, create_effect, create_root, create_signal, provide_context,
use_context,
};
use tokio::sync::broadcast;
use tokio::sync::oneshot;
type Quit = Rc<RefCell<Option<Result<(), std::io::Error>>>>;
#[derive(Debug, Clone, Copy)]
pub struct Runtime {
quit: Signal<Quit>,
redraw: Signal<()>,
}
impl Runtime {
#[inline]
pub fn quit(&self) {
self.quit
.update(|result| *(result.borrow_mut()) = Some(Ok(())));
}
#[inline]
pub fn force_redraw(&self) {
self.redraw.set(());
}
}
pub fn init_app<B: Backend<Error = Error> + 'static, F: Render + 'static, C: Component<F>>(
app: C,
terminal: &'static mut Terminal<B>,
) -> (
RootHandle,
broadcast::Sender<Event>,
oneshot::Receiver<Result<(), Error>>,
) {
let (tx, quit) = oneshot::channel();
let mut quit_tx = Some(tx);
let (events, rx) = broadcast::channel(10);
let root = create_root(move || {
let quit = create_signal(Rc::new(RefCell::new(None)));
let redraw = create_signal(());
provide_context(Runtime { quit, redraw });
provide_context(Events(Rc::new(rx)));
create_effect(move || {
let runtime = use_context::<Runtime>();
if let Some(result) = runtime.quit.get_clone().borrow_mut().take()
&& let Some(quit) = quit_tx.take()
{
quit.send(result).unwrap()
}
});
on_resize(move |_, _| redraw.set(()));
let app = batch(move || app.create());
create_effect(move || {
redraw.track();
let res = terminal.draw(|frame| {
app.render(frame);
});
if let Err(err) = res {
quit.set(Rc::new(RefCell::new(Some(Err(err)))));
}
});
});
(root, events, quit)
}
pub async fn render_loop<
B: Backend<Error = Error> + 'static,
F: Render + 'static,
C: Component<F>,
>(
app: C,
terminal: &mut Terminal<B>,
) -> Result<(), std::io::Error> {
let terminal: &'static mut Terminal<B> = unsafe { std::mem::transmute(terminal) };
let (root, events_tx, mut quit) = init_app(app, terminal);
let cleanup = OnDrop(|| root.dispose());
let mut events = EventStream::new();
loop {
tokio::select! {
Some(terminal_event) = events.next() => {
events_tx.send(terminal_event?).unwrap();
},
result = &mut quit => {
mem::drop(cleanup);
return result.unwrap();
}
}
}
}