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 let div = crate::utils::get_block(borders, title, true, None);
162 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 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}