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}