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

veeso / tui-realm-stdlib / 15137208932

20 May 2025 12:13PM UTC coverage: 67.595% (-0.7%) from 68.289%
15137208932

push

github

web-flow
Fix clippy lints, apply some pedantic fixes and general small improvements (#32)

* style(examples): directly have values as a type instead of casting

* style(examples/utils): ignore unused warnings

* style: run clippy auto fix

and remove redundant tests from "label"

* style: apply some clippy pedantic auto fixes

* test: set specific strings for "should_panic"

So that other panics are catched as failed tests.

* style(bar_chart): remove casting to "u64" when "usize" is directly provided and needed

* refactor(table): move making rows to own function

To appease "clippy::too_many_lines" and slightly reduce nesting.

* style: add "#[must_use]" where applicable

To hint not to forget a value.

104 of 192 new or added lines in 19 files covered. (54.17%)

12 existing lines in 2 files now uncovered.

2985 of 4416 relevant lines covered (67.6%)

1.66 hits per line

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

79.67
/src/components/checkbox.rs
1
//! ## Checkbox
2
//!
3
//! `Checkbox` component renders a checkbox group
4

5
/**
6
 * MIT License
7
 *
8
 * termscp - Copyright (c) 2021 Christian Visintin
9
 *
10
 * Permission is hereby granted, free of charge, to any person obtaining a copy
11
 * of this software and associated documentation files (the "Software"), to deal
12
 * in the Software without restriction, including without limitation the rights
13
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
 * copies of the Software, and to permit persons to whom the Software is
15
 * furnished to do so, subject to the following conditions:
16
 *
17
 * The above copyright notice and this permission notice shall be included in all
18
 * copies or substantial portions of the Software.
19
 *
20
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
 * SOFTWARE.
27
 */
28
use tuirealm::command::{Cmd, CmdResult, Direction};
29
use tuirealm::props::{
30
    Alignment, AttrValue, Attribute, Borders, Color, PropPayload, PropValue, Props, Style,
31
};
32
use tuirealm::ratatui::text::Line as Spans;
33
use tuirealm::ratatui::{layout::Rect, text::Span, widgets::Tabs};
34
use tuirealm::{Frame, MockComponent, State, StateValue};
35

36
// -- states
37

38
/// ## CheckboxStates
39
///
40
/// CheckboxStates contains states for this component
41
#[derive(Default)]
42
pub struct CheckboxStates {
43
    pub choice: usize,         // Selected option
44
    pub choices: Vec<String>,  // Available choices
45
    pub selection: Vec<usize>, // Selected options
46
}
47

48
impl CheckboxStates {
49
    /// ### next_choice
50
    ///
51
    /// Move choice index to next choice
52
    pub fn next_choice(&mut self, rewind: bool) {
12✔
53
        if rewind && self.choice + 1 >= self.choices.len() {
12✔
54
            self.choice = 0;
1✔
55
        } else if self.choice + 1 < self.choices.len() {
11✔
56
            self.choice += 1;
9✔
57
        }
9✔
58
    }
12✔
59

60
    /// ### prev_choice
61
    ///
62
    /// Move choice index to previous choice
63
    pub fn prev_choice(&mut self, rewind: bool) {
6✔
64
        if rewind && self.choice == 0 && !self.choices.is_empty() {
6✔
65
            self.choice = self.choices.len() - 1;
1✔
66
        } else if self.choice > 0 {
5✔
67
            self.choice -= 1;
2✔
68
        }
3✔
69
    }
6✔
70

71
    /// ### toggle
72
    ///
73
    /// Check or uncheck the option
74
    pub fn toggle(&mut self) {
5✔
75
        let option = self.choice;
5✔
76
        if self.selection.contains(&option) {
5✔
77
            let target_index = self.selection.iter().position(|x| *x == option).unwrap();
3✔
78
            self.selection.remove(target_index);
2✔
79
        } else {
3✔
80
            self.selection.push(option);
3✔
81
        }
3✔
82
    }
5✔
83

84
    pub fn select(&mut self, i: usize) {
5✔
85
        if i < self.choices.len() && !self.selection.contains(&i) {
5✔
86
            self.selection.push(i);
5✔
87
        }
5✔
88
    }
5✔
89

90
    /// ### has
91
    ///
92
    /// Returns whether selection contains option
93
    #[must_use]
94
    pub fn has(&self, option: usize) -> bool {
2✔
95
        self.selection.contains(&option)
2✔
96
    }
2✔
97

98
    /// ### set_choices
99
    ///
100
    /// Set CheckboxStates choices from a vector of str
101
    /// In addition resets current selection and keep index if possible or set it to the first value
102
    /// available
103
    pub fn set_choices(&mut self, choices: &[String]) {
6✔
104
        self.choices = choices.to_vec();
6✔
105
        // Clear selection
106
        self.selection.clear();
6✔
107
        // Keep index if possible
108
        if self.choice >= self.choices.len() {
6✔
109
            self.choice = match self.choices.len() {
2✔
110
                0 => 0,
1✔
111
                l => l - 1,
1✔
112
            };
113
        }
4✔
114
    }
6✔
115
}
116

117
// -- component
118

119
/// ## Checkbox
120
///
121
/// Checkbox component represents a group of tabs to select from
122
#[derive(Default)]
123
#[must_use]
124
pub struct Checkbox {
125
    props: Props,
126
    pub states: CheckboxStates,
127
}
128

129
impl Checkbox {
130
    pub fn foreground(mut self, fg: Color) -> Self {
1✔
131
        self.attr(Attribute::Foreground, AttrValue::Color(fg));
1✔
132
        self
1✔
133
    }
1✔
134

135
    pub fn background(mut self, bg: Color) -> Self {
1✔
136
        self.attr(Attribute::Background, AttrValue::Color(bg));
1✔
137
        self
1✔
138
    }
1✔
139

140
    pub fn borders(mut self, b: Borders) -> Self {
1✔
141
        self.attr(Attribute::Borders, AttrValue::Borders(b));
1✔
142
        self
1✔
143
    }
1✔
144

145
    pub fn title<S: Into<String>>(mut self, t: S, a: Alignment) -> Self {
1✔
146
        self.attr(Attribute::Title, AttrValue::Title((t.into(), a)));
1✔
147
        self
1✔
148
    }
1✔
149

150
    pub fn inactive(mut self, s: Style) -> Self {
×
151
        self.attr(Attribute::FocusStyle, AttrValue::Style(s));
×
152
        self
×
153
    }
×
154

155
    pub fn rewind(mut self, r: bool) -> Self {
1✔
156
        self.attr(Attribute::Rewind, AttrValue::Flag(r));
1✔
157
        self
1✔
158
    }
1✔
159

160
    pub fn choices<S: AsRef<str>>(mut self, choices: &[S]) -> Self {
1✔
161
        self.attr(
1✔
162
            Attribute::Content,
1✔
163
            AttrValue::Payload(PropPayload::Vec(
164
                choices
1✔
165
                    .iter()
1✔
166
                    .map(|x| PropValue::Str(x.as_ref().to_string()))
5✔
167
                    .collect(),
1✔
168
            )),
169
        );
170
        self
1✔
171
    }
1✔
172

173
    pub fn values(mut self, selected: &[usize]) -> Self {
1✔
174
        // Set state
175
        self.attr(
1✔
176
            Attribute::Value,
1✔
177
            AttrValue::Payload(PropPayload::Vec(
178
                selected.iter().map(|x| PropValue::Usize(*x)).collect(),
2✔
179
            )),
180
        );
181
        self
1✔
182
    }
1✔
183

184
    fn rewindable(&self) -> bool {
8✔
185
        self.props
8✔
186
            .get_or(Attribute::Rewind, AttrValue::Flag(false))
8✔
187
            .unwrap_flag()
8✔
188
    }
8✔
189
}
190

191
impl MockComponent for Checkbox {
192
    fn view(&mut self, render: &mut Frame, area: Rect) {
×
193
        if self.props.get_or(Attribute::Display, AttrValue::Flag(true)) == AttrValue::Flag(true) {
×
194
            let foreground = self
×
195
                .props
×
196
                .get_or(Attribute::Foreground, AttrValue::Color(Color::Reset))
×
197
                .unwrap_color();
×
198
            let background = self
×
199
                .props
×
200
                .get_or(Attribute::Background, AttrValue::Color(Color::Reset))
×
201
                .unwrap_color();
×
202
            let borders = self
×
203
                .props
×
204
                .get_or(Attribute::Borders, AttrValue::Borders(Borders::default()))
×
205
                .unwrap_borders();
×
206
            let title = self
×
207
                .props
×
208
                .get_ref(Attribute::Title)
×
209
                .and_then(|x| x.as_title());
×
210
            let focus = self
×
211
                .props
×
212
                .get_or(Attribute::Focus, AttrValue::Flag(false))
×
213
                .unwrap_flag();
×
214
            let inactive_style = self
×
215
                .props
×
216
                .get(Attribute::FocusStyle)
×
217
                .map(|x| x.unwrap_style());
×
218
            let div = crate::utils::get_block(borders, title, focus, inactive_style);
×
219
            // Make colors
220
            let (bg, fg, block_color): (Color, Color, Color) = match &focus {
×
221
                true => (foreground, background, foreground),
×
222
                false => (Color::Reset, foreground, Color::Reset),
×
223
            };
224
            // Make choices
225
            let choices: Vec<Spans> = self
×
226
                .states
×
227
                .choices
×
228
                .iter()
×
229
                .enumerate()
×
230
                .map(|(idx, x)| {
×
NEW
231
                    let checkbox: &str = if self.states.has(idx) { "☑ " } else { "☐ " };
×
NEW
232
                    let (fg, bg) = if focus {
×
NEW
233
                        if self.states.choice == idx {
×
NEW
234
                            (fg, bg)
×
235
                        } else {
NEW
236
                            (bg, fg)
×
237
                        }
238
                    } else {
NEW
239
                        (fg, bg)
×
240
                    };
241
                    // Make spans
242
                    Spans::from(vec![
×
243
                        Span::styled(checkbox, Style::default().fg(fg).bg(bg)),
×
244
                        Span::styled(x.to_string(), Style::default().fg(fg).bg(bg)),
×
245
                    ])
246
                })
×
247
                .collect();
×
248
            let checkbox: Tabs = Tabs::new(choices)
×
249
                .block(div)
×
250
                .select(self.states.choice)
×
251
                .style(Style::default().fg(block_color));
×
252
            render.render_widget(checkbox, area);
×
253
        }
×
254
    }
×
255

256
    fn query(&self, attr: Attribute) -> Option<AttrValue> {
×
257
        self.props.get(attr)
×
258
    }
×
259

260
    fn attr(&mut self, attr: Attribute, value: AttrValue) {
9✔
261
        match attr {
9✔
262
            Attribute::Content => {
263
                // Reset choices
264
                let current_selection = self.states.selection.clone();
2✔
265
                let choices: Vec<String> = value
2✔
266
                    .unwrap_payload()
2✔
267
                    .unwrap_vec()
2✔
268
                    .iter()
2✔
269
                    .cloned()
2✔
270
                    .map(|x| x.unwrap_str())
11✔
271
                    .collect();
2✔
272
                self.states.set_choices(&choices);
2✔
273
                // Preserve selection if possible
274
                for c in current_selection {
4✔
275
                    self.states.select(c);
2✔
276
                }
2✔
277
            }
278
            Attribute::Value => {
279
                // Clear section
280
                self.states.selection.clear();
2✔
281
                for c in value.unwrap_payload().unwrap_vec() {
3✔
282
                    self.states.select(c.unwrap_usize());
3✔
283
                }
3✔
284
            }
285
            attr => {
5✔
286
                self.props.set(attr, value);
5✔
287
            }
5✔
288
        }
289
    }
9✔
290

291
    /// ### get_state
292
    ///
293
    /// Get current state from component
294
    /// For this component returns the vec of selected items
295
    fn state(&self) -> State {
5✔
296
        State::Vec(
297
            self.states
5✔
298
                .selection
5✔
299
                .iter()
5✔
300
                .map(|x| StateValue::Usize(*x))
6✔
301
                .collect(),
5✔
302
        )
303
    }
5✔
304

305
    fn perform(&mut self, cmd: Cmd) -> CmdResult {
11✔
306
        match cmd {
8✔
307
            Cmd::Move(Direction::Right) => {
308
                // Increment choice
309
                self.states.next_choice(self.rewindable());
6✔
310
                CmdResult::None
6✔
311
            }
312
            Cmd::Move(Direction::Left) => {
313
                // Decrement choice
314
                self.states.prev_choice(self.rewindable());
2✔
315
                CmdResult::None
2✔
316
            }
317
            Cmd::Toggle => {
318
                self.states.toggle();
2✔
319
                CmdResult::Changed(self.state())
2✔
320
            }
321
            Cmd::Submit => {
322
                // Return Submit
323
                CmdResult::Submit(self.state())
1✔
324
            }
325
            _ => CmdResult::None,
×
326
        }
327
    }
11✔
328
}
329

330
#[cfg(test)]
331
mod test {
332

333
    use super::*;
334

335
    use pretty_assertions::{assert_eq, assert_ne};
336
    use tuirealm::props::{PropPayload, PropValue};
337

338
    #[test]
339
    fn test_components_checkbox_states() {
1✔
340
        let mut states: CheckboxStates = CheckboxStates::default();
1✔
341
        assert_eq!(states.choice, 0);
1✔
342
        assert_eq!(states.choices.len(), 0);
1✔
343
        assert_eq!(states.selection.len(), 0);
1✔
344
        let choices: &[String] = &[
1✔
345
            "lemon".to_string(),
1✔
346
            "strawberry".to_string(),
1✔
347
            "vanilla".to_string(),
1✔
348
            "chocolate".to_string(),
1✔
349
        ];
1✔
350
        states.set_choices(choices);
1✔
351
        assert_eq!(states.choice, 0);
1✔
352
        assert_eq!(states.choices.len(), 4);
1✔
353
        assert_eq!(states.selection.len(), 0);
1✔
354
        // Select
355
        states.toggle();
1✔
356
        assert_eq!(states.selection, vec![0]);
1✔
357
        // Move
358
        states.prev_choice(false);
1✔
359
        assert_eq!(states.choice, 0);
1✔
360
        states.next_choice(false);
1✔
361
        assert_eq!(states.choice, 1);
1✔
362
        states.next_choice(false);
1✔
363
        assert_eq!(states.choice, 2);
1✔
364
        states.toggle();
1✔
365
        assert_eq!(states.selection, vec![0, 2]);
1✔
366
        // Forward overflow
367
        states.next_choice(false);
1✔
368
        states.next_choice(false);
1✔
369
        assert_eq!(states.choice, 3);
1✔
370
        states.prev_choice(false);
1✔
371
        assert_eq!(states.choice, 2);
1✔
372
        states.toggle();
1✔
373
        assert_eq!(states.selection, vec![0]);
1✔
374
        // has
375
        assert_eq!(states.has(0), true);
1✔
376
        assert_ne!(states.has(2), true);
1✔
377
        // Update
378
        let choices: &[String] = &["lemon".to_string(), "strawberry".to_string()];
1✔
379
        states.set_choices(choices);
1✔
380
        assert_eq!(states.choice, 1); // Move to first index available
1✔
381
        assert_eq!(states.choices.len(), 2);
1✔
382
        assert_eq!(states.selection.len(), 0);
1✔
383
        let choices: &[String] = &[];
1✔
384
        states.set_choices(choices);
1✔
385
        assert_eq!(states.choice, 0); // Move to first index available
1✔
386
        assert_eq!(states.choices.len(), 0);
1✔
387
        assert_eq!(states.selection.len(), 0);
1✔
388
        // Rewind
389
        let choices: &[String] = &[
1✔
390
            "lemon".to_string(),
1✔
391
            "strawberry".to_string(),
1✔
392
            "vanilla".to_string(),
1✔
393
            "chocolate".to_string(),
1✔
394
        ];
1✔
395
        states.set_choices(choices);
1✔
396
        assert_eq!(states.choice, 0);
1✔
397
        states.prev_choice(true);
1✔
398
        assert_eq!(states.choice, 3);
1✔
399
        states.next_choice(true);
1✔
400
        assert_eq!(states.choice, 0);
1✔
401
        states.next_choice(true);
1✔
402
        assert_eq!(states.choice, 1);
1✔
403
        states.prev_choice(true);
1✔
404
        assert_eq!(states.choice, 0);
1✔
405
    }
1✔
406

407
    #[test]
408
    fn test_components_checkbox() {
1✔
409
        // Make component
410
        let mut component = Checkbox::default()
1✔
411
            .background(Color::Blue)
1✔
412
            .foreground(Color::Red)
1✔
413
            .borders(Borders::default())
1✔
414
            .title("Which food do you prefer?", Alignment::Center)
1✔
415
            .choices(&["Pizza", "Hummus", "Ramen", "Gyoza", "Pasta"])
1✔
416
            .values(&[1, 4])
1✔
417
            .rewind(false);
1✔
418
        // Verify states
419
        assert_eq!(component.states.selection, vec![1, 4]);
1✔
420
        assert_eq!(component.states.choice, 0);
1✔
421
        assert_eq!(component.states.choices.len(), 5);
1✔
422
        component.attr(
1✔
423
            Attribute::Content,
1✔
424
            AttrValue::Payload(PropPayload::Vec(vec![
1✔
425
                PropValue::Str(String::from("Pizza")),
1✔
426
                PropValue::Str(String::from("Hummus")),
1✔
427
                PropValue::Str(String::from("Ramen")),
1✔
428
                PropValue::Str(String::from("Gyoza")),
1✔
429
                PropValue::Str(String::from("Pasta")),
1✔
430
                PropValue::Str(String::from("Falafel")),
1✔
431
            ])),
1✔
432
        );
433
        assert_eq!(component.states.selection, vec![1, 4]);
1✔
434
        assert_eq!(component.states.choices.len(), 6);
1✔
435
        // Get value
436
        component.attr(
1✔
437
            Attribute::Value,
1✔
438
            AttrValue::Payload(PropPayload::Vec(vec![PropValue::Usize(1)])),
1✔
439
        );
440
        assert_eq!(component.states.selection, vec![1]);
1✔
441
        assert_eq!(component.states.choices.len(), 6);
1✔
442
        assert_eq!(component.state(), State::Vec(vec![StateValue::Usize(1)]));
1✔
443
        // Handle events
444
        assert_eq!(
1✔
445
            component.perform(Cmd::Move(Direction::Left)),
1✔
446
            CmdResult::None,
447
        );
448
        assert_eq!(component.state(), State::Vec(vec![StateValue::Usize(1)]));
1✔
449
        // Toggle
450
        assert_eq!(
1✔
451
            component.perform(Cmd::Toggle),
1✔
452
            CmdResult::Changed(State::Vec(vec![StateValue::Usize(1), StateValue::Usize(0)]))
1✔
453
        );
454
        // Left again
455
        assert_eq!(
1✔
456
            component.perform(Cmd::Move(Direction::Left)),
1✔
457
            CmdResult::None,
458
        );
459
        assert_eq!(component.states.choice, 0);
1✔
460
        // Right
461
        assert_eq!(
1✔
462
            component.perform(Cmd::Move(Direction::Right)),
1✔
463
            CmdResult::None,
464
        );
465
        // Toggle
466
        assert_eq!(
1✔
467
            component.perform(Cmd::Toggle),
1✔
468
            CmdResult::Changed(State::Vec(vec![StateValue::Usize(0)]))
1✔
469
        );
470
        // Right again
471
        assert_eq!(
1✔
472
            component.perform(Cmd::Move(Direction::Right)),
1✔
473
            CmdResult::None,
474
        );
475
        assert_eq!(component.states.choice, 2);
1✔
476
        // Right again
477
        assert_eq!(
1✔
478
            component.perform(Cmd::Move(Direction::Right)),
1✔
479
            CmdResult::None,
480
        );
481
        assert_eq!(component.states.choice, 3);
1✔
482
        // Right again
483
        assert_eq!(
1✔
484
            component.perform(Cmd::Move(Direction::Right)),
1✔
485
            CmdResult::None,
486
        );
487
        assert_eq!(component.states.choice, 4);
1✔
488
        // Right again
489
        assert_eq!(
1✔
490
            component.perform(Cmd::Move(Direction::Right)),
1✔
491
            CmdResult::None,
492
        );
493
        assert_eq!(component.states.choice, 5);
1✔
494
        // Right again
495
        assert_eq!(
1✔
496
            component.perform(Cmd::Move(Direction::Right)),
1✔
497
            CmdResult::None,
498
        );
499
        assert_eq!(component.states.choice, 5);
1✔
500
        // Submit
501
        assert_eq!(
1✔
502
            component.perform(Cmd::Submit),
1✔
503
            CmdResult::Submit(State::Vec(vec![StateValue::Usize(0)])),
1✔
504
        );
505
    }
1✔
506
}
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