tui_realm_stdlib/components/
line_gauge.rs1use tuirealm::command::{Cmd, CmdResult};
2use tuirealm::component::Component;
3use tuirealm::props::{
4 AttrValue, Attribute, Borders, Color, PropPayload, PropValue, Props, QueryResult, SpanStatic,
5 Style, TextModifiers, Title,
6};
7use tuirealm::ratatui::Frame;
8use tuirealm::ratatui::layout::Rect;
9use tuirealm::ratatui::text::Span;
10use tuirealm::ratatui::widgets::LineGauge as TuiLineGauge;
11use tuirealm::state::State;
12
13use crate::prop_ext::CommonProps;
14
15#[derive(Default)]
25#[must_use]
26pub struct LineGauge {
27 common: CommonProps,
28 props: Props,
29}
30
31impl LineGauge {
32 pub fn foreground(mut self, fg: Color) -> Self {
34 self.attr(Attribute::Foreground, AttrValue::Color(fg));
35 self
36 }
37
38 pub fn background(mut self, bg: Color) -> Self {
40 self.attr(Attribute::Background, AttrValue::Color(bg));
41 self
42 }
43
44 pub fn modifiers(mut self, m: TextModifiers) -> Self {
46 self.attr(Attribute::TextProps, AttrValue::TextModifiers(m));
47 self
48 }
49
50 pub fn style(mut self, style: Style) -> Self {
54 self.attr(Attribute::Style, AttrValue::Style(style));
55 self
56 }
57
58 pub fn inactive(mut self, s: Style) -> Self {
60 self.attr(Attribute::UnfocusedBorderStyle, AttrValue::Style(s));
61 self
62 }
63
64 pub fn borders(mut self, b: Borders) -> Self {
66 self.attr(Attribute::Borders, AttrValue::Borders(b));
67 self
68 }
69
70 pub fn title<T: Into<Title>>(mut self, title: T) -> Self {
72 self.attr(Attribute::Title, AttrValue::Title(title.into()));
73 self
74 }
75
76 pub fn label<S: Into<String>>(mut self, s: S) -> Self {
78 self.attr(Attribute::Text, AttrValue::String(s.into()));
80 self
81 }
82
83 pub fn progress(mut self, p: f64) -> Self {
85 Self::assert_progress(p);
86 self.attr(
87 Attribute::Value,
88 AttrValue::Payload(PropPayload::Single(PropValue::F64(p))),
89 );
90 self
91 }
92
93 pub fn line_style<F: Into<SpanStatic>, U: Into<SpanStatic>>(
97 mut self,
98 filled: F,
99 unfilled: U,
100 ) -> Self {
101 self.attr(
102 Attribute::HighlightedStr,
103 AttrValue::Payload(PropPayload::Pair((
104 PropValue::TextSpan(filled.into()),
105 PropValue::TextSpan(unfilled.into()),
106 ))),
107 );
108
109 self
110 }
111
112 fn get_line_style(&self) -> Option<(&Span<'_>, &Span<'_>)> {
113 self.props
114 .get(Attribute::HighlightedStr)
115 .and_then(AttrValue::as_payload)
116 .and_then(PropPayload::as_pair)
117 .and_then(|pair| Some((pair.0.as_textspan()?, pair.1.as_textspan()?)))
118 }
119
120 fn assert_progress(p: f64) {
121 assert!(
122 (0.0..=1.0).contains(&p),
123 "Progress value must be in range [0.0, 1.0]"
124 );
125 }
126}
127
128impl Component for LineGauge {
129 fn view(&mut self, render: &mut Frame, area: Rect) {
130 if !self.common.display {
131 return;
132 }
133
134 let label = self
136 .props
137 .get(Attribute::Text)
138 .and_then(AttrValue::as_string)
139 .map(String::as_str)
140 .unwrap_or_default();
141 let percentage = self
143 .props
144 .get(Attribute::Value)
145 .and_then(AttrValue::as_payload)
146 .and_then(PropPayload::as_single)
147 .and_then(PropValue::as_f64)
148 .unwrap_or_default();
149
150 let mut widget = TuiLineGauge::default()
151 .style(self.common.style)
152 .filled_style(self.common.style)
153 .label(label)
154 .ratio(percentage);
155
156 if let Some(block) = self.common.get_block() {
157 widget = widget.block(block);
158 }
159
160 if let Some(line_style) = self.get_line_style() {
161 widget = widget
162 .filled_symbol(&line_style.0.content)
163 .filled_style(line_style.0.style)
164 .unfilled_symbol(&line_style.1.content)
165 .unfilled_style(line_style.1.style);
166 }
167
168 render.render_widget(widget, area);
170 }
171
172 fn query<'a>(&'a self, attr: Attribute) -> Option<QueryResult<'a>> {
173 if let Some(value) = self.common.get_for_query(attr) {
174 return Some(value);
175 }
176
177 self.props.get_for_query(attr)
178 }
179
180 fn attr(&mut self, attr: Attribute, value: AttrValue) {
181 if let Some(value) = self.common.set(attr, value) {
182 if let Attribute::Value = attr
183 && let AttrValue::Payload(p) = value.clone()
184 {
185 Self::assert_progress(p.unwrap_single().unwrap_f64());
186 }
187 self.props.set(attr, value);
188 }
189 }
190
191 fn state(&self) -> State {
192 State::None
193 }
194
195 fn perform(&mut self, cmd: Cmd) -> CmdResult {
196 CmdResult::Invalid(cmd)
197 }
198}
199
200#[cfg(test)]
201mod test {
202
203 use pretty_assertions::assert_eq;
204 use tuirealm::props::{BorderType, HorizontalAlignment};
205 use tuirealm::ratatui::symbols::line::{DOUBLE_HORIZONTAL, HORIZONTAL};
206
207 use super::*;
208
209 #[test]
210 fn test_components_progress_bar() {
211 let component = LineGauge::default()
212 .background(Color::Red)
213 .foreground(Color::White)
214 .progress(0.60)
215 .title(Title::from("Downloading file...").alignment(HorizontalAlignment::Center))
216 .label("60% - ETA 00:20")
217 .line_style(DOUBLE_HORIZONTAL, HORIZONTAL)
218 .borders(Borders::default());
219 assert_eq!(component.state(), State::None);
221 }
222
223 #[test]
224 #[should_panic = "Progress value must be in range [0.0, 1.0]"]
225 fn line_gauge_bad_prog() {
226 let _ = LineGauge::default()
227 .background(Color::Red)
228 .foreground(Color::White)
229 .progress(6.0)
230 .title(Title::from("Downloading file...").alignment(HorizontalAlignment::Center))
231 .label("60% - ETA 00:20")
232 .borders(Borders::default());
233 }
234
235 #[test]
236 fn should_allow_styling_line() {
237 let _ = LineGauge::default()
238 .borders(
239 Borders::default()
240 .color(Color::Blue)
241 .modifiers(BorderType::Rounded),
242 )
243 .foreground(Color::Blue)
244 .label("0%")
245 .title(Title::from("Loading...").alignment(HorizontalAlignment::Center))
246 .line_style(
247 Span::styled(HORIZONTAL, Style::new().fg(Color::Red)),
248 Span::styled(HORIZONTAL, Style::new().fg(Color::Gray)),
249 )
250 .progress(0.0);
251 }
252}