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            let div = crate::utils::get_block(borders, title, true, None);
115            // Make progress bar
116            render.render_widget(
117                Gauge::default()
118                    .block(div)
119                    .gauge_style(
120                        Style::default()
121                            .fg(foreground)
122                            .bg(background)
123                            .add_modifier(modifiers),
124                    )
125                    .label(label)
126                    .ratio(percentage),
127                area,
128            );
129        }
130    }
131
132    fn query(&self, attr: Attribute) -> Option<AttrValue> {
133        self.props.get(attr)
134    }
135
136    fn attr(&mut self, attr: Attribute, value: AttrValue) {
137        if let Attribute::Value = attr {
138            if let AttrValue::Payload(p) = value.clone() {
139                Self::assert_progress(p.unwrap_one().unwrap_f64());
140            }
141        }
142        self.props.set(attr, value);
143    }
144
145    fn state(&self) -> State {
146        State::None
147    }
148
149    fn perform(&mut self, _cmd: Cmd) -> CmdResult {
150        CmdResult::None
151    }
152}
153
154#[cfg(test)]
155mod test {
156
157    use super::*;
158
159    use pretty_assertions::assert_eq;
160
161    #[test]
162    fn test_components_progress_bar() {
163        let component = ProgressBar::default()
164            .background(Color::Red)
165            .foreground(Color::White)
166            .progress(0.60)
167            .title("Downloading file...", Alignment::Center)
168            .label("60% - ETA 00:20")
169            .borders(Borders::default());
170        // Get value
171        assert_eq!(component.state(), State::None);
172    }
173
174    #[test]
175    #[should_panic = "Progress value must be in range [0.0, 1.0]"]
176    fn test_components_progress_bar_bad_prog() {
177        let _ = ProgressBar::default()
178            .background(Color::Red)
179            .foreground(Color::White)
180            .progress(6.0)
181            .title("Downloading file...", Alignment::Center)
182            .label("60% - ETA 00:20")
183            .borders(Borders::default());
184    }
185}