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
162            let normal_style = Style::default()
163                .fg(foreground)
164                .bg(background)
165                .add_modifier(modifiers);
166
167            let div = crate::utils::get_block(borders, title, true, None);
168            // Make progress bar
169            render.render_widget(
170                TuiLineGauge::default()
171                    .block(div)
172                    .style(normal_style)
173                    .filled_style(normal_style)
174                    .line_set(self.line_set())
175                    .label(label)
176                    .ratio(percentage),
177                area,
178            );
179        }
180    }
181
182    fn query(&self, attr: Attribute) -> Option<AttrValue> {
183        self.props.get(attr)
184    }
185
186    fn attr(&mut self, attr: Attribute, value: AttrValue) {
187        if let Attribute::Style = attr {
188            if let AttrValue::Payload(s) = value.clone() {
189                Self::assert_line_style(s.unwrap_one().unwrap_u8());
190            }
191        }
192        if let Attribute::Value = attr {
193            if let AttrValue::Payload(p) = value.clone() {
194                Self::assert_progress(p.unwrap_one().unwrap_f64());
195            }
196        }
197        self.props.set(attr, value);
198    }
199
200    fn state(&self) -> State {
201        State::None
202    }
203
204    fn perform(&mut self, _cmd: Cmd) -> CmdResult {
205        CmdResult::None
206    }
207}
208
209#[cfg(test)]
210mod test {
211
212    use super::*;
213
214    use pretty_assertions::assert_eq;
215
216    #[test]
217    fn test_components_progress_bar() {
218        let component = LineGauge::default()
219            .background(Color::Red)
220            .foreground(Color::White)
221            .progress(0.60)
222            .title("Downloading file...", Alignment::Center)
223            .label("60% - ETA 00:20")
224            .style(LINE_GAUGE_STYLE_DOUBLE)
225            .borders(Borders::default());
226        // Get value
227        assert_eq!(component.state(), State::None);
228    }
229
230    #[test]
231    #[should_panic = "Progress value must be in range [0.0, 1.0]"]
232    fn line_gauge_bad_prog() {
233        let _ = LineGauge::default()
234            .background(Color::Red)
235            .foreground(Color::White)
236            .progress(6.0)
237            .title("Downloading file...", Alignment::Center)
238            .label("60% - ETA 00:20")
239            .borders(Borders::default());
240    }
241
242    #[test]
243    #[should_panic = "Invalid line style"]
244    fn line_gauge_bad_symbol() {
245        let _ = LineGauge::default()
246            .background(Color::Red)
247            .foreground(Color::White)
248            .style(254)
249            .title("Downloading file...", Alignment::Center)
250            .label("60% - ETA 00:20")
251            .borders(Borders::default());
252    }
253}