tui_components/components/
checkbox.rs

1use crossterm::event::KeyCode;
2use tui::{
3    buffer::Buffer,
4    layout::Rect,
5    style::{Color, Style},
6    text::{Span, Spans},
7    widgets::{Paragraph, Widget},
8};
9
10use crate::{Component, Event, Spannable};
11
12pub const TRUE_CHAR: char = '☑';
13pub const FALSE_CHAR: char = '☐';
14
15#[derive(Debug, Default)]
16pub struct Checkbox {
17    pub value: bool,
18}
19
20impl Checkbox {
21    pub fn new(value: bool) -> Self {
22        Self { value }
23    }
24
25    pub fn invert(&mut self) {
26        self.value = !self.value;
27    }
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum CheckboxResponse {
32    Edited,
33    None,
34    Submit,
35    Exit,
36}
37
38impl Component for Checkbox {
39    type Response = CheckboxResponse;
40    type DrawResponse = ();
41
42    fn handle_event(&mut self, event: crate::Event) -> Self::Response {
43        if let Event::Key(key) = event {
44            match key.code {
45                KeyCode::Char('t') | KeyCode::Char('y') => {
46                    self.value = true;
47                    CheckboxResponse::Edited
48                }
49                KeyCode::Char('f') | KeyCode::Char('n') => {
50                    self.value = false;
51                    CheckboxResponse::Edited
52                }
53                KeyCode::Down | KeyCode::Up => {
54                    self.value = !self.value;
55                    CheckboxResponse::Edited
56                }
57                KeyCode::Backspace => CheckboxResponse::Exit,
58                KeyCode::Enter => CheckboxResponse::Submit,
59                _ => CheckboxResponse::None,
60            }
61        } else {
62            CheckboxResponse::None
63        }
64    }
65
66    fn draw(&mut self, rect: Rect, buffer: &mut Buffer) -> Self::DrawResponse {
67        let spans = Spans::from(vec![
68            Span::styled("> ", Style::default()),
69            if self.value {
70                Span::styled(TRUE_CHAR.to_string(), Style::default().fg(Color::Green))
71            } else {
72                Span::styled(FALSE_CHAR.to_string(), Style::default().fg(Color::Red))
73            },
74        ]);
75        let paragraph = Paragraph::new(spans);
76        Widget::render(paragraph, rect, buffer);
77    }
78}
79
80impl Spannable for Checkbox {
81    fn get_spans<'a, 'b>(&'a self) -> Spans<'b> {
82        let mut spans = Spans::default();
83        spans.0.push(Span::raw(String::from("> ")));
84        if self.value {
85            spans.0.push(Span::styled(
86                TRUE_CHAR.to_string(),
87                Style::default().fg(Color::Green),
88            ));
89        } else {
90            spans.0.push(Span::styled(
91                FALSE_CHAR.to_string(),
92                Style::default().fg(Color::Yellow),
93            ));
94        }
95        spans
96    }
97}