tui/components/
checkbox.rs1use crossterm::event::KeyCode;
2
3use crate::components::{Component, Event, ViewContext};
4use crate::line::Line;
5use crate::rendering::frame::Frame;
6
7pub struct Checkbox {
9 pub checked: bool,
10}
11
12impl Checkbox {
13 pub fn new(checked: bool) -> Self {
14 Self { checked }
15 }
16
17 pub fn to_json(&self) -> serde_json::Value {
18 serde_json::Value::Bool(self.checked)
19 }
20}
21
22impl Component for Checkbox {
23 type Message = ();
24
25 async fn on_event(&mut self, event: &Event) -> Option<Vec<Self::Message>> {
26 let Event::Key(key) = event else {
27 return None;
28 };
29 match key.code {
30 KeyCode::Char(' ') => {
31 self.checked = !self.checked;
32 Some(vec![])
33 }
34 _ => None,
35 }
36 }
37
38 fn render(&mut self, context: &ViewContext) -> Frame {
39 Frame::new(self.render_field(context, true))
40 }
41}
42
43impl Checkbox {
44 pub fn render_field(&self, context: &ViewContext, focused: bool) -> Vec<Line> {
45 let display = if self.checked { "[x]" } else { "[ ]" };
46 let style = if focused { context.theme.primary() } else { context.theme.text_primary() };
47 vec![Line::styled(display, style)]
48 }
49}
50
51#[cfg(test)]
52mod tests {
53 use super::*;
54 use crossterm::event::{KeyEvent, KeyModifiers};
55
56 fn key(code: KeyCode) -> KeyEvent {
57 KeyEvent::new(code, KeyModifiers::NONE)
58 }
59
60 #[tokio::test]
61 async fn space_toggles() {
62 let mut cb = Checkbox::new(false);
63 cb.on_event(&Event::Key(key(KeyCode::Char(' ')))).await;
64 assert!(cb.checked);
65 cb.on_event(&Event::Key(key(KeyCode::Char(' ')))).await;
66 assert!(!cb.checked);
67 }
68
69 #[test]
70 fn to_json_returns_bool() {
71 assert_eq!(Checkbox::new(true).to_json(), serde_json::json!(true));
72 assert_eq!(Checkbox::new(false).to_json(), serde_json::json!(false));
73 }
74
75 #[tokio::test]
76 async fn other_keys_are_ignored() {
77 let mut cb = Checkbox::new(false);
78 let outcome = cb.on_event(&Event::Key(key(KeyCode::Char('a')))).await;
79 assert!(outcome.is_none());
80 assert!(!cb.checked);
81 }
82}