Skip to main content

tui_realm_stdlib/components/
gauge.rs

1//! `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.
2
3use tuirealm::command::{Cmd, CmdResult};
4use tuirealm::component::Component;
5use tuirealm::props::{
6    AttrValue, Attribute, Borders, Color, PropPayload, PropValue, Props, QueryResult, Style,
7    TextModifiers, Title,
8};
9use tuirealm::ratatui::Frame;
10use tuirealm::ratatui::layout::Rect;
11use tuirealm::ratatui::widgets::Gauge as TuiGauge;
12use tuirealm::state::State;
13
14use crate::prop_ext::CommonProps;
15
16// -- Component
17
18/// `Gauge` provides a multi-line component which shows the progress. It is possible to set the style for the progress bar and the text shown above it.
19///
20/// Read more in [`Gauge`](TuiGauge).
21///
22/// If only a single-line Gauge is necessary, use [`LineGauge`](crate::components::LineGauge) instead.
23#[derive(Default)]
24#[must_use]
25pub struct Gauge {
26    common: CommonProps,
27    props: Props,
28}
29
30impl Gauge {
31    /// Set the main foreground color. This may get overwritten by individual text styles.
32    pub fn foreground(mut self, fg: Color) -> Self {
33        self.attr(Attribute::Foreground, AttrValue::Color(fg));
34        self
35    }
36
37    /// Set the main background color. This may get overwritten by individual text styles.
38    pub fn background(mut self, bg: Color) -> Self {
39        self.attr(Attribute::Background, AttrValue::Color(bg));
40        self
41    }
42
43    /// Set the main text modifiers. This may get overwritten by individual text styles.
44    pub fn modifiers(mut self, m: TextModifiers) -> Self {
45        self.attr(Attribute::TextProps, AttrValue::TextModifiers(m));
46        self
47    }
48
49    /// Set the main style. This may get overwritten by individual text styles.
50    ///
51    /// This option will overwrite any previous [`foreground`](Self::foreground), [`background`](Self::background) and [`modifiers`](Self::modifiers)!
52    pub fn style(mut self, style: Style) -> Self {
53        self.attr(Attribute::Style, AttrValue::Style(style));
54        self
55    }
56
57    /// Set a custom style for the border when the component is unfocused.
58    pub fn inactive(mut self, s: Style) -> Self {
59        self.attr(Attribute::UnfocusedBorderStyle, AttrValue::Style(s));
60        self
61    }
62
63    /// Add a border to the component.
64    pub fn borders(mut self, b: Borders) -> Self {
65        self.attr(Attribute::Borders, AttrValue::Borders(b));
66        self
67    }
68
69    /// Add a title to the component.
70    pub fn title<T: Into<Title>>(mut self, title: T) -> Self {
71        self.attr(Attribute::Title, AttrValue::Title(title.into()));
72        self
73    }
74
75    /// Set a label text for the Bar.
76    pub fn label<S: Into<String>>(mut self, s: S) -> Self {
77        // TODO: we should consider using Span
78        self.attr(Attribute::Text, AttrValue::String(s.into()));
79        self
80    }
81
82    /// Set the initial progress value.
83    pub fn progress(mut self, p: f64) -> Self {
84        Self::assert_progress(p);
85        self.attr(
86            Attribute::Value,
87            AttrValue::Payload(PropPayload::Single(PropValue::F64(p))),
88        );
89        self
90    }
91
92    fn assert_progress(p: f64) {
93        assert!(
94            (0.0..=1.0).contains(&p),
95            "Progress value must be in range [0.0, 1.0]"
96        );
97    }
98}
99
100impl Component for Gauge {
101    fn view(&mut self, render: &mut Frame, area: Rect) {
102        if !self.common.display {
103            return;
104        }
105
106        // Text
107        let label = self
108            .props
109            .get(Attribute::Text)
110            .and_then(AttrValue::as_string);
111        // Get percentage
112        let percentage = self
113            .props
114            .get(Attribute::Value)
115            .and_then(AttrValue::as_payload)
116            .and_then(PropPayload::as_single)
117            .and_then(PropValue::as_f64)
118            .unwrap_or_default();
119
120        // Make progress bar
121        let mut widget = TuiGauge::default()
122            .style(self.common.style)
123            .gauge_style(self.common.style)
124            .ratio(percentage)
125            .use_unicode(true);
126
127        if let Some(label) = label {
128            widget = widget.label(label.as_str());
129        }
130
131        if let Some(block) = self.common.get_block() {
132            widget = widget.block(block);
133        }
134
135        render.render_widget(widget, area);
136    }
137
138    fn query<'a>(&'a self, attr: Attribute) -> Option<QueryResult<'a>> {
139        if let Some(value) = self.common.get_for_query(attr) {
140            return Some(value);
141        }
142
143        self.props.get_for_query(attr)
144    }
145
146    fn attr(&mut self, attr: Attribute, value: AttrValue) {
147        if let Some(value) = self.common.set(attr, value) {
148            if let Attribute::Value = attr
149                && let AttrValue::Payload(p) = value.clone()
150            {
151                Self::assert_progress(p.unwrap_single().unwrap_f64());
152            }
153            self.props.set(attr, value);
154        }
155    }
156
157    fn state(&self) -> State {
158        State::None
159    }
160
161    fn perform(&mut self, cmd: Cmd) -> CmdResult {
162        CmdResult::Invalid(cmd)
163    }
164}
165
166#[cfg(test)]
167mod test {
168
169    use pretty_assertions::assert_eq;
170    use tuirealm::props::HorizontalAlignment;
171
172    use super::*;
173
174    #[test]
175    fn test_components_progress_bar() {
176        let component = Gauge::default()
177            .background(Color::Red)
178            .foreground(Color::White)
179            .progress(0.60)
180            .title(Title::from("Downloading file...").alignment(HorizontalAlignment::Center))
181            .label("60% - ETA 00:20")
182            .borders(Borders::default());
183        // Get value
184        assert_eq!(component.state(), State::None);
185    }
186
187    #[test]
188    #[should_panic = "Progress value must be in range [0.0, 1.0]"]
189    fn test_components_progress_bar_bad_prog() {
190        let _ = Gauge::default()
191            .background(Color::Red)
192            .foreground(Color::White)
193            .progress(6.0)
194            .title(Title::from("Downloading file...").alignment(HorizontalAlignment::Center))
195            .label("60% - ETA 00:20")
196            .borders(Borders::default());
197    }
198}