use ratatui::crossterm::event::{self, Event, poll};
use ratatui::prelude::*;
use std::any::Any;
use std::io;
use std::sync::{
Arc,
atomic::{AtomicBool, Ordering},
mpsc,
};
use std::thread;
impl<T> PartialEq for dyn Subscription<T> {
fn eq(&self, other: &Self) -> bool {
self.equals_a(other)
}
}
pub struct QuitFlag {
quit: Arc<AtomicBool>,
}
impl QuitFlag {
fn new() -> Self {
Self {
quit: Arc::new(AtomicBool::new(false)),
}
}
pub fn raise(&self) {
self.quit.store(true, Ordering::Relaxed);
}
pub fn raised(&self) -> bool {
self.quit.load(Ordering::Relaxed)
}
fn clone(&self) -> QuitFlag {
QuitFlag {
quit: self.quit.clone(),
}
}
}
pub struct Sender<T: 'static> {
sender: mpsc::Sender<Message<T>>,
}
impl<T> Sender<T> {
fn new(sender: mpsc::Sender<Message<T>>) -> Self {
Self { sender }
}
pub fn send(&self, msg: T) {
let msg = Message::UserEvent(msg);
let _ = self.sender.send(msg);
}
fn dup(&self) -> Self {
Sender {
sender: self.sender.clone(),
}
}
}
pub enum Message<T: 'static> {
UserEvent(T),
CrosstermEvent(Event),
}
pub type Subscriptions<M> = Vec<Box<dyn Subscription<M>>>;
pub trait Subscription<T>: DynEq + Send + Sync {
fn run(&self, sender: Sender<T>, alive: QuitFlag);
}
pub trait Command<T>: Send + Sync {
fn run(&self, sender: Sender<T>);
}
pub trait RusteyApp<T, M> {
fn init(&self) -> (T, Cmd<M>);
fn map_event(&self, model: &T, event: Event) -> Option<M>;
fn update(&self, model: &mut T, msg: M, quit_program: &QuitFlag) -> Cmd<M>;
fn subscriptions(&self, model: &T) -> Vec<Box<dyn Subscription<M>>>;
fn view(&self, frame: &mut Frame, model: &mut T);
}
pub type Cmd<T> = Option<Box<dyn Command<T>>>;
pub trait DynEq {
fn as_any(&self) -> &dyn Any;
fn equals_a(&self, _: &dyn DynEq) -> bool;
}
impl<S: 'static + PartialEq> DynEq for S {
fn as_any(&self) -> &dyn Any {
self
}
fn equals_a(&self, other: &dyn DynEq) -> bool {
other.as_any().downcast_ref::<S>() == Some(self)
}
}
pub struct SubRec<T> {
sub: Arc<Box<dyn Subscription<T>>>,
halt_flag: QuitFlag,
thread: Option<thread::JoinHandle<()>>,
}
impl<T: 'static> PartialEq for SubRec<T> {
fn eq(&self, other: &Self) -> bool {
self.sub.equals_a(&other.sub)
}
}
impl<T: Send + 'static> SubRec<T> {
pub fn new(sub: Box<dyn Subscription<T>>) -> SubRec<T> {
Self {
sub: Arc::new(sub),
halt_flag: QuitFlag::new(),
thread: None,
}
}
pub fn run(&mut self, sender: Sender<T>) {
let sub = self.sub.clone();
let halt_flag = self.halt_flag.clone();
self.thread = Some(thread::spawn(move || sub.run(sender, halt_flag)));
}
pub fn stop(&mut self) {
self.halt_flag.raise();
}
}
fn handle<M>(cmd: Cmd<M>, sender: Sender<M>)
where
M: 'static + Send + Sync,
{
if let Some(c) = cmd {
thread::spawn(move || {
c.run(sender);
});
}
}
fn start_event_thread<M: Send>(
sender: std::sync::mpsc::Sender<Message<M>>,
) -> thread::JoinHandle<()> {
thread::spawn(move || {
loop {
let poll = poll(std::time::Duration::from_millis(100));
if let Ok(true) = poll {
let event = event::read().unwrap();
let _ = sender.send(Message::CrosstermEvent(event));
}
}
})
}
pub fn run<T, M>(app: &dyn RusteyApp<T, M>) -> io::Result<()>
where
M: 'static + Send + Sync,
{
let mut terminal = ratatui::init();
let (mut model, mut cmd) = app.init();
let quit_program = QuitFlag::new();
let (sender, receiver) = std::sync::mpsc::channel::<Message<M>>();
let user_sender = Sender::new(sender.clone());
let initial_subscriptions = app.subscriptions(&model);
let _event_reader = start_event_thread(sender);
let subs: Vec<SubRec<M>> = initial_subscriptions.into_iter().map(SubRec::new).collect();
let mut subs: Vec<SubRec<M>> = subs
.into_iter()
.map(|mut sub| {
sub.run(user_sender.dup());
sub
})
.collect();
loop {
handle(cmd, user_sender.dup());
terminal.draw(|f| app.view(f, &mut model))?;
let msg = receiver.recv().unwrap();
let msg = match msg {
Message::CrosstermEvent(event) => app.map_event(&model, event),
Message::UserEvent(msg) => Some(msg),
};
cmd = if let Some(msg) = msg {
app.update(&mut model, msg, &quit_program)
} else {
None
};
let new_subscriptions = app.subscriptions(&model);
let mut new_subs: Vec<SubRec<M>> = new_subscriptions.into_iter().map(SubRec::new).collect();
subs.retain_mut(|sub| {
let pos = new_subs.iter().position(|new_sub| sub == new_sub);
if let Some(pos1) = pos {
new_subs.swap_remove(pos1);
true
} else {
sub.stop();
false
}
});
new_subs.iter_mut().for_each(|s| {
s.run(user_sender.dup());
});
subs.append(&mut new_subs);
if quit_program.raised() {
break;
}
}
ratatui::restore();
Ok(())
}