use crate::channel::broadcast;
use crate::channel::oneshot;
use crate::{Component, Events, Render};
use futures_lite::Stream;
use futures_lite::StreamExt;
use futures_lite::future::or;
use futures_lite::future::yield_now;
use ratatui::Terminal;
use ratatui::prelude::Backend;
use std::cell::Ref;
use std::cell::RefCell;
use std::cell::RefMut;
use std::fmt::Debug;
use std::rc::Rc;
use sycamore_reactive::{
RootHandle, Signal, batch, create_effect, create_root, create_signal, provide_context,
};
#[derive(Debug, Clone, Copy)]
pub struct Runtime {
quit: Signal<bool>,
redraw: Signal<()>,
}
impl Runtime {
#[inline]
pub fn quit(&self) {
self.quit.set(true);
}
#[inline]
pub fn force_redraw(&self) {
self.redraw.set(());
}
}
pub struct ReactiveApp<B: Backend, E> {
root: RootHandle,
event_tx: broadcast::Sender<E>,
quit_rx: oneshot::Receiver<Result<(), B::Error>>,
terminal: Rc<RefCell<Terminal<B>>>,
}
impl<B: Backend, E> ReactiveApp<B, E> {
pub fn new<F: Render + 'static, C: Component<F>>(
app: C,
terminal: Terminal<B>,
) -> ReactiveApp<B, E>
where
B: 'static,
E: 'static,
{
let (quit_tx, quit_rx) = oneshot::channel();
let (event_tx, event_rx) = broadcast::channel();
let terminal = Rc::new(RefCell::new(terminal));
let root = create_root(|| {
let quit = create_signal(false);
let redraw = create_signal(());
provide_context(Runtime { quit, redraw });
provide_context(Events(event_rx));
{
let quit_tx = quit_tx.clone();
create_effect(move || {
if quit.get() {
quit_tx.send(Ok(()));
}
});
}
let app = batch(move || app.create());
let terminal = terminal.clone();
create_effect(move || {
redraw.track();
let mut terminal = terminal.borrow_mut();
let res = terminal.draw(|frame| {
app.render(frame);
});
if let Err(err) = res {
quit_tx.send(Err(err));
}
});
});
ReactiveApp {
root,
event_tx,
quit_rx,
terminal,
}
}
#[inline]
pub fn terminal(&self) -> Ref<'_, Terminal<B>> {
self.terminal.borrow()
}
#[inline]
pub fn terminal_mut(&mut self) -> RefMut<'_, Terminal<B>> {
self.terminal.borrow_mut()
}
#[inline]
pub async fn handle_event(&self, event: E) {
self.event_tx.send(event);
yield_now().await;
}
#[inline]
pub async fn wait(&mut self) -> Result<(), B::Error> {
(&mut self.quit_rx).await
}
#[inline]
pub async fn run<S: Stream<Item = Result<E, B::Error>> + Unpin>(
mut self,
mut event_stream: S,
) -> Result<(), B::Error> {
or(&mut self.quit_rx, async {
while let Some(event) = event_stream.next().await {
self.event_tx.send(event?);
}
Ok(())
})
.await
}
}
impl<B: Backend, E> Drop for ReactiveApp<B, E> {
#[inline]
fn drop(&mut self) {
self.root.dispose();
}
}