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