tui_realm_stdlib/components/
line_gauge.rs1use 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#[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 if self.props.get_or(Attribute::Display, AttrValue::Flag(true)) == AttrValue::Flag(true) {
123 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 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 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 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}