use crate::{line::draw, tree};
use std::{
io,
ops::RangeInclusive,
sync::atomic::{AtomicBool, Ordering},
time::Duration,
};
#[derive(Clone)]
pub struct Options {
pub output_is_terminal: bool,
pub colored: bool,
pub timestamp: bool,
pub terminal_dimensions: (u16, u16),
pub hide_cursor: bool,
pub level_filter: Option<RangeInclusive<tree::Level>>,
pub initial_delay: Option<Duration>,
pub frames_per_second: f32,
pub keep_running_if_progress_is_empty: bool,
}
impl Default for Options {
fn default() -> Self {
Options {
output_is_terminal: true,
colored: true,
timestamp: false,
terminal_dimensions: (80, 20),
hide_cursor: false,
level_filter: None,
initial_delay: None,
frames_per_second: 6.0,
keep_running_if_progress_is_empty: true,
}
}
}
pub struct JoinHandle {
inner: Option<std::thread::JoinHandle<io::Result<()>>>,
connection: std::sync::mpsc::SyncSender<Event>,
disconnected: bool,
}
impl JoinHandle {
pub fn detach(mut self) {
self.disconnect();
self.forget();
}
pub fn disconnect(&mut self) {
self.disconnected = true;
}
pub fn forget(&mut self) {
self.inner.take();
}
pub fn wait(mut self) {
self.inner.take().and_then(|h| h.join().ok());
}
pub fn shutdown(&mut self) {
if !self.disconnected {
self.connection.send(Event::Tick).ok();
self.connection.send(Event::Quit).ok();
}
}
pub fn shutdown_and_wait(mut self) {
self.shutdown();
self.wait();
}
}
impl Drop for JoinHandle {
fn drop(&mut self) {
self.shutdown();
self.inner.take().and_then(|h| h.join().ok());
}
}
#[derive(Debug)]
enum Event {
Tick,
Quit,
}
pub fn render(mut out: impl io::Write + Send + 'static, progress: tree::Root, config: Options) -> JoinHandle {
let Options {
output_is_terminal,
colored,
timestamp,
level_filter,
terminal_dimensions,
initial_delay,
frames_per_second,
keep_running_if_progress_is_empty,
hide_cursor,
} = config;
let config = draw::Options {
output_is_terminal,
terminal_dimensions,
colored,
timestamp,
keep_running_if_progress_is_empty,
level_filter,
hide_cursor,
};
let (event_send, event_recv) = std::sync::mpsc::sync_channel::<Event>(1);
let show_cursor = possibly_hide_cursor(&mut out, event_send.clone(), hide_cursor);
static SHOW_PROGRESS: AtomicBool = AtomicBool::new(false);
let handle = std::thread::spawn({
let tick_send = event_send.clone();
move || {
{
let initial_delay = initial_delay.unwrap_or_else(Duration::default);
SHOW_PROGRESS.store(initial_delay == Duration::default(), Ordering::Relaxed);
if !SHOW_PROGRESS.load(Ordering::Relaxed) {
std::thread::spawn(move || {
std::thread::sleep(initial_delay);
SHOW_PROGRESS.store(true, Ordering::Relaxed);
});
}
}
let mut state = draw::State::default();
let secs = 1.0 / frames_per_second;
let _ticker = std::thread::spawn(move || loop {
if tick_send.send(Event::Tick).is_err() {
break;
}
std::thread::sleep(Duration::from_secs_f32(secs));
});
for event in event_recv {
match event {
Event::Tick => {
draw::all(
&mut out,
&progress,
SHOW_PROGRESS.load(Ordering::Relaxed),
&mut state,
&config,
)?;
}
Event::Quit => break,
}
}
if show_cursor {
crosstermion::execute!(out, crosstermion::cursor::Show).ok();
}
Ok(())
}
});
JoinHandle {
inner: Some(handle),
connection: event_send,
disconnected: false,
}
}
#[allow(unused_mut)]
fn possibly_hide_cursor(
out: &mut impl io::Write,
quit_send: std::sync::mpsc::SyncSender<Event>,
mut hide_cursor: bool,
) -> bool {
#[cfg(not(feature = "ctrlc"))]
drop(quit_send);
#[cfg(feature = "ctrlc")]
if hide_cursor {
hide_cursor = ctrlc::set_handler(move || drop(quit_send.send(Event::Quit).ok())).is_ok();
}
if hide_cursor {
crosstermion::execute!(out, crosstermion::cursor::Hide).is_ok()
} else {
false
}
}