1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
use crossterm::event::KeyCode;
use tui::{
    buffer::Buffer,
    layout::Rect,
    style::{Color, Style},
    text::{Span, Spans},
    widgets::{Paragraph, Widget},
};

use crate::{Component, Event, Spannable};

pub const TRUE_CHAR: char = '☑';
pub const FALSE_CHAR: char = '☐';

#[derive(Debug, Default)]
pub struct Checkbox {
    pub value: bool,
}

impl Checkbox {
    pub fn new(value: bool) -> Self {
        Self { value }
    }

    pub fn invert(&mut self) {
        self.value = !self.value;
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CheckboxResponse {
    Edited,
    None,
    Submit,
    Exit,
}

impl Component for Checkbox {
    type Response = CheckboxResponse;
    type DrawResponse = ();

    fn handle_event(&mut self, event: crate::Event) -> Self::Response {
        if let Event::Key(key) = event {
            match key.code {
                KeyCode::Char('t') | KeyCode::Char('y') => {
                    self.value = true;
                    CheckboxResponse::Edited
                }
                KeyCode::Char('f') | KeyCode::Char('n') => {
                    self.value = false;
                    CheckboxResponse::Edited
                }
                KeyCode::Down | KeyCode::Up => {
                    self.value = !self.value;
                    CheckboxResponse::Edited
                }
                KeyCode::Backspace => CheckboxResponse::Exit,
                KeyCode::Enter => CheckboxResponse::Submit,
                _ => CheckboxResponse::None,
            }
        } else {
            CheckboxResponse::None
        }
    }

    fn draw(&mut self, rect: Rect, buffer: &mut Buffer) -> Self::DrawResponse {
        let spans = Spans::from(vec![
            Span::styled("> ", Style::default()),
            if self.value {
                Span::styled(TRUE_CHAR.to_string(), Style::default().fg(Color::Green))
            } else {
                Span::styled(FALSE_CHAR.to_string(), Style::default().fg(Color::Red))
            },
        ]);
        let paragraph = Paragraph::new(spans);
        Widget::render(paragraph, rect, buffer);
    }
}

impl Spannable for Checkbox {
    fn get_spans<'a, 'b>(&'a self) -> Spans<'b> {
        let mut spans = Spans::default();
        spans.0.push(Span::raw(String::from("> ")));
        if self.value {
            spans.0.push(Span::styled(
                TRUE_CHAR.to_string(),
                Style::default().fg(Color::Green),
            ));
        } else {
            spans.0.push(Span::styled(
                FALSE_CHAR.to_string(),
                Style::default().fg(Color::Yellow),
            ));
        }
        spans
    }
}