Async-first terminal UI spinners and progress bars
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201//! Progress bar UI element.//!//! A [`Bar`] is a pair of characters (one for the filled portion, one for the empty portion)//! together with optional borders, an in-between separator, and per-portion styling.//! Pre-defined variants live in the [`styles`] module.//!//! Bars are composed into a [`Theme`](crate::Theme) and consumed by//! [`FutureExt`](crate::future::FutureExt) and [`StreamExt`](crate::stream::StreamExt).//!//! ```rust//! use strides::bar;//!//! let bar = bar::styles::THIN_LINE//! .with_border("[", "]")//! .with_filled_style(owo_colors::Style::new().bright_purple());//! ```
use std::fmt::{self, Display, Formatter, Write as _};
use owo_colors::OwoColorize;
/// Pre-defined progress bar styles. Each constant is a ready-to-use [`Bar`] that can be/// further customized with the builder methods on [`Bar`].pub mod styles { use super::Bar;
/// Parallelogram blocks: `▱▱▱▰▰▰`. pub const PARALLELOGRAM: Bar = Bar::new('▱', '▰');
/// Light shading to full block: `░░░███`. pub const SHADED: Bar = Bar::new('░', '█');
/// Light to medium shading: `░░░▒▒▒`. pub const MEDIUM_SHADED: Bar = Bar::new('░', '▒');
/// Medium to dark shading: `▒▒▒▓▓▓`. pub const HEAVY_SHADED: Bar = Bar::new('▒', '▓');
/// Braille dots: `⣀⣀⣀⣿⣿⣿`. pub const DOTTED: Bar = Bar::new('⣀', '⣿');
/// Thin to thick horizontal line: `───━━━`. pub const THIN_LINE: Bar = Bar::new('─', '━');
/// Triple dash, light to heavy: `┄┄┄┅┅┅`. pub const TRIPLE_DASH: Bar = Bar::new('┄', '┅');
/// Middle dots, small to large: `···•••`. pub const MID_DOTS: Bar = Bar::new('·', '•');
/// Dashed to equals sign: `╌╌╌═══`. pub const EQUALS: Bar = Bar::new('╌', '═');}
/// Progress bar style characters.#[derive(Default, Clone)]pub struct Bar<'a> { /// Character to symbolize incompleteness. empty: Option<char>, /// Character to symbolize completeness. complete: Option<char>, /// Characters in between complete and incomplete. in_between: Option<&'a str>, /// Left border character left_border: Option<&'a str>, /// Right border character right_border: Option<&'a str>, /// Style applied to the filled portion of the bar. filled_style: Option<owo_colors::Style>, /// Style applied to the empty portion of the bar. empty_style: Option<owo_colors::Style>,}
impl Bar<'_> { /// Create a new bar from the character used for the empty portion and the character used for /// the filled portion. See the [`styles`] module for pre-defined variants. pub const fn new(empty: char, complete: char) -> Self { Self { empty: Some(empty), complete: Some(complete), in_between: None, left_border: None, right_border: None, filled_style: None, empty_style: None, } }
pub(crate) const fn empty() -> Self { Self { empty: None, complete: None, in_between: None, left_border: None, right_border: None, filled_style: None, empty_style: None, } }
/// Render the bar to a string of the given character `width` with `completed` interpreted as /// a fraction in `0.0..=1.0`. Borders and the in-between separator are added outside that /// width. Mostly useful when integrating the bar into a custom renderer. pub fn render(&self, width: usize, completed: f64) -> String { let mut buf = String::new(); self.render_into(&mut buf, width, completed); buf }
/// Like [`render`](Self::render) but appends into `buf` instead of allocating a new `String`. /// Use this on hot paths where the same buffer can be cleared and reused across frames. pub fn render_into(&self, buf: &mut String, width: usize, completed: f64) { let completed = (completed * width as f64) as usize; let remaining = width.saturating_sub(completed);
if let Some(left) = self.left_border { buf.push_str(left); }
if let Some(c) = self.complete { let run = CharRun::new(c, completed); match self.filled_style { Some(style) => { let _ = write!(buf, "{}", run.style(style)); } None => { let _ = write!(buf, "{run}"); } } }
if let Some(in_between) = self.in_between { buf.push_str(in_between); }
if let Some(c) = self.empty { let run = CharRun::new(c, remaining); match self.empty_style { Some(style) => { let _ = write!(buf, "{}", run.style(style)); } None => { let _ = write!(buf, "{run}"); } } }
if let Some(right) = self.right_border { buf.push_str(right); } }
/// Insert `chars` between the filled and empty portions, useful for a tip character such as /// `>` or `▶`. pub const fn with_in_between(mut self, chars: &'static str) -> Self { self.in_between = Some(chars); self }
/// Wrap the bar with `left` and `right` border strings, for example `"["` and `"]"`. pub const fn with_border(mut self, left: &'static str, right: &'static str) -> Self { self.left_border = Some(left); self.right_border = Some(right); self }
/// Style the filled portion of the bar. pub const fn with_filled_style(mut self, style: owo_colors::Style) -> Self { self.filled_style = Some(style); self }
/// Style the empty portion of the bar. pub const fn with_empty_style(mut self, style: owo_colors::Style) -> Self { self.empty_style = Some(style); self }}
/// A repeated character rendered through `Display` so the styling layer can wrap it without/// materializing the intermediate run as a `String`.struct CharRun { ch: char, count: usize,}
impl CharRun { const fn new(ch: char, count: usize) -> Self { Self { ch, count } }}
impl Display for CharRun { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { for _ in 0..self.count { f.write_char(self.ch)?; } Ok(()) }}