tui_realm_stdlib/components/
progress_bar.rs

1//! ## ProgressBar
2//!
3//! `ProgressBar` provides a component which shows the progress. It is possible to set the style for the progress bar and the text shown above it.
4
5use tuirealm::command::{Cmd, CmdResult};
6use tuirealm::props::{
7    Alignment, AttrValue, Attribute, Borders, Color, PropPayload, PropValue, Props, Style,
8    TextModifiers,
9};
10use tuirealm::ratatui::{layout::Rect, widgets::Gauge};
11use tuirealm::{Frame, MockComponent, State};
12
13// -- Component
14
15/// ## ProgressBar
16///
17/// provides a component which shows the progress. It is possible to set the style for the progress bar and the text shown above it.
18#[derive(Default)]
19#[must_use]
20pub struct ProgressBar {
21    props: Props,
22}
23
24impl ProgressBar {
25    pub fn foreground(mut self, fg: Color) -> Self {
26        self.attr(Attribute::Foreground, AttrValue::Color(fg));
27        self
28    }
29
30    pub fn background(mut self, bg: Color) -> Self {
31        self.attr(Attribute::Background, AttrValue::Color(bg));
32        self
33    }
34
35    pub fn borders(mut self, b: Borders) -> Self {
36        self.attr(Attribute::Borders, AttrValue::Borders(b));
37        self
38    }
39
40    pub fn modifiers(mut self, m: TextModifiers) -> Self {
41        self.attr(Attribute::TextProps, AttrValue::TextModifiers(m));
42        self
43    }
44
45    pub fn title<S: Into<String>>(mut self, t: S, a: Alignment) -> Self {
46        self.attr(Attribute::Title, AttrValue::Title((t.into(), a)));
47        self
48    }
49
50    pub fn label<S: Into<String>>(mut self, s: S) -> Self {
51        self.attr(Attribute::Text, AttrValue::String(s.into()));
52        self
53    }
54
55    pub fn progress(mut self, p: f64) -> Self {
56        Self::assert_progress(p);
57        self.attr(
58            Attribute::Value,
59            AttrValue::Payload(PropPayload::One(PropValue::F64(p))),
60        );
61        self
62    }
63
64    fn assert_progress(p: f64) {
65        assert!(
66            (0.0..=1.0).contains(&p),
67            "Progress value must be in range [0.0, 1.0]"
68        );
69    }
70}
71
72impl MockComponent for ProgressBar {
73    fn view(&mut self, render: &mut Frame, area: Rect) {
74        // Make a Span
75        if self.props.get_or(Attribute::Display, AttrValue::Flag(true)) == AttrValue::Flag(true) {
76            // Text
77            let label = self
78                .props
79                .get_or(Attribute::Text, AttrValue::String(String::default()))
80                .unwrap_string();
81            let foreground = self
82                .props
83                .get_or(Attribute::Foreground, AttrValue::Color(Color::Reset))
84                .unwrap_color();
85            let background = self
86                .props
87                .get_or(Attribute::Background, AttrValue::Color(Color::Reset))
88                .unwrap_color();
89            let modifiers = self
90                .props
91                .get_or(
92                    Attribute::TextProps,
93                    AttrValue::TextModifiers(TextModifiers::empty()),
94                )
95                .unwrap_text_modifiers();
96            let borders = self
97                .props
98                .get_or(Attribute::Borders, AttrValue::Borders(Borders::default()))
99                .unwrap_borders();
100            let title = self
101                .props
102                .get_ref(Attribute::Title)
103                .and_then(|x| x.as_title());
104            // Get percentage
105            let percentage = self
106                .props
107                .get_or(
108                    Attribute::Value,
109                    AttrValue::Payload(PropPayload::One(PropValue::F64(0.0))),
110                )
111                .unwrap_payload()
112                .unwrap_one()
113                .unwrap_f64();
114
115            let normal_style = Style::default()
116                .fg(foreground)
117                .bg(background)
118                .add_modifier(modifiers);
119
120            let div = crate::utils::get_block(borders, title, true, None);
121            // Make progress bar
122            render.render_widget(
123                Gauge::default()
124                    .block(div)
125                    .style(normal_style)
126                    .gauge_style(normal_style)
127                    .label(label)
128                    .ratio(percentage)
129                    .use_unicode(true),
130                area,
131            );
132        }
133    }
134
135    fn query(&self, attr: Attribute) -> Option<AttrValue> {
136        self.props.get(attr)
137    }
138
139    fn attr(&mut self, attr: Attribute, value: AttrValue) {
140        if let Attribute::Value = attr {
141            if let AttrValue::Payload(p) = value.clone() {
142                Self::assert_progress(p.unwrap_one().unwrap_f64());
143            }
144        }
145        self.props.set(attr, value);
146    }
147
148    fn state(&self) -> State {
149        State::None
150    }
151
152    fn perform(&mut self, _cmd: Cmd) -> CmdResult {
153        CmdResult::None
154    }
155}
156
157#[cfg(test)]
158mod test {
159
160    use super::*;
161
162    use pretty_assertions::assert_eq;
163
164    #[test]
165    fn test_components_progress_bar() {
166        let component = ProgressBar::default()
167            .background(Color::Red)
168            .foreground(Color::White)
169            .progress(0.60)
170            .title("Downloading file...", Alignment::Center)
171            .label("60% - ETA 00:20")
172            .borders(Borders::default());
173        // Get value
174        assert_eq!(component.state(), State::None);
175    }
176
177    #[test]
178    #[should_panic = "Progress value must be in range [0.0, 1.0]"]
179    fn test_components_progress_bar_bad_prog() {
180        let _ = ProgressBar::default()
181            .background(Color::Red)
182            .foreground(Color::White)
183            .progress(6.0)
184            .title("Downloading file...", Alignment::Center)
185            .label("60% - ETA 00:20")
186            .borders(Borders::default());
187    }
188}