jk_tui_base/
lib.rs

1#![feature(trait_alias)]
2use core::fmt::Debug;
3use crossterm::event::{self, EventStream};
4use futures::{future::join_all, StreamExt};
5use std::io;
6use std::marker::Send;
7use std::ops::*;
8use std::time::Duration;
9use tokio::{
10    select,
11    sync::broadcast::{self, error::*},
12};
13
14pub trait EventKind = Debug + Clone + Send;
15pub trait ConvertFn<Event: EventKind> = Fn(event::Event) -> Option<Event> + Send + Sync + 'static;
16pub trait StateFn<Event: EventKind> =
17    FnMut(Event) -> Result<bool, io::Error> + Sync + Send + 'static;
18pub trait RenderFn = Fn() -> Result<(), io::Error> + Sync + Send + 'static;
19
20#[derive(Debug)]
21enum Error<Event: EventKind> {
22    RecvError(broadcast::error::RecvError),
23    SendError(broadcast::error::SendError<Event>),
24    IoError(io::Error),
25}
26
27impl<T: EventKind> From<broadcast::error::RecvError> for Error<T> {
28    fn from(e: RecvError) -> Error<T> {
29        Error::RecvError(e)
30    }
31}
32
33impl<T: EventKind> From<broadcast::error::SendError<T>> for Error<T> {
34    fn from(e: SendError<T>) -> Error<T> {
35        Error::SendError(e)
36    }
37}
38
39impl<T: EventKind> From<io::Error> for Error<T> {
40    fn from(e: io::Error) -> Error<T> {
41        Error::IoError(e)
42    }
43}
44
45pub async fn jk_tui_main<Event: EventKind + 'static>(
46    convert: impl ConvertFn<Event>,
47    mut statefn: impl StateFn<Event>,
48) {
49    let (shutdown_tx, mut shutdown_rx) = broadcast::channel(1);
50    let (tx, mut rx) = broadcast::channel(16);
51    let renderfn: Option<Box<dyn RenderFn>> = None;
52
53    let threads = vec![
54        tokio::spawn(async move {
55            let mut es = EventStream::new().map(|x| convert(x.ok()?));
56            loop {
57                let shutdown_init = shutdown_rx.recv();
58                let event_stream = es.next();
59                select! {
60                    _ = shutdown_init => { break; },
61                    e = event_stream => {
62                        match e {
63                            Some(event) => {
64                                   tx.send(event).expect("failure during send!");
65                            },
66                            _ => {}
67                        }
68                    },
69                }
70            }
71            Ok::<(), Error<Event>>(())
72        }),
73        tokio::spawn(async move {
74            loop {
75                if let Some(cte) = rx.recv().await? {
76                    println!("received: {:?}", cte);
77                    if statefn(cte)? {
78                        shutdown_tx
79                            .send(true)
80                            .expect("failure during shutdown initiation!");
81                        break;
82                    }
83                }
84            }
85            Ok::<(), Error<Event>>(())
86        }),
87        tokio::spawn(async move {
88            if let Some(render) = renderfn {
89                let delay = Duration::from_millis(100);
90                loop {
91                    render()?;
92                    tokio::time::sleep(delay).await;
93                }
94            }
95            Ok::<(), Error<Event>>(())
96        }),
97    ];
98    join_all(threads).await;
99}