use crossterm::event::{Event, EventStream};
use futures_util::stream::StreamExt;
use ratatui::Terminal;
use ratatui::prelude::Backend;
use reactive_graph::effect::{Effect, RenderEffect};
use reactive_graph::owner::{Owner, provide_context};
use reactive_graph::signal::{ReadSignal, Trigger, signal};
use reactive_graph::traits::{Get, Notify, Set, Track};
use std::io;
use std::io::Error;
use tokio::sync::watch;
use crate::{Component, Render, on_resize};
#[derive(Debug, Clone, Copy)]
pub(crate) struct Events {
event: ReadSignal<Event>,
}
impl Events {
#[inline]
pub fn last(&self) -> Event {
self.event.get()
}
}
#[derive(Debug, Clone, Copy)]
pub struct Runtime {
quit: Trigger,
redraw: Trigger,
}
impl Runtime {
#[inline]
pub fn quit(&self) {
self.quit.notify();
}
#[inline]
pub fn force_redraw(&self) {
self.redraw.notify();
}
}
pub(super) async fn render_loop<
B: Backend<Error = Error> + 'static,
F: Render + 'static,
C: Component<F>,
>(
app: C,
mut terminal: Terminal<B>,
) -> Result<(), io::Error> {
let owner = Owner::new();
let quit = Trigger::new();
let (event, set_event) = signal(Event::FocusGained);
let redraw = Trigger::new();
let (tx, mut quit_rx) = watch::channel(());
let app = owner.with(move || {
provide_context(Runtime { quit, redraw });
provide_context(Events { event });
on_resize(move |_, _| redraw.notify());
Effect::watch(
move || quit.track(),
move |_, _, _| {
tx.send(()).unwrap();
},
false,
);
app.create()
});
let render_effect = RenderEffect::new_with_value(
move |_| {
redraw.track();
let res = terminal.draw(|frame| {
app.render(frame);
});
if let Err(err) = res {
quit.notify();
Err(err)
} else {
Ok(())
}
},
Some(Ok(())),
);
let mut events = EventStream::new();
loop {
tokio::select! {
Some(event) = events.next() => {
set_event.set(event?);
},
_ = quit_rx.changed() => {
render_effect.take_value().unwrap()?;
break;
}
}
}
Ok(())
}