use alloc::string::{String, ToString};
use ratatui_core::buffer::Buffer;
use ratatui_core::layout::Rect;
use ratatui_core::style::{Style, Styled};
use ratatui_core::text::Line;
use ratatui_core::widgets::Widget;
use unicode_width::UnicodeWidthStr;
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
pub struct Bar<'a> {
pub(super) value: u64,
pub(super) label: Option<Line<'a>>,
pub(super) style: Style,
pub(super) value_style: Style,
pub(super) text_value: Option<String>,
}
impl<'a> Bar<'a> {
pub const fn new(value: u64) -> Self {
Self {
value,
label: None,
style: Style::new(),
value_style: Style::new(),
text_value: None,
}
}
pub fn with_label<T: Into<Line<'a>>>(label: T, value: u64) -> Self {
Self {
value,
label: Some(label.into()),
style: Style::new(),
value_style: Style::new(),
text_value: None,
}
}
#[must_use = "method moves the value of self and returns the modified value"]
pub const fn value(mut self, value: u64) -> Self {
self.value = value;
self
}
#[must_use = "method moves the value of self and returns the modified value"]
pub fn label<T: Into<Line<'a>>>(mut self, label: T) -> Self {
self.label = Some(label.into());
self
}
#[must_use = "method moves the value of self and returns the modified value"]
pub fn style<S: Into<Style>>(mut self, style: S) -> Self {
self.style = style.into();
self
}
#[must_use = "method moves the value of self and returns the modified value"]
pub fn value_style<S: Into<Style>>(mut self, style: S) -> Self {
self.value_style = style.into();
self
}
#[must_use = "method moves the value of self and returns the modified value"]
pub fn text_value<T: Into<String>>(mut self, text_value: T) -> Self {
self.text_value = Some(text_value.into());
self
}
pub(super) fn render_value_with_different_styles(
&self,
buf: &mut Buffer,
area: Rect,
bar_length: usize,
default_value_style: Style,
bar_style: Style,
) {
let value = self.value.to_string();
let text = self.text_value.as_ref().unwrap_or(&value);
if !text.is_empty() {
let style = default_value_style.patch(self.value_style);
buf.set_stringn(area.x, area.y, text, bar_length, style);
if text.len() > bar_length {
let bar_length = text
.char_indices()
.take_while(|(i, _)| *i < bar_length)
.last()
.map_or(0, |(i, c)| i + c.len_utf8());
let (first, second) = text.split_at(bar_length);
let style = bar_style.patch(self.style);
buf.set_stringn(
area.x + first.len() as u16,
area.y,
second,
area.width as usize - first.len(),
style,
);
}
}
}
pub(super) fn render_value(
&self,
buf: &mut Buffer,
max_width: u16,
x: u16,
y: u16,
default_value_style: Style,
ticks: u64,
) {
if self.value != 0 {
const TICKS_PER_LINE: u64 = 8;
let value = self.value.to_string();
let value_label = self.text_value.as_ref().unwrap_or(&value);
let width = value_label.width() as u16;
if width < max_width || (width == max_width && ticks >= TICKS_PER_LINE) {
buf.set_string(
x + (max_width.saturating_sub(value_label.len() as u16) >> 1),
y,
value_label,
default_value_style.patch(self.value_style),
);
}
}
}
pub(super) fn render_label(
&self,
buf: &mut Buffer,
max_width: u16,
x: u16,
y: u16,
default_label_style: Style,
) {
let width = self
.label
.as_ref()
.map_or(0, Line::width)
.min(max_width as usize) as u16;
let area = Rect {
x: x + (max_width.saturating_sub(width)) / 2,
y,
width,
height: 1,
};
buf.set_style(area, default_label_style);
if let Some(label) = &self.label {
label.render(area, buf);
}
}
}
impl Styled for Bar<'_> {
type Item = Self;
fn style(&self) -> Style {
self.style
}
fn set_style<S: Into<Style>>(mut self, style: S) -> Self::Item {
self.style = style.into();
self
}
}
#[cfg(test)]
mod tests {
use ratatui_core::style::{Color, Modifier, Style, Stylize};
use super::*;
#[test]
fn test_bar_new() {
let bar = Bar::new(42).label(Line::from("Label"));
assert_eq!(bar.label, Some(Line::from("Label")));
assert_eq!(bar.value, 42);
}
#[test]
fn test_bar_with_label() {
let bar = Bar::with_label("Label", 42);
assert_eq!(bar.label, Some(Line::from("Label")));
assert_eq!(bar.value, 42);
}
#[test]
fn test_bar_stylized() {
let bar = Bar::default().red().bold();
assert_eq!(
bar.style,
Style::default().fg(Color::Red).add_modifier(Modifier::BOLD)
);
}
}