use ratatui::{
prelude::{Buffer, Rect},
style::Style,
text::Span,
widgets::StatefulWidget,
};
#[derive(Debug, Clone, Default)]
pub struct ThrobberState {
index: i8,
}
impl ThrobberState {
pub fn calc_step(&mut self, step: i8) {
self.index = self.index.saturating_add(step);
}
#[allow(clippy::cast_possible_truncation)]
pub fn normalize(&mut self, throbber: &Throbber) {
let len = throbber.throbber_set.symbols.len() as i8;
if len <= 0 {
} else {
self.index %= len;
if self.index < 0 {
self.index += len;
}
}
}
}
#[allow(clippy::struct_field_names)]
#[derive(Debug, Clone)]
pub struct Throbber<'a> {
label: Option<Span<'a>>,
style: Style,
throbber_style: Style,
throbber_set: throbber::Set,
use_type: throbber::WhichUse,
}
impl Default for Throbber<'_> {
fn default() -> Self {
Self {
label: None,
style: Style::default(),
throbber_style: Style::default(),
throbber_set: throbber::BRAILLE_EIGHT_DOUBLE,
use_type: throbber::WhichUse::Spin,
}
}
}
impl<'a> Throbber<'a> {
#[must_use]
pub fn label<T>(mut self, label: T) -> Self
where
T: Into<Span<'a>>,
{
self.label = Some(label.into());
self
}
#[must_use]
pub fn throbber_set(mut self, set: throbber::Set) -> Self {
self.throbber_set = set;
self
}
#[must_use]
pub fn use_type(mut self, use_type: throbber::WhichUse) -> Self {
self.use_type = use_type;
self
}
}
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
impl StatefulWidget for Throbber<'_> {
type State = ThrobberState;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
buf.set_style(area, self.style);
let throbber_area = area;
if throbber_area.height < 1 {
return;
}
let symbol = match self.use_type {
throbber::WhichUse::Spin => {
state.normalize(&self);
let len = self.throbber_set.symbols.len() as i8;
if 0 <= state.index && state.index < len {
self.throbber_set.symbols[state.index as usize]
} else {
self.throbber_set.empty
}
}
};
let symbol_span = Span::styled(format!("{symbol} "), self.throbber_style);
let (col, row) = buf.set_span(
throbber_area.left(),
throbber_area.top(),
&symbol_span,
symbol_span.width() as u16,
);
if let Some(label) = self.label {
if throbber_area.right() <= col {
return;
}
buf.set_span(col, row, &label, label.width() as u16);
}
}
}
#[allow(clippy::module_inception, clippy::doc_link_with_quotes)]
pub mod throbber {
#[derive(Debug, Clone)]
pub struct Set {
pub empty: &'static str,
pub symbols: &'static [&'static str],
}
#[derive(Debug, Clone)]
pub enum WhichUse {
Spin,
}
#[cfg(not(feature = "integration"))]
pub const BRAILLE_EIGHT_DOUBLE: Set = Set {
empty: " ",
symbols: &["⣧", "⣏", "⡟", "⠿", "⢻", "⣹", "⣼", "⣶"],
};
#[cfg(feature = "integration")]
pub const BRAILLE_EIGHT_DOUBLE: Set = Set {
empty: " ",
symbols: &["⣧", "⣧", "⣧", "⣧", "⣧", "⣧", "⣧", "⣧"],
};
}