use crate::{term::Colorizer, utils::divmod};
use std::num::NonZeroU16;
#[cfg(feature = "gradient")]
use crate::utils;
#[cfg(feature = "gradient")]
use colorgrad::{Gradient, GradientBuilder, LinearGradient};
#[cfg(feature = "gradient")]
use std::sync::Arc;
#[cfg(feature = "unicode")]
use unicode_segmentation::UnicodeSegmentation;
const BAR_FILLUP: [&str; 8] = [
"\u{2581}", "\u{2582}", "\u{2583}", "\u{2584}", "\u{2585}", "\u{2586}", "\u{2587}", "\u{2588}",
];
const BAR_TQDM: [&str; 8] = [
"\u{258F}", "\u{258E}", "\u{258D}", "\u{258C}", "\u{258B}", "\u{258A}", "\u{2589}", "\u{2588}",
];
const BAR_TQDM_ASCII: [&str; 10] = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "#"];
#[derive(Clone, Debug)]
pub enum Animation {
Arrow,
Classic,
Custom(Vec<String>, Option<String>),
FillUp,
FiraCode,
Tqdm,
TqdmAscii,
}
impl Animation {
pub fn custom(charset: &[&str], fill: Option<&str>) -> Self {
Self::Custom(
charset.iter().map(|x| String::from(*x)).collect(),
fill.map(|x| x.to_owned()),
)
}
pub fn render(&self, ncols: NonZeroU16, progress: f32) -> String {
assert!((0.0..=1.0).contains(&progress));
let ncols = ncols.get();
match self {
Self::Arrow | Self::Classic => {
let block = (ncols as f32 * progress) as u16;
let (bar_completed, bar_head, bar_uncompleted) = match self {
Self::Arrow => ("=", ">", " "),
Self::Classic => ("#", "#", "."),
_ => unreachable!(),
};
bar_completed.repeat(block as usize)
+ &if progress >= 1.0 {
String::new()
} else {
bar_head.to_owned() + &bar_uncompleted.repeat((ncols - block - 1) as usize)
}
}
Self::FiraCode => {
let block = (ncols as f32 * progress) as u16;
"\u{EE03}".to_owned()
+ &"\u{EE04}".repeat(block as usize)
+ &"\u{EE01}".repeat((ncols - block) as usize)
+ if progress >= 1.0 {
"\u{EE05}"
} else {
"\u{EE02}"
}
}
_ => {
let mut bar_uncompleted = None;
let charset = match self {
Self::Custom(custom_charset, fill) => {
bar_uncompleted = fill.as_ref().map(|x| x.as_str());
custom_charset.iter().map(|x| x.as_str()).collect()
}
Self::FillUp => Vec::from(BAR_FILLUP),
Self::TqdmAscii => Vec::from(BAR_TQDM_ASCII),
_ => Vec::from(BAR_TQDM),
};
let nsyms = charset.len() - 1;
let (bar_length, frac_bar_length) =
divmod((progress * ncols as f32 * nsyms as f32) as usize, nsyms);
let mut bar_animation = charset.last().unwrap().repeat(bar_length);
if ncols > bar_length as u16 {
bar_animation += charset[frac_bar_length + 1];
let bar_uncompleted = bar_uncompleted.unwrap_or(" ");
bar_animation +=
&bar_uncompleted.repeat((ncols - bar_length as u16 - 1) as usize);
}
bar_animation
}
}
}
pub fn fmt_render(&self, ncols: NonZeroU16, progress: f32, colour: &Option<Colour>) -> String {
let (bar_open, bar_close) = match self {
Self::Arrow | Self::Classic => ("[", "]"),
Self::FiraCode => (" ", ""),
_ => ("|", "|"),
};
let render = self.render(ncols, progress);
if let Some(colour) = colour {
bar_open.to_owned() + &colour.apply(&render) + bar_close
} else {
bar_open.to_owned() + &render + bar_close
}
}
pub fn spaces(&self) -> u8 {
match self {
Self::FiraCode => 3,
_ => 2,
}
}
}
impl From<&str> for Animation {
fn from(animation: &str) -> Self {
match animation.to_lowercase().as_str() {
"arrow" => Self::Arrow,
"classic" => Self::Classic,
"fillup" => Self::FillUp,
"firacode" => Self::FiraCode,
"ascii" => Self::TqdmAscii,
_ => Self::Tqdm,
}
}
}
#[derive(Debug, Clone)]
pub enum Colour {
Solid(String),
#[cfg(feature = "gradient")]
#[cfg_attr(docsrs, doc(cfg(feature = "gradient")))]
Gradient(Arc<LinearGradient>),
}
impl Colour {
#[cfg(feature = "gradient")]
#[cfg_attr(docsrs, doc(cfg(feature = "gradient")))]
pub fn gradient(colors: &[&str]) -> Self {
Self::Gradient(Arc::new(
GradientBuilder::new()
.html_colors(colors)
.build()
.expect("failed to compile custom gradient"),
))
}
#[cfg(feature = "gradient")]
#[cfg_attr(docsrs, doc(cfg(feature = "gradient")))]
pub fn rainbow() -> Self {
Self::Gradient(Arc::new(
GradientBuilder::new()
.html_colors(&[
"violet", "indigo", "blue", "green", "yellow", "orange", "red",
])
.build()
.expect("failed to compile rainbow gradient"),
))
}
pub fn solid(color: &str) -> Self {
Self::Solid(color.to_owned())
}
pub fn apply(&self, text: &str) -> String {
match self {
#[cfg(feature = "gradient")]
Colour::Gradient(gradient) => {
let mut colors = gradient
.colors(utils::len(text))
.into_iter()
.map(|x| x.to_css_hex());
let mut gradient_text = String::new();
#[cfg(feature = "unicode")]
let characters = text.graphemes(true);
#[cfg(not(feature = "unicode"))]
let characters = text.chars();
for character in characters {
#[cfg(not(feature = "unicode"))]
let character = character.to_string();
#[cfg(not(feature = "unicode"))]
let character = character.as_str();
if let Some(color) = colors.next() {
gradient_text += &character.colorize(&color);
} else {
gradient_text += character;
}
}
gradient_text
}
Colour::Solid(colour) => text.colorize(colour),
}
}
}
impl From<&str> for Colour {
fn from(value: &str) -> Self {
let value = value.to_lowercase();
#[cfg(feature = "gradient")]
if value.starts_with("gradient(") {
return Self::gradient(
&value
.trim_start_matches("gradient(")
.trim_end_matches(')')
.split(',')
.map(|x| {
x.trim()
.trim_start_matches('\"')
.trim_start_matches('\'')
.trim_end_matches('\"')
.trim_end_matches('\'')
})
.collect::<Vec<&str>>(),
);
}
Self::Solid(value)
}
}