use crate::element::Element;
use crate::input::Key;
use crate::renderer::Blaeck;
use crossterm::event::{Event, EventStream};
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
use futures::StreamExt;
use std::io::{self, Write};
use std::time::Duration;
use tokio::sync::mpsc;
use tokio::time::{interval, Interval};
pub type Result<T> = std::io::Result<T>;
#[derive(Debug)]
pub enum AppEvent<M> {
Key(Key),
Message(M),
Tick,
Exit,
}
pub type Sender<M> = mpsc::Sender<M>;
pub type Receiver<M> = mpsc::Receiver<M>;
pub fn channel<M>(buffer: usize) -> (Sender<M>, Receiver<M>) {
mpsc::channel(buffer)
}
#[derive(Clone)]
pub struct AsyncAppConfig {
pub tick_interval: Option<Duration>,
pub exit_on_ctrl_c: bool,
pub message_buffer: usize,
}
impl Default for AsyncAppConfig {
fn default() -> Self {
Self {
tick_interval: Some(Duration::from_millis(100)),
exit_on_ctrl_c: true,
message_buffer: 32,
}
}
}
pub struct AsyncApp<W: Write, M: Send + 'static = ()> {
blaeck: Blaeck<W>,
config: AsyncAppConfig,
tx: Sender<M>,
rx: Receiver<M>,
should_exit: bool,
}
impl<M: Send + 'static> AsyncApp<io::Stdout, M> {
pub fn new() -> Result<Self> {
Self::with_config(AsyncAppConfig::default())
}
pub fn with_config(config: AsyncAppConfig) -> Result<Self> {
let stdout = io::stdout();
let blaeck = Blaeck::new(stdout)?;
let (tx, rx) = mpsc::channel(config.message_buffer);
Ok(Self {
blaeck,
config,
tx,
rx,
should_exit: false,
})
}
}
impl<W: Write, M: Send + 'static> AsyncApp<W, M> {
pub fn with_writer(writer: W, config: AsyncAppConfig) -> Result<Self> {
let blaeck = Blaeck::new(writer)?;
let (tx, rx) = mpsc::channel(config.message_buffer);
Ok(Self {
blaeck,
config,
tx,
rx,
should_exit: false,
})
}
pub fn sender(&self) -> Sender<M> {
self.tx.clone()
}
pub fn exit(&mut self) {
self.should_exit = true;
}
pub fn should_exit(&self) -> bool {
self.should_exit
}
pub fn blaeck(&self) -> &Blaeck<W> {
&self.blaeck
}
pub fn blaeck_mut(&mut self) -> &mut Blaeck<W> {
&mut self.blaeck
}
pub async fn run<R, H>(mut self, mut render: R, mut handle: H) -> Result<()>
where
R: FnMut(&mut Self) -> Element,
H: FnMut(&mut Self, AppEvent<M>),
{
enable_raw_mode()?;
let ui = render(&mut self);
self.blaeck.render(ui)?;
let mut event_stream = EventStream::new();
let mut tick_interval: Option<Interval> = self.config.tick_interval.map(|d| interval(d));
loop {
if self.should_exit {
break;
}
let event = tokio::select! {
maybe_event = event_stream.next() => {
match maybe_event {
Some(Ok(Event::Key(key_event))) => {
let key = Key::from(key_event);
if self.config.exit_on_ctrl_c && key.is_ctrl_c() {
self.should_exit = true;
break;
}
Some(AppEvent::Key(key))
}
Some(Ok(_)) => None, Some(Err(_)) => None,
None => {
self.should_exit = true;
break;
}
}
}
maybe_msg = self.rx.recv() => {
maybe_msg.map(AppEvent::Message)
}
_ = async {
if let Some(ref mut interval) = tick_interval {
interval.tick().await
} else {
std::future::pending::<tokio::time::Instant>().await
}
} => {
Some(AppEvent::Tick)
}
};
if let Some(evt) = event {
handle(&mut self, evt);
let ui = render(&mut self);
self.blaeck.render(ui)?;
}
}
disable_raw_mode()?;
self.blaeck.unmount()?;
Ok(())
}
pub async fn run_simple<R, H>(mut self, mut render: R, mut handle: H) -> Result<()>
where
R: FnMut(&mut Self) -> Element,
H: FnMut(&mut Self, Key),
{
enable_raw_mode()?;
let ui = render(&mut self);
self.blaeck.render(ui)?;
let mut event_stream = EventStream::new();
loop {
if self.should_exit {
break;
}
match event_stream.next().await {
Some(Ok(Event::Key(key_event))) => {
let key = Key::from(key_event);
if self.config.exit_on_ctrl_c && key.is_ctrl_c() {
self.should_exit = true;
break;
}
handle(&mut self, key);
let ui = render(&mut self);
self.blaeck.render(ui)?;
}
Some(Ok(_)) => {} Some(Err(_)) => {}
None => break,
}
}
disable_raw_mode()?;
self.blaeck.unmount()?;
Ok(())
}
}
pub async fn poll_key_async(timeout: Duration) -> Result<Option<Key>> {
let mut event_stream = EventStream::new();
tokio::select! {
maybe_event = event_stream.next() => {
match maybe_event {
Some(Ok(Event::Key(key_event))) => Ok(Some(Key::from(key_event))),
Some(Ok(_)) => Ok(None),
Some(Err(e)) => Err(e),
None => Ok(None),
}
}
_ = tokio::time::sleep(timeout) => {
Ok(None)
}
}
}
pub async fn read_key_async() -> Result<Key> {
let mut event_stream = EventStream::new();
loop {
match event_stream.next().await {
Some(Ok(Event::Key(key_event))) => return Ok(Key::from(key_event)),
Some(Ok(_)) => continue,
Some(Err(e)) => return Err(e),
None => {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"Event stream ended",
))
}
}
}
}
pub async fn run_with_updates<S, R, U>(
mut blaeck: Blaeck<impl Write>,
mut state: S,
mut render: R,
mut update: U,
) -> Result<()>
where
R: FnMut(&S) -> Element,
U: FnMut(&mut S) -> std::future::Ready<bool>,
{
enable_raw_mode()?;
let mut event_stream = EventStream::new();
let mut tick = interval(Duration::from_millis(50));
loop {
let ui = render(&state);
blaeck.render(ui)?;
tokio::select! {
maybe_event = event_stream.next() => {
if let Some(Ok(Event::Key(key_event))) = maybe_event {
let key = Key::from(key_event);
if key.is_ctrl_c() {
break;
}
}
}
_ = tick.tick() => {
if !update(&mut state).await {
break;
}
}
}
}
disable_raw_mode()?;
blaeck.unmount()?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_async_app_config_default() {
let config = AsyncAppConfig::default();
assert!(config.tick_interval.is_some());
assert!(config.exit_on_ctrl_c);
assert_eq!(config.message_buffer, 32);
}
#[test]
fn test_channel_creation() {
let (tx, mut rx) = channel::<i32>(10);
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
tx.send(42).await.unwrap();
let msg = rx.recv().await.unwrap();
assert_eq!(msg, 42);
});
}
#[test]
fn test_app_event_debug() {
let event: AppEvent<String> = AppEvent::Tick;
let debug_str = format!("{:?}", event);
assert!(debug_str.contains("Tick"));
}
}