use crate::SpannedStrExt;
use std::{
sync::{Arc, RwLock},
thread::{self, JoinHandle}
};
use crossbeam_channel::{bounded, Receiver, TryRecvError};
use cursive_core::{
Printer, Vec2,
view::View,
theme::{ColorStyle, PaletteColor},
utils::markup::StyledString
};
type WorkerThread<T> = Arc<RwLock<Option<JoinHandle<T>>>>;
#[derive(Clone)]
pub struct LoadingAnimation<T: Send + Sync + 'static> {
worker: WorkerThread<T>,
recv: Receiver<()>,
message: StyledString,
width: usize,
anim_x: usize,
reversed: bool
}
impl<T: Send + Sync> LoadingAnimation<T> {
pub fn new<U, M: Into<StyledString>>(message: M, task: U) -> LoadingAnimation<T>
where U: FnOnce() -> T + Send + Sync + 'static
{
let (sender, recv) = bounded(0);
let worker = Arc::new(RwLock::new(
Some(
thread::spawn(move || {
let out = task();
sender.send(()).unwrap();
out
})
)
));
let message = message.into();
LoadingAnimation {
worker,
recv,
width: message.char_len(),
message,
anim_x: 0,
reversed: false
}
}
pub fn is_done(&self) -> bool {
match self.recv.try_recv() {
Ok(()) => true,
Err(e) => e == TryRecvError::Disconnected
}
}
#[track_caller]
pub fn finish(&mut self) -> Option<T> {
let worker = self.worker.write().unwrap().take()?;
Some(worker.join().unwrap())
}
}
impl<T: Send + Sync> View for LoadingAnimation<T> {
fn draw(&self, printer: &Printer) {
let style = ColorStyle::new(
PaletteColor::Highlight,
PaletteColor::Background,
);
printer.with_color(style, |printer| {
printer.print((self.anim_x, 0), "███");
});
printer.print_styled((0, 1), self.message.as_spanned_str());
}
fn layout(&mut self, _: Vec2) {
if self.width > 0 {
if self.reversed {
if self.anim_x == 0 {
self.reversed = false;
}
else {
self.anim_x -= 1;
}
}
else if self.anim_x < self.width - 3 {
self.anim_x += 1;
}
else {
self.reversed = true;
}
}
}
fn required_size(&mut self, _: Vec2) -> Vec2 { Vec2::new(self.message.char_len(), 2) }
}