ratflow 0.1.0

A minimalistic framework for building TUI applications using a reactive architecture.
Documentation
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(())
}