use tuirealm::command::{Cmd, CmdResult};
use tuirealm::component::Component;
use tuirealm::props::{
AttrValue, Attribute, Borders, Color, PropPayload, PropValue, Props, QueryResult, SpanStatic,
Style, TextModifiers, Title,
};
use tuirealm::ratatui::Frame;
use tuirealm::ratatui::layout::Rect;
use tuirealm::ratatui::text::Span;
use tuirealm::ratatui::widgets::LineGauge as TuiLineGauge;
use tuirealm::state::State;
use crate::prop_ext::CommonProps;
#[derive(Default)]
#[must_use]
pub struct LineGauge {
common: CommonProps,
props: Props,
}
impl LineGauge {
pub fn foreground(mut self, fg: Color) -> Self {
self.attr(Attribute::Foreground, AttrValue::Color(fg));
self
}
pub fn background(mut self, bg: Color) -> Self {
self.attr(Attribute::Background, AttrValue::Color(bg));
self
}
pub fn modifiers(mut self, m: TextModifiers) -> Self {
self.attr(Attribute::TextProps, AttrValue::TextModifiers(m));
self
}
pub fn style(mut self, style: Style) -> Self {
self.attr(Attribute::Style, AttrValue::Style(style));
self
}
pub fn inactive(mut self, s: Style) -> Self {
self.attr(Attribute::UnfocusedBorderStyle, AttrValue::Style(s));
self
}
pub fn borders(mut self, b: Borders) -> Self {
self.attr(Attribute::Borders, AttrValue::Borders(b));
self
}
pub fn title<T: Into<Title>>(mut self, title: T) -> Self {
self.attr(Attribute::Title, AttrValue::Title(title.into()));
self
}
pub fn label<S: Into<String>>(mut self, s: S) -> Self {
self.attr(Attribute::Text, AttrValue::String(s.into()));
self
}
pub fn progress(mut self, p: f64) -> Self {
Self::assert_progress(p);
self.attr(
Attribute::Value,
AttrValue::Payload(PropPayload::Single(PropValue::F64(p))),
);
self
}
pub fn line_style<F: Into<SpanStatic>, U: Into<SpanStatic>>(
mut self,
filled: F,
unfilled: U,
) -> Self {
self.attr(
Attribute::HighlightedStr,
AttrValue::Payload(PropPayload::Pair((
PropValue::TextSpan(filled.into()),
PropValue::TextSpan(unfilled.into()),
))),
);
self
}
fn get_line_style(&self) -> Option<(&Span<'_>, &Span<'_>)> {
self.props
.get(Attribute::HighlightedStr)
.and_then(AttrValue::as_payload)
.and_then(PropPayload::as_pair)
.and_then(|pair| Some((pair.0.as_textspan()?, pair.1.as_textspan()?)))
}
fn assert_progress(p: f64) {
assert!(
(0.0..=1.0).contains(&p),
"Progress value must be in range [0.0, 1.0]"
);
}
}
impl Component for LineGauge {
fn view(&mut self, render: &mut Frame, area: Rect) {
if !self.common.display {
return;
}
let label = self
.props
.get(Attribute::Text)
.and_then(AttrValue::as_string)
.map(String::as_str)
.unwrap_or_default();
let percentage = self
.props
.get(Attribute::Value)
.and_then(AttrValue::as_payload)
.and_then(PropPayload::as_single)
.and_then(PropValue::as_f64)
.unwrap_or_default();
let mut widget = TuiLineGauge::default()
.style(self.common.style)
.filled_style(self.common.style)
.label(label)
.ratio(percentage);
if let Some(block) = self.common.get_block() {
widget = widget.block(block);
}
if let Some(line_style) = self.get_line_style() {
widget = widget
.filled_symbol(&line_style.0.content)
.filled_style(line_style.0.style)
.unfilled_symbol(&line_style.1.content)
.unfilled_style(line_style.1.style);
}
render.render_widget(widget, area);
}
fn query<'a>(&'a self, attr: Attribute) -> Option<QueryResult<'a>> {
if let Some(value) = self.common.get_for_query(attr) {
return Some(value);
}
self.props.get_for_query(attr)
}
fn attr(&mut self, attr: Attribute, value: AttrValue) {
if let Some(value) = self.common.set(attr, value) {
if let Attribute::Value = attr
&& let AttrValue::Payload(p) = value.clone()
{
Self::assert_progress(p.unwrap_single().unwrap_f64());
}
self.props.set(attr, value);
}
}
fn state(&self) -> State {
State::None
}
fn perform(&mut self, cmd: Cmd) -> CmdResult {
CmdResult::Invalid(cmd)
}
}
#[cfg(test)]
mod test {
use pretty_assertions::assert_eq;
use tuirealm::props::{BorderType, HorizontalAlignment};
use tuirealm::ratatui::symbols::line::{DOUBLE_HORIZONTAL, HORIZONTAL};
use super::*;
#[test]
fn test_components_progress_bar() {
let component = LineGauge::default()
.background(Color::Red)
.foreground(Color::White)
.progress(0.60)
.title(Title::from("Downloading file...").alignment(HorizontalAlignment::Center))
.label("60% - ETA 00:20")
.line_style(DOUBLE_HORIZONTAL, HORIZONTAL)
.borders(Borders::default());
assert_eq!(component.state(), State::None);
}
#[test]
#[should_panic = "Progress value must be in range [0.0, 1.0]"]
fn line_gauge_bad_prog() {
let _ = LineGauge::default()
.background(Color::Red)
.foreground(Color::White)
.progress(6.0)
.title(Title::from("Downloading file...").alignment(HorizontalAlignment::Center))
.label("60% - ETA 00:20")
.borders(Borders::default());
}
#[test]
fn should_allow_styling_line() {
let _ = LineGauge::default()
.borders(
Borders::default()
.color(Color::Blue)
.modifiers(BorderType::Rounded),
)
.foreground(Color::Blue)
.label("0%")
.title(Title::from("Loading...").alignment(HorizontalAlignment::Center))
.line_style(
Span::styled(HORIZONTAL, Style::new().fg(Color::Red)),
Span::styled(HORIZONTAL, Style::new().fg(Color::Gray)),
)
.progress(0.0);
}
}