use better_term::{Color, flush_styles};
#[cfg(feature="crossterm")]
use crossterm::{cursor, execute};
#[cfg(feature="crossterm")]
use std::io::stdout;
#[derive(Debug, Clone)]
pub enum BarType {
Bar, RawBar, Dots, Line, }
#[derive(Debug, Clone)]
pub struct KBar {
#[cfg(feature="crossterm")]
x: u16,
#[cfg(feature="crossterm")]
y: u16,
color: bool,
bar_type: BarType,
show_percent: bool,
bar_length: u16,
repeat_at: u8,
percent: u8,
}
impl KBar {
#[cfg(not(feature="crossterm"))]
pub fn new(bar_type: BarType, color: bool, show_percent: bool, bar_length: u16) -> Self {
Self {
percent: 0,
color, bar_type, bar_length, show_percent,
repeat_at: 0,
}
}
#[cfg(not(feature="crossterm"))]
fn set_pos(&mut self) {
print!("\r");
}
#[cfg(feature="crossterm")]
pub fn new(bar_type: BarType, color: bool, show_percent: bool, bar_length: u16) -> Self {
Self::new_at(0, 0, bar_type, color, show_percent, bar_length)
}
#[cfg(feature="crossterm")]
pub fn new_at(x: u16, y: u16, bar_type: BarType, color: bool, show_percent: bool, bar_length: u16) -> Self {
Self {
x, y, percent: 0,
color, bar_type, bar_length, show_percent,
repeat_at: 0,
}
}
#[cfg(feature="crossterm")]
pub fn new_at_cursor(bar_type: BarType, color: bool, show_percent: bool, bar_length: u16) -> Result<Self, String> {
let pos = crossterm::cursor::position();
if pos.is_err() {
return Err(pos.unwrap_err().to_string());
}
let (x, y) = pos.unwrap();
Ok(Self::new_at(x, y, bar_type, color, show_percent, bar_length))
}
#[cfg(feature="crossterm")]
fn set_pos(&mut self) -> Result<(), String> {
let r = execute!(stdout(), cursor::MoveTo(self.x, self.y));
if r.is_err() {
return Err(r.unwrap_err().to_string());
}
Ok(())
}
pub fn update(&mut self, mut percent: u8) {
if percent > 100 {
percent = 100;
}
self.percent = percent;
}
fn draw_dots_and_line(&mut self, output: String, color: Color) -> String {
format!("{}{}", output, if self.show_percent {
if self.color {
format!(" {}{}%", color, self.percent)
} else {
format!(" {}%", self.percent)
}
} else {
format!("")
})
}
pub fn draw(&mut self) {
#[cfg(feature="crossterm")]
{
let r = self.set_pos();
if r.is_err() {
panic!("Failed to set cursor position!");
}
}
#[cfg(not(feature="crossterm"))]
self.set_pos();
let red = 255 - ((self.percent as f32 / 100.0) * 200.0) as u8;
let green = (self.percent as f32 / 100.0 * 200.0) as u8;
let color = Color::RGB(red, green, 25);
let output = match self.bar_type {
BarType::RawBar => {
let chunk_weight = 100 / self.bar_length;
let bar_completion = self.percent as usize / chunk_weight as usize;
if self.color {
let bar = format!("{}{}", color, "█".repeat(bar_completion));
format!("{}{}", bar, if self.show_percent {
format!(" {}{}{}%", color, self.percent, Color::White)
} else { format!("") })
} else {
let bar = format!("{}", "█".repeat(bar_completion));
format!("{}{}", bar, if self.show_percent {
format!(" {}%", self.percent)
} else { format!("") })
}
}
BarType::Bar => {
let chunk_weight = 100 / self.bar_length;
let bar_completion = self.percent as usize / chunk_weight as usize;
let mut bar_uncomplete = (100 - (self.percent)) as usize / chunk_weight as usize;
let add = bar_completion + bar_uncomplete;
if add < self.bar_length as usize {
bar_uncomplete += 1;
}
if add > self.bar_length as usize {
bar_uncomplete -= 1;
}
if self.color {
let completed_bar = format!("{}{}", color, "█".repeat(bar_completion));
let uncompleted_bar = format!("{}{}", Color::BrightBlack, "█".repeat(bar_uncomplete));
format!("{dc}[{}{}{dc}]{}", completed_bar, uncompleted_bar, if self.show_percent {
format!(" {}{}{}%", color, self.percent, Color::White)
} else { format!("") }, dc = Color::White)
} else {
let completed_bar = format!("{}", "█".repeat(bar_completion));
let uncompleted_bar = format!("{}", "=".repeat(bar_uncomplete));
format!("[{}{}]{}", completed_bar, uncompleted_bar, if self.show_percent {
format!(" {}%", self.percent)
} else { format!("") })
}
}
BarType::Dots => {
if self.percent % 2 == 0 {
self.repeat_at += 1;
if self.repeat_at == 3 {
self.repeat_at = 0;
}
}
let output = match self.repeat_at {
0 => {
format!(". ")
}
1 => {
format!(".. ")
}
_ => {
format!("...")
}
};
self.draw_dots_and_line(output, color)
}
BarType::Line => {
if self.percent % 2 == 0 {
self.repeat_at += 1;
if self.repeat_at == 4 {
self.repeat_at = 0;
}
}
let output = match self.repeat_at {
0 => {
format!("|")
}
1 => {
format!("/")
}
2 => {
format!("-")
}
_ => {
format!("\\")
}
};
self.draw_dots_and_line(output, color)
}
};
#[cfg(feature="crossterm")]
print!("{}", output);
#[cfg(not(feature="crossterm"))]
print!("\r{}", output);
if self.color {
flush_styles()
}
}
pub fn is_complete(&self) -> bool {
self.percent == 100
}
}
#[cfg(feature="crossterm")]
pub fn hide_cursor() {
execute!(stdout(), crossterm::cursor::Hide).expect("Failed to hide cursor!");
}
#[cfg(feature="crossterm")]
pub fn show_cursor() {
execute!(stdout(), crossterm::cursor::Show).expect("Failed to show cursor!");
}
impl Default for KBar {
#[cfg(feature="crossterm")]
fn default() -> Self {
Self::new_at_cursor(BarType::Bar, true, true, 20).expect("Failed to make default KBar")
}
#[cfg(not(feature="crossterm"))]
fn default() -> Self {
Self::new(BarType::Bar, true, true, 20)
}
}
#[cfg(test)]
mod tests {
use std::thread;
use std::time::Duration;
use crossterm::execute;
use crossterm::terminal::ClearType;
use crate::{BarType, KBar};
use crate::{hide_cursor, show_cursor};
#[test]
fn t1() {
use std::io::stdout;
execute!(stdout(), crossterm::terminal::Clear(ClearType::All)).expect("Failed to clear screen!");
let mut kbar = KBar::new_at(0, 1, BarType::RawBar,
true, true, 20);
let mut pbar2 = KBar::new_at(0, 3, BarType::Bar,
true, true, 20);
let mut pbar3 = KBar::new_at(7, 5, BarType::Dots,
true, true, 20);
let mut pbar4 = KBar::new_at(8, 7, BarType::Line,
true, true, 20);
hide_cursor();
execute!(stdout(), crossterm::cursor::MoveTo(0, 5)).expect("Failed to move!");
print!("Loading");
execute!(stdout(), crossterm::cursor::MoveTo(0, 7)).expect("Failed to move!");
print!("Loading");
let max = 1000;
for x in 0..max {
let percent = ((x as f32 / (max - 1) as f32) * 100.0) as u8;
kbar.update(percent);
kbar.draw();
pbar2.update(percent);
pbar2.draw();
pbar3.update(percent);
pbar3.draw();
pbar4.update(percent);
pbar4.draw();
thread::sleep(Duration::from_millis(10));
}
println!();
show_cursor();
}
}