use crate::components::{Box as RnkBox, Line, Span, Text};
use crate::core::{Color, Element};
#[derive(Debug, Clone)]
pub struct ProgressSymbols {
pub filled: char,
pub empty: char,
pub head: Option<char>,
pub bracket_left: Option<char>,
pub bracket_right: Option<char>,
}
impl Default for ProgressSymbols {
fn default() -> Self {
Self {
filled: '█',
empty: '░',
head: None,
bracket_left: Some('['),
bracket_right: Some(']'),
}
}
}
impl ProgressSymbols {
pub fn block() -> Self {
Self::default()
}
pub fn line() -> Self {
Self {
filled: '━',
empty: '─',
head: Some('╸'),
bracket_left: None,
bracket_right: None,
}
}
pub fn dot() -> Self {
Self {
filled: '●',
empty: '○',
head: None,
bracket_left: Some('⟨'),
bracket_right: Some('⟩'),
}
}
pub fn ascii() -> Self {
Self {
filled: '#',
empty: '-',
head: Some('>'),
bracket_left: Some('['),
bracket_right: Some(']'),
}
}
pub fn thin() -> Self {
Self {
filled: '─',
empty: ' ',
head: Some('●'),
bracket_left: None,
bracket_right: None,
}
}
}
#[derive(Debug, Clone)]
pub struct Progress {
progress: f32,
width: u16,
symbols: ProgressSymbols,
filled_color: Option<Color>,
empty_color: Option<Color>,
show_percent: bool,
label: Option<String>,
key: Option<String>,
}
impl Progress {
pub fn new() -> Self {
Self {
progress: 0.0,
width: 20,
symbols: ProgressSymbols::default(),
filled_color: None,
empty_color: None,
show_percent: false,
label: None,
key: None,
}
}
pub fn progress(mut self, progress: f32) -> Self {
self.progress = progress.clamp(0.0, 1.0);
self
}
pub fn ratio(mut self, current: usize, total: usize) -> Self {
if total == 0 {
self.progress = 0.0;
} else {
self.progress = (current as f32 / total as f32).clamp(0.0, 1.0);
}
self
}
pub fn width(mut self, width: u16) -> Self {
self.width = width;
self
}
pub fn symbols(mut self, symbols: ProgressSymbols) -> Self {
self.symbols = symbols;
self
}
pub fn filled_color(mut self, color: Color) -> Self {
self.filled_color = Some(color);
self
}
pub fn empty_color(mut self, color: Color) -> Self {
self.empty_color = Some(color);
self
}
pub fn show_percent(mut self, show: bool) -> Self {
self.show_percent = show;
self
}
pub fn label(mut self, label: impl Into<String>) -> Self {
self.label = Some(label.into());
self
}
pub fn key(mut self, key: impl Into<String>) -> Self {
self.key = Some(key.into());
self
}
pub fn into_element(self) -> Element {
let mut spans = Vec::new();
if let Some(bracket) = self.symbols.bracket_left {
spans.push(Span::new(bracket.to_string()));
}
let bracket_width = self.symbols.bracket_left.is_some() as u16
+ self.symbols.bracket_right.is_some() as u16;
let bar_width = self.width.saturating_sub(bracket_width) as usize;
let filled_width = (self.progress * bar_width as f32).round() as usize;
let empty_width = bar_width.saturating_sub(filled_width);
let (actual_filled, has_head) =
if self.symbols.head.is_some() && filled_width > 0 && filled_width < bar_width {
(filled_width.saturating_sub(1), true)
} else {
(filled_width, false)
};
if actual_filled > 0 {
let filled_str: String =
std::iter::repeat_n(self.symbols.filled, actual_filled).collect();
let mut filled_span = Span::new(filled_str);
if let Some(color) = self.filled_color {
filled_span = filled_span.color(color);
}
spans.push(filled_span);
}
if has_head && let Some(head) = self.symbols.head {
let mut head_span = Span::new(head.to_string());
if let Some(color) = self.filled_color {
head_span = head_span.color(color);
}
spans.push(head_span);
}
if empty_width > 0 {
let empty_str: String = std::iter::repeat_n(self.symbols.empty, empty_width).collect();
let mut empty_span = Span::new(empty_str);
if let Some(color) = self.empty_color {
empty_span = empty_span.color(color);
} else {
empty_span = empty_span.dim();
}
spans.push(empty_span);
}
if let Some(bracket) = self.symbols.bracket_right {
spans.push(Span::new(bracket.to_string()));
}
if self.show_percent {
let percent = format!(" {:3.0}%", self.progress * 100.0);
spans.push(Span::new(percent));
}
if let Some(label) = self.label {
spans.push(Span::new(format!(" {}", label)));
}
let text = Text::line(Line::from_spans(spans));
let mut container = RnkBox::new().child(text.into_element());
if let Some(key) = self.key {
container = container.key(key);
}
container.into_element()
}
}
impl Default for Progress {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct Gauge {
progress: f32,
label: Option<String>,
color: Option<Color>,
key: Option<String>,
}
impl Gauge {
pub fn new() -> Self {
Self {
progress: 0.0,
label: None,
color: None,
key: None,
}
}
pub fn progress(mut self, progress: f32) -> Self {
self.progress = progress.clamp(0.0, 1.0);
self
}
pub fn label(mut self, label: impl Into<String>) -> Self {
self.label = Some(label.into());
self
}
pub fn color(mut self, color: Color) -> Self {
self.color = Some(color);
self
}
pub fn key(mut self, key: impl Into<String>) -> Self {
self.key = Some(key.into());
self
}
pub fn into_element(self) -> Element {
let width = 10usize;
let filled = (self.progress * width as f32).round() as usize;
let empty = width - filled;
let mut spans = Vec::new();
let filled_str: String = std::iter::repeat_n('█', filled).collect();
let empty_str: String = std::iter::repeat_n('░', empty).collect();
let mut filled_span = Span::new(filled_str);
if let Some(color) = self.color {
filled_span = filled_span.color(color);
}
spans.push(filled_span);
spans.push(Span::new(empty_str).dim());
let percent = format!(" {:3.0}%", self.progress * 100.0);
let mut percent_span = Span::new(percent);
if let Some(color) = self.color {
percent_span = percent_span.color(color);
}
spans.push(percent_span.bold());
if let Some(label) = self.label {
spans.push(Span::new(format!(" {}", label)));
}
let text = Text::line(Line::from_spans(spans));
let mut container = RnkBox::new().child(text.into_element());
if let Some(key) = self.key {
container = container.key(key);
}
container.into_element()
}
}
impl Default for Gauge {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_progress_creation() {
let progress = Progress::new().progress(0.5).width(20);
assert_eq!(progress.width, 20);
assert!((progress.progress - 0.5).abs() < 0.01);
}
#[test]
fn test_progress_ratio() {
let progress = Progress::new().ratio(50, 100);
assert!((progress.progress - 0.5).abs() < 0.01);
}
#[test]
fn test_progress_symbols() {
let block = ProgressSymbols::block();
assert_eq!(block.filled, '█');
let ascii = ProgressSymbols::ascii();
assert_eq!(ascii.filled, '#');
}
#[test]
fn test_gauge_creation() {
let gauge = Gauge::new().progress(0.75).label("CPU");
assert!((gauge.progress - 0.75).abs() < 0.01);
assert_eq!(gauge.label, Some("CPU".to_string()));
}
}