• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

veeso / tui-realm-stdlib / 20395971316

20 Dec 2025 02:51PM UTC coverage: 68.439% (+0.5%) from 67.987%
20395971316

push

github

web-flow
fix: Fix Styling inconsistencies (#44)

7 of 84 new or added lines in 10 files covered. (8.33%)

6 existing lines in 5 files now uncovered.

3038 of 4439 relevant lines covered (68.44%)

1.84 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

58.52
/src/components/bar_chart.rs
1
//! ## BarChart
2
//!
3
//! A chart with bars
4

5
use std::collections::LinkedList;
6
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
7
use tuirealm::props::{
8
    Alignment, AttrValue, Attribute, Borders, Color, PropPayload, PropValue, Props, Style,
9
};
10
use tuirealm::ratatui::{layout::Rect, widgets::BarChart as TuiBarChart};
11
use tuirealm::{Frame, MockComponent, State};
12

13
// -- Props
14

15
use super::props::{
16
    BAR_CHART_BARS_GAP, BAR_CHART_BARS_STYLE, BAR_CHART_LABEL_STYLE, BAR_CHART_MAX_BARS,
17
    BAR_CHART_VALUES_STYLE,
18
};
19

20
// -- states
21

22
/// ### BarChartStates
23
///
24
/// Bar chart states
25
#[derive(Default)]
26
pub struct BarChartStates {
27
    pub cursor: usize,
28
}
29

30
impl BarChartStates {
31
    /// ### move_cursor_left
32
    ///
33
    /// Move cursor to the left
34
    pub fn move_cursor_left(&mut self) {
3✔
35
        if self.cursor > 0 {
3✔
36
            self.cursor -= 1;
2✔
37
        }
2✔
38
    }
3✔
39

40
    /// ### move_cursor_right
41
    ///
42
    /// Move cursor to the right
43
    pub fn move_cursor_right(&mut self, data_len: usize) {
3✔
44
        if data_len > 0 && self.cursor + 1 < data_len {
3✔
45
            self.cursor += 1;
2✔
46
        }
2✔
47
    }
3✔
48

49
    /// ### reset_cursor
50
    ///
51
    /// Reset cursor to 0
52
    pub fn reset_cursor(&mut self) {
2✔
53
        self.cursor = 0;
2✔
54
    }
2✔
55

56
    /// ### cursor_at_end
57
    ///
58
    /// Move cursor to the end of the chart
59
    pub fn cursor_at_end(&mut self, data_len: usize) {
2✔
60
        if data_len > 0 {
2✔
61
            self.cursor = data_len - 1;
2✔
62
        } else {
2✔
63
            self.cursor = 0;
×
64
        }
×
65
    }
2✔
66
}
67

68
// -- component
69

70
/// ### BarChart
71
///
72
/// A component to display a chart with bars.
73
/// The bar chart can work both in "active" and "disabled" mode.
74
///
75
/// #### Disabled mode
76
///
77
/// When in disabled mode, the chart won't be interactive, so you won't be able to move through data using keys.
78
/// If you have more data than the maximum amount of bars that can be displayed, you'll have to update data to display the remaining entries
79
///
80
/// #### Active mode
81
///
82
/// While in active mode (default) you can put as many entries as you wish. You can move with arrows and END/HOME keys
83
#[derive(Default)]
84
#[must_use]
85
pub struct BarChart {
86
    props: Props,
87
    pub states: BarChartStates,
88
}
89

90
impl BarChart {
91
    pub fn foreground(mut self, fg: Color) -> Self {
×
92
        self.attr(Attribute::Foreground, AttrValue::Color(fg));
×
93
        self
×
94
    }
×
95

96
    pub fn background(mut self, bg: Color) -> Self {
×
97
        self.attr(Attribute::Background, AttrValue::Color(bg));
×
98
        self
×
99
    }
×
100

101
    pub fn borders(mut self, b: Borders) -> Self {
1✔
102
        self.attr(Attribute::Borders, AttrValue::Borders(b));
1✔
103
        self
1✔
104
    }
1✔
105

106
    pub fn title<S: Into<String>>(mut self, t: S, a: Alignment) -> Self {
1✔
107
        self.attr(Attribute::Title, AttrValue::Title((t.into(), a)));
1✔
108
        self
1✔
109
    }
1✔
110

111
    pub fn disabled(mut self, disabled: bool) -> Self {
1✔
112
        self.attr(Attribute::Disabled, AttrValue::Flag(disabled));
1✔
113
        self
1✔
114
    }
1✔
115

116
    pub fn inactive(mut self, s: Style) -> Self {
×
117
        self.attr(Attribute::FocusStyle, AttrValue::Style(s));
×
118
        self
×
119
    }
×
120

121
    pub fn data(mut self, data: &[(&str, u64)]) -> Self {
1✔
122
        let mut list: LinkedList<PropPayload> = LinkedList::new();
1✔
123
        for (a, b) in data {
12✔
124
            list.push_back(PropPayload::Tup2((
12✔
125
                PropValue::Str((*a).to_string()),
12✔
126
                PropValue::U64(*b),
12✔
127
            )));
12✔
128
        }
12✔
129
        self.attr(
1✔
130
            Attribute::Dataset,
1✔
131
            AttrValue::Payload(PropPayload::Linked(list)),
1✔
132
        );
133
        self
1✔
134
    }
1✔
135

136
    pub fn bar_gap(mut self, gap: u16) -> Self {
1✔
137
        self.attr(Attribute::Custom(BAR_CHART_BARS_GAP), AttrValue::Size(gap));
1✔
138
        self
1✔
139
    }
1✔
140

141
    pub fn bar_style(mut self, s: Style) -> Self {
1✔
142
        self.attr(Attribute::Custom(BAR_CHART_BARS_STYLE), AttrValue::Style(s));
1✔
143
        self
1✔
144
    }
1✔
145

146
    pub fn label_style(mut self, s: Style) -> Self {
1✔
147
        self.attr(
1✔
148
            Attribute::Custom(BAR_CHART_LABEL_STYLE),
1✔
149
            AttrValue::Style(s),
1✔
150
        );
151
        self
1✔
152
    }
1✔
153

154
    pub fn max_bars(mut self, l: usize) -> Self {
1✔
155
        self.attr(Attribute::Custom(BAR_CHART_MAX_BARS), AttrValue::Length(l));
1✔
156
        self
1✔
157
    }
1✔
158

159
    pub fn value_style(mut self, s: Style) -> Self {
1✔
160
        self.attr(
1✔
161
            Attribute::Custom(BAR_CHART_VALUES_STYLE),
1✔
162
            AttrValue::Style(s),
1✔
163
        );
164
        self
1✔
165
    }
1✔
166

167
    pub fn width(mut self, w: u16) -> Self {
1✔
168
        self.attr(Attribute::Width, AttrValue::Size(w));
1✔
169
        self
1✔
170
    }
1✔
171

172
    fn is_disabled(&self) -> bool {
4✔
173
        self.props
4✔
174
            .get_or(Attribute::Disabled, AttrValue::Flag(false))
4✔
175
            .unwrap_flag()
4✔
176
    }
4✔
177

178
    /// ### data_len
179
    ///
180
    /// Retrieve current data len from properties
181
    fn data_len(&self) -> usize {
2✔
182
        self.props
2✔
183
            .get(Attribute::Dataset)
2✔
184
            .map_or(0, |x| x.unwrap_payload().unwrap_linked().len())
2✔
185
    }
2✔
186

187
    fn get_data(&self, start: usize, len: usize) -> Vec<(String, u64)> {
×
188
        if let Some(PropPayload::Linked(list)) = self
×
189
            .props
×
190
            .get(Attribute::Dataset)
×
191
            .map(|x| x.unwrap_payload())
×
192
        {
193
            // Recalc len
194
            let len: usize = std::cmp::min(len, self.data_len() - start);
×
195
            // Prepare data storage
196
            let mut data: Vec<(String, u64)> = Vec::with_capacity(len);
×
197
            for (cursor, item) in list.iter().enumerate() {
×
198
                // If before start, continue
199
                if cursor < start {
×
200
                    continue;
×
201
                }
×
202
                // Push item
203
                if let PropPayload::Tup2((PropValue::Str(label), PropValue::U64(value))) = item {
×
204
                    data.push((label.clone(), *value));
×
205
                }
×
206
                // Break
207
                if data.len() >= len {
×
208
                    break;
×
209
                }
×
210
            }
211

212
            data
×
213
        } else {
214
            Vec::new()
×
215
        }
216
    }
×
217
}
218

219
impl MockComponent for BarChart {
220
    fn view(&mut self, render: &mut Frame, area: Rect) {
×
221
        if self.props.get_or(Attribute::Display, AttrValue::Flag(true)) == AttrValue::Flag(true) {
×
222
            let foreground = self
×
223
                .props
×
224
                .get_or(Attribute::Foreground, AttrValue::Color(Color::Reset))
×
225
                .unwrap_color();
×
226
            let background = self
×
227
                .props
×
228
                .get_or(Attribute::Background, AttrValue::Color(Color::Reset))
×
229
                .unwrap_color();
×
230
            let borders = self
×
231
                .props
×
232
                .get_or(Attribute::Borders, AttrValue::Borders(Borders::default()))
×
233
                .unwrap_borders();
×
234
            let title = self
×
235
                .props
×
236
                .get_ref(Attribute::Title)
×
237
                .and_then(|x| x.as_title());
×
238
            let focus = self
×
239
                .props
×
240
                .get_or(Attribute::Focus, AttrValue::Flag(false))
×
241
                .unwrap_flag();
×
242
            let inactive_style = self
×
243
                .props
×
244
                .get(Attribute::FocusStyle)
×
245
                .map(|x| x.unwrap_style());
×
246
            let active: bool = if self.is_disabled() { true } else { focus };
×
NEW
247
            let normal_style = Style::default().bg(background).fg(foreground);
×
NEW
248
            let div = crate::utils::get_block(borders, title, active, inactive_style);
×
249
            // Get max elements
250
            let data_max_len = self
×
251
                .props
×
252
                .get(Attribute::Custom(BAR_CHART_MAX_BARS))
×
253
                .map_or(self.data_len(), |x| x.unwrap_length());
×
254
            // Get data
255
            let data = self.get_data(self.states.cursor, data_max_len);
×
256
            let data_ref: Vec<(&str, u64)> = data.iter().map(|x| (x.0.as_str(), x.1)).collect();
×
257
            // Create widget
NEW
258
            let mut widget: TuiBarChart = TuiBarChart::default()
×
NEW
259
                .style(normal_style)
×
NEW
260
                .block(div)
×
NEW
261
                .data(data_ref.as_slice());
×
262
            if let Some(gap) = self
×
263
                .props
×
264
                .get(Attribute::Custom(BAR_CHART_BARS_GAP))
×
265
                .map(|x| x.unwrap_size())
×
266
            {
×
267
                widget = widget.bar_gap(gap);
×
268
            }
×
269
            if let Some(width) = self.props.get(Attribute::Width).map(|x| x.unwrap_size()) {
×
270
                widget = widget.bar_width(width);
×
271
            }
×
272
            if let Some(style) = self
×
273
                .props
×
274
                .get(Attribute::Custom(BAR_CHART_BARS_STYLE))
×
275
                .map(|x| x.unwrap_style())
×
276
            {
×
277
                widget = widget.bar_style(style);
×
278
            }
×
279
            if let Some(style) = self
×
280
                .props
×
281
                .get(Attribute::Custom(BAR_CHART_LABEL_STYLE))
×
282
                .map(|x| x.unwrap_style())
×
283
            {
×
284
                widget = widget.label_style(style);
×
285
            }
×
286
            if let Some(style) = self
×
287
                .props
×
288
                .get(Attribute::Custom(BAR_CHART_VALUES_STYLE))
×
289
                .map(|x| x.unwrap_style())
×
290
            {
×
291
                widget = widget.value_style(style);
×
292
            }
×
293
            // Render
294
            render.render_widget(widget, area);
×
295
        }
×
296
    }
×
297

298
    fn query(&self, attr: Attribute) -> Option<AttrValue> {
×
299
        self.props.get(attr)
×
300
    }
×
301

302
    fn attr(&mut self, attr: Attribute, value: AttrValue) {
10✔
303
        self.props.set(attr, value);
10✔
304
    }
10✔
305

306
    fn perform(&mut self, cmd: Cmd) -> CmdResult {
4✔
307
        if !self.is_disabled() {
4✔
308
            match cmd {
2✔
309
                Cmd::Move(Direction::Left) => {
1✔
310
                    self.states.move_cursor_left();
1✔
311
                }
1✔
312
                Cmd::Move(Direction::Right) => {
1✔
313
                    self.states.move_cursor_right(self.data_len());
1✔
314
                }
1✔
315
                Cmd::GoTo(Position::Begin) => {
1✔
316
                    self.states.reset_cursor();
1✔
317
                }
1✔
318
                Cmd::GoTo(Position::End) => {
1✔
319
                    self.states.cursor_at_end(self.data_len());
1✔
320
                }
1✔
321
                _ => {}
×
322
            }
323
        }
×
324
        CmdResult::None
4✔
325
    }
4✔
326

327
    fn state(&self) -> State {
1✔
328
        State::None
1✔
329
    }
1✔
330
}
331

332
#[cfg(test)]
333
mod test {
334

335
    use super::*;
336

337
    use pretty_assertions::assert_eq;
338

339
    #[test]
340
    fn test_components_bar_chart_states() {
1✔
341
        let mut states: BarChartStates = BarChartStates::default();
1✔
342
        assert_eq!(states.cursor, 0);
1✔
343
        // Incr
344
        states.move_cursor_right(2);
1✔
345
        assert_eq!(states.cursor, 1);
1✔
346
        // At end
347
        states.move_cursor_right(2);
1✔
348
        assert_eq!(states.cursor, 1);
1✔
349
        // Decr
350
        states.move_cursor_left();
1✔
351
        assert_eq!(states.cursor, 0);
1✔
352
        // At begin
353
        states.move_cursor_left();
1✔
354
        assert_eq!(states.cursor, 0);
1✔
355
        // Move at end
356
        states.cursor_at_end(3);
1✔
357
        assert_eq!(states.cursor, 2);
1✔
358
        states.reset_cursor();
1✔
359
        assert_eq!(states.cursor, 0);
1✔
360
    }
1✔
361

362
    #[test]
363
    fn test_components_bar_chart() {
1✔
364
        let mut component: BarChart = BarChart::default()
1✔
365
            .disabled(false)
1✔
366
            .title("my incomes", Alignment::Center)
1✔
367
            .label_style(Style::default().fg(Color::Yellow))
1✔
368
            .bar_style(Style::default().fg(Color::LightYellow))
1✔
369
            .bar_gap(2)
1✔
370
            .width(4)
1✔
371
            .borders(Borders::default())
1✔
372
            .max_bars(6)
1✔
373
            .value_style(Style::default().fg(Color::LightBlue))
1✔
374
            .data(&[
1✔
375
                ("january", 250),
1✔
376
                ("february", 300),
1✔
377
                ("march", 275),
1✔
378
                ("april", 312),
1✔
379
                ("may", 420),
1✔
380
                ("june", 170),
1✔
381
                ("july", 220),
1✔
382
                ("august", 160),
1✔
383
                ("september", 180),
1✔
384
                ("october", 470),
1✔
385
                ("november", 380),
1✔
386
                ("december", 820),
1✔
387
            ]);
1✔
388
        // Commands
389
        assert_eq!(component.state(), State::None);
1✔
390
        // -> Right
391
        assert_eq!(
1✔
392
            component.perform(Cmd::Move(Direction::Right)),
1✔
393
            CmdResult::None
394
        );
395
        assert_eq!(component.states.cursor, 1);
1✔
396
        // <- Left
397
        assert_eq!(
1✔
398
            component.perform(Cmd::Move(Direction::Left)),
1✔
399
            CmdResult::None
400
        );
401
        assert_eq!(component.states.cursor, 0);
1✔
402
        // End
403
        assert_eq!(component.perform(Cmd::GoTo(Position::End)), CmdResult::None);
1✔
404
        assert_eq!(component.states.cursor, 11);
1✔
405
        // Home
406
        assert_eq!(
1✔
407
            component.perform(Cmd::GoTo(Position::Begin)),
1✔
408
            CmdResult::None
409
        );
410
        assert_eq!(component.states.cursor, 0);
1✔
411
    }
1✔
412
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc