tui_realm_stdlib/components/
line_gauge.rs

1//! ## LineGauge
2//!
3//! `LineGauge` is a line gauge
4
5use super::props::{
6    LINE_GAUGE_STYLE_DOUBLE, LINE_GAUGE_STYLE_NORMAL, LINE_GAUGE_STYLE_ROUND,
7    LINE_GAUGE_STYLE_THICK,
8};
9
10use tuirealm::command::{Cmd, CmdResult};
11use tuirealm::props::{
12    Alignment, AttrValue, Attribute, Borders, Color, PropPayload, PropValue, Props, Style,
13    TextModifiers,
14};
15use tuirealm::ratatui::{
16    layout::Rect,
17    symbols::line::{DOUBLE, NORMAL, ROUNDED, Set, THICK},
18    widgets::LineGauge as TuiLineGauge,
19};
20use tuirealm::{Frame, MockComponent, State};
21
22// -- Component
23
24/// ## LineGauge
25///
26/// provides a component which shows the progress. It is possible to set the style for the progress bar and the text shown above it.
27#[derive(Default)]
28#[must_use]
29pub struct LineGauge {
30    props: Props,
31}
32
33impl LineGauge {
34    pub fn foreground(mut self, fg: Color) -> Self {
35        self.attr(Attribute::Foreground, AttrValue::Color(fg));
36        self
37    }
38
39    pub fn background(mut self, bg: Color) -> Self {
40        self.attr(Attribute::Background, AttrValue::Color(bg));
41        self
42    }
43
44    pub fn borders(mut self, b: Borders) -> Self {
45        self.attr(Attribute::Borders, AttrValue::Borders(b));
46        self
47    }
48
49    pub fn modifiers(mut self, m: TextModifiers) -> Self {
50        self.attr(Attribute::TextProps, AttrValue::TextModifiers(m));
51        self
52    }
53
54    pub fn title<S: Into<String>>(mut self, t: S, a: Alignment) -> Self {
55        self.attr(Attribute::Title, AttrValue::Title((t.into(), a)));
56        self
57    }
58
59    pub fn label<S: Into<String>>(mut self, s: S) -> Self {
60        self.attr(Attribute::Text, AttrValue::String(s.into()));
61        self
62    }
63
64    pub fn progress(mut self, p: f64) -> Self {
65        Self::assert_progress(p);
66        self.attr(
67            Attribute::Value,
68            AttrValue::Payload(PropPayload::One(PropValue::F64(p))),
69        );
70        self
71    }
72
73    pub fn style(mut self, s: u8) -> Self {
74        Self::assert_line_style(s);
75        self.attr(
76            Attribute::Style,
77            AttrValue::Payload(PropPayload::One(PropValue::U8(s))),
78        );
79        self
80    }
81
82    fn line_set(&self) -> Set {
83        match self
84            .props
85            .get_or(
86                Attribute::Style,
87                AttrValue::Payload(PropPayload::One(PropValue::U8(LINE_GAUGE_STYLE_NORMAL))),
88            )
89            .unwrap_payload()
90        {
91            PropPayload::One(PropValue::U8(LINE_GAUGE_STYLE_DOUBLE)) => DOUBLE,
92            PropPayload::One(PropValue::U8(LINE_GAUGE_STYLE_ROUND)) => ROUNDED,
93            PropPayload::One(PropValue::U8(LINE_GAUGE_STYLE_THICK)) => THICK,
94            _ => NORMAL,
95        }
96    }
97
98    fn assert_line_style(s: u8) {
99        if !(&[
100            LINE_GAUGE_STYLE_DOUBLE,
101            LINE_GAUGE_STYLE_NORMAL,
102            LINE_GAUGE_STYLE_ROUND,
103            LINE_GAUGE_STYLE_THICK,
104        ]
105        .contains(&s))
106        {
107            panic!("Invalid line style");
108        }
109    }
110
111    fn assert_progress(p: f64) {
112        assert!(
113            (0.0..=1.0).contains(&p),
114            "Progress value must be in range [0.0, 1.0]"
115        );
116    }
117}
118
119impl MockComponent for LineGauge {
120    fn view(&mut self, render: &mut Frame, area: Rect) {
121        // Make a Span
122        if self.props.get_or(Attribute::Display, AttrValue::Flag(true)) == AttrValue::Flag(true) {
123            // Text
124            let label = self
125                .props
126                .get_or(Attribute::Text, AttrValue::String(String::default()))
127                .unwrap_string();
128            let foreground = self
129                .props
130                .get_or(Attribute::Foreground, AttrValue::Color(Color::Reset))
131                .unwrap_color();
132            let background = self
133                .props
134                .get_or(Attribute::Background, AttrValue::Color(Color::Reset))
135                .unwrap_color();
136            let borders = self
137                .props
138                .get_or(Attribute::Borders, AttrValue::Borders(Borders::default()))
139                .unwrap_borders();
140            let modifiers = self
141                .props
142                .get_or(
143                    Attribute::TextProps,
144                    AttrValue::TextModifiers(TextModifiers::empty()),
145                )
146                .unwrap_text_modifiers();
147            let title = self
148                .props
149                .get_ref(Attribute::Title)
150                .and_then(|x| x.as_title());
151            // Get percentage
152            let percentage = self
153                .props
154                .get_or(
155                    Attribute::Value,
156                    AttrValue::Payload(PropPayload::One(PropValue::F64(0.0))),
157                )
158                .unwrap_payload()
159                .unwrap_one()
160                .unwrap_f64();
161            let div = crate::utils::get_block(borders, title, true, None);
162            // Make progress bar
163            render.render_widget(
164                TuiLineGauge::default()
165                    .block(div)
166                    .filled_style(
167                        Style::default()
168                            .fg(foreground)
169                            .bg(background)
170                            .add_modifier(modifiers),
171                    )
172                    .line_set(self.line_set())
173                    .label(label)
174                    .ratio(percentage),
175                area,
176            );
177        }
178    }
179
180    fn query(&self, attr: Attribute) -> Option<AttrValue> {
181        self.props.get(attr)
182    }
183
184    fn attr(&mut self, attr: Attribute, value: AttrValue) {
185        if let Attribute::Style = attr {
186            if let AttrValue::Payload(s) = value.clone() {
187                Self::assert_line_style(s.unwrap_one().unwrap_u8());
188            }
189        }
190        if let Attribute::Value = attr {
191            if let AttrValue::Payload(p) = value.clone() {
192                Self::assert_progress(p.unwrap_one().unwrap_f64());
193            }
194        }
195        self.props.set(attr, value);
196    }
197
198    fn state(&self) -> State {
199        State::None
200    }
201
202    fn perform(&mut self, _cmd: Cmd) -> CmdResult {
203        CmdResult::None
204    }
205}
206
207#[cfg(test)]
208mod test {
209
210    use super::*;
211
212    use pretty_assertions::assert_eq;
213
214    #[test]
215    fn test_components_progress_bar() {
216        let component = LineGauge::default()
217            .background(Color::Red)
218            .foreground(Color::White)
219            .progress(0.60)
220            .title("Downloading file...", Alignment::Center)
221            .label("60% - ETA 00:20")
222            .style(LINE_GAUGE_STYLE_DOUBLE)
223            .borders(Borders::default());
224        // Get value
225        assert_eq!(component.state(), State::None);
226    }
227
228    #[test]
229    #[should_panic = "Progress value must be in range [0.0, 1.0]"]
230    fn line_gauge_bad_prog() {
231        let _ = LineGauge::default()
232            .background(Color::Red)
233            .foreground(Color::White)
234            .progress(6.0)
235            .title("Downloading file...", Alignment::Center)
236            .label("60% - ETA 00:20")
237            .borders(Borders::default());
238    }
239
240    #[test]
241    #[should_panic = "Invalid line style"]
242    fn line_gauge_bad_symbol() {
243        let _ = LineGauge::default()
244            .background(Color::Red)
245            .foreground(Color::White)
246            .style(254)
247            .title("Downloading file...", Alignment::Center)
248            .label("60% - ETA 00:20")
249            .borders(Borders::default());
250    }
251}