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