use std::{
fmt::Display,
sync::{Arc, RwLock, RwLockWriteGuard},
time::Duration,
};
use indicatif::ProgressStyle;
use crate::{theme::THEME, ThemeState};
#[derive(Default)]
pub(crate) struct ProgressBarState {
pub template: String,
pub grouped: bool,
pub last: bool,
pub stopped: bool,
}
#[derive(Clone)]
pub struct ProgressBar {
pub(crate) bar: indicatif::ProgressBar,
pub(crate) options: Arc<RwLock<ProgressBarState>>,
}
impl ProgressBar {
pub fn new(len: u64) -> Self {
let this = Self {
bar: indicatif::ProgressBar::new(len),
options: Default::default(),
};
this.options_write().template = THEME.lock().unwrap().default_progress_template();
this
}
pub fn with_spinner_template(self) -> Self {
self.options_write().template = THEME.lock().unwrap().default_spinner_template();
self
}
pub fn with_download_template(self) -> Self {
self.options_write().template = THEME.lock().unwrap().default_download_template();
self
}
pub fn with_template(self, template: &str) -> Self {
self.options_write().template = template.to_string();
self
}
pub fn position(&self) -> u64 {
self.bar.position()
}
pub fn length(&self) -> Option<u64> {
self.bar.length()
}
pub fn inc(&self, delta: u64) {
self.bar.inc(delta)
}
pub fn is_finished(&self) -> bool {
self.options().stopped
}
pub fn set_message(&self, message: impl Display) {
self.bar.set_message(message.to_string());
}
pub fn set_length(&self, len: u64) {
self.bar.set_length(len);
}
pub fn start(&self, message: impl Display) {
let theme = THEME.lock().unwrap();
let options = self.options();
self.bar.set_style(
ProgressStyle::with_template(&theme.format_progress_start(
&options.template,
options.grouped,
options.last,
))
.unwrap()
.tick_chars(&theme.spinner_chars())
.progress_chars(&theme.progress_chars()),
);
self.bar
.set_message(theme.format_progress_message(&message.to_string()));
self.bar.enable_steady_tick(Duration::from_millis(100));
}
pub fn stop(&self, message: impl Display) {
self.finish_with_state(message, &ThemeState::Submit);
}
pub fn cancel(&self, message: impl Display) {
self.finish_with_state(message, &ThemeState::Cancel);
}
pub fn error(&self, message: impl Display) {
self.finish_with_state(message, &ThemeState::Error("".into()));
}
pub fn clear(&self) {
self.finish_with_state("", &ThemeState::Submit);
}
#[inline]
pub(crate) fn options_write(&self) -> RwLockWriteGuard<'_, ProgressBarState> {
self.options.write().unwrap()
}
#[inline]
pub(crate) fn options(&self) -> RwLockWriteGuard<'_, ProgressBarState> {
self.options.write().unwrap()
}
pub(crate) fn redraw_finished(&self, message: impl Display, state: &ThemeState) -> usize {
let theme = THEME.lock().unwrap();
let options = self.options.read().unwrap();
let msg = theme.format_progress_with_state(
&message.to_string(),
options.grouped,
options.last,
state,
);
self.bar.println(msg.clone());
msg.lines().count()
}
pub(crate) fn redraw_active(&self) {
if !self.options().stopped {
self.redraw_active_as_started();
} else {
self.redraw_active_as_stopped();
}
}
fn redraw_active_as_started(&self) {
let theme = THEME.lock().unwrap();
let options = self.options();
self.bar.set_style(
ProgressStyle::with_template(&theme.format_progress_start(
&options.template,
options.grouped,
options.last,
))
.unwrap()
.tick_chars(&theme.spinner_chars())
.progress_chars(&theme.progress_chars()),
);
}
fn redraw_active_as_stopped(&self) {
let theme = THEME.lock().unwrap();
let options = self.options();
self.bar.set_style(
ProgressStyle::with_template(&theme.format_progress_with_state(
&self.bar.message(),
options.grouped,
options.last,
&ThemeState::Active,
))
.unwrap(),
);
}
fn finish_with_state(&self, message: impl Display, state: &ThemeState) {
if self.options().stopped {
return;
}
self.options_write().stopped = true;
if !self.options().grouped {
self.redraw_finished(message, state);
self.bar.finish_and_clear();
} else {
self.bar.set_message(message.to_string());
self.redraw_active_as_stopped();
}
}
}